diff options
Diffstat (limited to 'libs')
350 files changed, 7637 insertions, 1981 deletions
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java index 290fefa5abfa..544f0f38f48c 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java @@ -169,6 +169,11 @@ class DividerPresenter implements View.OnTouchListener { @GuardedBy("mLock") private int mDividerPosition; + /** Indicates if there are containers to be finished since the divider has appeared. */ + @GuardedBy("mLock") + @VisibleForTesting + private boolean mHasContainersToFinish = false; + DividerPresenter(int taskId, @NonNull DragEventCallback dragEventCallback, @NonNull Executor callbackExecutor) { mTaskId = taskId; @@ -180,7 +185,8 @@ class DividerPresenter implements View.OnTouchListener { void updateDivider( @NonNull WindowContainerTransaction wct, @NonNull TaskFragmentParentInfo parentInfo, - @Nullable SplitContainer topSplitContainer) { + @Nullable SplitContainer topSplitContainer, + boolean isTaskFragmentVanished) { if (!Flags.activityEmbeddingInteractiveDividerFlag()) { return; } @@ -188,6 +194,18 @@ class DividerPresenter implements View.OnTouchListener { synchronized (mLock) { // Clean up the decor surface if top SplitContainer is null. if (topSplitContainer == null) { + // Check if there are containers to finish but the TaskFragment hasn't vanished yet. + // Don't remove the decor surface and divider if so as the removal should happen in + // a following step when the TaskFragment has vanished. This ensures that the decor + // surface is removed only after the resulting Activity is ready to be shown, + // otherwise there may be flicker. + if (mHasContainersToFinish) { + if (isTaskFragmentVanished) { + setHasContainersToFinish(false); + } else { + return; + } + } removeDecorSurfaceAndDivider(wct); return; } @@ -868,11 +886,15 @@ class DividerPresenter implements View.OnTouchListener { } } + void setHasContainersToFinish(boolean hasContainersToFinish) { + synchronized (mLock) { + mHasContainersToFinish = hasContainersToFinish; + } + } + private static boolean isDraggingToFullscreenAllowed( @NonNull DividerAttributes dividerAttributes) { - // TODO(b/293654166) Use DividerAttributes.isDraggingToFullscreenAllowed when extension is - // updated to v7. - return false; + return dividerAttributes.isDraggingToFullscreenAllowed(); } /** 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 f9a6caf42e6e..9ea2943bc6da 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java @@ -17,6 +17,7 @@ package androidx.window.extensions.embedding; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.window.TaskFragmentAnimationParams.DEFAULT_ANIMATION_BACKGROUND_COLOR; 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_DIM_ON_TASK; @@ -29,6 +30,7 @@ import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAs import static androidx.window.extensions.embedding.SplitContainer.shouldFinishPrimaryWithSecondary; import static androidx.window.extensions.embedding.SplitContainer.shouldFinishSecondaryWithPrimary; +import android.annotation.ColorInt; import android.app.Activity; import android.app.WindowConfiguration.WindowingMode; import android.content.Intent; @@ -48,6 +50,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; +import com.android.window.flags.Flags; import java.util.Map; import java.util.concurrent.Executor; @@ -391,13 +394,34 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { if (splitAttributes == null) { return TaskFragmentAnimationParams.DEFAULT; } - final AnimationBackground animationBackground = splitAttributes.getAnimationBackground(); + final TaskFragmentAnimationParams.Builder builder = + new TaskFragmentAnimationParams.Builder(); + final int animationBackgroundColor = getAnimationBackgroundColor(splitAttributes); + builder.setAnimationBackgroundColor(animationBackgroundColor); + if (Flags.activityEmbeddingAnimationCustomizationFlag()) { + final int openAnimationResId = + splitAttributes.getAnimationParams().getOpenAnimationResId(); + builder.setOpenAnimationResId(openAnimationResId); + final int closeAnimationResId = + splitAttributes.getAnimationParams().getCloseAnimationResId(); + builder.setCloseAnimationResId(closeAnimationResId); + final int changeAnimationResId = + splitAttributes.getAnimationParams().getChangeAnimationResId(); + builder.setChangeAnimationResId(changeAnimationResId); + } + return builder.build(); + } + + @ColorInt + private static int getAnimationBackgroundColor(@NonNull SplitAttributes splitAttributes) { + int animationBackgroundColor = DEFAULT_ANIMATION_BACKGROUND_COLOR; + AnimationBackground animationBackground = splitAttributes.getAnimationBackground(); + if (Flags.activityEmbeddingAnimationCustomizationFlag()) { + animationBackground = splitAttributes.getAnimationParams().getAnimationBackground(); + } if (animationBackground instanceof AnimationBackground.ColorBackground colorBackground) { - return new TaskFragmentAnimationParams.Builder() - .setAnimationBackgroundColor(colorBackground.getColor()) - .build(); - } else { - return TaskFragmentAnimationParams.DEFAULT; + animationBackgroundColor = colorBackground.getColor(); } + return animationBackgroundColor; } } 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 f78e2b5170fc..7ddda1f98809 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -673,7 +673,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen break; case TYPE_TASK_FRAGMENT_VANISHED: mPresenter.removeTaskFragmentInfo(info); - onTaskFragmentVanished(wct, info); + onTaskFragmentVanished(wct, info, taskId); break; case TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED: onTaskFragmentParentInfoChanged(wct, taskId, @@ -834,7 +834,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @VisibleForTesting @GuardedBy("mLock") void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct, - @NonNull TaskFragmentInfo taskFragmentInfo) { + @NonNull TaskFragmentInfo taskFragmentInfo, int taskId) { final TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken()); if (container != null) { // Cleanup if the TaskFragment vanished is not requested by the organizer. @@ -843,6 +843,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen updateContainersInTaskIfVisible(wct, container.getTaskId()); } cleanupTaskFragment(taskFragmentInfo.getFragmentToken()); + final TaskContainer taskContainer = getTaskContainer(taskId); + if (taskContainer != null) { + // Update the divider to clean up any decor surfaces. + updateDivider(wct, taskContainer, true /* isTaskFragmentVanished */); + } } /** @@ -884,7 +889,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // The divider need to be updated even if shouldUpdateContainer is false, because the decor // surface may change in TaskFragmentParentInfo, which requires divider update but not // container update. - updateDivider(wct, taskContainer); + updateDivider(wct, taskContainer, false /* isTaskFragmentVanished */); // If the last direct activity of the host task is dismissed and there's an always-on-top // overlay container in the task, the overlay container should also be dismissed. @@ -899,14 +904,23 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @GuardedBy("mLock") void updateContainersInTaskIfVisible(@NonNull WindowContainerTransaction wct, int taskId) { final TaskContainer taskContainer = getTaskContainer(taskId); - if (taskContainer != null && taskContainer.isVisible()) { + if (taskContainer == null) { + return; + } + + if (taskContainer.isVisible()) { updateContainersInTask(wct, taskContainer); + } else if (Flags.fixNoContainerUpdateWithoutResize()) { + // the TaskFragmentContainers need to be updated when the task becomes visible + taskContainer.mTaskFragmentContainersNeedsUpdate = true; } } @GuardedBy("mLock") private void updateContainersInTask(@NonNull WindowContainerTransaction wct, @NonNull TaskContainer taskContainer) { + taskContainer.mTaskFragmentContainersNeedsUpdate = false; + // Update all TaskFragments in the Task. Make a copy of the list since some may be // removed on updating. final List<TaskFragmentContainer> containers = taskContainer.getTaskFragmentContainers(); @@ -3257,12 +3271,15 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } @GuardedBy("mLock") - void updateDivider( - @NonNull WindowContainerTransaction wct, @NonNull TaskContainer taskContainer) { + void updateDivider(@NonNull WindowContainerTransaction wct, + @NonNull TaskContainer taskContainer, boolean isTaskFragmentVanished) { final DividerPresenter dividerPresenter = mDividerPresenters.get(taskContainer.getTaskId()); final TaskFragmentParentInfo parentInfo = taskContainer.getTaskFragmentParentInfo(); - dividerPresenter.updateDivider( - wct, parentInfo, taskContainer.getTopNonFinishingSplitContainer()); + final SplitContainer topSplitContainer = taskContainer.getTopNonFinishingSplitContainer(); + if (dividerPresenter != null) { + dividerPresenter.updateDivider( + wct, parentInfo, topSplitContainer, isTaskFragmentVanished); + } } @Override @@ -3292,6 +3309,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen final List<TaskFragmentContainer> containersToFinish = new ArrayList<>(); taskContainer.updateTopSplitContainerForDivider( dividerPresenter, containersToFinish); + if (!containersToFinish.isEmpty()) { + dividerPresenter.setHasContainersToFinish(true); + } for (final TaskFragmentContainer container : containersToFinish) { mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */); } 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 d888fa9d6feb..eb1fc23d6b00 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -20,8 +20,10 @@ import static android.content.pm.PackageManager.MATCH_ALL; import static androidx.window.extensions.embedding.DividerPresenter.getBoundsOffsetForDivider; import static androidx.window.extensions.embedding.SplitAttributesHelper.isReversedLayout; +import static androidx.window.extensions.embedding.SplitController.TAG; import static androidx.window.extensions.embedding.WindowAttributes.DIM_AREA_ON_TASK; +import android.annotation.AnimRes; import android.app.Activity; import android.app.ActivityThread; import android.app.WindowConfiguration; @@ -31,9 +33,11 @@ import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Configuration; +import android.content.res.Resources; import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; +import android.util.Log; import android.util.Pair; import android.util.Size; import android.view.View; @@ -56,6 +60,7 @@ import androidx.window.extensions.layout.FoldingFeature; import androidx.window.extensions.layout.WindowLayoutComponentImpl; import androidx.window.extensions.layout.WindowLayoutInfo; +import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.window.flags.Flags; @@ -125,6 +130,16 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { static final int RESULT_EXPAND_FAILED_NO_TF_INFO = 2; /** + * The key of {@link ActivityStack} alignment relative to its parent container. + * <p> + * See {@link ContainerPosition} for possible values. + * <p> + * Note that this constants must align with the definition in WM Jetpack library. + */ + private static final String KEY_ACTIVITY_STACK_ALIGNMENT = + "androidx.window.embedding.ActivityStackAlignment"; + + /** * Result of {@link #expandSplitContainerIfNeeded(WindowContainerTransaction, SplitContainer, * Activity, Activity, Intent)} */ @@ -374,7 +389,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { updateTaskFragmentWindowingModeIfRegistered(wct, secondaryContainer, windowingMode); updateAnimationParams(wct, primaryContainer.getTaskFragmentToken(), splitAttributes); updateAnimationParams(wct, secondaryContainer.getTaskFragmentToken(), splitAttributes); - mController.updateDivider(wct, taskContainer); + mController.updateDivider(wct, taskContainer, false /* isTaskFragmentVanished */); } private void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct, @@ -649,14 +664,114 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { // TODO(b/243518738): Update to resizeTaskFragment after we migrate WCT#setRelativeBounds // and WCT#setWindowingMode to take fragmentToken. resizeTaskFragmentIfRegistered(wct, container, relativeBounds); - int windowingMode = container.getTaskContainer().getWindowingModeForTaskFragment( - relativeBounds); + final TaskContainer taskContainer = container.getTaskContainer(); + final int windowingMode = taskContainer.getWindowingModeForTaskFragment(relativeBounds); updateTaskFragmentWindowingModeIfRegistered(wct, container, windowingMode); - // Always use default animation for standalone ActivityStack. - updateAnimationParams(wct, fragmentToken, TaskFragmentAnimationParams.DEFAULT); + if (container.isOverlay() && isOverlayTransitionSupported()) { + // Use the overlay transition for the overlay container if it's supported. + final TaskFragmentAnimationParams params = createOverlayAnimationParams(relativeBounds, + taskContainer.getBounds(), container); + updateAnimationParams(wct, fragmentToken, params); + } else { + // Otherwise, fallabck to use the default animation params. + updateAnimationParams(wct, fragmentToken, TaskFragmentAnimationParams.DEFAULT); + } setTaskFragmentDimOnTask(wct, fragmentToken, dimOnTask); } + private static boolean isOverlayTransitionSupported() { + return Flags.moveAnimationOptionsToChange() + && Flags.activityEmbeddingOverlayPresentationFlag(); + } + + @NonNull + private static TaskFragmentAnimationParams createOverlayAnimationParams( + @NonNull Rect relativeBounds, @NonNull Rect parentContainerBounds, + @NonNull TaskFragmentContainer container) { + if (relativeBounds.isEmpty()) { + return TaskFragmentAnimationParams.DEFAULT; + } + + final int positionFromOptions = container.getLaunchOptions() + .getInt(KEY_ACTIVITY_STACK_ALIGNMENT , -1); + final int position = positionFromOptions != -1 ? positionFromOptions + // Fallback to calculate from bounds if the info can't be retrieved from options. + : getOverlayPosition(relativeBounds, parentContainerBounds); + + return new TaskFragmentAnimationParams.Builder() + .setOpenAnimationResId(getOpenAnimationResourcesId(position)) + .setChangeAnimationResId(R.anim.overlay_task_fragment_change) + .setCloseAnimationResId(getCloseAnimationResourcesId(position)) + .build(); + } + + @VisibleForTesting + @ContainerPosition + static int getOverlayPosition( + @NonNull Rect relativeBounds, @NonNull Rect parentContainerBounds) { + final Rect relativeParentBounds = new Rect(parentContainerBounds); + relativeParentBounds.offsetTo(0, 0); + final int leftMatch = (relativeParentBounds.left == relativeBounds.left) ? 1 : 0; + final int topMatch = (relativeParentBounds.top == relativeBounds.top) ? 1 : 0; + final int rightMatch = (relativeParentBounds.right == relativeBounds.right) ? 1 : 0; + final int bottomMatch = (relativeParentBounds.bottom == relativeBounds.bottom) ? 1 : 0; + + // Flag format: {left|top|right|bottom}. Note that overlay container could be shrunk and + // centered, which makes only one of overlay container edge matches the parent container. + final int directionFlag = (leftMatch << 3) + (topMatch << 2) + (rightMatch << 1) + + bottomMatch; + + final int position = switch (directionFlag) { + // Only the left edge match or only the right edge not match: should be on the left of + // the parent container. + case 0b1000, 0b1101 -> CONTAINER_POSITION_LEFT; + // Only the top edge match or only the bottom edge not match: should be on the top of + // the parent container. + case 0b0100, 0b1110 -> CONTAINER_POSITION_TOP; + // Only the right edge match or only the left edge not match: should be on the right of + // the parent container. + case 0b0010, 0b0111 -> CONTAINER_POSITION_RIGHT; + // Only the bottom edge match or only the top edge not match: should be on the bottom of + // the parent container. + case 0b0001, 0b1011 -> CONTAINER_POSITION_BOTTOM; + default -> { + Log.w(TAG, "Unsupported position:" + Integer.toBinaryString(directionFlag) + + " fallback to treat it as right. Relative parent bounds: " + + relativeParentBounds + ", relative overlay bounds:" + relativeBounds); + yield CONTAINER_POSITION_RIGHT; + } + }; + return position; + } + + @AnimRes + private static int getOpenAnimationResourcesId(@ContainerPosition int position) { + return switch (position) { + case CONTAINER_POSITION_LEFT -> R.anim.overlay_task_fragment_open_from_left; + case CONTAINER_POSITION_TOP -> R.anim.overlay_task_fragment_open_from_top; + case CONTAINER_POSITION_RIGHT -> R.anim.overlay_task_fragment_open_from_right; + case CONTAINER_POSITION_BOTTOM -> R.anim.overlay_task_fragment_open_from_bottom; + default -> { + Log.w(TAG, "Unknown position:" + position); + yield Resources.ID_NULL; + } + }; + } + + @AnimRes + private static int getCloseAnimationResourcesId(@ContainerPosition int position) { + return switch (position) { + case CONTAINER_POSITION_LEFT -> R.anim.overlay_task_fragment_close_to_left; + case CONTAINER_POSITION_TOP -> R.anim.overlay_task_fragment_close_to_top; + case CONTAINER_POSITION_RIGHT -> R.anim.overlay_task_fragment_close_to_right; + case CONTAINER_POSITION_BOTTOM -> R.anim.overlay_task_fragment_close_to_bottom; + default -> { + Log.w(TAG, "Unknown position:" + position); + yield Resources.ID_NULL; + } + }; + } + /** * Returns the expanded bounds if the {@code relBounds} violate minimum dimension or are not * fully covered by the task bounds. Otherwise, returns {@code relBounds}. @@ -757,7 +872,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { void expandTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container) { super.expandTaskFragment(wct, container); - mController.updateDivider(wct, container.getTaskContainer()); + mController.updateDivider( + wct, container.getTaskContainer(), false /* isTaskFragmentVanished */); } static boolean shouldShowSplit(@NonNull SplitContainer splitContainer) { 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 ee00c4cd67eb..20ad53ee19a8 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java @@ -108,6 +108,12 @@ class TaskContainer { private boolean mPlaceholderRuleSuppressed; /** + * {@code true} if the TaskFragments in this Task needs to be updated next time the Task + * becomes visible. See {@link #shouldUpdateContainer(TaskFragmentParentInfo)} + */ + boolean mTaskFragmentContainersNeedsUpdate; + + /** * The {@link TaskContainer} constructor * * @param taskId The ID of the Task, which must match {@link Activity#getTaskId()} with @@ -185,7 +191,8 @@ class TaskContainer { // If the task properties equals regardless of starting position, don't // need to update the container. - return mInfo.getConfiguration().diffPublicOnly(configuration) != 0 + return mTaskFragmentContainersNeedsUpdate + || mInfo.getConfiguration().diffPublicOnly(configuration) != 0 || mInfo.getDisplayId() != info.getDisplayId(); } 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 070fa5bcfae4..859bc2cc40f3 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java @@ -17,6 +17,7 @@ package androidx.window.extensions.layout; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.Display.INVALID_DISPLAY; import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_FLAT; import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_HALF_OPENED; @@ -41,6 +42,7 @@ import androidx.annotation.GuardedBy; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.UiContext; +import androidx.annotation.VisibleForTesting; import androidx.window.common.CommonFoldingFeature; import androidx.window.common.DeviceStateManagerFoldingFeatureProducer; import androidx.window.common.EmptyLifecycleCallbacksAdapter; @@ -138,6 +140,10 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { throw new IllegalArgumentException("Context must be a UI Context, which should be" + " an Activity, WindowContext or InputMethodService"); } + if (context.getAssociatedDisplayId() == INVALID_DISPLAY) { + Log.w(TAG, "The registered Context is a UI Context but not associated with any" + + " display. This Context may not receive any WindowLayoutInfo update"); + } mFoldingFeatureProducer.getData((features) -> { WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(context, features); consumer.accept(newWindowLayout); @@ -257,7 +263,8 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { } } - private void onDisplayFeaturesChanged(List<CommonFoldingFeature> storedFeatures) { + @VisibleForTesting + void onDisplayFeaturesChanged(List<CommonFoldingFeature> storedFeatures) { synchronized (mLock) { mLastReportedFoldingFeatures.clear(); mLastReportedFoldingFeatures.addAll(storedFeatures); @@ -409,9 +416,10 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { * @return true if the display features should be reported for the UI Context, false otherwise. */ private boolean shouldReportDisplayFeatures(@NonNull @UiContext Context context) { - int displayId = context.getDisplay().getDisplayId(); + int displayId = context.getAssociatedDisplayId(); if (displayId != DEFAULT_DISPLAY) { - // Display features are not supported on secondary displays. + // Display features are not supported on secondary displays or the context is not + // associated with any display. return false; } 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 4267749dfa6b..c5aaddc4e0ed 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,6 +29,7 @@ import android.view.WindowManager; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import androidx.window.extensions.embedding.AnimationBackground; +import androidx.window.extensions.embedding.AnimationParams; import androidx.window.extensions.embedding.SplitAttributes; import org.junit.Before; @@ -112,5 +113,13 @@ public class WindowExtensionsTest { .isEqualTo(new SplitAttributes.SplitType.RatioSplitType(0.5f)); assertThat(splitAttributes.getAnimationBackground()) .isEqualTo(AnimationBackground.ANIMATION_BACKGROUND_DEFAULT); + assertThat(splitAttributes.getAnimationParams().getAnimationBackground()) + .isEqualTo(AnimationBackground.ANIMATION_BACKGROUND_DEFAULT); + assertThat(splitAttributes.getAnimationParams().getOpenAnimationResId()) + .isEqualTo(AnimationParams.DEFAULT_ANIMATION_RESOURCES_ID); + assertThat(splitAttributes.getAnimationParams().getCloseAnimationResId()) + .isEqualTo(AnimationParams.DEFAULT_ANIMATION_RESOURCES_ID); + assertThat(splitAttributes.getAnimationParams().getChangeAnimationResId()) + .isEqualTo(AnimationParams.DEFAULT_ANIMATION_RESOURCES_ID); } } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java index 4f51815ed05d..bc18cd289e05 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java @@ -82,6 +82,7 @@ import java.util.concurrent.Executor; */ @Presubmit @SmallTest +@SuppressWarnings("GuardedBy") @RunWith(AndroidJUnit4.class) public class DividerPresenterTest { @Rule @@ -186,7 +187,8 @@ public class DividerPresenterTest { mDividerPresenter.updateDivider( mTransaction, mParentInfo, - mSplitContainer); + mSplitContainer, + false /* isTaskFragmentVanished */); assertNotEquals(mProperties, mDividerPresenter.mProperties); verify(mRenderer).update(); @@ -206,7 +208,8 @@ public class DividerPresenterTest { mDividerPresenter.updateDivider( mTransaction, mParentInfo, - mSplitContainer); + mSplitContainer, + false /* isTaskFragmentVanished */); assertNotEquals(mProperties, mDividerPresenter.mProperties); verify(mRenderer).update(); @@ -222,7 +225,8 @@ public class DividerPresenterTest { mDividerPresenter.updateDivider( mTransaction, mParentInfo, - mSplitContainer); + mSplitContainer, + false /* isTaskFragmentVanished */); assertEquals(mProperties, mDividerPresenter.mProperties); verify(mRenderer, never()).update(); @@ -234,7 +238,42 @@ public class DividerPresenterTest { mDividerPresenter.updateDivider( mTransaction, mParentInfo, - null /* splitContainer */); + null /* splitContainer */, + false /* isTaskFragmentVanished */); + final TaskFragmentOperation taskFragmentOperation = new TaskFragmentOperation.Builder( + OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE) + .build(); + + verify(mTransaction).addTaskFragmentOperation( + mPrimaryContainerToken, taskFragmentOperation); + verify(mRenderer).release(); + assertNull(mDividerPresenter.mRenderer); + assertNull(mDividerPresenter.mProperties); + assertNull(mDividerPresenter.mDecorSurfaceOwner); + } + + @Test + public void testUpdateDivider_noChangeWhenHasContainersToFinishButTaskFragmentNotVanished() { + mDividerPresenter.setHasContainersToFinish(true); + mDividerPresenter.updateDivider( + mTransaction, + mParentInfo, + null /* splitContainer */, + false /* isTaskFragmentVanished */); + + assertEquals(mProperties, mDividerPresenter.mProperties); + verify(mRenderer, never()).update(); + verify(mTransaction, never()).addTaskFragmentOperation(any(), any()); + } + + @Test + public void testUpdateDivider_dividerRemovedWhenHasContainersToFinishAndTaskFragmentVanished() { + mDividerPresenter.setHasContainersToFinish(true); + mDividerPresenter.updateDivider( + mTransaction, + mParentInfo, + null /* splitContainer */, + true /* isTaskFragmentVanished */); final TaskFragmentOperation taskFragmentOperation = new TaskFragmentOperation.Builder( OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE) .build(); @@ -254,7 +293,8 @@ public class DividerPresenterTest { mDividerPresenter.updateDivider( mTransaction, mParentInfo, - mSplitContainer); + mSplitContainer, + false /* isTaskFragmentVanished */); final TaskFragmentOperation taskFragmentOperation = new TaskFragmentOperation.Builder( OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE) .build(); diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java index 7a0b9a0ece6b..325750243744 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java @@ -30,6 +30,11 @@ import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSpli import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPlaceholderRuleBuilder; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTfContainer; +import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_BOTTOM; +import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_LEFT; +import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_RIGHT; +import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_TOP; +import static androidx.window.extensions.embedding.SplitPresenter.getOverlayPosition; import static androidx.window.extensions.embedding.SplitPresenter.sanitizeBounds; import static androidx.window.extensions.embedding.WindowAttributes.DIM_AREA_ON_TASK; @@ -666,8 +671,8 @@ public class OverlayPresentationTest { attributes.getRelativeBounds()); verify(mSplitPresenter).updateTaskFragmentWindowingModeIfRegistered(mTransaction, container, WINDOWING_MODE_MULTI_WINDOW); - verify(mSplitPresenter).updateAnimationParams(mTransaction, token, - TaskFragmentAnimationParams.DEFAULT); + verify(mSplitPresenter).updateAnimationParams(eq(mTransaction), eq(token), + any(TaskFragmentAnimationParams.class)); verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, true); verify(mSplitPresenter, never()).setTaskFragmentPinned(any(), any(TaskFragmentContainer.class), anyBoolean()); @@ -691,8 +696,8 @@ public class OverlayPresentationTest { attributes.getRelativeBounds()); verify(mSplitPresenter).updateTaskFragmentWindowingModeIfRegistered(mTransaction, container, WINDOWING_MODE_MULTI_WINDOW); - verify(mSplitPresenter).updateAnimationParams(mTransaction, token, - TaskFragmentAnimationParams.DEFAULT); + verify(mSplitPresenter).updateAnimationParams(eq(mTransaction), eq(token), + any(TaskFragmentAnimationParams.class)); verify(mSplitPresenter, never()).setTaskFragmentIsolatedNavigation(any(), any(TaskFragmentContainer.class), anyBoolean()); verify(mSplitPresenter).setTaskFragmentPinned(mTransaction, container, true); @@ -870,6 +875,59 @@ public class OverlayPresentationTest { eq(overlayContainer.getTaskFragmentToken()), eq(activityToken)); } + // TODO(b/243518738): Rewrite with TestParameter. + @Test + public void testGetOverlayPosition() { + assertWithMessage("It must be position left for left overlay.") + .that(getOverlayPosition(new Rect( + TASK_BOUNDS.left, + TASK_BOUNDS.top, + TASK_BOUNDS.right / 2, + TASK_BOUNDS.bottom), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_LEFT); + assertWithMessage("It must be position left for shrunk left overlay.") + .that(getOverlayPosition(new Rect( + TASK_BOUNDS.left, + TASK_BOUNDS.top + 20, + TASK_BOUNDS.right / 2, + TASK_BOUNDS.bottom - 20), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_LEFT); + assertWithMessage("It must be position left for top overlay.") + .that(getOverlayPosition(new Rect( + TASK_BOUNDS.left, + TASK_BOUNDS.top, + TASK_BOUNDS.right, + TASK_BOUNDS.bottom / 2), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_TOP); + assertWithMessage("It must be position left for shrunk top overlay.") + .that(getOverlayPosition(new Rect( + TASK_BOUNDS.left + 20, + TASK_BOUNDS.top, + TASK_BOUNDS.right - 20, + TASK_BOUNDS.bottom / 2), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_TOP); + assertWithMessage("It must be position left for right overlay.") + .that(getOverlayPosition(new Rect( + TASK_BOUNDS.right / 2, + TASK_BOUNDS.top, + TASK_BOUNDS.right, + TASK_BOUNDS.bottom), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_RIGHT); + assertWithMessage("It must be position left for shrunk right overlay.") + .that(getOverlayPosition(new Rect( + TASK_BOUNDS.right / 2, + TASK_BOUNDS.top + 20, + TASK_BOUNDS.right, + TASK_BOUNDS.bottom - 20), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_RIGHT); + assertWithMessage("It must be position left for bottom overlay.") + .that(getOverlayPosition(new Rect( + TASK_BOUNDS.left, + TASK_BOUNDS.bottom / 2, + TASK_BOUNDS.right, + TASK_BOUNDS.bottom), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_BOTTOM); + assertWithMessage("It must be position left for shrunk bottom overlay.") + .that(getOverlayPosition(new Rect( + TASK_BOUNDS.left + 20, + TASK_BOUNDS.bottom / 20, + TASK_BOUNDS.right - 20, + TASK_BOUNDS.bottom), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_BOTTOM); + } + /** * A simplified version of {@link SplitController#createOrUpdateOverlayTaskFragmentIfNeeded} */ 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 640b1fced455..efeec82b782e 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 @@ -200,12 +200,14 @@ public class SplitControllerTest { public void testOnTaskFragmentVanished() { final TaskFragmentContainer tf = createTfContainer(mSplitController, mActivity); doReturn(tf.getTaskFragmentToken()).when(mInfo).getFragmentToken(); + doReturn(createTestTaskContainer()).when(mSplitController).getTaskContainer(TASK_ID); // The TaskFragment has been removed in the server, we only need to cleanup the reference. - mSplitController.onTaskFragmentVanished(mTransaction, mInfo); + mSplitController.onTaskFragmentVanished(mTransaction, mInfo, TASK_ID); verify(mSplitPresenter, never()).deleteTaskFragment(any(), any()); verify(mSplitController).removeContainer(tf); + verify(mSplitController).updateDivider(any(), any(), anyBoolean()); verify(mTransaction, never()).finishActivity(any()); } @@ -1152,7 +1154,7 @@ public class SplitControllerTest { .setTaskFragmentInfo(info)); mSplitController.onTransactionReady(transaction); - verify(mSplitController).onTaskFragmentVanished(any(), eq(info)); + verify(mSplitController).onTaskFragmentVanished(any(), eq(info), anyInt()); verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any(), anyInt(), anyBoolean()); } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/layout/WindowLayoutComponentImplTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/layout/WindowLayoutComponentImplTest.java new file mode 100644 index 000000000000..ff0a82fe05d6 --- /dev/null +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/layout/WindowLayoutComponentImplTest.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.extensions.layout; + +import static org.mockito.Mockito.mock; + +import android.content.Context; +import android.content.ContextWrapper; +import android.platform.test.annotations.Presubmit; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; +import androidx.window.common.DeviceStateManagerFoldingFeatureProducer; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Collections; + +/** + * Test class for {@link WindowLayoutComponentImpl}. + * + * Build/Install/Run: + * atest WMJetpackUnitTests:WindowLayoutComponentImplTest + */ +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +public class WindowLayoutComponentImplTest { + + private WindowLayoutComponentImpl mWindowLayoutComponent; + + @Before + public void setUp() { + mWindowLayoutComponent = new WindowLayoutComponentImpl( + ApplicationProvider.getApplicationContext(), + mock(DeviceStateManagerFoldingFeatureProducer.class)); + } + + @Test + public void testAddWindowLayoutListenerOnFakeUiContext_noCrash() { + final Context fakeUiContext = createTestContext(); + + mWindowLayoutComponent.addWindowLayoutInfoListener(fakeUiContext, info -> {}); + + mWindowLayoutComponent.onDisplayFeaturesChanged(Collections.emptyList()); + } + + private static Context createTestContext() { + return new FakeUiContext(ApplicationProvider.getApplicationContext()); + } + + /** + * A {@link android.content.Context} overrides {@link android.content.Context#isUiContext} to + * {@code true}. + */ + private static class FakeUiContext extends ContextWrapper { + + FakeUiContext(Context base) { + super(base); + } + + @Override + public boolean isUiContext() { + return true; + } + } +} diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp index 25d3067a34bc..dbcad8aab45b 100644 --- a/libs/WindowManager/Shell/Android.bp +++ b/libs/WindowManager/Shell/Android.bp @@ -88,7 +88,7 @@ genrule { ], tools: ["protologtool"], cmd: "$(location protologtool) transform-protolog-calls " + - "--protolog-class com.android.internal.protolog.common.ProtoLog " + + "--protolog-class com.android.internal.protolog.ProtoLog " + "--loggroups-class com.android.wm.shell.protolog.ShellProtoLogGroup " + "--loggroups-jar $(location :wm_shell_protolog-groups) " + "--viewer-config-file-path /system_ext/etc/wmshell.protolog.pb " + @@ -107,7 +107,7 @@ genrule { ], tools: ["protologtool"], cmd: "$(location protologtool) generate-viewer-config " + - "--protolog-class com.android.internal.protolog.common.ProtoLog " + + "--protolog-class com.android.internal.protolog.ProtoLog " + "--loggroups-class com.android.wm.shell.protolog.ShellProtoLogGroup " + "--loggroups-jar $(location :wm_shell_protolog-groups) " + "--viewer-config-type json " + @@ -124,7 +124,7 @@ genrule { ], tools: ["protologtool"], cmd: "$(location protologtool) generate-viewer-config " + - "--protolog-class com.android.internal.protolog.common.ProtoLog " + + "--protolog-class com.android.internal.protolog.ProtoLog " + "--loggroups-class com.android.wm.shell.protolog.ShellProtoLogGroup " + "--loggroups-jar $(location :wm_shell_protolog-groups) " + "--viewer-config-type proto " + @@ -190,6 +190,15 @@ java_library { ], } +java_library { + name: "WindowManager-Shell-shared-desktopMode", + + srcs: [ + "shared/**/desktopmode/*.java", + "shared/**/desktopmode/*.kt", + ], +} + android_library { name: "WindowManager-Shell", srcs: [ diff --git a/libs/WindowManager/Shell/AndroidManifest.xml b/libs/WindowManager/Shell/AndroidManifest.xml index bcb1d292fce2..52ae93f5ebf1 100644 --- a/libs/WindowManager/Shell/AndroidManifest.xml +++ b/libs/WindowManager/Shell/AndroidManifest.xml @@ -29,6 +29,7 @@ android:name=".desktopmode.DesktopWallpaperActivity" android:excludeFromRecents="true" android:launchMode="singleInstance" + android:showForAllUsers="true" android:theme="@style/DesktopWallpaperTheme" /> <activity diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig index 112eb617e7a6..3ff40e0886a4 100644 --- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig +++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig @@ -121,3 +121,20 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "enable_taskbar_on_phones" + namespace: "multitasking" + description: "Enables taskbar on phones" + bug: "348007377" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "enable_bubble_bar_in_persistent_task_bar" + namespace: "multitasking" + description: "Enable bubble bar to be shown in the persistent task bar" + bug: "346391377" +} diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp b/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp new file mode 100644 index 000000000000..1871203c7600 --- /dev/null +++ b/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp @@ -0,0 +1,95 @@ +// Copyright (C) 2024 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], + default_team: "trendy_team_multitasking_windowing", +} + +android_app { + name: "WMShellRobolectricScreenshotTestApp", + platform_apis: true, + certificate: "platform", + static_libs: [ + "WindowManager-Shell", + "platform-screenshot-diff-core", + ], + asset_dirs: ["goldens/robolectric"], + manifest: "AndroidManifestRobolectric.xml", + use_resource_processor: true, +} + +android_robolectric_test { + name: "WMShellRobolectricScreenshotTests", + instrumentation_for: "WMShellRobolectricScreenshotTestApp", + upstream: true, + java_resource_dirs: [ + "robolectric/config", + ], + srcs: [ + "src/**/*.kt", + ], + static_libs: [ + "junit", + "androidx.test.runner", + "androidx.test.rules", + "androidx.test.ext.junit", + "truth", + "platform-parametric-runner-lib", + ], + auto_gen_config: true, +} + +android_test { + name: "WMShellMultivalentScreenshotTestsOnDevice", + srcs: [ + "src/**/*.kt", + ], + static_libs: [ + "WindowManager-Shell", + "junit", + "androidx.test.runner", + "androidx.test.rules", + "androidx.test.ext.junit", + "truth", + "platform-parametric-runner-lib", + "platform-screenshot-diff-core", + ], + libs: [ + "android.test.base", + "android.test.runner", + ], + jni_libs: [ + "libdexmakerjvmtiagent", + "libstaticjvmtiagent", + ], + kotlincflags: ["-Xjvm-default=all"], + optimize: { + enabled: false, + }, + test_suites: ["device-tests"], + platform_apis: true, + certificate: "platform", + aaptflags: [ + "--extra-packages", + "com.android.wm.shell", + ], + manifest: "AndroidManifest.xml", + asset_dirs: ["goldens/onDevice"], +} diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidManifest.xml b/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidManifest.xml new file mode 100644 index 000000000000..467dc6a5cb81 --- /dev/null +++ b/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidManifest.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.wm.shell.multivalentscreenshot"> + + <application android:debuggable="true" android:supportsRtl="true" > + <uses-library android:name="android.test.runner" /> + <activity + android:name="platform.test.screenshot.ScreenshotActivity" + android:exported="true"> + </activity> + </application> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:label="Multivalent screenshot tests for WindowManager-Shell" + android:targetPackage="com.android.wm.shell.multivalentscreenshot"> + </instrumentation> + + <!-- this permission is required by Tuner Service in screenshot tests --> + <uses-permission android:name="android.permission.MANAGE_USERS" /> +</manifest> diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidManifestRobolectric.xml b/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidManifestRobolectric.xml new file mode 100644 index 000000000000..b4bdaeaf0eac --- /dev/null +++ b/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidManifestRobolectric.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.wm.shell.multivalentscreenshot"> + <application android:debuggable="true" android:supportsRtl="true"> + <activity + android:name="platform.test.screenshot.ScreenshotActivity" + android:exported="true"> + </activity> + </application> +</manifest> diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidTest.xml b/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidTest.xml new file mode 100644 index 000000000000..75793ae69d27 --- /dev/null +++ b/libs/WindowManager/Shell/multivalentScreenshotTests/AndroidTest.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration description="Runs Tests for WindowManagerShellLib"> + <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" /> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="install-arg" value="-t" /> + <option name="test-file-name" value="WMShellMultivalentScreenshotTestsOnDevice.apk" /> + </target_preparer> + + <option name="test-suite-tag" value="apct" /> + <option name="test-tag" value="WMShellMultivalentScreenshotTestsOnDevice" /> + <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> + <option name="directory-keys" value="/data/user/0/com.android.wm.shell.multivalentscreenshot/files/wmshell_screenshots" /> + <option name="collect-on-run-ended-only" value="true" /> + </metrics_collector> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.wm.shell.multivalentscreenshot" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + <option name="hidden-api-checks" value="false"/> + </test> +</configuration> diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/OWNERS b/libs/WindowManager/Shell/multivalentScreenshotTests/OWNERS new file mode 100644 index 000000000000..dc11241fb76b --- /dev/null +++ b/libs/WindowManager/Shell/multivalentScreenshotTests/OWNERS @@ -0,0 +1,4 @@ +atsjenk@google.com +liranb@google.com +madym@google.com +mpodolian@google.com
\ No newline at end of file diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/dark_portrait_bubbles_education.png b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/dark_portrait_bubbles_education.png Binary files differnew file mode 100644 index 000000000000..027b28e7ace7 --- /dev/null +++ b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/dark_portrait_bubbles_education.png diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/light_portrait_bubbles_education.png b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/light_portrait_bubbles_education.png Binary files differnew file mode 100644 index 000000000000..027b28e7ace7 --- /dev/null +++ b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/light_portrait_bubbles_education.png diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/robolectric/phone/dark_portrait_bubbles_education.png b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/robolectric/phone/dark_portrait_bubbles_education.png Binary files differnew file mode 100644 index 000000000000..e02c89ae07bd --- /dev/null +++ b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/robolectric/phone/dark_portrait_bubbles_education.png diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/robolectric/phone/light_portrait_bubbles_education.png b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/robolectric/phone/light_portrait_bubbles_education.png Binary files differnew file mode 100644 index 000000000000..e02c89ae07bd --- /dev/null +++ b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/robolectric/phone/light_portrait_bubbles_education.png diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/robolectric/config/robolectric.properties b/libs/WindowManager/Shell/multivalentScreenshotTests/robolectric/config/robolectric.properties new file mode 100644 index 000000000000..d50d976c9e84 --- /dev/null +++ b/libs/WindowManager/Shell/multivalentScreenshotTests/robolectric/config/robolectric.properties @@ -0,0 +1,3 @@ +sdk=NEWEST_SDK +graphicsMode=NATIVE + diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/bubbles/BubbleEducationViewScreenshotTest.kt b/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/bubbles/BubbleEducationViewScreenshotTest.kt new file mode 100644 index 000000000000..d35f493a8f60 --- /dev/null +++ b/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/bubbles/BubbleEducationViewScreenshotTest.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.wm.shell.bubbles + +import android.view.LayoutInflater +import com.android.wm.shell.common.bubbles.BubblePopupView +import com.android.wm.shell.testing.goldenpathmanager.WMShellGoldenPathManager +import com.android.wm.shell.R +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters +import platform.test.screenshot.DeviceEmulationSpec +import platform.test.screenshot.Displays +import platform.test.screenshot.ViewScreenshotTestRule +import platform.test.screenshot.getEmulatedDevicePathConfig + +@RunWith(ParameterizedAndroidJunit4::class) +class BubbleEducationViewScreenshotTest(emulationSpec: DeviceEmulationSpec) { + companion object { + @Parameters(name = "{0}") + @JvmStatic + fun getTestSpecs() = DeviceEmulationSpec.forDisplays(Displays.Phone, isLandscape = false) + } + + @get:Rule + val screenshotRule = + ViewScreenshotTestRule( + emulationSpec, + WMShellGoldenPathManager(getEmulatedDevicePathConfig(emulationSpec)) + ) + + @Test + fun bubblesEducation() { + screenshotRule.screenshotTest("bubbles_education") { activity -> + activity.actionBar?.hide() + val view = + LayoutInflater.from(activity) + .inflate(R.layout.bubble_bar_stack_education, null) as BubblePopupView + view.setup() + view + } + } +} diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/testing/goldenpathmanager/WMShellGoldenPathManager.kt b/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/testing/goldenpathmanager/WMShellGoldenPathManager.kt new file mode 100644 index 000000000000..901b79b9b1a0 --- /dev/null +++ b/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/testing/goldenpathmanager/WMShellGoldenPathManager.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.wm.shell.testing.goldenpathmanager + +import android.os.Build +import androidx.test.platform.app.InstrumentationRegistry +import platform.test.screenshot.GoldenPathManager +import platform.test.screenshot.PathConfig + +/** A WM Shell specific implementation of [GoldenPathManager]. */ +class WMShellGoldenPathManager(pathConfig: PathConfig) : + GoldenPathManager( + appContext = InstrumentationRegistry.getInstrumentation().context, + assetsPathRelativeToBuildRoot = assetPath, + deviceLocalPath = deviceLocalPath, + pathConfig = pathConfig, + ) { + + private companion object { + private const val ASSETS_PATH = + "frameworks/base/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice" + private const val ASSETS_PATH_ROBO = + "frameworks/base/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/" + + "robolectric" + private val assetPath: String + get() = if (Build.FINGERPRINT.contains("robolectric")) ASSETS_PATH_ROBO else ASSETS_PATH + private val deviceLocalPath: String + get() = + InstrumentationRegistry.getInstrumentation() + .targetContext + .filesDir + .absolutePath + .toString() + "/wmshell_screenshots" + } + override fun toString(): String { + // This string is appended to all actual/expected screenshots on the device, so make sure + // it is a static value. + return "WMShellGoldenPathManager" + } +} diff --git a/libs/WindowManager/Shell/multivalentScreenshotTestsForDevice b/libs/WindowManager/Shell/multivalentScreenshotTestsForDevice new file mode 120000 index 000000000000..e879efc81ec1 --- /dev/null +++ b/libs/WindowManager/Shell/multivalentScreenshotTestsForDevice @@ -0,0 +1 @@ +multivalentScreenshotTests
\ No newline at end of file diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt index 9e1440d5716b..ae60d8bc2596 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt @@ -27,7 +27,7 @@ import android.view.WindowManager import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.internal.protolog.common.ProtoLog +import com.android.internal.protolog.ProtoLog import com.android.wm.shell.R import com.android.wm.shell.bubbles.BubblePositioner.MAX_HEIGHT import com.android.wm.shell.common.bubbles.BubbleBarLocation diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt index 327e2059557c..5e673338bad3 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt @@ -32,7 +32,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry import com.android.internal.logging.testing.UiEventLoggerFake -import com.android.internal.protolog.common.ProtoLog +import com.android.internal.protolog.ProtoLog import com.android.launcher3.icons.BubbleIconFactory import com.android.wm.shell.Flags import com.android.wm.shell.R diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt index ace2c131050c..935d12916f56 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt @@ -27,7 +27,7 @@ import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation -import com.android.internal.protolog.common.ProtoLog +import com.android.internal.protolog.ProtoLog import com.android.wm.shell.R import com.android.wm.shell.bubbles.BubblePositioner import com.android.wm.shell.bubbles.DeviceConfig diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_open_in_browser.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_open_in_browser.xml new file mode 100644 index 000000000000..7d912a24c443 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_open_in_browser.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="960" android:viewportHeight="960" android:tint="?attr/colorControlNormal"> + <path android:fillColor="@android:color/black" android:pathData="M160,880Q127,880 103.5,856.5Q80,833 80,800L80,440Q80,407 103.5,383.5Q127,360 160,360L240,360L240,160Q240,127 263.5,103.5Q287,80 320,80L800,80Q833,80 856.5,103.5Q880,127 880,160L880,520Q880,553 856.5,576.5Q833,600 800,600L720,600L720,800Q720,833 696.5,856.5Q673,880 640,880L160,880ZM160,800L640,800Q640,800 640,800Q640,800 640,800L640,520L160,520L160,800Q160,800 160,800Q160,800 160,800ZM720,520L800,520Q800,520 800,520Q800,520 800,520L800,240L320,240L320,360L640,360Q673,360 696.5,383.5Q720,407 720,440L720,520Z"/> +</vector> diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml index 34f03c2f226b..501bedd50f55 100644 --- a/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml +++ b/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml @@ -19,7 +19,7 @@ android:layout_height="wrap_content" android:layout_width="wrap_content" android:orientation="vertical" - android:id="@+id/bubble_bar_expanded_view"> + android:id="@+id/bubble_expanded_view"> <com.android.wm.shell.bubbles.bar.BubbleBarHandleView android:id="@+id/bubble_bar_handle_view" diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml index d5724cc6a420..419d5c0af1a4 100644 --- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml +++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml @@ -135,5 +135,24 @@ android:drawableTint="?androidprv:attr/materialColorOnSurface" style="@style/DesktopModeHandleMenuActionButton"/> </LinearLayout> + + <LinearLayout + android:id="@+id/open_in_browser_pill" + android:layout_width="match_parent" + android:layout_height="@dimen/desktop_mode_handle_menu_open_in_browser_pill_height" + android:layout_marginTop="@dimen/desktop_mode_handle_menu_pill_spacing_margin" + android:layout_marginStart="1dp" + android:orientation="vertical" + android:elevation="1dp" + android:background="@drawable/desktop_mode_decor_handle_menu_background"> + + <Button + android:id="@+id/open_in_browser_button" + android:contentDescription="@string/open_in_browser_text" + android:text="@string/open_in_browser_text" + android:drawableStart="@drawable/desktop_mode_ic_handle_menu_open_in_browser" + android:drawableTint="?androidprv:attr/materialColorOnSurface" + style="@style/DesktopModeHandleMenuActionButton"/> + </LinearLayout> </LinearLayout> diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml index 7d5f9cdbebc8..5fe3f2af63a0 100644 --- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml +++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml @@ -14,88 +14,100 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:id="@+id/maximize_menu" - style="?android:attr/buttonBarStyle" android:layout_width="@dimen/desktop_mode_maximize_menu_width" android:layout_height="@dimen/desktop_mode_maximize_menu_height" - android:orientation="horizontal" - android:gravity="center" - android:padding="16dp" android:background="@drawable/desktop_mode_maximize_menu_background" android:elevation="1dp"> <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="vertical"> + android:id="@+id/container" + android:layout_width="@dimen/desktop_mode_maximize_menu_width" + android:layout_height="@dimen/desktop_mode_maximize_menu_height" + android:orientation="horizontal" + android:padding="16dp" + android:gravity="center"> - <Button - android:layout_width="94dp" - android:layout_height="60dp" - android:id="@+id/maximize_menu_maximize_button" - style="?android:attr/buttonBarButtonStyle" - android:stateListAnimator="@null" - android:layout_marginRight="8dp" - android:layout_marginBottom="4dp" - android:alpha="0"/> - - <TextView - android:id="@+id/maximize_menu_maximize_window_text" - android:layout_width="94dp" - android:layout_height="18dp" - android:textSize="11sp" - android:layout_marginBottom="76dp" - android:gravity="center" - android:fontFamily="google-sans-text" - android:text="@string/desktop_mode_maximize_menu_maximize_text" - android:textColor="?androidprv:attr/materialColorOnSurface" - android:alpha="0"/> - </LinearLayout> - - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="vertical"> <LinearLayout - android:id="@+id/maximize_menu_snap_menu_layout" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:orientation="horizontal" - android:padding="4dp" - android:background="@drawable/desktop_mode_maximize_menu_layout_background" - android:layout_marginBottom="4dp" - android:alpha="0"> - <Button - android:id="@+id/maximize_menu_snap_left_button" - style="?android:attr/buttonBarButtonStyle" - android:layout_width="41dp" - android:layout_height="@dimen/desktop_mode_maximize_menu_button_height" - android:layout_marginRight="4dp" - android:background="@drawable/desktop_mode_maximize_menu_button_background" - android:stateListAnimator="@null"/> + android:orientation="vertical"> <Button - android:id="@+id/maximize_menu_snap_right_button" + android:layout_width="94dp" + android:layout_height="60dp" + android:id="@+id/maximize_menu_maximize_button" style="?android:attr/buttonBarButtonStyle" - android:layout_width="41dp" - android:layout_height="@dimen/desktop_mode_maximize_menu_button_height" - android:background="@drawable/desktop_mode_maximize_menu_button_background" - android:stateListAnimator="@null"/> + android:stateListAnimator="@null" + android:layout_marginRight="8dp" + android:layout_marginBottom="4dp" + android:alpha="0"/> + + <TextView + android:id="@+id/maximize_menu_maximize_window_text" + android:layout_width="94dp" + android:layout_height="18dp" + android:textSize="11sp" + android:layout_marginBottom="76dp" + android:gravity="center" + android:fontFamily="google-sans-text" + android:text="@string/desktop_mode_maximize_menu_maximize_text" + android:textColor="?androidprv:attr/materialColorOnSurface" + android:alpha="0"/> + </LinearLayout> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical"> + <LinearLayout + android:id="@+id/maximize_menu_snap_menu_layout" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:padding="4dp" + android:background="@drawable/desktop_mode_maximize_menu_layout_background" + android:layout_marginBottom="4dp" + android:alpha="0"> + <Button + android:id="@+id/maximize_menu_snap_left_button" + style="?android:attr/buttonBarButtonStyle" + android:layout_width="41dp" + android:layout_height="@dimen/desktop_mode_maximize_menu_button_height" + android:layout_marginRight="4dp" + android:background="@drawable/desktop_mode_maximize_menu_button_background" + android:stateListAnimator="@null"/> + + <Button + android:id="@+id/maximize_menu_snap_right_button" + style="?android:attr/buttonBarButtonStyle" + android:layout_width="41dp" + android:layout_height="@dimen/desktop_mode_maximize_menu_button_height" + android:background="@drawable/desktop_mode_maximize_menu_button_background" + android:stateListAnimator="@null"/> + </LinearLayout> + <TextView + android:id="@+id/maximize_menu_snap_window_text" + android:layout_width="94dp" + android:layout_height="18dp" + android:textSize="11sp" + android:layout_marginBottom="76dp" + android:layout_gravity="center" + android:gravity="center" + android:fontFamily="google-sans-text" + android:text="@string/desktop_mode_maximize_menu_snap_text" + android:textColor="?androidprv:attr/materialColorOnSurface" + android:alpha="0"/> </LinearLayout> - <TextView - android:id="@+id/maximize_menu_snap_window_text" - android:layout_width="94dp" - android:layout_height="18dp" - android:textSize="11sp" - android:layout_marginBottom="76dp" - android:layout_gravity="center" - android:gravity="center" - android:fontFamily="google-sans-text" - android:text="@string/desktop_mode_maximize_menu_snap_text" - android:textColor="?androidprv:attr/materialColorOnSurface" - android:alpha="0"/> </LinearLayout> -</LinearLayout> + + <!-- Empty view intentionally placed in front of everything else and matching the menu size + used to monitor input events over the entire menu. --> + <View + android:id="@+id/maximize_menu_overlay" + android:layout_width="@dimen/desktop_mode_maximize_menu_width" + android:layout_height="@dimen/desktop_mode_maximize_menu_height"/> +</FrameLayout> diff --git a/libs/WindowManager/Shell/res/values-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml index 1c8f5e60c5c9..8b328e2c79cf 100644 --- a/libs/WindowManager/Shell/res/values-af/strings.xml +++ b/libs/WindowManager/Shell/res/values-af/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml index 81ab3ab15aad..b005a01711eb 100644 --- a/libs/WindowManager/Shell/res/values-am/strings.xml +++ b/libs/WindowManager/Shell/res/values-am/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"አረፋ"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"ያቀናብሩ"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"አረፋ ተሰናብቷል።"</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml index 3974c39d4803..8c283d33eff6 100644 --- a/libs/WindowManager/Shell/res/values-ar/strings.xml +++ b/libs/WindowManager/Shell/res/values-ar/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"فقاعة"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"إدارة"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"تم إغلاق الفقاعة."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml index a1ce1b3b9513..ef92587ad274 100644 --- a/libs/WindowManager/Shell/res/values-as/strings.xml +++ b/libs/WindowManager/Shell/res/values-as/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"বাবল"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"পৰিচালনা কৰক"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"বাবল অগ্ৰাহ্য কৰা হৈছে"</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-az/strings.xml b/libs/WindowManager/Shell/res/values-az/strings.xml index 71dfe5ac6bed..04b2f1c23bc4 100644 --- a/libs/WindowManager/Shell/res/values-az/strings.xml +++ b/libs/WindowManager/Shell/res/values-az/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> 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 f48360991d49..47bc105f19bc 100644 --- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml +++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml index 532ecc64358f..6ad7553a8383 100644 --- a/libs/WindowManager/Shell/res/values-be/strings.xml +++ b/libs/WindowManager/Shell/res/values-be/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Усплывальнае апавяшчэнне"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Кіраваць"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Усплывальнае апавяшчэнне адхілена."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml index 8f828badcf47..a9e0bce81376 100644 --- a/libs/WindowManager/Shell/res/values-bg/strings.xml +++ b/libs/WindowManager/Shell/res/values-bg/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Балонче"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Управление"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Балончето е отхвърлено."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml index e0a2ea824be0..29de1007e311 100644 --- a/libs/WindowManager/Shell/res/values-bn/strings.xml +++ b/libs/WindowManager/Shell/res/values-bn/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"বাবল"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"ম্যানেজ করুন"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"বাবল বাতিল করা হয়েছে।"</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml index 41c72c1d3a03..5f1da7571d95 100644 --- a/libs/WindowManager/Shell/res/values-bs/strings.xml +++ b/libs/WindowManager/Shell/res/values-bs/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml index 679227248ea5..d70de794ba17 100644 --- a/libs/WindowManager/Shell/res/values-ca/strings.xml +++ b/libs/WindowManager/Shell/res/values-ca/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml index 150a6e642529..ca00fec12e86 100644 --- a/libs/WindowManager/Shell/res/values-cs/strings.xml +++ b/libs/WindowManager/Shell/res/values-cs/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <string name="restart_button_description" msgid="4564728020654658478">"Klepnutím tuto aplikaci restartujete kvůli lepšímu zobrazení"</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> diff --git a/libs/WindowManager/Shell/res/values-da/strings.xml b/libs/WindowManager/Shell/res/values-da/strings.xml index 8878910a4d2c..d50d2f0f135d 100644 --- a/libs/WindowManager/Shell/res/values-da/strings.xml +++ b/libs/WindowManager/Shell/res/values-da/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml index 7b91559e9179..7f44f83a1790 100644 --- a/libs/WindowManager/Shell/res/values-de/strings.xml +++ b/libs/WindowManager/Shell/res/values-de/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml index 14e5e2f87ab8..a3a5ccd839e4 100644 --- a/libs/WindowManager/Shell/res/values-el/strings.xml +++ b/libs/WindowManager/Shell/res/values-el/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Συννεφάκι"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Διαχείριση"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Το συννεφάκι παραβλέφθηκε."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml index 7427b62679be..edc4f4e25c2a 100644 --- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml index cb9ee4f6b6b3..d7e23fd8dfd8 100644 --- a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml @@ -84,6 +84,8 @@ <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="bubble_shortcut_label" msgid="666269077944378311">"Bubbles"</string> + <string name="bubble_shortcut_long_label" msgid="6088437544312894043">"Show Bubbles"</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> diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml index 7427b62679be..edc4f4e25c2a 100644 --- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml index 7427b62679be..edc4f4e25c2a 100644 --- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml index 8498807f9fdb..1da8c275ce54 100644 --- a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml @@ -84,6 +84,8 @@ <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="bubble_shortcut_label" msgid="666269077944378311">"Bubbles"</string> + <string name="bubble_shortcut_long_label" msgid="6088437544312894043">"Show Bubbles"</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> diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml index 406c1f37c455..8653e5932a04 100644 --- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml +++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml index 0583d79da127..8f59c9c91d20 100644 --- a/libs/WindowManager/Shell/res/values-es/strings.xml +++ b/libs/WindowManager/Shell/res/values-es/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml index 70547f566ea6..3d86eb4a91d9 100644 --- a/libs/WindowManager/Shell/res/values-et/strings.xml +++ b/libs/WindowManager/Shell/res/values-et/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml index 4be35eac6c1f..4e7bdd246d10 100644 --- a/libs/WindowManager/Shell/res/values-eu/strings.xml +++ b/libs/WindowManager/Shell/res/values-eu/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml index 32d5f5f34fb8..a4711d4180bc 100644 --- a/libs/WindowManager/Shell/res/values-fa/strings.xml +++ b/libs/WindowManager/Shell/res/values-fa/strings.xml @@ -53,7 +53,7 @@ <string name="accessibility_split_top" msgid="2789329702027147146">"تقسیم از بالا"</string> <string name="accessibility_split_bottom" msgid="8694551025220868191">"تقسیم از پایین"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"استفاده از حالت یکدستی"</string> - <string name="one_handed_tutorial_description" msgid="3486582858591353067">"برای خارج شدن، از پایین صفحهنمایش تند بهطرف بالا بکشید یا در هر جایی از بالای برنامه که میخواهید ضربه بزنید"</string> + <string name="one_handed_tutorial_description" msgid="3486582858591353067">"برای خارج شدن، از پایین صفحهنمایش تند بهطرف بالا بکشید یا در هر جایی از بالای برنامه که میخواهید تکضرب بزنید"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"آغاز «حالت یکدستی»"</string> <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"خروج از «حالت یکدستی»"</string> <string name="bubbles_settings_button_description" msgid="1301286017420516912">"تنظیمات برای حبابکهای <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> @@ -71,28 +71,32 @@ <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_description" msgid="4215862563054175407">"مکالمههای جدید بهصورت نمادهای شناور یا حبابکها نشان داده میشوند. برای باز کردن حبابکها تکضرب بزنید. برای جابهجایی، آن را بکشید."</string> <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"کنترل حبابکها در هرزمانی"</string> - <string name="bubbles_user_education_manage" msgid="3460756219946517198">"برای خاموش کردن حبابکها از این برنامه، روی «مدیریت» ضربه بزنید"</string> + <string name="bubbles_user_education_manage" msgid="3460756219946517198">"برای خاموش کردن حبابکها از این برنامه، روی «مدیریت» تکضرب بزنید"</string> <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"متوجهام"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"هیچ حبابک جدیدی وجود ندارد"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"حبابکهای اخیر و حبابکهای ردشده اینجا ظاهر خواهند شد"</string> <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"گپ زدن بااستفاده از حبابک"</string> - <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"مکالمههای جدید بهصورت نماد در گوشه پایین صفحهنمایش نشان داده میشود. برای ازهم بازکردن آنها ضربه بزنید یا برای بستن، آنها را بکشید."</string> + <string name="bubble_bar_education_stack_text" msgid="2446934610817409820">"مکالمههای جدید بهصورت نماد در گوشه پایین صفحهنمایش نشان داده میشود. برای ازهم بازکردن آنها تکضرب بزنید یا برای بستن، آنها را بکشید."</string> <string name="bubble_bar_education_manage_title" msgid="6148404487810835924">"کنترل حبابکها در هرزمانی"</string> - <string name="bubble_bar_education_manage_text" msgid="3199732148641842038">"برای مدیریت اینکه کدام برنامهها و مکالمهها حباب داشته باشند، ضربه بزنید"</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="4564728020654658478">"برای داشتن نمایی بهتر، ضربه بزنید تا این برنامه بازراهاندازی شود"</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> + <string name="restart_button_description" msgid="4564728020654658478">"برای داشتن نمایی بهتر، تکضرب بزنید تا این برنامه بازراهاندازی شود"</string> <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"نسبت ابعادی این برنامه را در «تنظیمات» تغییر دهید"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"تغییر نسبت ابعادی"</string> - <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"دوربین مشکل دارد؟\nبرای تنظیم مجدد اندازه ضربه بزنید"</string> - <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"مشکل برطرف نشد؟\nبرای برگرداندن ضربه بزنید"</string> - <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"دوربین مشکلی ندارد؟ برای بستن ضربه بزنید."</string> + <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"دوربین مشکل دارد؟\nبرای تنظیم مجدد اندازه تکضرب بزنید"</string> + <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"مشکل برطرف نشد؟\nبرای برگرداندن تکضرب بزنید"</string> + <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"دوربین مشکلی ندارد؟ برای بستن تکضرب بزنید."</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"از چندین برنامه بهطور همزمان استفاده کنید"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"برای حالت صفحهٔ دونیمه، در برنامهای دیگر بکشید"</string> - <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"برای جابهجا کردن برنامه، بیرون از آن دوضربه بزنید"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"برای جابهجا کردن برنامه، بیرون از آن دو تکضرب بزنید"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"متوجهام"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"برای اطلاعات بیشتر، گسترده کنید."</string> <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"برای نمایش بهتر بازراهاندازی شود؟"</string> @@ -100,7 +104,7 @@ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"لغو کردن"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"بازراهاندازی"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"دوباره نشان داده نشود"</string> - <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"برای جابهجا کردن این برنامه\nدوضربه بزنید"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"برای جابهجا کردن این برنامه\nدو تکضرب بزنید"</string> <string name="maximize_button_text" msgid="1650859196290301963">"بزرگ کردن"</string> <string name="minimize_button_text" msgid="271592547935841753">"کوچک کردن"</string> <string name="close_button_text" msgid="2913281996024033299">"بستن"</string> diff --git a/libs/WindowManager/Shell/res/values-fi/strings.xml b/libs/WindowManager/Shell/res/values-fi/strings.xml index 6f03545e5542..577d625ba8f8 100644 --- a/libs/WindowManager/Shell/res/values-fi/strings.xml +++ b/libs/WindowManager/Shell/res/values-fi/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml index 3492f136c4f9..7feb772eb10a 100644 --- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml +++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml @@ -31,11 +31,11 @@ <string name="accessibility_action_pip_resize" msgid="4623966104749543182">"Redimensionner"</string> <string name="accessibility_action_pip_stash" msgid="4060775037619702641">"Ajouter à la réserve"</string> <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Retirer de la réserve"</string> - <string name="dock_forced_resizable" msgid="7429086980048964687">"L\'application peut ne pas fonctionner avec l\'écran partagé"</string> - <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"L\'application ne prend pas en charge l\'écran partagé"</string> - <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Cette application ne peut être ouverte que dans une seule fenêtre."</string> - <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Il est possible que l\'application ne fonctionne pas sur un écran secondaire."</string> - <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"L\'application ne peut pas être lancée sur des écrans secondaires."</string> + <string name="dock_forced_resizable" msgid="7429086980048964687">"L\'appli peut ne pas fonctionner avec l\'écran partagé"</string> + <string name="dock_non_resizeble_failed_to_dock_text" msgid="2733543750291266047">"L\'appli ne prend pas en charge l\'écran partagé"</string> + <string name="dock_multi_instances_not_supported_text" msgid="5011042177901502928">"Cette appli ne peut être ouverte que dans une seule fenêtre."</string> + <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Il est possible que l\'appli ne fonctionne pas sur un écran secondaire."</string> + <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"L\'appli ne peut pas être lancée sur des écrans secondaires."</string> <string name="accessibility_divider" msgid="6407584574218956849">"Séparateur d\'écran partagé"</string> <string name="divider_title" msgid="1963391955593749442">"Séparateur d\'écran partagé"</string> <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Plein écran à la gauche"</string> @@ -56,7 +56,7 @@ <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Pour quitter, balayez l\'écran du bas vers le haut, ou touchez n\'importe où sur l\'écran en haut de l\'appli"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Démarrer le mode Une main"</string> <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Quitter le mode Une main"</string> - <string name="bubbles_settings_button_description" msgid="1301286017420516912">"Paramètres pour les bulles de l\'application <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="bubbles_settings_button_description" msgid="1301286017420516912">"Paramètres pour les bulles de l\'appli <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"Menu déroulant"</string> <string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"Replacer sur la pile"</string> <string name="bubble_content_description_single" msgid="8495748092720065813">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> de <xliff:g id="APP_NAME">%2$s</xliff:g>"</string> @@ -73,7 +73,7 @@ <string name="bubbles_user_education_title" msgid="2112319053732691899">"Clavarder en utilisant des bulles"</string> <string name="bubbles_user_education_description" msgid="4215862563054175407">"Les nouvelles conversations s\'affichent sous forme d\'icônes flottantes (de bulles). Touchez une bulle pour l\'ouvrir. Faites-la glisser pour la déplacer."</string> <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Paramètres des bulles"</string> - <string name="bubbles_user_education_manage" msgid="3460756219946517198">"Toucher Gérer pour désactiver les bulles de cette application"</string> + <string name="bubbles_user_education_manage" msgid="3460756219946517198">"Toucher Gérer pour désactiver les bulles de cette appli"</string> <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> @@ -84,29 +84,33 @@ <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="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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> + <string name="restart_button_description" msgid="4564728020654658478">"Touchez pour redémarrer cette appli afin d\'obtenir un meilleur affichage"</string> + <string name="user_aspect_ratio_settings_button_hint" msgid="734835849600713016">"Changer les proportions de cette appli dans les paramètres"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Modifier les proportions"</string> <string name="camera_compat_treatment_suggested_button_description" msgid="8103916969024076767">"Problèmes d\'appareil photo?\nTouchez pour réajuster"</string> <string name="camera_compat_treatment_applied_button_description" msgid="2944157113330703897">"Problème non résolu?\nTouchez pour rétablir"</string> <string name="camera_compat_dismiss_button_description" msgid="2795364433503817511">"Aucun problème d\'appareil photo? Touchez pour ignorer."</string> <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"Voir et en faire plus"</string> - <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Faites glisser une autre application pour utiliser l\'écran partagé"</string> - <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Touchez deux fois à côté d\'une application pour la repositionner"</string> + <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"Faites glisser une autre appli pour utiliser l\'écran partagé"</string> + <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Touchez deux fois à côté d\'une appli pour la repositionner"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Développer pour en savoir plus."</string> <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Redémarrer pour un meilleur affichage?"</string> - <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Vous pouvez redémarrer l\'application pour qu\'elle s\'affiche mieux sur votre écran, mais il se peut que vous perdiez votre progression ou toute modification non enregistrée"</string> + <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Vous pouvez redémarrer l\'appli pour qu\'elle s\'affiche mieux sur votre écran, mais il se peut que vous perdiez votre progression ou toute modification non enregistrée"</string> <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Annuler"</string> <string name="letterbox_restart_restart" msgid="8529976234412442973">"Redémarrer"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne plus afficher"</string> - <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Toucher deux fois pour\ndéplacer cette application"</string> + <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Toucher deux fois pour\ndéplacer cette appli"</string> <string name="maximize_button_text" msgid="1650859196290301963">"Agrandir"</string> <string name="minimize_button_text" msgid="271592547935841753">"Réduire"</string> <string name="close_button_text" msgid="2913281996024033299">"Fermer"</string> <string name="back_button_text" msgid="1469718707134137085">"Retour"</string> <string name="handle_text" msgid="1766582106752184456">"Identifiant"</string> - <string name="app_icon_text" msgid="2823268023931811747">"Icône de l\'application"</string> + <string name="app_icon_text" msgid="2823268023931811747">"Icône de l\'appli"</string> <string name="fullscreen_text" msgid="1162316685217676079">"Plein écran"</string> <string name="desktop_text" msgid="1077633567027630454">"Mode Bureau"</string> <string name="split_screen_text" msgid="1396336058129570886">"Écran divisé"</string> diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml index 4002e4d04d51..4d14d0b85f3e 100644 --- a/libs/WindowManager/Shell/res/values-fr/strings.xml +++ b/libs/WindowManager/Shell/res/values-fr/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml index c371f7f62feb..e5b67c2aaad1 100644 --- a/libs/WindowManager/Shell/res/values-gl/strings.xml +++ b/libs/WindowManager/Shell/res/values-gl/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-gu/strings.xml b/libs/WindowManager/Shell/res/values-gu/strings.xml index 7e3d7a373be4..e2a52dccd8ea 100644 --- a/libs/WindowManager/Shell/res/values-gu/strings.xml +++ b/libs/WindowManager/Shell/res/values-gu/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"બબલ"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"મેનેજ કરો"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"બબલ છોડી દેવાયો."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml index cd0f4e3618f7..f75e0e0528e1 100644 --- a/libs/WindowManager/Shell/res/values-hi/strings.xml +++ b/libs/WindowManager/Shell/res/values-hi/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"बबल"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"मैनेज करें"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"बबल खारिज किया गया."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml index 27d4cfcf22d5..ed80c505d756 100644 --- a/libs/WindowManager/Shell/res/values-hr/strings.xml +++ b/libs/WindowManager/Shell/res/values-hr/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-hu/strings.xml b/libs/WindowManager/Shell/res/values-hu/strings.xml index a8cc5c120efc..32a31063bd90 100644 --- a/libs/WindowManager/Shell/res/values-hu/strings.xml +++ b/libs/WindowManager/Shell/res/values-hu/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-hy/strings.xml b/libs/WindowManager/Shell/res/values-hy/strings.xml index 7f372774241a..65ca704ded09 100644 --- a/libs/WindowManager/Shell/res/values-hy/strings.xml +++ b/libs/WindowManager/Shell/res/values-hy/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Պղպջակ"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Կառավարել"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Ամպիկը փակվեց։"</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml index 3cf55fa0ede2..975dd72f67d7 100644 --- a/libs/WindowManager/Shell/res/values-in/strings.xml +++ b/libs/WindowManager/Shell/res/values-in/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml index 6aa56f9858ad..11c47189dce0 100644 --- a/libs/WindowManager/Shell/res/values-is/strings.xml +++ b/libs/WindowManager/Shell/res/values-is/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-it/strings.xml b/libs/WindowManager/Shell/res/values-it/strings.xml index 3c1d5e4dac02..168c8cc5936f 100644 --- a/libs/WindowManager/Shell/res/values-it/strings.xml +++ b/libs/WindowManager/Shell/res/values-it/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml index a0c3b3a95ca8..fd4cd1adaa2a 100644 --- a/libs/WindowManager/Shell/res/values-iw/strings.xml +++ b/libs/WindowManager/Shell/res/values-iw/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"בועה"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"ניהול"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"הבועה נסגרה."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml index fb726c180997..64ddec9450ae 100644 --- a/libs/WindowManager/Shell/res/values-ja/strings.xml +++ b/libs/WindowManager/Shell/res/values-ja/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"バブル"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"管理"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ふきだしが非表示になっています。"</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml index e9f620a17203..cab8807b86d1 100644 --- a/libs/WindowManager/Shell/res/values-ka/strings.xml +++ b/libs/WindowManager/Shell/res/values-ka/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"ბუშტი"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"მართვა"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ბუშტი დაიხურა."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml index 34e41038f285..4ff5b85b36fd 100644 --- a/libs/WindowManager/Shell/res/values-kk/strings.xml +++ b/libs/WindowManager/Shell/res/values-kk/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Көпіршік"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Басқару"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Қалқыма хабар жабылды."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml index 362bbad4ec12..ba7a32495659 100644 --- a/libs/WindowManager/Shell/res/values-km/strings.xml +++ b/libs/WindowManager/Shell/res/values-km/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"ពពុះ"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"គ្រប់គ្រង"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"បានច្រានចោលសារលេចឡើង។"</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml index 77cc4a44f81a..423e8d53a654 100644 --- a/libs/WindowManager/Shell/res/values-kn/strings.xml +++ b/libs/WindowManager/Shell/res/values-kn/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"ಬಬಲ್"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"ನಿರ್ವಹಿಸಿ"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ಬಬಲ್ ವಜಾಗೊಳಿಸಲಾಗಿದೆ."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml index caa114fd6f88..0d1c6216776b 100644 --- a/libs/WindowManager/Shell/res/values-ko/strings.xml +++ b/libs/WindowManager/Shell/res/values-ko/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"버블"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"관리"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"대화창을 닫았습니다."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml index 302c0071a73a..f17e9ca891c0 100644 --- a/libs/WindowManager/Shell/res/values-ky/strings.xml +++ b/libs/WindowManager/Shell/res/values-ky/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Көбүк"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Башкаруу"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Калкып чыкма билдирме жабылды."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-lo/strings.xml b/libs/WindowManager/Shell/res/values-lo/strings.xml index a3519636b71f..195e4d56a1c1 100644 --- a/libs/WindowManager/Shell/res/values-lo/strings.xml +++ b/libs/WindowManager/Shell/res/values-lo/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml index e4dd7398f679..63ad580a81cc 100644 --- a/libs/WindowManager/Shell/res/values-lt/strings.xml +++ b/libs/WindowManager/Shell/res/values-lt/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-lv/strings.xml b/libs/WindowManager/Shell/res/values-lv/strings.xml index 99aebf626322..268d89324f54 100644 --- a/libs/WindowManager/Shell/res/values-lv/strings.xml +++ b/libs/WindowManager/Shell/res/values-lv/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-mk/strings.xml b/libs/WindowManager/Shell/res/values-mk/strings.xml index c152c60fa631..0a0027fa1bae 100644 --- a/libs/WindowManager/Shell/res/values-mk/strings.xml +++ b/libs/WindowManager/Shell/res/values-mk/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Балонче"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Управувајте"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Балончето е отфрлено."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-ml/strings.xml b/libs/WindowManager/Shell/res/values-ml/strings.xml index 90275cdb517a..07809e1a0014 100644 --- a/libs/WindowManager/Shell/res/values-ml/strings.xml +++ b/libs/WindowManager/Shell/res/values-ml/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"ബബിൾ"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"മാനേജ് ചെയ്യുക"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ബബിൾ ഡിസ്മിസ് ചെയ്തു."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml index 5e43506ab621..99bd2dffca53 100644 --- a/libs/WindowManager/Shell/res/values-mn/strings.xml +++ b/libs/WindowManager/Shell/res/values-mn/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Бөмбөлөг"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Удирдах"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Бөмбөлгийг үл хэрэгссэн."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml index 5874bffc9199..ac57e0a549b4 100644 --- a/libs/WindowManager/Shell/res/values-mr/strings.xml +++ b/libs/WindowManager/Shell/res/values-mr/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"बबल"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"व्यवस्थापित करा"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"बबल डिसमिस केला."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml index 4de8a7b03547..6bc2fbb27c51 100644 --- a/libs/WindowManager/Shell/res/values-ms/strings.xml +++ b/libs/WindowManager/Shell/res/values-ms/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml index 5b9e9cb7353e..12c19edcaeb1 100644 --- a/libs/WindowManager/Shell/res/values-my/strings.xml +++ b/libs/WindowManager/Shell/res/values-my/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"ပူဖောင်းဖောက်သံ"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"စီမံရန်"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ပူဖောင်းကွက် ဖယ်လိုက်သည်။"</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml index 6005be4fb5c9..27b79019a57a 100644 --- a/libs/WindowManager/Shell/res/values-nb/strings.xml +++ b/libs/WindowManager/Shell/res/values-nb/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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 Innstillinger"</string> <string name="user_aspect_ratio_settings_button_description" msgid="4315566801697411684">"Endre høyde/bredde-forholdet"</string> @@ -113,7 +117,7 @@ <string name="more_button_text" msgid="3655388105592893530">"Mer"</string> <string name="float_button_text" msgid="9221657008391364581">"Svevende"</string> <string name="select_text" msgid="5139083974039906583">"Velg"</string> - <string name="screenshot_text" msgid="1477704010087786671">"Skjermdump"</string> + <string name="screenshot_text" msgid="1477704010087786671">"Skjermbilde"</string> <string name="close_text" msgid="4986518933445178928">"Lukk"</string> <string name="collapse_menu_text" msgid="7515008122450342029">"Lukk menyen"</string> <string name="expand_menu_text" msgid="3847736164494181168">"Åpne menyen"</string> diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml index a5bd2ab5c10b..25d033738c86 100644 --- a/libs/WindowManager/Shell/res/values-ne/strings.xml +++ b/libs/WindowManager/Shell/res/values-ne/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"बबल"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"व्यवस्थापन गर्नुहोस्"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"बबल हटाइयो।"</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml index 0cd27c5c1457..4ad343cb1a4e 100644 --- a/libs/WindowManager/Shell/res/values-nl/strings.xml +++ b/libs/WindowManager/Shell/res/values-nl/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-or/strings.xml b/libs/WindowManager/Shell/res/values-or/strings.xml index bf751852a255..966d40440ac7 100644 --- a/libs/WindowManager/Shell/res/values-or/strings.xml +++ b/libs/WindowManager/Shell/res/values-or/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"ବବଲ୍"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"ପରିଚାଳନା କରନ୍ତୁ"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ବବଲ୍ ଖାରଜ କରାଯାଇଛି।"</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml index 325c1e80c433..9feaf41cda7f 100644 --- a/libs/WindowManager/Shell/res/values-pa/strings.xml +++ b/libs/WindowManager/Shell/res/values-pa/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"ਬੁਲਬੁਲਾ"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"ਪ੍ਰਬੰਧਨ ਕਰੋ"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ਬਬਲ ਨੂੰ ਖਾਰਜ ਕੀਤਾ ਗਿਆ।"</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml index a7648c8e323b..1c7fbf8e80d3 100644 --- a/libs/WindowManager/Shell/res/values-pl/strings.xml +++ b/libs/WindowManager/Shell/res/values-pl/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml index e47d151337b2..5c2de2ad0d31 100644 --- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml index 1210fe8fda05..6f76525473eb 100644 --- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml index e47d151337b2..5c2de2ad0d31 100644 --- a/libs/WindowManager/Shell/res/values-pt/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-ro/strings.xml b/libs/WindowManager/Shell/res/values-ro/strings.xml index ae871f3dd42b..6e85e7849d95 100644 --- a/libs/WindowManager/Shell/res/values-ro/strings.xml +++ b/libs/WindowManager/Shell/res/values-ro/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml index 971e146ba77e..1b41983cd1a3 100644 --- a/libs/WindowManager/Shell/res/values-ru/strings.xml +++ b/libs/WindowManager/Shell/res/values-ru/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Всплывающая подсказка"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Настроить"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Всплывающий чат закрыт."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-si/strings.xml b/libs/WindowManager/Shell/res/values-si/strings.xml index ef1381cbe635..6fd37e91c8b0 100644 --- a/libs/WindowManager/Shell/res/values-si/strings.xml +++ b/libs/WindowManager/Shell/res/values-si/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"බුබුළු"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"කළමනා කරන්න"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"බුබුල ඉවත දමා ඇත."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-sk/strings.xml b/libs/WindowManager/Shell/res/values-sk/strings.xml index 55a03122483b..dabbf397d38f 100644 --- a/libs/WindowManager/Shell/res/values-sk/strings.xml +++ b/libs/WindowManager/Shell/res/values-sk/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml index bb123dcdbfb6..3ade33810cc8 100644 --- a/libs/WindowManager/Shell/res/values-sl/strings.xml +++ b/libs/WindowManager/Shell/res/values-sl/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml index c74a8cd23338..ee1aa00c5cbe 100644 --- a/libs/WindowManager/Shell/res/values-sq/strings.xml +++ b/libs/WindowManager/Shell/res/values-sq/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-sr/strings.xml b/libs/WindowManager/Shell/res/values-sr/strings.xml index 0694a973dc1e..b2868ca84dac 100644 --- a/libs/WindowManager/Shell/res/values-sr/strings.xml +++ b/libs/WindowManager/Shell/res/values-sr/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Облачић"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Управљајте"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Облачић је одбачен."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml index 8e0bcfe91679..66118efd5da7 100644 --- a/libs/WindowManager/Shell/res/values-sv/strings.xml +++ b/libs/WindowManager/Shell/res/values-sv/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml index 41180abcf712..863b49b1f010 100644 --- a/libs/WindowManager/Shell/res/values-sw/strings.xml +++ b/libs/WindowManager/Shell/res/values-sw/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml index 01ac78d984f3..74e0207bf62e 100644 --- a/libs/WindowManager/Shell/res/values-ta/strings.xml +++ b/libs/WindowManager/Shell/res/values-ta/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"பபிள்"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"நிர்வகி"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"குமிழ் நிராகரிக்கப்பட்டது."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml index 6224e72c19fe..35711567e760 100644 --- a/libs/WindowManager/Shell/res/values-te/strings.xml +++ b/libs/WindowManager/Shell/res/values-te/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"బబుల్"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"మేనేజ్ చేయండి"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"బబుల్ విస్మరించబడింది."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml index fe0b74c469f4..47694164270e 100644 --- a/libs/WindowManager/Shell/res/values-th/strings.xml +++ b/libs/WindowManager/Shell/res/values-th/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"บับเบิล"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"จัดการ"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"ปิดบับเบิลแล้ว"</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml index 786e99cfe8c8..be18d88194c1 100644 --- a/libs/WindowManager/Shell/res/values-tl/strings.xml +++ b/libs/WindowManager/Shell/res/values-tl/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-tr/strings.xml b/libs/WindowManager/Shell/res/values-tr/strings.xml index e953f5808aff..4c8c53610711 100644 --- a/libs/WindowManager/Shell/res/values-tr/strings.xml +++ b/libs/WindowManager/Shell/res/values-tr/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml index fbdf42e582d1..7cc1a0406f97 100644 --- a/libs/WindowManager/Shell/res/values-uk/strings.xml +++ b/libs/WindowManager/Shell/res/values-uk/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"Спливаюче сповіщення"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"Налаштувати"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"Спливаюче сповіщення закрито."</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-ur/strings.xml b/libs/WindowManager/Shell/res/values-ur/strings.xml index 5562fa70bf09..8b9f29969d80 100644 --- a/libs/WindowManager/Shell/res/values-ur/strings.xml +++ b/libs/WindowManager/Shell/res/values-ur/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"بلبلہ"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"نظم کریں"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"بلبلہ برخاست کر دیا گیا۔"</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml index 50e42329a1a0..55c6b32c909b 100644 --- a/libs/WindowManager/Shell/res/values-uz/strings.xml +++ b/libs/WindowManager/Shell/res/values-uz/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml index 6da85881210d..07a6b6f6c2b4 100644 --- a/libs/WindowManager/Shell/res/values-vi/strings.xml +++ b/libs/WindowManager/Shell/res/values-vi/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml index 4318caf26199..908095a163a7 100644 --- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml +++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"气泡"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"管理"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"已关闭消息气泡。"</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml index 72cd39d8e00a..c8550b4e0611 100644 --- a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml +++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"氣泡"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"管理"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"對話氣泡已關閉。"</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml index c06d7b105694..67048335de64 100644 --- a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml +++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml @@ -84,6 +84,10 @@ <string name="notification_bubble_title" msgid="6082910224488253378">"泡泡"</string> <string name="manage_bubbles_text" msgid="7730624269650594419">"管理"</string> <string name="accessibility_bubble_dismissed" msgid="8367471990421247357">"已關閉泡泡。"</string> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values-zu/strings.xml b/libs/WindowManager/Shell/res/values-zu/strings.xml index 755414e52762..96b4faec06b8 100644 --- a/libs/WindowManager/Shell/res/values-zu/strings.xml +++ b/libs/WindowManager/Shell/res/values-zu/strings.xml @@ -84,6 +84,10 @@ <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> + <!-- no translation found for bubble_shortcut_label (666269077944378311) --> + <skip /> + <!-- no translation found for bubble_shortcut_long_label (6088437544312894043) --> + <skip /> <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> diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index 595d34664cfa..d143263b69a5 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -507,8 +507,11 @@ <!-- The height of the handle menu's "More Actions" pill in desktop mode. --> <dimen name="desktop_mode_handle_menu_more_actions_pill_height">52dp</dimen> + <!-- The height of the handle menu's "Open in browser" pill in desktop mode. --> + <dimen name="desktop_mode_handle_menu_open_in_browser_pill_height">52dp</dimen> + <!-- The height of the handle menu in desktop mode. --> - <dimen name="desktop_mode_handle_menu_height">328dp</dimen> + <dimen name="desktop_mode_handle_menu_height">380dp</dimen> <!-- The top margin of the handle menu in desktop mode. --> <dimen name="desktop_mode_handle_menu_margin_top">4dp</dimen> diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml index 47846746b205..4e7cfb638a12 100644 --- a/libs/WindowManager/Shell/res/values/strings.xml +++ b/libs/WindowManager/Shell/res/values/strings.xml @@ -280,6 +280,8 @@ <string name="select_text">Select</string> <!-- Accessibility text for the handle menu screenshot button [CHAR LIMIT=NONE] --> <string name="screenshot_text">Screenshot</string> + <!-- Accessibility text for the handle menu open in browser button [CHAR LIMIT=NONE] --> + <string name="open_in_browser_text">Open in browser</string> <!-- Accessibility text for the handle menu close button [CHAR LIMIT=NONE] --> <string name="close_text">Close</string> <!-- Accessibility text for the handle menu close menu button [CHAR LIMIT=NONE] --> diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt new file mode 100644 index 000000000000..f0d80a02243a --- /dev/null +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.shared.desktopmode + +import android.content.Context +import android.provider.Settings +import android.util.Log +import com.android.window.flags.Flags + +/* + * A shared class to check desktop mode flags state. + * + * The class computes whether a Desktop Windowing flag should be enabled by using the aconfig flag + * value and the developer option override state (if applicable). + **/ +enum class DesktopModeFlags( + // Function called to obtain aconfig flag value. + private val flagFunction: () -> Boolean, + // Whether the flag state should be affected by developer option. + private val shouldOverrideByDevOption: Boolean +) { + // All desktop mode related flags will be added here + DESKTOP_WINDOWING_MODE(Flags::enableDesktopWindowingMode, true), + WALLPAPER_ACTIVITY(Flags::enableDesktopWindowingWallpaperActivity, true); + + /** + * Determines state of flag based on the actual flag and desktop mode developer option overrides. + * + * Note, this method makes sure that a constant developer toggle overrides is read until reboot. + */ + fun isEnabled(context: Context): Boolean = + if (!Flags.showDesktopWindowingDevOption() || + !shouldOverrideByDevOption || + context.contentResolver == null) { + flagFunction() + } else { + val shouldToggleBeEnabledByDefault = DesktopModeStatus.shouldDevOptionBeEnabledByDefault() + when (getToggleOverride(context)) { + ToggleOverride.OVERRIDE_UNSET -> flagFunction() + // When toggle override matches its default state, don't override flags. This helps users + // reset their feature overrides. + ToggleOverride.OVERRIDE_OFF -> + if (shouldToggleBeEnabledByDefault) false else flagFunction() + ToggleOverride.OVERRIDE_ON -> if (shouldToggleBeEnabledByDefault) flagFunction() else true + } + } + + private fun getToggleOverride(context: Context): ToggleOverride { + val override = + cachedToggleOverride + ?: run { + val override = getToggleOverrideFromSystem(context) + // Cache toggle override the first time we encounter context. Override does not change + // with context, as context is just used to fetch System Property and Settings.Global + cachedToggleOverride = override + Log.d(TAG, "Toggle override initialized to: $override") + override + } + + return override + } + + private fun getToggleOverrideFromSystem(context: Context): ToggleOverride { + // A non-persistent System Property is used to store override to ensure it remains + // constant till reboot. + val overrideFromSystemProperties: ToggleOverride? = + System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, null).convertToToggleOverride() + return overrideFromSystemProperties + ?: run { + // Read Setting Global if System Property is not present (just after reboot) + // or not valid (user manually changed the value) + val overrideFromSettingsGlobal = + convertToToggleOverrideWithFallback( + Settings.Global.getInt( + context.contentResolver, + Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, + ToggleOverride.OVERRIDE_UNSET.setting), + ToggleOverride.OVERRIDE_UNSET) + // Initialize System Property + System.setProperty( + SYSTEM_PROPERTY_OVERRIDE_KEY, overrideFromSettingsGlobal.setting.toString()) + + overrideFromSettingsGlobal + } + } + + /** + * Override state of desktop mode developer option toggle. + * + * @property setting The integer value that is associated with the developer option toggle + * override + */ + enum class ToggleOverride(val setting: Int) { + /** No override is set. */ + OVERRIDE_UNSET(-1), + /** Override to off. */ + OVERRIDE_OFF(0), + /** Override to on. */ + OVERRIDE_ON(1) + } + + private fun String?.convertToToggleOverride(): ToggleOverride? { + val intValue = this?.toIntOrNull() ?: return null + return settingToToggleOverrideMap[intValue] + ?: run { + Log.w(TAG, "Unknown toggleOverride int $intValue") + null + } + } + + companion object { + private const val TAG = "DesktopModeFlags" + + /** + * Key for non-persistent System Property which is used to store desktop windowing developer + * option overrides. + */ + private const val SYSTEM_PROPERTY_OVERRIDE_KEY = "sys.wmshell.desktopmode.dev_toggle_override" + + /** + * Local cache for toggle override, which is initialized once on its first access. It needs to + * be refreshed only on reboots as overridden state takes effect on reboots. + */ + private var cachedToggleOverride: ToggleOverride? = null + + private val settingToToggleOverrideMap = ToggleOverride.entries.associateBy { it.setting } + + @JvmStatic + fun convertToToggleOverrideWithFallback( + overrideInt: Int, + fallbackOverride: ToggleOverride + ): ToggleOverride { + return settingToToggleOverrideMap[overrideInt] + ?: run { + Log.w(TAG, "Unknown toggleOverride int $overrideInt") + fallbackOverride + } + } + } +} diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java index 4876f327a650..fc4710f8f67b 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/DesktopModeStatus.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.shared; +package com.android.wm.shell.shared.desktopmode; import android.annotation.NonNull; import android.content.Context; @@ -27,8 +27,11 @@ import com.android.window.flags.Flags; /** * Constants for desktop mode feature */ +// TODO(b/237575897): Move this file to the `com.android.wm.shell.shared.desktopmode` package public class DesktopModeStatus { + private static final String TAG = "DesktopModeStatus"; + /** * Flag to indicate whether task resizing is veiled. */ @@ -98,15 +101,6 @@ public class DesktopModeStatus { "persist.wm.debug.desktop_max_task_limit", DEFAULT_MAX_TASK_LIMIT); /** - * Return {@code true} if desktop windowing is enabled. Only to be used for testing. Callers - * should use {@link #canEnterDesktopMode(Context)} to query the state of desktop windowing. - */ - @VisibleForTesting - public static boolean isEnabled() { - return Flags.enableDesktopWindowingMode(); - } - - /** * Return {@code true} if veiled resizing is active. If false, fluid resizing is used. */ public static boolean isVeiledResizeEnabled() { @@ -120,7 +114,7 @@ public class DesktopModeStatus { */ public static boolean useWindowShadow(boolean isFocusedWindow) { return USE_WINDOW_SHADOWS - || (USE_WINDOW_SHADOWS_FOCUSED_WINDOW && isFocusedWindow); + || (USE_WINDOW_SHADOWS_FOCUSED_WINDOW && isFocusedWindow); } /** @@ -154,10 +148,24 @@ public class DesktopModeStatus { } /** + * Return {@code true} if desktop mode dev option should be shown on current device + */ + public static boolean canShowDesktopModeDevOption(@NonNull Context context) { + return isDeviceEligibleForDesktopMode(context) && Flags.showDesktopWindowingDevOption(); + } + + /** Returns if desktop mode dev option should be enabled if there is no user override. */ + public static boolean shouldDevOptionBeEnabledByDefault() { + return Flags.enableDesktopWindowingMode(); + } + + /** * Return {@code true} if desktop mode is enabled and can be entered on the current device. */ public static boolean canEnterDesktopMode(@NonNull Context context) { - return (!enforceDeviceRestrictions() || isDesktopModeSupported(context)) && isEnabled(); + if (!isDeviceEligibleForDesktopMode(context)) return false; + + return DesktopModeFlags.DESKTOP_WINDOWING_MODE.isEnabled(context); } /** @@ -181,4 +189,11 @@ public class DesktopModeStatus { return DESKTOP_DENSITY_OVERRIDE >= DESKTOP_DENSITY_MIN && DESKTOP_DENSITY_OVERRIDE <= DESKTOP_DENSITY_MAX; } + + /** + * Return {@code true} if desktop mode is unrestricted and is supported in the device. + */ + private static boolean isDeviceEligibleForDesktopMode(@NonNull Context context) { + return !enforceDeviceRestrictions() || isDesktopModeSupported(context); + } } diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/OWNERS b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/OWNERS new file mode 100644 index 000000000000..2fabd4a33586 --- /dev/null +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/OWNERS @@ -0,0 +1 @@ +file:/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java index ef9bf008b294..514307fed4f9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java @@ -19,7 +19,7 @@ package com.android.wm.shell; import com.android.internal.protolog.LegacyProtoLogImpl; import com.android.internal.protolog.common.ILogger; import com.android.internal.protolog.common.IProtoLog; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellInit; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java index 2e5448a9e8d5..b9bf136837a6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java @@ -29,7 +29,7 @@ import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.sysui.ShellInit; import java.io.PrintWriter; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java index 5143d419597b..9f01316d5b5c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java @@ -38,7 +38,7 @@ import android.window.SystemPerformanceHinter; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.sysui.ShellInit; import java.io.PrintWriter; 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 3ded7d246499..f014e559d4b4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -24,6 +24,9 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static com.android.wm.shell.compatui.impl.CompatUIEventsKt.CAMERA_CONTROL_STATE_UPDATE; +import static com.android.wm.shell.compatui.impl.CompatUIEventsKt.SIZE_COMPAT_RESTART_BUTTON_APPEARED; +import static com.android.wm.shell.compatui.impl.CompatUIEventsKt.SIZE_COMPAT_RESTART_BUTTON_CLICKED; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG; import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS; @@ -31,7 +34,6 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager.RunningTaskInfo; -import android.app.CameraCompatTaskInfo.CameraCompatControlState; import android.app.TaskInfo; import android.app.WindowConfiguration; import android.content.LocusId; @@ -52,11 +54,16 @@ import android.window.TaskAppearedInfo; import android.window.TaskOrganizer; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.FrameworkStatsLog; import com.android.wm.shell.common.ScreenshotUtils; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.compatui.CompatUIController; +import com.android.wm.shell.compatui.api.CompatUIHandler; +import com.android.wm.shell.compatui.api.CompatUIInfo; +import com.android.wm.shell.compatui.impl.CompatUIEvents.CameraControlStateUpdated; +import com.android.wm.shell.compatui.impl.CompatUIEvents.SizeCompatRestartButtonAppeared; +import com.android.wm.shell.compatui.impl.CompatUIEvents.SizeCompatRestartButtonClicked; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.startingsurface.StartingWindowController; import com.android.wm.shell.sysui.ShellCommandHandler; @@ -75,8 +82,7 @@ import java.util.function.Consumer; * Unified task organizer for all components in the shell. * TODO(b/167582004): may consider consolidating this class and TaskOrganizer */ -public class ShellTaskOrganizer extends TaskOrganizer implements - CompatUIController.CompatUICallback { +public class ShellTaskOrganizer extends TaskOrganizer { private static final String TAG = "ShellTaskOrganizer"; // Intentionally using negative numbers here so the positive numbers can be used @@ -124,6 +130,15 @@ public class ShellTaskOrganizer extends TaskOrganizer implements } /** + * Limited scope callback to notify when a task is removed from the system. This signal is + * not synchronized with anything (or any transition), and should not be used in cases where + * that is necessary. + */ + public interface TaskVanishedListener { + default void onTaskVanished(RunningTaskInfo taskInfo) {} + } + + /** * Callbacks for events on a task with a locus id. */ public interface LocusIdListener { @@ -167,6 +182,9 @@ public class ShellTaskOrganizer extends TaskOrganizer implements private final ArraySet<FocusListener> mFocusListeners = new ArraySet<>(); + // Listeners that should be notified when a task is removed + private final ArraySet<TaskVanishedListener> mTaskVanishedListeners = new ArraySet<>(); + private final Object mLock = new Object(); private StartingWindowController mStartingWindow; @@ -182,12 +200,11 @@ public class ShellTaskOrganizer extends TaskOrganizer implements * In charge of showing compat UI. Can be {@code null} if the device doesn't support size * compat or if this isn't the main {@link ShellTaskOrganizer}. * - * <p>NOTE: only the main {@link ShellTaskOrganizer} should have a {@link CompatUIController}, - * and register itself as a {@link CompatUIController.CompatUICallback}. Subclasses should be - * initialized with a {@code null} {@link CompatUIController}. + * <p>NOTE: only the main {@link ShellTaskOrganizer} should have a {@link CompatUIHandler}, + * Subclasses should be initialized with a {@code null} {@link CompatUIHandler}. */ @Nullable - private final CompatUIController mCompatUI; + private final CompatUIHandler mCompatUI; @NonNull private final ShellCommandHandler mShellCommandHandler; @@ -211,7 +228,7 @@ public class ShellTaskOrganizer extends TaskOrganizer implements public ShellTaskOrganizer(ShellInit shellInit, ShellCommandHandler shellCommandHandler, - @Nullable CompatUIController compatUI, + @Nullable CompatUIHandler compatUI, Optional<UnfoldAnimationController> unfoldAnimationController, Optional<RecentTasksController> recentTasks, ShellExecutor mainExecutor) { @@ -223,7 +240,7 @@ public class ShellTaskOrganizer extends TaskOrganizer implements protected ShellTaskOrganizer(ShellInit shellInit, ShellCommandHandler shellCommandHandler, ITaskOrganizerController taskOrganizerController, - @Nullable CompatUIController compatUI, + @Nullable CompatUIHandler compatUI, Optional<UnfoldAnimationController> unfoldAnimationController, Optional<RecentTasksController> recentTasks, ShellExecutor mainExecutor) { @@ -240,7 +257,21 @@ public class ShellTaskOrganizer extends TaskOrganizer implements private void onInit() { mShellCommandHandler.addDumpCallback(this::dump, this); if (mCompatUI != null) { - mCompatUI.setCompatUICallback(this); + mCompatUI.setCallback(compatUIEvent -> { + switch(compatUIEvent.getEventId()) { + case SIZE_COMPAT_RESTART_BUTTON_APPEARED: + onSizeCompatRestartButtonAppeared(compatUIEvent.asType()); + break; + case SIZE_COMPAT_RESTART_BUTTON_CLICKED: + onSizeCompatRestartButtonClicked(compatUIEvent.asType()); + break; + case CAMERA_CONTROL_STATE_UPDATE: + onCameraControlStateUpdated(compatUIEvent.asType()); + break; + default: + + } + }); } registerOrganizer(); } @@ -409,7 +440,7 @@ public class ShellTaskOrganizer extends TaskOrganizer implements } /** - * Removes listener. + * Removes a locus id listener. */ public void removeLocusIdListener(LocusIdListener listener) { synchronized (mLock) { @@ -430,7 +461,7 @@ public class ShellTaskOrganizer extends TaskOrganizer implements } /** - * Removes listener. + * Removes a focus listener. */ public void removeFocusListener(FocusListener listener) { synchronized (mLock) { @@ -439,6 +470,24 @@ public class ShellTaskOrganizer extends TaskOrganizer implements } /** + * Adds a listener to be notified when a task vanishes. + */ + public void addTaskVanishedListener(TaskVanishedListener listener) { + synchronized (mLock) { + mTaskVanishedListeners.add(listener); + } + } + + /** + * Removes a task-vanished listener. + */ + public void removeTaskVanishedListener(TaskVanishedListener listener) { + synchronized (mLock) { + mTaskVanishedListeners.remove(listener); + } + } + + /** * Returns a surface which can be used to attach overlays to the home root task */ @NonNull @@ -614,6 +663,9 @@ public class ShellTaskOrganizer extends TaskOrganizer implements t.apply(); ProtoLog.v(WM_SHELL_TASK_ORG, "Removing overlay surface"); } + for (TaskVanishedListener l : mTaskVanishedListeners) { + l.onTaskVanished(taskInfo); + } if (!ENABLE_SHELL_TRANSITIONS && (appearedInfo.getLeash() != null)) { // Preemptively clean up the leash only if shell transitions are not enabled @@ -647,6 +699,22 @@ public class ShellTaskOrganizer extends TaskOrganizer implements } } + /** + * Shows/hides the given task surface. Not for general use as changing the task visibility may + * conflict with other Transitions. This is currently ONLY used to temporarily hide a task + * while a drag is in session. + */ + public void setTaskSurfaceVisibility(int taskId, boolean visible) { + synchronized (mLock) { + final TaskAppearedInfo info = mTasks.get(taskId); + if (info != null) { + SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + t.setVisibility(info.getLeash(), visible); + t.apply(); + } + } + } + private boolean updateTaskListenerIfNeeded(RunningTaskInfo taskInfo, SurfaceControl leash, TaskListener oldListener, TaskListener newListener) { if (oldListener == newListener) return false; @@ -694,8 +762,26 @@ public class ShellTaskOrganizer extends TaskOrganizer implements } } - @Override - public void onSizeCompatRestartButtonAppeared(int taskId) { + /** Reparents a child window surface to the task surface. */ + public void reparentChildSurfaceToTask(int taskId, SurfaceControl sc, + SurfaceControl.Transaction t) { + final TaskListener taskListener; + synchronized (mLock) { + taskListener = mTasks.contains(taskId) + ? getTaskListener(mTasks.get(taskId).getTaskInfo()) + : null; + } + if (taskListener == null) { + ProtoLog.w(WM_SHELL_TASK_ORG, "Failed to find Task to reparent surface taskId=%d", + taskId); + return; + } + taskListener.reparentChildSurfaceToTask(taskId, sc, t); + } + + @VisibleForTesting + void onSizeCompatRestartButtonAppeared(@NonNull SizeCompatRestartButtonAppeared compatUIEvent) { + final int taskId = compatUIEvent.getTaskId(); final TaskAppearedInfo info; synchronized (mLock) { info = mTasks.get(taskId); @@ -707,8 +793,9 @@ public class ShellTaskOrganizer extends TaskOrganizer implements FrameworkStatsLog.SIZE_COMPAT_RESTART_BUTTON_EVENT_REPORTED__EVENT__APPEARED); } - @Override - public void onSizeCompatRestartButtonClicked(int taskId) { + @VisibleForTesting + void onSizeCompatRestartButtonClicked(@NonNull SizeCompatRestartButtonClicked compatUIEvent) { + final int taskId = compatUIEvent.getTaskId(); final TaskAppearedInfo info; synchronized (mLock) { info = mTasks.get(taskId); @@ -721,8 +808,10 @@ public class ShellTaskOrganizer extends TaskOrganizer implements restartTaskTopActivityProcessIfVisible(info.getTaskInfo().token); } - @Override - public void onCameraControlStateUpdated(int taskId, @CameraCompatControlState int state) { + @VisibleForTesting + void onCameraControlStateUpdated(@NonNull CameraControlStateUpdated compatUIEvent) { + final int taskId = compatUIEvent.getTaskId(); + final int state = compatUIEvent.getState(); final TaskAppearedInfo info; synchronized (mLock) { info = mTasks.get(taskId); @@ -733,22 +822,6 @@ public class ShellTaskOrganizer extends TaskOrganizer implements updateCameraCompatControlState(info.getTaskInfo().token, state); } - /** Reparents a child window surface to the task surface. */ - public void reparentChildSurfaceToTask(int taskId, SurfaceControl sc, - SurfaceControl.Transaction t) { - final TaskListener taskListener; - synchronized (mLock) { - taskListener = mTasks.contains(taskId) - ? getTaskListener(mTasks.get(taskId).getTaskInfo()) - : null; - } - if (taskListener == null) { - ProtoLog.w(WM_SHELL_TASK_ORG, "Failed to find Task to reparent surface taskId=%d", - taskId); - return; - } - taskListener.reparentChildSurfaceToTask(taskId, sc, t); - } private void logSizeCompatRestartButtonEventReported(@NonNull TaskAppearedInfo info, int event) { @@ -777,10 +850,10 @@ public class ShellTaskOrganizer extends TaskOrganizer implements // on this Task if there is any. if (taskListener == null || !taskListener.supportCompatUI() || !taskInfo.appCompatTaskInfo.hasCompatUI() || !taskInfo.isVisible) { - mCompatUI.onCompatInfoChanged(taskInfo, null /* taskListener */); + mCompatUI.onCompatInfoChanged(new CompatUIInfo(taskInfo, null /* taskListener */)); return; } - mCompatUI.onCompatInfoChanged(taskInfo, taskListener); + mCompatUI.onCompatInfoChanged(new CompatUIInfo(taskInfo, taskListener)); } private TaskListener getTaskListener(RunningTaskInfo runningTaskInfo) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java index a426b206b0cd..5696a544152c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java @@ -31,6 +31,7 @@ import static com.android.wm.shell.transition.Transitions.TRANSIT_TASK_FRAGMENT_ import android.animation.Animator; import android.animation.ValueAnimator; import android.content.Context; +import android.graphics.Point; import android.graphics.Rect; import android.os.IBinder; import android.util.ArraySet; @@ -45,6 +46,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; +import com.android.window.flags.Flags; import com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationAdapter.SnapshotAdapter; import com.android.wm.shell.common.ScreenshotUtils; import com.android.wm.shell.shared.TransitionUtil; @@ -263,7 +265,10 @@ class ActivityEmbeddingAnimationRunner { for (TransitionInfo.Change change : openingChanges) { final Animation animation = animationProvider.get(info, change, openingWholeScreenBounds); - if (animation.getDuration() == 0) { + if (shouldUseJumpCutForAnimation(animation)) { + if (Flags.activityEmbeddingAnimationCustomizationFlag()) { + return new ArrayList<>(); + } continue; } final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter( @@ -288,7 +293,10 @@ class ActivityEmbeddingAnimationRunner { } final Animation animation = animationProvider.get(info, change, closingWholeScreenBounds); - if (animation.getDuration() == 0) { + if (shouldUseJumpCutForAnimation(animation)) { + if (Flags.activityEmbeddingAnimationCustomizationFlag()) { + return new ArrayList<>(); + } continue; } final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter( @@ -398,7 +406,15 @@ class ActivityEmbeddingAnimationRunner { // This is because the TaskFragment surface/change won't contain the Activity's before its // reparent. Animation changeAnimation = null; - Rect parentBounds = new Rect(); + final Rect parentBounds = new Rect(); + // We use a single boolean value to record the backdrop override because the override used + // for overlay and we restrict to single overlay animation. We should fix the assumption + // if we allow multiple overlay transitions. + // The backdrop logic is mainly for animations of split animations. The backdrop should be + // disabled if there is any open/close target in the same transition as the change target. + // However, the overlay change animation usually contains one change target, and shows + // backdrop unexpectedly. + Boolean overrideShowBackdrop = null; for (TransitionInfo.Change change : info.getChanges()) { if (change.getMode() != TRANSIT_CHANGE || change.getStartAbsBounds().equals(change.getEndAbsBounds())) { @@ -421,21 +437,29 @@ class ActivityEmbeddingAnimationRunner { } } - // The TaskFragment may be enter/exit split, so we take the union of both as the parent - // size. - parentBounds.union(boundsAnimationChange.getStartAbsBounds()); - parentBounds.union(boundsAnimationChange.getEndAbsBounds()); - if (boundsAnimationChange != change) { - // Union the change starting bounds in case the activity is resized and reparented - // to a TaskFragment. In that case, the TaskFragment may not cover the activity's - // starting bounds. - parentBounds.union(change.getStartAbsBounds()); + final TransitionInfo.AnimationOptions options = boundsAnimationChange + .getAnimationOptions(); + if (options != null) { + final Animation overrideAnimation = mAnimationSpec.loadCustomAnimationFromOptions( + options, TRANSIT_CHANGE); + if (overrideAnimation != null) { + overrideShowBackdrop = overrideAnimation.getShowBackdrop(); + } } + calculateParentBounds(change, boundsAnimationChange, parentBounds); // There are two animations in the array. The first one is for the start leash // (snapshot), and the second one is for the end leash (TaskFragment). - final Animation[] animations = mAnimationSpec.createChangeBoundsChangeAnimations(change, - parentBounds); + final Animation[] animations = + mAnimationSpec.createChangeBoundsChangeAnimations(info, change, parentBounds); + // Jump cut if either animation has zero for duration. + if (Flags.activityEmbeddingAnimationCustomizationFlag()) { + for (Animation animation : animations) { + if (shouldUseJumpCutForAnimation(animation)) { + return new ArrayList<>(); + } + } + } // Keep track as we might need to add background color for the animation. // Although there may be multiple change animation, record one of them is sufficient // because the background color will be added to the root leash for the whole animation. @@ -466,7 +490,7 @@ class ActivityEmbeddingAnimationRunner { // If there is no corresponding open/close window with the change, we should show background // color to cover the empty part of the screen. - boolean shouldShouldBackgroundColor = true; + boolean shouldShowBackgroundColor = true; // Handle the other windows that don't have bounds change in the same transition. for (TransitionInfo.Change change : info.getChanges()) { if (handledChanges.contains(change)) { @@ -482,17 +506,26 @@ class ActivityEmbeddingAnimationRunner { // window without bounds change. animation = ActivityEmbeddingAnimationSpec.createNoopAnimation(change); } else if (TransitionUtil.isClosingType(change.getMode())) { - animation = mAnimationSpec.createChangeBoundsCloseAnimation(change, parentBounds); - shouldShouldBackgroundColor = false; + animation = + mAnimationSpec.createChangeBoundsCloseAnimation(info, change, parentBounds); + shouldShowBackgroundColor = false; } else { - animation = mAnimationSpec.createChangeBoundsOpenAnimation(change, parentBounds); - shouldShouldBackgroundColor = false; + animation = + mAnimationSpec.createChangeBoundsOpenAnimation(info, change, parentBounds); + shouldShowBackgroundColor = false; + } + if (Flags.activityEmbeddingAnimationCustomizationFlag()) { + if (shouldUseJumpCutForAnimation(animation)) { + return new ArrayList<>(); + } } adapters.add(new ActivityEmbeddingAnimationAdapter(animation, change, TransitionUtil.getRootFor(change, info))); } - if (shouldShouldBackgroundColor && changeAnimation != null) { + shouldShowBackgroundColor = overrideShowBackdrop != null + ? overrideShowBackdrop : shouldShowBackgroundColor; + if (shouldShowBackgroundColor && changeAnimation != null) { // Change animation may leave part of the screen empty. Show background color to cover // that. changeAnimation.setShowBackdrop(true); @@ -502,6 +535,39 @@ class ActivityEmbeddingAnimationRunner { } /** + * Calculates parent bounds of the animation target by {@code change}. + */ + @VisibleForTesting + static void calculateParentBounds(@NonNull TransitionInfo.Change change, + @NonNull TransitionInfo.Change boundsAnimationChange, @NonNull Rect outParentBounds) { + if (Flags.activityEmbeddingOverlayPresentationFlag()) { + final Point endParentSize = change.getEndParentSize(); + if (endParentSize.equals(0, 0)) { + return; + } + final Point endRelPosition = change.getEndRelOffset(); + final Point endAbsPosition = new Point(change.getEndAbsBounds().left, + change.getEndAbsBounds().top); + final Point parentEndAbsPosition = new Point(endAbsPosition.x - endRelPosition.x, + endAbsPosition.y - endRelPosition.y); + outParentBounds.set(parentEndAbsPosition.x, parentEndAbsPosition.y, + parentEndAbsPosition.x + endParentSize.x, + parentEndAbsPosition.y + endParentSize.y); + } else { + // The TaskFragment may be enter/exit split, so we take the union of both as + // the parent size. + outParentBounds.union(boundsAnimationChange.getStartAbsBounds()); + outParentBounds.union(boundsAnimationChange.getEndAbsBounds()); + if (boundsAnimationChange != change) { + // Union the change starting bounds in case the activity is resized and + // reparented to a TaskFragment. In that case, the TaskFragment may not cover + // the activity's starting bounds. + outParentBounds.union(change.getStartAbsBounds()); + } + } + } + + /** * Takes a screenshot of the given {@code screenshotChange} surface if WM Core hasn't taken one. * The screenshot leash should be attached to the {@code animationChange} surface which we will * animate later. @@ -595,6 +661,12 @@ class ActivityEmbeddingAnimationRunner { return true; } + /** Whether or not to use jump cut based on the animation. */ + @VisibleForTesting + static boolean shouldUseJumpCutForAnimation(@NonNull Animation animation) { + return animation.getDuration() == 0; + } + /** Updates the changes to end states in {@code startTransaction} for jump cut animation. */ private void prepareForJumpCut(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) { 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 b9868629e64b..3046307702c2 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 @@ -18,6 +18,8 @@ package com.android.wm.shell.activityembedding; import static android.app.ActivityOptions.ANIM_CUSTOM; +import static android.view.WindowManager.TRANSIT_CHANGE; +import static android.window.TransitionInfo.AnimationOptions.DEFAULT_ANIMATION_RESOURCES_ID; import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_NONE; import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation; @@ -27,6 +29,8 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.graphics.Rect; +import android.util.Log; +import android.view.WindowManager; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.AnimationSet; @@ -42,7 +46,6 @@ import com.android.window.flags.Flags; import com.android.wm.shell.shared.TransitionUtil; /** Animation spec for ActivityEmbedding transition. */ -// TODO(b/206557124): provide an easier way to customize animation class ActivityEmbeddingAnimationSpec { private static final String TAG = "ActivityEmbeddingAnimSpec"; @@ -91,8 +94,14 @@ class ActivityEmbeddingAnimationSpec { /** Animation for window that is opening in a change transition. */ @NonNull - Animation createChangeBoundsOpenAnimation(@NonNull TransitionInfo.Change change, - @NonNull Rect parentBounds) { + Animation createChangeBoundsOpenAnimation(@NonNull TransitionInfo info, + @NonNull TransitionInfo.Change change, @NonNull Rect parentBounds) { + if (Flags.activityEmbeddingAnimationCustomizationFlag()) { + final Animation customAnimation = loadCustomAnimation(info, change, TRANSIT_CHANGE); + if (customAnimation != null) { + return customAnimation; + } + } // Use end bounds for opening. final Rect bounds = change.getEndAbsBounds(); final int startLeft; @@ -119,8 +128,14 @@ class ActivityEmbeddingAnimationSpec { /** Animation for window that is closing in a change transition. */ @NonNull - Animation createChangeBoundsCloseAnimation(@NonNull TransitionInfo.Change change, - @NonNull Rect parentBounds) { + Animation createChangeBoundsCloseAnimation(@NonNull TransitionInfo info, + @NonNull TransitionInfo.Change change, @NonNull Rect parentBounds) { + if (Flags.activityEmbeddingAnimationCustomizationFlag()) { + final Animation customAnimation = loadCustomAnimation(info, change, TRANSIT_CHANGE); + if (customAnimation != null) { + return customAnimation; + } + } // Use start bounds for closing. final Rect bounds = change.getStartAbsBounds(); final int endTop; @@ -151,8 +166,17 @@ class ActivityEmbeddingAnimationSpec { * the second one is for the end leash. */ @NonNull - Animation[] createChangeBoundsChangeAnimations(@NonNull TransitionInfo.Change change, - @NonNull Rect parentBounds) { + Animation[] createChangeBoundsChangeAnimations(@NonNull TransitionInfo info, + @NonNull TransitionInfo.Change change, @NonNull Rect parentBounds) { + if (Flags.activityEmbeddingAnimationCustomizationFlag()) { + // TODO(b/293658614): Support more complicated animations that may need more than a noop + // animation as the start leash. + final Animation noopAnimation = createNoopAnimation(change); + final Animation customAnimation = loadCustomAnimation(info, change, TRANSIT_CHANGE); + if (customAnimation != null) { + return new Animation[]{noopAnimation, customAnimation}; + } + } // Both start bounds and end bounds are in screen coordinates. We will post translate // to the local coordinates in ActivityEmbeddingAnimationAdapter#onAnimationUpdate final Rect startBounds = change.getStartAbsBounds(); @@ -203,7 +227,7 @@ class ActivityEmbeddingAnimationSpec { Animation loadOpenAnimation(@NonNull TransitionInfo info, @NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) { final boolean isEnter = TransitionUtil.isOpeningType(change.getMode()); - final Animation customAnimation = loadCustomAnimation(info, change, isEnter); + final Animation customAnimation = loadCustomAnimation(info, change, change.getMode()); final Animation animation; if (customAnimation != null) { animation = customAnimation; @@ -230,7 +254,7 @@ class ActivityEmbeddingAnimationSpec { Animation loadCloseAnimation(@NonNull TransitionInfo info, @NonNull TransitionInfo.Change change, @NonNull Rect wholeAnimationBounds) { final boolean isEnter = TransitionUtil.isOpeningType(change.getMode()); - final Animation customAnimation = loadCustomAnimation(info, change, isEnter); + final Animation customAnimation = loadCustomAnimation(info, change, change.getMode()); final Animation animation; if (customAnimation != null) { animation = customAnimation; @@ -263,18 +287,46 @@ class ActivityEmbeddingAnimationSpec { @Nullable private Animation loadCustomAnimation(@NonNull TransitionInfo info, - @NonNull TransitionInfo.Change change, boolean isEnter) { + @NonNull TransitionInfo.Change change, @WindowManager.TransitionType int mode) { final TransitionInfo.AnimationOptions options; if (Flags.moveAnimationOptionsToChange()) { options = change.getAnimationOptions(); } else { options = info.getAnimationOptions(); } + return loadCustomAnimationFromOptions(options, mode); + } + + @Nullable + Animation loadCustomAnimationFromOptions(@Nullable TransitionInfo.AnimationOptions options, + @WindowManager.TransitionType int mode) { if (options == null || options.getType() != ANIM_CUSTOM) { return null; } - final Animation anim = mTransitionAnimation.loadAnimationRes(options.getPackageName(), - isEnter ? options.getEnterResId() : options.getExitResId()); + final int resId; + if (TransitionUtil.isOpeningType(mode)) { + resId = options.getEnterResId(); + } else if (TransitionUtil.isClosingType(mode)) { + resId = options.getExitResId(); + } else if (mode == TRANSIT_CHANGE) { + resId = options.getChangeResId(); + } else { + Log.w(TAG, "Unknown transit type:" + mode); + resId = DEFAULT_ANIMATION_RESOURCES_ID; + } + // Use the default animation if the resources ID is not specified. + if (resId == DEFAULT_ANIMATION_RESOURCES_ID) { + return null; + } + + final Animation anim; + if (Flags.activityEmbeddingAnimationCustomizationFlag()) { + // TODO(b/293658614): Consider allowing custom animations from non-default packages. + // Enforce limiting to animations from the default "android" package for now. + anim = mTransitionAnimation.loadDefaultAnimationRes(resId); + } else { + anim = mTransitionAnimation.loadAnimationRes(options.getPackageName(), resId); + } if (anim != null) { return anim; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index 7041ea307b0f..ece02711070e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -59,7 +59,7 @@ import android.window.IBackAnimationRunner; import android.window.IOnBackInvokedCallback; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.LatencyTracker; import com.android.internal.view.AppearanceRegion; import com.android.wm.shell.R; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java index 4988a9481d21..e24df0bdc05d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java @@ -30,7 +30,7 @@ import android.window.IOnBackInvokedCallback; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.jank.Cuj.CujType; -import com.android.wm.shell.common.InteractionJankMonitorUtils; +import com.android.internal.jank.InteractionJankMonitor; /** * Used to register the animation callback and runner, it will trigger result if gesture was finish @@ -86,20 +86,21 @@ public class BackAnimationRunner { */ void startAnimation(RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, Runnable finishedCallback) { + InteractionJankMonitor interactionJankMonitor = InteractionJankMonitor.getInstance(); final IRemoteAnimationFinishedCallback callback = new IRemoteAnimationFinishedCallback.Stub() { @Override public void onAnimationFinished() { if (shouldMonitorCUJ(apps)) { - InteractionJankMonitorUtils.endTracing(mCujType); + interactionJankMonitor.end(mCujType); } finishedCallback.run(); } }; mWaitingAnimation = false; if (shouldMonitorCUJ(apps)) { - InteractionJankMonitorUtils.beginTracing( - mCujType, mContext, apps[0].leash, /* tag */ null); + interactionJankMonitor.begin( + apps[0].leash, mContext, mCujType); } try { getRunner().onAnimationStart(TRANSIT_OLD_UNSET, apps, wallpapers, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt index a3111b31a2f9..4f04c5c28412 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt @@ -48,7 +48,7 @@ import com.android.internal.dynamicanimation.animation.SpringForce import com.android.internal.jank.Cuj import com.android.internal.policy.ScreenDecorationsUtils import com.android.internal.policy.SystemBarUtils -import com.android.internal.protolog.common.ProtoLog +import com.android.internal.protolog.ProtoLog import com.android.wm.shell.R import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.animation.Interpolators @@ -324,6 +324,7 @@ abstract class CrossActivityBackAnimation( enteringHasSameLetterbox = false lastPostCommitFlingScale = SPRING_SCALE gestureProgress = 0f + triggerBack = false } protected fun applyTransform( @@ -499,10 +500,12 @@ abstract class CrossActivityBackAnimation( } override fun onBackCancelled() { + triggerBack = false progressAnimator.onBackCancelled { finishAnimation() } } override fun onBackInvoked() { + triggerBack = true progressAnimator.reset() onGestureCommitted(progressAnimator.velocity) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java index 381914a58cf2..103a65422504 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java @@ -49,7 +49,7 @@ import android.window.IOnBackInvokedCallback; import com.android.internal.policy.ScreenDecorationsUtils; import com.android.internal.policy.SystemBarUtils; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.animation.Interpolators; import com.android.wm.shell.shared.annotations.ShellMainThread; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt index c738ce542f8a..e266e2cd7eea 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt @@ -28,7 +28,7 @@ import android.window.BackMotionEvent import android.window.BackNavigationInfo import com.android.internal.R import com.android.internal.policy.TransitionAnimation -import com.android.internal.protolog.common.ProtoLog +import com.android.internal.protolog.ProtoLog import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.protolog.ShellProtoLogGroup import com.android.wm.shell.shared.annotations.ShellMainThread 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 1279fc42c066..7dbbb04e4406 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 @@ -48,7 +48,7 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.InstanceId; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.launcher3.icons.BubbleIconFactory; import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView; import com.android.wm.shell.bubbles.bar.BubbleBarLayerView; @@ -894,11 +894,22 @@ public class Bubble implements BubbleViewProvider { } @Nullable - Intent getAppBubbleIntent() { + @VisibleForTesting + public Intent getAppBubbleIntent() { return mAppIntent; } /** + * Sets the intent for a bubble that is an app bubble (one for which {@link #mIsAppBubble} is + * true). + * + * @param appIntent The intent to set for the app bubble. + */ + void setAppBubbleIntent(Intent appIntent) { + mAppIntent = appIntent; + } + + /** * Returns whether this bubble is from an app versus a notification. */ public boolean isAppBubble() { 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 d2c36e6b637c..e36f6e6c04c5 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 @@ -84,7 +84,7 @@ 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.protolog.ProtoLog; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.CollectionUtils; import com.android.launcher3.icons.BubbleIconFactory; @@ -1450,6 +1450,8 @@ public class BubbleController implements ConfigurationChangeListener, if (b != null) { // It's in the overflow, so remove it & reinflate mBubbleData.dismissBubbleWithKey(appBubbleKey, Bubbles.DISMISS_NOTIF_CANCEL); + // Update the bubble entry in the overflow with the latest intent. + b.setAppBubbleIntent(intent); } else { // App bubble does not exist, lets add and expand it b = Bubble.createAppBubble(intent, user, icon, mMainExecutor); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java index 761e02598460..4e6c517b9194 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java @@ -35,7 +35,7 @@ import android.view.View; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.FrameworkStatsLog; import com.android.wm.shell.R; import com.android.wm.shell.bubbles.Bubbles.DismissReason; 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 c7ccd50af550..f7a5c271a729 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 @@ -67,7 +67,7 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.ScreenDecorationsUtils; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.Flags; import com.android.wm.shell.R; import com.android.wm.shell.common.AlphaOptimizedButton; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java index 18e04d14c71b..bf98ef82b475 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java @@ -42,7 +42,7 @@ import androidx.annotation.Nullable; import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.ContrastColorUtil; import com.android.wm.shell.Flags; import com.android.wm.shell.R; 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 2382545ab324..0cf187bd9c0f 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 @@ -29,7 +29,7 @@ import android.view.WindowManager; import androidx.annotation.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.launcher3.icons.IconNormalizer; import com.android.wm.shell.R; import com.android.wm.shell.common.bubbles.BubbleBarLocation; 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 09bec8c37b9a..f93f19d5d1eb 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 @@ -78,7 +78,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.protolog.ProtoLog; import com.android.internal.util.FrameworkStatsLog; import com.android.wm.shell.Flags; import com.android.wm.shell.R; 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 0b66bcb6930e..c79d9c4942bf 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 @@ -35,7 +35,7 @@ import android.view.ViewGroup; import androidx.annotation.Nullable; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.taskview.TaskView; /** 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 137568458e3c..9429c9e71b3b 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 @@ -29,7 +29,7 @@ import android.view.InputMonitor; import androidx.annotation.Nullable; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.bubbles.BubblesNavBarMotionEventHandler.MotionEventListener; /** 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 b7107f09b17f..d4f53ab353ea 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 @@ -28,7 +28,7 @@ import android.view.ViewConfiguration; import androidx.annotation.Nullable; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; /** * Handles {@link MotionEvent}s for bubbles that begin in the nav bar area diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java index aa4129a14dbc..fbef6b5e4a99 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java @@ -38,7 +38,7 @@ import androidx.dynamicanimation.animation.FloatPropertyCompat; import androidx.dynamicanimation.animation.SpringAnimation; import androidx.dynamicanimation.animation.SpringForce; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.animation.FlingAnimationUtils; import com.android.wm.shell.animation.Interpolators; import com.android.wm.shell.bubbles.BubbleExpandedView; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/CreateBubbleShortcutActivity.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/CreateBubbleShortcutActivity.kt index a124f95d7431..c93c11eb2fc2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/CreateBubbleShortcutActivity.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/CreateBubbleShortcutActivity.kt @@ -20,10 +20,10 @@ import android.app.Activity import android.content.pm.ShortcutManager import android.graphics.drawable.Icon import android.os.Bundle +import com.android.internal.protolog.ProtoLog import com.android.wm.shell.Flags import com.android.wm.shell.R import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES -import com.android.wm.shell.util.KtProtoLog /** Activity to create a shortcut to open bubbles */ class CreateBubbleShortcutActivity : Activity() { @@ -31,7 +31,7 @@ class CreateBubbleShortcutActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if (Flags.enableRetrievableBubbles()) { - KtProtoLog.d(WM_SHELL_BUBBLES, "Creating a shortcut for bubbles") + ProtoLog.d(WM_SHELL_BUBBLES, "Creating a shortcut for bubbles") createShortcut() } finish() diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/ShowBubblesActivity.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/ShowBubblesActivity.kt index ae7940ca1b65..e578e9e76979 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/ShowBubblesActivity.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/shortcut/ShowBubblesActivity.kt @@ -21,9 +21,9 @@ import android.content.ComponentName import android.content.Context import android.content.Intent import android.os.Bundle +import com.android.internal.protolog.ProtoLog import com.android.wm.shell.Flags import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES -import com.android.wm.shell.util.KtProtoLog /** Activity that sends a broadcast to open bubbles */ class ShowBubblesActivity : Activity() { @@ -37,7 +37,7 @@ class ShowBubblesActivity : Activity() { // Set the package as the receiver is not exported `package` = packageName } - KtProtoLog.v(WM_SHELL_BUBBLES, "Sending broadcast to show bubbles") + ProtoLog.v(WM_SHELL_BUBBLES, "Sending broadcast to show bubbles") sendBroadcast(intent) } finish() diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java deleted file mode 100644 index 86f00b83cadd..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java +++ /dev/null @@ -1,84 +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.common; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.Context; -import android.text.TextUtils; -import android.view.SurfaceControl; -import android.view.View; - -import com.android.internal.jank.Cuj.CujType; -import com.android.internal.jank.InteractionJankMonitor; - -/** Utils class for simplfy InteractionJank trancing call */ -public class InteractionJankMonitorUtils { - - /** - * Begin a trace session. - * - * @param cujType the specific {@link CujType}. - * @param view the view to trace - * @param tag the tag to distinguish different flow of same type CUJ. - */ - public static void beginTracing(@CujType int cujType, - @NonNull View view, @Nullable String tag) { - final InteractionJankMonitor.Configuration.Builder builder = - InteractionJankMonitor.Configuration.Builder.withView(cujType, view); - if (!TextUtils.isEmpty(tag)) { - builder.setTag(tag); - } - InteractionJankMonitor.getInstance().begin(builder); - } - - /** - * Begin a trace session. - * - * @param cujType the specific {@link CujType}. - * @param context the context - * @param surface the surface to trace - * @param tag the tag to distinguish different flow of same type CUJ. - */ - public static void beginTracing(@CujType int cujType, - @NonNull Context context, @NonNull SurfaceControl surface, @Nullable String tag) { - final InteractionJankMonitor.Configuration.Builder builder = - InteractionJankMonitor.Configuration.Builder.withSurface(cujType, context, surface); - if (!TextUtils.isEmpty(tag)) { - builder.setTag(tag); - } - InteractionJankMonitor.getInstance().begin(builder); - } - - /** - * End a trace session. - * - * @param cujType the specific {@link CujType}. - */ - public static void endTracing(@CujType int cujType) { - InteractionJankMonitor.getInstance().end(cujType); - } - - /** - * Cancel the trace session. - * - * @param cujType the specific {@link CujType}. - */ - public static void cancelTracing(@CujType int cujType) { - InteractionJankMonitor.getInstance().cancel(cujType); - } -} 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 index 81592c35e4ac..e92b0b59d2ef 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/LaunchAdjacentController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/LaunchAdjacentController.kt @@ -17,8 +17,8 @@ package com.android.wm.shell.common import android.window.WindowContainerToken import android.window.WindowContainerTransaction +import com.android.internal.protolog.ProtoLog 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 @@ -30,7 +30,7 @@ class LaunchAdjacentController(private val syncQueue: SyncTransactionQueue) { var launchAdjacentEnabled: Boolean = true set(value) { if (field != value) { - KtProtoLog.d(WM_SHELL_TASK_ORG, "set launch adjacent flag root enabled=%b", value) + ProtoLog.d(WM_SHELL_TASK_ORG, "set launch adjacent flag root enabled=%b", value) field = value container?.let { c -> if (value) { @@ -52,7 +52,7 @@ class LaunchAdjacentController(private val syncQueue: SyncTransactionQueue) { * @see WindowContainerTransaction.setLaunchAdjacentFlagRoot */ fun setLaunchAdjacentRoot(container: WindowContainerToken) { - KtProtoLog.d(WM_SHELL_TASK_ORG, "set new launch adjacent flag root container") + ProtoLog.d(WM_SHELL_TASK_ORG, "set new launch adjacent flag root container") this.container = container if (launchAdjacentEnabled) { enableContainer(container) @@ -67,7 +67,7 @@ class LaunchAdjacentController(private val syncQueue: SyncTransactionQueue) { * @see WindowContainerTransaction.clearLaunchAdjacentFlagRoot */ fun clearLaunchAdjacentRoot() { - KtProtoLog.d(WM_SHELL_TASK_ORG, "clear launch adjacent flag root container") + ProtoLog.d(WM_SHELL_TASK_ORG, "clear launch adjacent flag root container") container?.let { disableContainer(it) container = null @@ -75,14 +75,14 @@ class LaunchAdjacentController(private val syncQueue: SyncTransactionQueue) { } private fun enableContainer(container: WindowContainerToken) { - KtProtoLog.v(WM_SHELL_TASK_ORG, "enable launch adjacent flag root container") + ProtoLog.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") + ProtoLog.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/MultiInstanceHelper.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt index 9e8dfb5f0c6f..a6be64070ac1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt @@ -23,9 +23,9 @@ import android.content.pm.PackageManager import android.os.UserHandle import android.view.WindowManager.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI import com.android.internal.annotations.VisibleForTesting +import com.android.internal.protolog.ProtoLog import com.android.wm.shell.R import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL -import com.android.wm.shell.util.KtProtoLog import java.util.Arrays /** @@ -52,7 +52,7 @@ class MultiInstanceHelper @JvmOverloads constructor( val packageName = componentName.packageName for (pkg in staticAppsSupportingMultiInstance) { if (pkg == packageName) { - KtProtoLog.v(WM_SHELL, "application=%s in allowlist supports multi-instance", + ProtoLog.v(WM_SHELL, "application=%s in allowlist supports multi-instance", packageName) return true } @@ -70,10 +70,10 @@ class MultiInstanceHelper @JvmOverloads constructor( // If the above call doesn't throw a NameNotFoundException, then the activity property // should override the application property value if (activityProp.isBoolean) { - KtProtoLog.v(WM_SHELL, "activity=%s supports multi-instance", componentName) + ProtoLog.v(WM_SHELL, "activity=%s supports multi-instance", componentName) return activityProp.boolean } else { - KtProtoLog.w(WM_SHELL, "Warning: property=%s for activity=%s has non-bool type=%d", + ProtoLog.w(WM_SHELL, "Warning: property=%s for activity=%s has non-bool type=%d", PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName, activityProp.type) } } catch (nnfe: PackageManager.NameNotFoundException) { @@ -85,10 +85,10 @@ class MultiInstanceHelper @JvmOverloads constructor( val appProp = packageManager.getProperty( PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName) if (appProp.isBoolean) { - KtProtoLog.v(WM_SHELL, "application=%s supports multi-instance", packageName) + ProtoLog.v(WM_SHELL, "application=%s supports multi-instance", packageName) return appProp.boolean } else { - KtProtoLog.w(WM_SHELL, + ProtoLog.w(WM_SHELL, "Warning: property=%s for application=%s has non-bool type=%d", PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName, appProp.type) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java index e261d92bda5c..f7923924789e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java @@ -28,7 +28,7 @@ import android.window.WindowContainerTransaction; import android.window.WindowContainerTransactionCallback; import android.window.WindowOrganizer; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.transition.LegacyTransitions; import java.util.ArrayList; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java index 43c92cab6a68..43f9cb984322 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java @@ -32,7 +32,7 @@ import android.util.ArraySet; import android.view.Surface; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.sysui.ShellInit; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java index 6ffeb97f50fa..8e026f04ac31 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java @@ -27,7 +27,9 @@ import android.util.DisplayMetrics; import android.util.Size; import android.view.Gravity; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; +import com.android.wm.shell.protolog.ShellProtoLogGroup; import java.io.PrintWriter; @@ -39,6 +41,9 @@ public class PipBoundsAlgorithm { private static final String TAG = PipBoundsAlgorithm.class.getSimpleName(); private static final float INVALID_SNAP_FRACTION = -1f; + // The same value (with the same name) is used in Launcher. + private static final float PIP_ASPECT_RATIO_MISMATCH_THRESHOLD = 0.01f; + @NonNull private final PipBoundsState mPipBoundsState; @NonNull protected final PipDisplayLayoutState mPipDisplayLayoutState; @NonNull protected final SizeSpecSource mSizeSpecSource; @@ -206,9 +211,27 @@ public class PipBoundsAlgorithm { */ public static boolean isSourceRectHintValidForEnterPip(Rect sourceRectHint, Rect destinationBounds) { - return sourceRectHint != null - && sourceRectHint.width() > destinationBounds.width() - && sourceRectHint.height() > destinationBounds.height(); + if (sourceRectHint == null || sourceRectHint.isEmpty()) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "isSourceRectHintValidForEnterPip=false, empty hint"); + return false; + } + if (sourceRectHint.width() <= destinationBounds.width() + || sourceRectHint.height() <= destinationBounds.height()) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "isSourceRectHintValidForEnterPip=false, hint(%s) is smaller" + + " than destination(%s)", sourceRectHint, destinationBounds); + return false; + } + final float reportedRatio = destinationBounds.width() / (float) destinationBounds.height(); + final float inferredRatio = sourceRectHint.width() / (float) sourceRectHint.height(); + if (Math.abs(reportedRatio - inferredRatio) > PIP_ASPECT_RATIO_MISMATCH_THRESHOLD) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "isSourceRectHintValidForEnterPip=false, hint(%s) does not match" + + " destination(%s) aspect ratio", sourceRectHint, destinationBounds); + return false; + } + return true; } public float getDefaultAspectRatio() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java index 7ceaaea3962f..64a1b0c804da 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java @@ -32,7 +32,7 @@ import android.util.ArraySet; import android.util.Size; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.function.TriConsumer; import com.android.wm.shell.R; import com.android.wm.shell.common.DisplayLayout; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPerfHintController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPerfHintController.java index c421dec025f2..b9c698e5d8b4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPerfHintController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPerfHintController.java @@ -26,7 +26,7 @@ import android.window.SystemPerformanceHinter.HighPerfSession; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.shared.annotations.ShellMainThread; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt index a09720dd6a70..dcf84d927ad3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt @@ -30,10 +30,11 @@ 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.internal.protolog.ProtoLog import com.android.wm.shell.Flags import com.android.wm.shell.protolog.ShellProtoLogGroup import kotlin.math.abs +import kotlin.math.roundToInt /** A class that includes convenience methods. */ object PipUtils { @@ -149,16 +150,16 @@ object PipUtils { val appBoundsAspRatio = appBounds.width().toFloat() / appBounds.height() val width: Int val height: Int - var left = 0 - var top = 0 + var left = appBounds.left + var top = appBounds.top if (appBoundsAspRatio < aspectRatio) { width = appBounds.width() - height = Math.round(width / aspectRatio) - top = (appBounds.height() - height) / 2 + height = (width / aspectRatio).roundToInt() + top = appBounds.top + (appBounds.height() - height) / 2 } else { height = appBounds.height() - width = Math.round(height * aspectRatio) - left = (appBounds.width() - width) / 2 + width = (height * aspectRatio).roundToInt() + left = appBounds.left + (appBounds.width() - width) / 2 } return Rect(left, top, left + width, top + height) } 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 c2242a8b87fa..1bc179551825 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 @@ -54,7 +54,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.animation.Interpolators; import com.android.wm.shell.protolog.ShellProtoLogGroup; @@ -336,11 +336,6 @@ public class DividerView extends FrameLayout implements View.OnTouchListener { setTouching(); mStartPos = touchPos; mMoving = false; - // This triggers initialization of things like the resize veil in preparation for - // showing it when the user moves the divider past the slop, and has to be done - // before onStartDragging() which starts the jank interaction tracing - mSplitLayout.updateDividerBounds(mSplitLayout.getDividerPosition(), - false /* shouldUseParallaxEffect */); mSplitLayout.onStartDragging(); break; case MotionEvent.ACTION_MOVE: @@ -497,6 +492,11 @@ public class DividerView extends FrameLayout implements View.OnTouchListener { return mHideHandle; } + /** Returns true if the divider is currently being physically controlled by the user. */ + boolean isMoving() { + return mMoving; + } + private class DoubleTapListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onDoubleTap(MotionEvent e) { 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 de016d3ae400..5097ed8866c9 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 @@ -269,7 +269,9 @@ public class SplitDecorManager extends WindowlessWindowManager { if (update) { if (immediately) { + t.setAlpha(mBackgroundLeash, showVeil ? 1f : 0f); t.setVisibility(mBackgroundLeash, showVeil); + t.setAlpha(mIconLeash, showVeil ? 1f : 0f); t.setVisibility(mIconLeash, showVeil); } else { startFadeAnimation(showVeil, false, null); 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 8ced76fd23af..51f9de8305f8 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 @@ -59,7 +59,8 @@ import android.window.WindowContainerTransaction; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.jank.InteractionJankMonitor; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.animation.Interpolators; @@ -67,7 +68,6 @@ 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.InteractionJankMonitorUtils; import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition; import com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition; import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; @@ -131,6 +131,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange private final boolean mDimNonImeSide; private final boolean mAllowLeftRightSplitInPortrait; + private final InteractionJankMonitor mInteractionJankMonitor; private boolean mIsLeftRightSplit; private ValueAnimator mDividerFlingAnimator; @@ -163,6 +164,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange mRootBounds.set(configuration.windowConfiguration.getBounds()); mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds); + mInteractionJankMonitor = InteractionJankMonitor.getInstance(); resetDividerPosition(); updateInvisibleRect(); } @@ -569,12 +571,11 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange } void onStartDragging() { - InteractionJankMonitorUtils.beginTracing(CUJ_SPLIT_SCREEN_RESIZE, mContext, - getDividerLeash(), null /* tag */); + mInteractionJankMonitor.begin(getDividerLeash(), mContext, CUJ_SPLIT_SCREEN_RESIZE); } void onDraggingCancelled() { - InteractionJankMonitorUtils.cancelTracing(CUJ_SPLIT_SCREEN_RESIZE); + mInteractionJankMonitor.cancel(CUJ_SPLIT_SCREEN_RESIZE); } void onDoubleTappedDivider() { @@ -638,7 +639,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange if (flingFinishedCallback != null) { flingFinishedCallback.run(); } - InteractionJankMonitorUtils.endTracing( + mInteractionJankMonitor.end( CUJ_SPLIT_SCREEN_RESIZE); return; } @@ -651,9 +652,18 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange .ofInt(from, to) .setDuration(duration); mDividerFlingAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); + + // If the divider is being physically controlled by the user, we use a cool parallax effect + // on the task windows. So if this "snap" animation is an extension of a user-controlled + // movement, we pass in true here to continue the parallax effect smoothly. + boolean isBeingMovedByUser = mSplitWindowManager.getDividerView() != null + && mSplitWindowManager.getDividerView().isMoving(); + mDividerFlingAnimator.addUpdateListener( animation -> updateDividerBounds( - (int) animation.getAnimatedValue(), false /* shouldUseParallaxEffect */) + (int) animation.getAnimatedValue(), + isBeingMovedByUser /* shouldUseParallaxEffect */ + ) ); mDividerFlingAnimator.addListener(new AnimatorListenerAdapter() { @Override @@ -661,7 +671,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange if (flingFinishedCallback != null) { flingFinishedCallback.run(); } - InteractionJankMonitorUtils.endTracing( + mInteractionJankMonitor.end( CUJ_SPLIT_SCREEN_RESIZE); mDividerFlingAnimator = null; } @@ -707,8 +717,8 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange set.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { - InteractionJankMonitorUtils.beginTracing(CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER, - mContext, getDividerLeash(), null /*tag*/); + mInteractionJankMonitor.begin(getDividerLeash(), + mContext, CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER); } @Override @@ -716,12 +726,12 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange mDividerPosition = dividerPos; updateBounds(mDividerPosition); finishCallback.accept(insets); - InteractionJankMonitorUtils.endTracing(CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER); + mInteractionJankMonitor.end(CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER); } @Override public void onAnimationCancel(Animator animation) { - InteractionJankMonitorUtils.cancelTracing(CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER); + mInteractionJankMonitor.cancel(CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER); } }); set.start(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java index 5d121c23c6e1..46c1a43f9efe 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java @@ -37,7 +37,6 @@ import android.view.LayoutInflater; import android.view.SurfaceControl; import android.view.SurfaceControlViewHost; import android.view.SurfaceSession; -import android.view.View; import android.view.WindowManager; import android.view.WindowlessWindowManager; @@ -192,7 +191,7 @@ public final class SplitWindowManager extends WindowlessWindowManager { mDividerView.setInteractive(interactive, hideHandle, from); } - View getDividerView() { + DividerView getDividerView() { return mDividerView; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt index 6781d08c9904..d1b2347a4411 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt @@ -19,6 +19,16 @@ package com.android.wm.shell.compatui import android.app.TaskInfo -fun isSingleTopActivityTranslucent(task: TaskInfo) = - task.isTopActivityTransparent && task.numActivities == 1 +import android.content.Context +import com.android.internal.R +// TODO(b/347289970): Consider replacing with API +fun isTopActivityExemptFromDesktopWindowing(context: Context, task: TaskInfo) = + isSystemUiTask(context, task) || (task.isTopActivityTransparent && task.numActivities == 1 + && !task.isTopActivityStyleFloating) + +private fun isSystemUiTask(context: Context, task: TaskInfo): Boolean { + val sysUiPackageName: String = + context.resources.getString(R.string.config_systemUi) + return task.baseActivity?.packageName == sysUiPackageName +} 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 bfac24b81d2f..c02c9cf3fd72 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 @@ -20,7 +20,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.CameraCompatTaskInfo.CameraCompatControlState; import android.app.TaskInfo; import android.content.ComponentName; import android.content.Context; @@ -50,6 +49,10 @@ 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.api.CompatUIEvent; +import com.android.wm.shell.compatui.api.CompatUIHandler; +import com.android.wm.shell.compatui.api.CompatUIInfo; +import com.android.wm.shell.compatui.impl.CompatUIEvents.SizeCompatRestartButtonClicked; import com.android.wm.shell.sysui.KeyguardChangeListener; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; @@ -71,17 +74,7 @@ import java.util.function.Predicate; * activities are in compatibility mode. */ public class CompatUIController implements OnDisplaysChangedListener, - DisplayImeController.ImePositionProcessor, KeyguardChangeListener { - - /** Callback for compat UI interaction. */ - public interface CompatUICallback { - /** Called when the size compat restart button appears. */ - void onSizeCompatRestartButtonAppeared(int taskId); - /** Called when the size compat restart button is clicked. */ - void onSizeCompatRestartButtonClicked(int taskId); - /** Called when the camera compat control state is updated. */ - void onCameraControlStateUpdated(int taskId, @CameraCompatControlState int state); - } + DisplayImeController.ImePositionProcessor, KeyguardChangeListener, CompatUIHandler { private static final String TAG = "CompatUIController"; @@ -170,7 +163,7 @@ public class CompatUIController implements OnDisplaysChangedListener, private final Function<Integer, Integer> mDisappearTimeSupplier; @Nullable - private CompatUICallback mCompatUICallback; + private Consumer<CompatUIEvent> mCallback; // Indicates if the keyguard is currently showing, in which case compat UIs shouldn't // be shown. @@ -230,20 +223,21 @@ public class CompatUIController implements OnDisplaysChangedListener, mCompatUIShellCommandHandler.onInit(); } - /** Sets the callback for Compat UI interactions. */ - public void setCompatUICallback(@NonNull CompatUICallback compatUiCallback) { - mCompatUICallback = compatUiCallback; + /** Sets the callback for UI interactions. */ + @Override + public void setCallback(@Nullable Consumer<CompatUIEvent> callback) { + mCallback = callback; } /** * Called when the Task info changed. Creates and updates the compat UI if there is an * activity in size compat, or removes the UI if there is no size compat activity. * - * @param taskInfo {@link TaskInfo} task the activity is in. - * @param taskListener listener to handle the Task Surface placement. + * @param compatUIInfo {@link CompatUIInfo} encapsulates information about the task and listener */ - public void onCompatInfoChanged(@NonNull TaskInfo taskInfo, - @Nullable ShellTaskOrganizer.TaskListener taskListener) { + public void onCompatInfoChanged(@NonNull CompatUIInfo compatUIInfo) { + final TaskInfo taskInfo = compatUIInfo.getTaskInfo(); + final ShellTaskOrganizer.TaskListener taskListener = compatUIInfo.getListener(); if (taskInfo != null && !taskInfo.appCompatTaskInfo.topActivityInSizeCompat) { mSetOfTaskIdsShowingRestartDialog.remove(taskInfo.taskId); } @@ -258,9 +252,15 @@ public class CompatUIController implements OnDisplaysChangedListener, return; } // We're showing the first reachability education so we ignore incoming TaskInfo - // until the education flow has completed or we double tap. + // until the education flow has completed or we double tap. The double-tap + // basically cancel all the onboarding flow. We don't have to ignore events in case + // the app is in size compat mode. if (mIsFirstReachabilityEducationRunning) { - return; + if (!taskInfo.appCompatTaskInfo.isFromLetterboxDoubleTap + && !taskInfo.appCompatTaskInfo.topActivityInSizeCompat) { + return; + } + mIsFirstReachabilityEducationRunning = false; } if (taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed) { if (taskInfo.appCompatTaskInfo.isLetterboxEducationEnabled) { @@ -278,17 +278,24 @@ public class CompatUIController implements OnDisplaysChangedListener, final boolean isFirstTimeVerticalReachabilityEdu = !topActivityPillarboxed && !mCompatUIConfiguration.hasSeenVerticalReachabilityEducation(taskInfo); if (isFirstTimeHorizontalReachabilityEdu || isFirstTimeVerticalReachabilityEdu) { - mIsFirstReachabilityEducationRunning = true; mCompatUIConfiguration.setSeenLetterboxEducation(taskInfo.userId); - createOrUpdateReachabilityEduLayout(taskInfo, taskListener); - return; + // We activate the first reachability education if the double-tap is enabled. + // If the double tap is not enabled (e.g. thin letterbox) we just set the value + // of the education being seen. + if (taskInfo.appCompatTaskInfo.isLetterboxDoubleTapEnabled) { + mIsFirstReachabilityEducationRunning = true; + createOrUpdateReachabilityEduLayout(taskInfo, taskListener); + return; + } } } } createOrUpdateCompatLayout(taskInfo, taskListener); createOrUpdateRestartDialogLayout(taskInfo, taskListener); if (mCompatUIConfiguration.getHasSeenLetterboxEducation(taskInfo.userId)) { - createOrUpdateReachabilityEduLayout(taskInfo, taskListener); + if (taskInfo.appCompatTaskInfo.isLetterboxDoubleTapEnabled) { + 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) { @@ -453,7 +460,7 @@ public class CompatUIController implements OnDisplaysChangedListener, CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener) { return new CompatUIWindowManager(context, - taskInfo, mSyncQueue, mCompatUICallback, taskListener, + taskInfo, mSyncQueue, mCallback, taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId), mCompatUIHintsState, mCompatUIConfiguration, this::onRestartButtonClicked); } @@ -465,9 +472,9 @@ public class CompatUIController implements OnDisplaysChangedListener, taskInfoState.first)) { // We need to show the dialog mSetOfTaskIdsShowingRestartDialog.add(taskInfoState.first.taskId); - onCompatInfoChanged(taskInfoState.first, taskInfoState.second); + onCompatInfoChanged(new CompatUIInfo(taskInfoState.first, taskInfoState.second)); } else { - mCompatUICallback.onSizeCompatRestartButtonClicked(taskInfoState.first.taskId); + mCallback.accept(new SizeCompatRestartButtonClicked(taskInfoState.first.taskId)); } } @@ -562,13 +569,13 @@ public class CompatUIController implements OnDisplaysChangedListener, private void onRestartDialogCallback( Pair<TaskInfo, ShellTaskOrganizer.TaskListener> stateInfo) { mTaskIdToRestartDialogWindowManagerMap.remove(stateInfo.first.taskId); - mCompatUICallback.onSizeCompatRestartButtonClicked(stateInfo.first.taskId); + mCallback.accept(new SizeCompatRestartButtonClicked(stateInfo.first.taskId)); } private void onRestartDialogDismissCallback( Pair<TaskInfo, ShellTaskOrganizer.TaskListener> stateInfo) { mSetOfTaskIdsShowingRestartDialog.remove(stateInfo.first.taskId); - onCompatInfoChanged(stateInfo.first, stateInfo.second); + onCompatInfoChanged(new CompatUIInfo(stateInfo.first, stateInfo.second)); } private void createOrUpdateReachabilityEduLayout(@NonNull TaskInfo taskInfo, 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 3ab1fad2b203..1931212df548 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 @@ -40,8 +40,10 @@ 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.SyncTransactionQueue; -import com.android.wm.shell.compatui.CompatUIController.CompatUICallback; import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState; +import com.android.wm.shell.compatui.api.CompatUIEvent; +import com.android.wm.shell.compatui.impl.CompatUIEvents.CameraControlStateUpdated; +import com.android.wm.shell.compatui.impl.CompatUIEvents.SizeCompatRestartButtonAppeared; import java.util.function.Consumer; @@ -50,10 +52,13 @@ import java.util.function.Consumer; */ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { - private final CompatUICallback mCallback; + @NonNull + private final Consumer<CompatUIEvent> mCallback; + @NonNull private final CompatUIConfiguration mCompatUIConfiguration; + @NonNull private final Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnRestartButtonClicked; // Remember the last reported states in case visibility changes due to keyguard or IME updates. @@ -65,6 +70,7 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { int mCameraCompatControlState = CAMERA_COMPAT_CONTROL_HIDDEN; @VisibleForTesting + @NonNull CompatUIHintsState mCompatUIHintsState; @Nullable @@ -73,11 +79,15 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { private final float mHideScmTolerance; - CompatUIWindowManager(Context context, TaskInfo taskInfo, - SyncTransactionQueue syncQueue, CompatUICallback callback, - ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout, - CompatUIHintsState compatUIHintsState, CompatUIConfiguration compatUIConfiguration, - Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onRestartButtonClicked) { + CompatUIWindowManager(@NonNull Context context, @NonNull TaskInfo taskInfo, + @NonNull SyncTransactionQueue syncQueue, + @NonNull Consumer<CompatUIEvent> callback, + @Nullable ShellTaskOrganizer.TaskListener taskListener, + @Nullable DisplayLayout displayLayout, + @NonNull CompatUIHintsState compatUIHintsState, + @NonNull CompatUIConfiguration compatUIConfiguration, + @NonNull Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> + onRestartButtonClicked) { super(context, taskInfo, syncQueue, taskListener, displayLayout); mCallback = callback; mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat; @@ -122,7 +132,7 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { updateVisibilityOfViews(); if (mHasSizeCompat) { - mCallback.onSizeCompatRestartButtonAppeared(mTaskId); + mCallback.accept(new SizeCompatRestartButtonAppeared(mTaskId)); } return mLayout; @@ -177,7 +187,7 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { mCameraCompatControlState == CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED ? CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED : CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED; - mCallback.onCameraControlStateUpdated(mTaskId, mCameraCompatControlState); + mCallback.accept(new CameraControlStateUpdated(mTaskId, mCameraCompatControlState)); mLayout.updateCameraTreatmentButton(mCameraCompatControlState); } @@ -188,7 +198,7 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { return; } mCameraCompatControlState = CAMERA_COMPAT_CONTROL_DISMISSED; - mCallback.onCameraControlStateUpdated(mTaskId, CAMERA_COMPAT_CONTROL_DISMISSED); + mCallback.accept(new CameraControlStateUpdated(mTaskId, CAMERA_COMPAT_CONTROL_DISMISSED)); mLayout.setCameraControlVisibility(/* show= */ false); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIEvent.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIEvent.kt new file mode 100644 index 000000000000..4a0cf9843722 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIEvent.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.compatui.api + +/** + * Abstraction for all the possible Compat UI Component events. + */ +interface CompatUIEvent { + /** + * Unique event identifier + */ + val eventId: Int + + @Suppress("UNCHECKED_CAST") + fun <T : CompatUIEvent> asType(): T? = this as? T + + fun <T : CompatUIEvent> asType(clazz: Class<T>): T? { + return if (clazz.isInstance(this)) clazz.cast(this) else null + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIHandler.kt new file mode 100644 index 000000000000..817e554b550e --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIHandler.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.wm.shell.compatui.api + +import java.util.function.Consumer + +/** + * Abstraction for the objects responsible to handle all the CompatUI components and the + * communication with the server. + */ +interface CompatUIHandler { + /** + * Invoked when a new model is coming from the server. + */ + fun onCompatInfoChanged(compatUIInfo: CompatUIInfo) + + /** + * Optional reference to the object responsible to send {@link CompatUIEvent} + */ + fun setCallback(compatUIEventSender: Consumer<CompatUIEvent>?) +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIInfo.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIInfo.kt new file mode 100644 index 000000000000..dbbf049792f5 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIInfo.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.compatui.api + +import android.app.TaskInfo +import com.android.wm.shell.ShellTaskOrganizer + +/** + * Encapsulate the info of the message from core. + */ +data class CompatUIInfo(val taskInfo: TaskInfo, val listener: ShellTaskOrganizer.TaskListener?)
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/CompatUIEvents.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/CompatUIEvents.kt new file mode 100644 index 000000000000..58ce8ed6c978 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/CompatUIEvents.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.compatui.impl + +import android.app.AppCompatTaskInfo +import android.app.CameraCompatTaskInfo +import com.android.wm.shell.compatui.api.CompatUIEvent + +internal const val SIZE_COMPAT_RESTART_BUTTON_APPEARED = 0 +internal const val SIZE_COMPAT_RESTART_BUTTON_CLICKED = 1 +internal const val CAMERA_CONTROL_STATE_UPDATE = 2 + +/** + * All the {@link CompatUIEvent} the Compat UI Framework can handle + */ +sealed class CompatUIEvents(override val eventId: Int) : CompatUIEvent { + /** Sent when the size compat restart button appears. */ + data class SizeCompatRestartButtonAppeared(val taskId: Int) : + CompatUIEvents(SIZE_COMPAT_RESTART_BUTTON_APPEARED) + + /** Sent when the size compat restart button is clicked. */ + data class SizeCompatRestartButtonClicked(val taskId: Int) : + CompatUIEvents(SIZE_COMPAT_RESTART_BUTTON_CLICKED) + + /** Sent when the camera compat control state is updated. */ + data class CameraControlStateUpdated( + val taskId: Int, + @CameraCompatTaskInfo.CameraCompatControlState val state: Int + ) : CompatUIEvents(CAMERA_CONTROL_STATE_UPDATE) +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt new file mode 100644 index 000000000000..a181eafada7d --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.compatui.impl + +import com.android.wm.shell.compatui.api.CompatUIEvent +import com.android.wm.shell.compatui.api.CompatUIHandler +import com.android.wm.shell.compatui.api.CompatUIInfo +import java.util.function.Consumer + +/** + * Default implementation of {@link CompatUIHandler} to handle CompatUI components + */ +class DefaultCompatUIHandler : CompatUIHandler { + + private var compatUIEventSender: Consumer<CompatUIEvent>? = null + override fun onCompatInfoChanged(compatUIInfo: CompatUIInfo) { + // Empty at the moment + } + + override fun setCallback(compatUIEventSender: Consumer<CompatUIEvent>?) { + this.compatUIEventSender = compatUIEventSender + } +}
\ No newline at end of file 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 609e5af5c5b0..9bdc0b2b55b4 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 @@ -71,6 +71,8 @@ import com.android.wm.shell.common.pip.SizeSpecSource; import com.android.wm.shell.compatui.CompatUIConfiguration; import com.android.wm.shell.compatui.CompatUIController; import com.android.wm.shell.compatui.CompatUIShellCommandHandler; +import com.android.wm.shell.compatui.api.CompatUIHandler; +import com.android.wm.shell.compatui.impl.DefaultCompatUIHandler; import com.android.wm.shell.desktopmode.DesktopMode; import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.desktopmode.DesktopTasksController; @@ -88,12 +90,12 @@ import com.android.wm.shell.recents.RecentTasks; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.recents.RecentsTransitionHandler; import com.android.wm.shell.recents.TaskStackTransitionObserver; -import com.android.wm.shell.shared.DesktopModeStatus; import com.android.wm.shell.shared.ShellTransitions; import com.android.wm.shell.shared.annotations.ShellAnimationThread; import com.android.wm.shell.shared.annotations.ShellBackgroundThread; import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.shared.annotations.ShellSplashscreenThread; +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.startingsurface.StartingSurface; @@ -211,7 +213,7 @@ public abstract class WMShellBaseModule { Context context, ShellInit shellInit, ShellCommandHandler shellCommandHandler, - Optional<CompatUIController> compatUI, + Optional<CompatUIHandler> compatUI, Optional<UnfoldAnimationController> unfoldAnimationController, Optional<RecentTasksController> recentTasksOptional, @ShellMainThread ShellExecutor mainExecutor) { @@ -230,7 +232,7 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides - static Optional<CompatUIController> provideCompatUIController( + static Optional<CompatUIHandler> provideCompatUIController( Context context, ShellInit shellInit, ShellController shellController, @@ -247,6 +249,9 @@ public abstract class WMShellBaseModule { if (!context.getResources().getBoolean(R.bool.config_enableCompatUIController)) { return Optional.empty(); } + if (Flags.appCompatUiFramework()) { + return Optional.of(new DefaultCompatUIHandler()); + } return Optional.of( new CompatUIController( context, @@ -898,7 +903,7 @@ public abstract class WMShellBaseModule { // Use optional-of-lazy for the dependency that this provider relies on. // Lazy ensures that this provider will not be the cause the dependency is created // when it will not be returned due to the condition below. - return desktopTasksController.flatMap((lazy)-> { + return desktopTasksController.flatMap((lazy) -> { if (DesktopModeStatus.canEnterDesktopMode(context)) { return Optional.of(lazy.get()); } @@ -917,7 +922,7 @@ public abstract class WMShellBaseModule { // Use optional-of-lazy for the dependency that this provider relies on. // Lazy ensures that this provider will not be the cause the dependency is created // when it will not be returned due to the condition below. - return desktopModeTaskRepository.flatMap((lazy)-> { + return desktopModeTaskRepository.flatMap((lazy) -> { if (DesktopModeStatus.canEnterDesktopMode(context)) { return Optional.of(lazy.get()); } 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 87bd84017dee..eeceaa943af2 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 @@ -17,6 +17,7 @@ package com.android.wm.shell.dagger; import android.annotation.Nullable; +import android.app.KeyguardManager; import android.content.Context; import android.content.pm.LauncherApps; import android.os.Handler; @@ -76,10 +77,10 @@ import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.recents.RecentsTransitionHandler; -import com.android.wm.shell.shared.DesktopModeStatus; import com.android.wm.shell.shared.annotations.ShellAnimationThread; import com.android.wm.shell.shared.annotations.ShellBackgroundThread; import com.android.wm.shell.shared.annotations.ShellMainThread; +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; @@ -220,7 +221,8 @@ public abstract class WMShellModule { SyncTransactionQueue syncQueue, Transitions transitions, Optional<DesktopTasksController> desktopTasksController, - RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) { + RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, + InteractionJankMonitor interactionJankMonitor) { if (DesktopModeStatus.canEnterDesktopMode(context)) { return new DesktopModeWindowDecorViewModel( context, @@ -237,7 +239,8 @@ public abstract class WMShellModule { syncQueue, transitions, desktopTasksController, - rootTaskDisplayAreaOrganizer); + rootTaskDisplayAreaOrganizer, + interactionJankMonitor); } return new CaptionWindowDecorViewModel( context, @@ -512,6 +515,7 @@ public abstract class WMShellModule { RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, DragAndDropController dragAndDropController, Transitions transitions, + KeyguardManager keyguardManager, EnterDesktopTaskTransitionHandler enterDesktopTransitionHandler, ExitDesktopTaskTransitionHandler exitDesktopTransitionHandler, ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler, @@ -526,7 +530,7 @@ public abstract class WMShellModule { Optional<RecentTasksController> recentTasksController) { return new DesktopTasksController(context, shellInit, shellCommandHandler, shellController, displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, - dragAndDropController, transitions, enterDesktopTransitionHandler, + dragAndDropController, transitions, keyguardManager, enterDesktopTransitionHandler, exitDesktopTransitionHandler, toggleResizeDesktopTaskTransitionHandler, dragToDesktopTransitionHandler, desktopModeTaskRepository, desktopModeLoggerTransitionObserver, launchAdjacentController, @@ -573,17 +577,18 @@ public abstract class WMShellModule { @WMSingleton @Provides static ToggleResizeDesktopTaskTransitionHandler provideToggleResizeDesktopTaskTransitionHandler( - Transitions transitions) { - return new ToggleResizeDesktopTaskTransitionHandler(transitions); + Transitions transitions, InteractionJankMonitor interactionJankMonitor) { + return new ToggleResizeDesktopTaskTransitionHandler(transitions, interactionJankMonitor); } @WMSingleton @Provides static ExitDesktopTaskTransitionHandler provideExitDesktopTaskTransitionHandler( Transitions transitions, - Context context - ) { - return new ExitDesktopTaskTransitionHandler(transitions, context); + Context context, + InteractionJankMonitor interactionJankMonitor) { + return new ExitDesktopTaskTransitionHandler( + transitions, context, interactionJankMonitor); } @WMSingleton @@ -599,11 +604,12 @@ public abstract class WMShellModule { Context context, Optional<DesktopModeTaskRepository> desktopModeTaskRepository, Transitions transitions, + ShellTaskOrganizer shellTaskOrganizer, ShellInit shellInit ) { return desktopModeTaskRepository.flatMap(repository -> Optional.of(new DesktopTasksTransitionObserver( - context, repository, transitions, shellInit)) + context, repository, transitions, shellTaskOrganizer, shellInit)) ); } @@ -642,6 +648,7 @@ public abstract class WMShellModule { ShellInit shellInit, ShellController shellController, ShellCommandHandler shellCommandHandler, + ShellTaskOrganizer shellTaskOrganizer, DisplayController displayController, UiEventLogger uiEventLogger, IconProvider iconProvider, @@ -649,8 +656,8 @@ public abstract class WMShellModule { Transitions transitions, @ShellMainThread ShellExecutor mainExecutor) { return new DragAndDropController(context, shellInit, shellController, shellCommandHandler, - displayController, uiEventLogger, iconProvider, globalDragListener, transitions, - mainExecutor); + shellTaskOrganizer, displayController, uiEventLogger, iconProvider, + globalDragListener, transitions, mainExecutor); } // diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java index 677fd5deffd3..240cf3b96e89 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java @@ -212,12 +212,13 @@ public abstract class Pip1Module { @WMSingleton @Provides static PipMotionHelper providePipMotionHelper(Context context, + @ShellMainThread ShellExecutor mainExecutor, PipBoundsState pipBoundsState, PipTaskOrganizer pipTaskOrganizer, PhonePipMenuController menuController, PipSnapAlgorithm pipSnapAlgorithm, PipTransitionController pipTransitionController, FloatingContentCoordinator floatingContentCoordinator, Optional<PipPerfHintController> pipPerfHintControllerOptional) { - return new PipMotionHelper(context, pipBoundsState, pipTaskOrganizer, + return new PipMotionHelper(context, mainExecutor, pipBoundsState, pipTaskOrganizer, menuController, pipSnapAlgorithm, pipTransitionController, floatingContentCoordinator, pipPerfHintControllerOptional); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt index 9192e6ed3175..400882a8da9a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt @@ -16,9 +16,9 @@ package com.android.wm.shell.desktopmode +import com.android.internal.protolog.ProtoLog import com.android.internal.util.FrameworkStatsLog import com.android.wm.shell.protolog.ShellProtoLogGroup -import com.android.wm.shell.util.KtProtoLog /** Event logger for logging desktop mode session events */ class DesktopModeEventLogger { @@ -27,7 +27,7 @@ class DesktopModeEventLogger { * entering desktop mode */ fun logSessionEnter(sessionId: Int, enterReason: EnterReason) { - KtProtoLog.v( + ProtoLog.v( ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, "DesktopModeLogger: Logging session enter, session: %s reason: %s", sessionId, @@ -47,7 +47,7 @@ class DesktopModeEventLogger { * exiting desktop mode */ fun logSessionExit(sessionId: Int, exitReason: ExitReason) { - KtProtoLog.v( + ProtoLog.v( ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, "DesktopModeLogger: Logging session exit, session: %s reason: %s", sessionId, @@ -67,7 +67,7 @@ class DesktopModeEventLogger { * session id [sessionId] */ fun logTaskAdded(sessionId: Int, taskUpdate: TaskUpdate) { - KtProtoLog.v( + ProtoLog.v( ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, "DesktopModeLogger: Logging task added, session: %s taskId: %s", sessionId, @@ -99,7 +99,7 @@ class DesktopModeEventLogger { * session id [sessionId] */ fun logTaskRemoved(sessionId: Int, taskUpdate: TaskUpdate) { - KtProtoLog.v( + ProtoLog.v( ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, "DesktopModeLogger: Logging task remove, session: %s taskId: %s", sessionId, @@ -131,7 +131,7 @@ class DesktopModeEventLogger { * having session id [sessionId] */ fun logTaskInfoChanged(sessionId: Int, taskUpdate: TaskUpdate) { - KtProtoLog.v( + ProtoLog.v( ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, "DesktopModeLogger: Logging task info changed, session: %s taskId: %s", sessionId, @@ -159,13 +159,24 @@ class DesktopModeEventLogger { } companion object { + /** + * Describes a task position and dimensions. + * + * @property instanceId instance id of the task + * @property uid uid of the app associated with the task + * @property taskHeight height of the task in px + * @property taskWidth width of the task in px + * @property taskX x-coordinate of the top-left corner + * @property taskY y-coordinate of the top-left corner + * + */ data class TaskUpdate( val instanceId: Int, val uid: Int, - val taskHeight: Int = Int.MIN_VALUE, - val taskWidth: Int = Int.MIN_VALUE, - val taskX: Int = Int.MIN_VALUE, - val taskY: Int = Int.MIN_VALUE, + val taskHeight: Int, + val taskWidth: Int, + val taskX: Int, + val taskY: Int, ) /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt index 641952b28bfb..066b5ad39d0f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt @@ -35,7 +35,7 @@ import androidx.core.util.plus import androidx.core.util.putAll import com.android.internal.logging.InstanceId import com.android.internal.logging.InstanceIdSequence -import com.android.internal.protolog.common.ProtoLog +import com.android.internal.protolog.ProtoLog import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate @@ -46,11 +46,10 @@ import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_ import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE -import com.android.wm.shell.shared.DesktopModeStatus +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.shared.TransitionUtil import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions -import com.android.wm.shell.util.KtProtoLog /** * A [Transitions.TransitionObserver] that observes transitions and the proposed changes to log @@ -106,7 +105,7 @@ class DesktopModeLoggerTransitionObserver( ) { // this was a new recents animation if (info.isExitToRecentsTransition() && tasksSavedForRecents.isEmpty()) { - KtProtoLog.v( + ProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopModeLogger: Recents animation running, saving tasks for later" ) @@ -132,7 +131,7 @@ class DesktopModeLoggerTransitionObserver( info.flags == 0 && tasksSavedForRecents.isNotEmpty() ) { - KtProtoLog.v( + ProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopModeLogger: Canceled recents animation, restoring tasks" ) @@ -202,7 +201,7 @@ class DesktopModeLoggerTransitionObserver( } } - KtProtoLog.v( + ProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopModeLogger: taskInfo map after processing changes %s", postTransitionFreeformTasks.size() @@ -282,17 +281,23 @@ class DesktopModeLoggerTransitionObserver( visibleFreeformTaskInfos.putAll(postTransitionVisibleFreeformTasks) } - // TODO(b/326231724) - Add logging around taskInfoChanges Updates /** Compare the old and new state of taskInfos and identify and log the changes */ private fun identifyAndLogTaskUpdates( sessionId: Int, preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>, postTransitionVisibleFreeformTasks: SparseArray<TaskInfo> ) { - // find new tasks that were added postTransitionVisibleFreeformTasks.forEach { taskId, taskInfo -> - if (!preTransitionVisibleFreeformTasks.containsKey(taskId)) { - desktopModeEventLogger.logTaskAdded(sessionId, buildTaskUpdateForTask(taskInfo)) + val currentTaskUpdate = buildTaskUpdateForTask(taskInfo) + val previousTaskInfo = preTransitionVisibleFreeformTasks[taskId] + when { + // new tasks added + previousTaskInfo == null -> + desktopModeEventLogger.logTaskAdded(sessionId, currentTaskUpdate) + // old tasks that were resized or repositioned + // TODO(b/347935387): Log changes only once they are stable. + buildTaskUpdateForTask(previousTaskInfo) != currentTaskUpdate -> + desktopModeEventLogger.logTaskInfoChanged(sessionId, currentTaskUpdate) } } @@ -304,13 +309,17 @@ class DesktopModeLoggerTransitionObserver( } } - // TODO(b/326231724: figure out how to get taskWidth and taskHeight from TaskInfo private fun buildTaskUpdateForTask(taskInfo: TaskInfo): TaskUpdate { - val taskUpdate = TaskUpdate(taskInfo.taskId, taskInfo.userId) - // add task x, y if available - taskInfo.positionInParent?.let { taskUpdate.copy(taskX = it.x, taskY = it.y) } - - return taskUpdate + val screenBounds = taskInfo.configuration.windowConfiguration.bounds + val positionInParent = taskInfo.positionInParent + return TaskUpdate( + instanceId = taskInfo.taskId, + uid = taskInfo.userId, + taskHeight = screenBounds.height(), + taskWidth = screenBounds.width(), + taskX = positionInParent.x, + taskY = positionInParent.y, + ) } /** Get [EnterReason] for this session enter */ 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 7d01580ecb6e..ca0586418041 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 @@ -26,8 +26,8 @@ import android.window.WindowContainerToken import androidx.core.util.forEach import androidx.core.util.keyIterator import androidx.core.util.valueIterator +import com.android.internal.protolog.ProtoLog 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 @@ -46,6 +46,9 @@ class DesktopModeTaskRepository { val activeTasks: ArraySet<Int> = ArraySet(), val visibleTasks: ArraySet<Int> = ArraySet(), val minimizedTasks: ArraySet<Int> = ArraySet(), + // Tasks that are closing, but are still visible + // TODO(b/332682201): Remove when the repository state is updated via TransitionObserver + val closingTasks: ArraySet<Int> = ArraySet(), // Tasks currently in freeform mode, ordered from top to bottom (top is at index 0). val freeformTasksInZOrder: ArrayList<Int> = ArrayList(), ) @@ -139,7 +142,7 @@ class DesktopModeTaskRepository { val added = displayData.getOrCreate(displayId).activeTasks.add(taskId) if (added) { - KtProtoLog.d( + ProtoLog.d( WM_SHELL_DESKTOP_MODE, "DesktopTaskRepo: add active task=%d displayId=%d", taskId, @@ -164,11 +167,47 @@ class DesktopModeTaskRepository { } } if (result) { - KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopTaskRepo: remove active task=%d", taskId) + ProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopTaskRepo: remove active task=%d", taskId) } return result } + /** + * Mark a task with given [taskId] as closing on given [displayId] + * + * @return `true` if the task was not closing on given [displayId] + */ + fun addClosingTask(displayId: Int, taskId: Int): Boolean { + val added = displayData.getOrCreate(displayId).closingTasks.add(taskId) + if (added) { + ProtoLog.d( + WM_SHELL_DESKTOP_MODE, + "DesktopTaskRepo: added closing task=%d displayId=%d", + taskId, + displayId + ) + } + return added + } + + /** + * Remove task with given [taskId] from closing tasks. + * + * @return `true` if the task was closing + */ + fun removeClosingTask(taskId: Int): Boolean { + var removed = false + displayData.forEach { _, data -> + if (data.closingTasks.remove(taskId)) { + removed = true + } + } + if (removed) { + ProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopTaskRepo: remove closing task=%d", taskId) + } + return removed + } + /** Check if a task with the given [taskId] was marked as an active task */ fun isActiveTask(taskId: Int): Boolean { return displayData.valueIterator().asSequence().any { data -> @@ -176,6 +215,10 @@ class DesktopModeTaskRepository { } } + /** Check if a task with the given [taskId] was marked as a closing task */ + fun isClosingTask(taskId: Int): Boolean = + displayData.valueIterator().asSequence().any { data -> taskId in data.closingTasks } + /** Whether a task is visible. */ fun isVisibleTask(taskId: Int): Boolean { return displayData.valueIterator().asSequence().any { data -> @@ -190,18 +233,27 @@ class DesktopModeTaskRepository { } } - /** Check if a task with the given [taskId] is the only active task on its display */ - fun isOnlyActiveTask(taskId: Int): Boolean { - return displayData.valueIterator().asSequence().any { data -> - data.activeTasks.singleOrNull() == taskId + /** + * Check if a task with the given [taskId] is the only visible, non-closing, not-minimized task + * on its display + */ + fun isOnlyVisibleNonClosingTask(taskId: Int): Boolean = + displayData.valueIterator().asSequence().any { data -> + data.visibleTasks + .subtract(data.closingTasks) + .subtract(data.minimizedTasks) + .singleOrNull() == taskId } - } /** Get a set of the active tasks for given [displayId] */ fun getActiveTasks(displayId: Int): ArraySet<Int> { return ArraySet(displayData[displayId]?.activeTasks) } + /** Returns the minimized tasks for the given [displayId]. */ + fun getMinimizedTasks(displayId: Int): ArraySet<Int> = + ArraySet(displayData[displayId]?.minimizedTasks) + /** * Returns whether Desktop Mode is currently showing any tasks, i.e. whether any Desktop Tasks * are visible. @@ -264,14 +316,14 @@ class DesktopModeTaskRepository { // Check if count changed if (prevCount != newCount) { - KtProtoLog.d( + ProtoLog.d( WM_SHELL_DESKTOP_MODE, "DesktopTaskRepo: update task visibility taskId=%d visible=%b displayId=%d", taskId, visible, displayId ) - KtProtoLog.d( + ProtoLog.d( WM_SHELL_DESKTOP_MODE, "DesktopTaskRepo: visibleTaskCount has changed from %d to %d", prevCount, @@ -289,7 +341,7 @@ class DesktopModeTaskRepository { /** Get number of tasks that are marked as visible on given [displayId] */ fun getVisibleTaskCount(displayId: Int): Int { - KtProtoLog.d( + ProtoLog.d( WM_SHELL_DESKTOP_MODE, "DesktopTaskRepo: visibleTaskCount= %d", displayData[displayId]?.visibleTasks?.size ?: 0 @@ -301,7 +353,7 @@ class DesktopModeTaskRepository { // TODO(b/342417921): Identify if there is additional checks needed to move tasks for // multi-display scenarios. fun addOrMoveFreeformTaskToTop(displayId: Int, taskId: Int) { - KtProtoLog.d( + ProtoLog.d( WM_SHELL_DESKTOP_MODE, "DesktopTaskRepo: add or move task to top: display=%d, taskId=%d", displayId, @@ -313,7 +365,7 @@ class DesktopModeTaskRepository { /** Mark a Task as minimized. */ fun minimizeTask(displayId: Int, taskId: Int) { - KtProtoLog.v( + ProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopModeTaskRepository: minimize Task: display=%d, task=%d", displayId, @@ -324,7 +376,7 @@ class DesktopModeTaskRepository { /** Mark a Task as non-minimized. */ fun unminimizeTask(displayId: Int, taskId: Int) { - KtProtoLog.v( + ProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopModeTaskRepository: unminimize Task: display=%d, task=%d", displayId, @@ -335,7 +387,7 @@ class DesktopModeTaskRepository { /** Remove the task from the ordered list. */ fun removeFreeformTask(displayId: Int, taskId: Int) { - KtProtoLog.d( + ProtoLog.d( WM_SHELL_DESKTOP_MODE, "DesktopTaskRepo: remove freeform task from ordered list: display=%d, taskId=%d", displayId, @@ -343,7 +395,7 @@ class DesktopModeTaskRepository { ) displayData[displayId]?.freeformTasksInZOrder?.remove(taskId) boundsBeforeMaximizeByTaskId.remove(taskId) - KtProtoLog.d( + ProtoLog.d( WM_SHELL_DESKTOP_MODE, "DesktopTaskRepo: remaining freeform tasks: %s", displayData[displayId]?.freeformTasksInZOrder?.toDumpString() ?: "" diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt index 217b1d356122..1bf125938e6f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt @@ -19,6 +19,8 @@ package com.android.wm.shell.desktopmode import android.app.ActivityManager.RunningTaskInfo +import android.app.TaskInfo +import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED import android.content.pm.ActivityInfo.isFixedOrientationLandscape import android.content.pm.ActivityInfo.isFixedOrientationPortrait import android.content.res.Configuration.ORIENTATION_LANDSCAPE @@ -105,7 +107,7 @@ fun calculateInitialBounds( * Calculates the largest size that can fit in a given area while maintaining a specific aspect * ratio. */ -private fun maximumSizeMaintainingAspectRatio( +fun maximumSizeMaintainingAspectRatio( taskInfo: RunningTaskInfo, targetArea: Size, aspectRatio: Float @@ -114,7 +116,8 @@ private fun maximumSizeMaintainingAspectRatio( val targetWidth = targetArea.width val finalHeight: Int val finalWidth: Int - if (isFixedOrientationPortrait(taskInfo.topActivityInfo!!.screenOrientation)) { + // Get orientation either through top activity or task's orientation + if (taskInfo.hasPortraitTopActivity()) { val tempWidth = (targetHeight / aspectRatio).toInt() if (tempWidth <= targetWidth) { finalHeight = targetHeight @@ -137,7 +140,7 @@ private fun maximumSizeMaintainingAspectRatio( } /** Calculates the aspect ratio of an activity from its fullscreen bounds. */ -private fun calculateAspectRatio(taskInfo: RunningTaskInfo): Float { +fun calculateAspectRatio(taskInfo: RunningTaskInfo): Float { if (taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed) { val appLetterboxWidth = taskInfo.appCompatTaskInfo.topActivityLetterboxWidth val appLetterboxHeight = taskInfo.appCompatTaskInfo.topActivityLetterboxHeight @@ -171,3 +174,41 @@ private fun positionInScreen(desiredSize: Size, screenBounds: Rect): Rect { desiredSize.height + heightOffset ) } + +/** + * Adjusts bounds to be positioned in the middle of the area provided, not necessarily the + * entire screen, as area can be offset by left and top start. + */ +fun centerInArea(desiredSize: Size, areaBounds: Rect, leftStart: Int, topStart: Int): Rect { + val heightOffset = (areaBounds.height() - desiredSize.height) / 2 + val widthOffset = (areaBounds.width() - desiredSize.width) / 2 + + val newLeft = leftStart + widthOffset + val newTop = topStart + heightOffset + val newRight = newLeft + desiredSize.width + val newBottom = newTop + desiredSize.height + + return Rect(newLeft, newTop, newRight, newBottom) +} + +fun TaskInfo.hasPortraitTopActivity(): Boolean { + val topActivityScreenOrientation = + topActivityInfo?.screenOrientation ?: SCREEN_ORIENTATION_UNSPECIFIED + val appBounds = configuration.windowConfiguration.appBounds + + return when { + // First check if activity has portrait screen orientation + topActivityScreenOrientation != SCREEN_ORIENTATION_UNSPECIFIED -> { + isFixedOrientationPortrait(topActivityScreenOrientation) + } + + // Then check if the activity is portrait when letterboxed + appCompatTaskInfo.topActivityBoundsLetterboxed -> appCompatTaskInfo.isTopActivityPillarboxed + + // Then check if the activity is portrait + appBounds != null -> appBounds.height() > appBounds.width() + + // Otherwise just take the orientation of the task + else -> isFixedOrientationPortrait(configuration.orientation) + } +} 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 c5111d68881d..18157d6255e3 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 @@ -18,6 +18,7 @@ package com.android.wm.shell.desktopmode import android.app.ActivityManager.RunningTaskInfo import android.app.ActivityOptions +import android.app.KeyguardManager import android.app.PendingIntent import android.app.TaskInfo import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME @@ -35,12 +36,12 @@ import android.graphics.Rect import android.graphics.Region import android.os.IBinder import android.os.SystemProperties +import android.util.Size import android.view.Display.DEFAULT_DISPLAY import android.view.SurfaceControl import android.view.WindowManager.TRANSIT_CHANGE import android.view.WindowManager.TRANSIT_NONE import android.view.WindowManager.TRANSIT_OPEN -import android.view.WindowManager.TRANSIT_TO_BACK import android.view.WindowManager.TRANSIT_TO_FRONT import android.window.RemoteTransition import android.window.TransitionInfo @@ -49,6 +50,7 @@ import android.window.WindowContainerTransaction import androidx.annotation.BinderThread import com.android.internal.annotations.VisibleForTesting import com.android.internal.policy.ScreenDecorationsUtils +import com.android.internal.protolog.ProtoLog import com.android.window.flags.Flags import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTaskOrganizer @@ -65,7 +67,7 @@ import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource 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.compatui.isSingleTopActivityTranslucent +import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener import com.android.wm.shell.draganddrop.DragAndDropController @@ -73,9 +75,10 @@ import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.recents.RecentTasksController import com.android.wm.shell.recents.RecentsTransitionHandler import com.android.wm.shell.recents.RecentsTransitionStateListener -import com.android.wm.shell.shared.DesktopModeStatus -import com.android.wm.shell.shared.DesktopModeStatus.DESKTOP_DENSITY_OVERRIDE -import com.android.wm.shell.shared.DesktopModeStatus.useDesktopOverrideDensity +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.DESKTOP_DENSITY_OVERRIDE +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.useDesktopOverrideDensity +import com.android.wm.shell.shared.TransitionUtil import com.android.wm.shell.shared.annotations.ExternalThread import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.splitscreen.SplitScreenController @@ -86,7 +89,6 @@ import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.sysui.ShellSharedConstants import com.android.wm.shell.transition.OneShotRemoteHandler import com.android.wm.shell.transition.Transitions -import com.android.wm.shell.util.KtProtoLog import com.android.wm.shell.windowdecor.DragPositioningCallbackUtility import com.android.wm.shell.windowdecor.MoveToDesktopAnimator import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener @@ -108,6 +110,7 @@ class DesktopTasksController( private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer, private val dragAndDropController: DragAndDropController, private val transitions: Transitions, + private val keyguardManager: KeyguardManager, private val enterDesktopTaskTransitionHandler: EnterDesktopTaskTransitionHandler, private val exitDesktopTaskTransitionHandler: ExitDesktopTaskTransitionHandler, private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler, @@ -155,8 +158,6 @@ class DesktopTasksController( visualIndicator = null } } - private val sysUIPackageName = context.resources.getString( - com.android.internal.R.string.config_systemUi) private val transitionAreaHeight get() = @@ -185,7 +186,7 @@ class DesktopTasksController( } private fun onInit() { - KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopTasksController") + ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopTasksController") shellCommandHandler.addDumpCallback(this::dump, this) shellCommandHandler.addCommandCallback("desktopmode", desktopModeShellCommandHandler, this) shellController.addExternalInterface( @@ -199,7 +200,7 @@ class DesktopTasksController( recentsTransitionHandler.addTransitionStateListener( object : RecentsTransitionStateListener { override fun onAnimationStateChanged(running: Boolean) { - KtProtoLog.v( + ProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopTasksController: recents animation state changed running=%b", running @@ -216,11 +217,6 @@ class DesktopTasksController( return visualIndicator } - // TODO(b/347289970): Consider replacing with API - private fun isSystemUIApplication(taskInfo: RunningTaskInfo): Boolean { - return taskInfo.baseActivity?.packageName == sysUIPackageName - } - fun setOnTaskResizeAnimationListener(listener: OnTaskResizeAnimationListener) { toggleResizeDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener) enterDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener) @@ -235,7 +231,7 @@ class DesktopTasksController( /** Show all tasks, that are part of the desktop, on top of launcher */ fun showDesktopApps(displayId: Int, remoteTransition: RemoteTransition? = null) { - KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: showDesktopApps") + ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: showDesktopApps") val wct = WindowContainerTransaction() bringDesktopAppsToFront(displayId, wct) @@ -286,7 +282,7 @@ class DesktopTasksController( moveToDesktop(allFocusedTasks[0].taskId, transitionSource = transitionSource) } else -> { - KtProtoLog.w( + ProtoLog.w( WM_SHELL_DESKTOP_MODE, "DesktopTasksController: Cannot enter desktop, expected less " + "than 3 focused tasks but found %d", @@ -316,7 +312,7 @@ class DesktopTasksController( transitionSource: DesktopModeTransitionSource, ): Boolean { recentTasksController?.findTaskInBackground(taskId)?.let { - KtProtoLog.v( + ProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopTasksController: moveToDesktopFromNonRunningTask taskId=%d", taskId @@ -348,23 +344,16 @@ class DesktopTasksController( wct: WindowContainerTransaction = WindowContainerTransaction(), transitionSource: DesktopModeTransitionSource, ) { - if (Flags.enableDesktopWindowingModalsPolicy() && isSingleTopActivityTranslucent(task)) { - KtProtoLog.w( - WM_SHELL_DESKTOP_MODE, - "DesktopTasksController: Cannot enter desktop, " + - "translucent top activity found. This is likely a modal dialog." - ) - return - } - if (isSystemUIApplication(task)) { - KtProtoLog.w( + if (Flags.enableDesktopWindowingModalsPolicy() + && isTopActivityExemptFromDesktopWindowing(context, task)) { + ProtoLog.w( WM_SHELL_DESKTOP_MODE, "DesktopTasksController: Cannot enter desktop, " + - "systemUI top activity found." + "ineligible top activity found." ) return } - KtProtoLog.v( + ProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopTasksController: moveToDesktop taskId=%d", task.taskId @@ -391,7 +380,7 @@ class DesktopTasksController( taskInfo: RunningTaskInfo, dragToDesktopValueAnimator: MoveToDesktopAnimator, ) { - KtProtoLog.v( + ProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopTasksController: startDragToDesktop taskId=%d", taskInfo.taskId @@ -407,14 +396,14 @@ class DesktopTasksController( * [startDragToDesktop]. */ private fun finalizeDragToDesktop(taskInfo: RunningTaskInfo, freeformBounds: Rect) { - KtProtoLog.v( + ProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopTasksController: finalizeDragToDesktop taskId=%d", taskInfo.taskId ) val wct = WindowContainerTransaction() exitSplitIfApplicable(wct, taskInfo) - moveHomeTaskToFront(wct) + moveHomeTask(wct, toTop = true) val taskToMinimize = bringDesktopAppsToFrontBeforeShowingNewTask(taskInfo.displayId, wct, taskInfo.taskId) addMoveToDesktopChanges(wct, taskInfo) @@ -442,12 +431,21 @@ class DesktopTasksController( * active task. * * @param wct transaction to modify if the last active task is closed + * @param displayId display id of the window that's being closed * @param taskId task id of the window that's being closed */ - fun onDesktopWindowClose(wct: WindowContainerTransaction, taskId: Int) { - if (desktopModeTaskRepository.isOnlyActiveTask(taskId)) { + fun onDesktopWindowClose(wct: WindowContainerTransaction, displayId: Int, taskId: Int) { + if (desktopModeTaskRepository.isOnlyVisibleNonClosingTask(taskId)) { removeWallpaperActivity(wct) } + if (!desktopModeTaskRepository.addClosingTask(displayId, taskId)) { + // Could happen if the task hasn't been removed from closing list after it disappeared + ProtoLog.w( + WM_SHELL_DESKTOP_MODE, + "DesktopTasksController: the task with taskId=%d is already closing!", + taskId + ) + } } /** Move a task with given `taskId` to fullscreen */ @@ -466,7 +464,7 @@ class DesktopTasksController( /** Move a desktop app to split screen. */ fun moveToSplit(task: RunningTaskInfo) { - KtProtoLog.v( + ProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopTasksController: moveToSplit taskId=%d", task.taskId @@ -499,7 +497,7 @@ class DesktopTasksController( * [startDragToDesktop]. */ fun cancelDragToDesktop(task: RunningTaskInfo) { - KtProtoLog.v( + ProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopTasksController: cancelDragToDesktop taskId=%d", task.taskId @@ -514,7 +512,7 @@ class DesktopTasksController( position: Point, transitionSource: DesktopModeTransitionSource ) { - KtProtoLog.v( + ProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopTasksController: moveToFullscreen with animation taskId=%d", task.taskId @@ -542,7 +540,7 @@ class DesktopTasksController( /** Move a task to the front */ fun moveTaskToFront(taskInfo: RunningTaskInfo) { - KtProtoLog.v( + ProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopTasksController: moveTaskToFront taskId=%d", taskInfo.taskId @@ -573,10 +571,10 @@ class DesktopTasksController( fun moveToNextDisplay(taskId: Int) { val task = shellTaskOrganizer.getRunningTaskInfo(taskId) if (task == null) { - KtProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: taskId=%d not found", taskId) + ProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: taskId=%d not found", taskId) return } - KtProtoLog.v( + ProtoLog.v( WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: taskId=%d taskDisplayId=%d", taskId, @@ -591,7 +589,7 @@ class DesktopTasksController( newDisplayId = displayIds.firstOrNull { displayId -> displayId < task.displayId } } if (newDisplayId == null) { - KtProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: next display not found") + ProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToNextDisplay: next display not found") return } moveToDisplay(task, newDisplayId) @@ -603,7 +601,7 @@ class DesktopTasksController( * No-op if task is already on that display per [RunningTaskInfo.displayId]. */ private fun moveToDisplay(task: RunningTaskInfo, displayId: Int) { - KtProtoLog.v( + ProtoLog.v( WM_SHELL_DESKTOP_MODE, "moveToDisplay: taskId=%d displayId=%d", task.taskId, @@ -611,13 +609,13 @@ class DesktopTasksController( ) if (task.displayId == displayId) { - KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "moveToDisplay: task already on display") + ProtoLog.d(WM_SHELL_DESKTOP_MODE, "moveToDisplay: task already on display") return } val displayAreaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId) if (displayAreaInfo == null) { - KtProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToDisplay: display not found") + ProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveToDisplay: display not found") return } @@ -638,13 +636,21 @@ class DesktopTasksController( fun toggleDesktopTaskSize(taskInfo: RunningTaskInfo) { val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return - val stableBounds = Rect() - displayLayout.getStableBounds(stableBounds) + val stableBounds = Rect().apply { displayLayout.getStableBounds(this) } + val currentTaskBounds = taskInfo.configuration.windowConfiguration.bounds val destinationBounds = Rect() - if (taskInfo.configuration.windowConfiguration.bounds == stableBounds) { - // The desktop task is currently occupying the whole stable bounds. If the bounds - // before the task was toggled to stable bounds were saved, toggle the task to those - // bounds. Otherwise, toggle to the default bounds. + + val isMaximized = if (taskInfo.isResizeable) { + currentTaskBounds == stableBounds + } else { + currentTaskBounds.width() == stableBounds.width() + || currentTaskBounds.height() == stableBounds.height() + } + + if (isMaximized) { + // The desktop task is at the maximized width and/or height of the stable bounds. + // If the task's pre-maximize stable bounds were saved, toggle the task to those bounds. + // Otherwise, toggle to the default bounds. val taskBoundsBeforeMaximize = desktopModeTaskRepository.removeBoundsBeforeMaximize(taskInfo.taskId) if (taskBoundsBeforeMaximize != null) { @@ -659,9 +665,20 @@ class DesktopTasksController( } else { // Save current bounds so that task can be restored back to original bounds if necessary // and toggle to the stable bounds. - val taskBounds = taskInfo.configuration.windowConfiguration.bounds - desktopModeTaskRepository.saveBoundsBeforeMaximize(taskInfo.taskId, taskBounds) - destinationBounds.set(stableBounds) + desktopModeTaskRepository.saveBoundsBeforeMaximize(taskInfo.taskId, currentTaskBounds) + + if (taskInfo.isResizeable) { + // if resizable then expand to entire stable bounds (full display minus insets) + destinationBounds.set(stableBounds) + } else { + // if non-resizable then calculate max bounds according to aspect ratio + val activityAspectRatio = calculateAspectRatio(taskInfo) + val newSize = maximumSizeMaintainingAspectRatio(taskInfo, + Size(stableBounds.width(), stableBounds.height()), activityAspectRatio) + val newBounds = centerInArea( + newSize, stableBounds, stableBounds.left, stableBounds.top) + destinationBounds.set(newBounds) + } } val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds) @@ -753,7 +770,7 @@ class DesktopTasksController( wct: WindowContainerTransaction, newTaskIdInFront: Int? = null ): RunningTaskInfo? { - KtProtoLog.v( + ProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopTasksController: bringDesktopAppsToFront, newTaskIdInFront=%s", newTaskIdInFront ?: "null" @@ -764,7 +781,7 @@ class DesktopTasksController( addWallpaperActivity(wct) } else { // Move home to front - moveHomeTaskToFront(wct) + moveHomeTask(wct, toTop = true) } val nonMinimizedTasksOrderedFrontToBack = @@ -790,15 +807,15 @@ class DesktopTasksController( return taskToMinimize } - private fun moveHomeTaskToFront(wct: WindowContainerTransaction) { + private fun moveHomeTask(wct: WindowContainerTransaction, toTop: Boolean) { shellTaskOrganizer .getRunningTasks(context.displayId) .firstOrNull { task -> task.activityType == ACTIVITY_TYPE_HOME } - ?.let { homeTask -> wct.reorder(homeTask.getToken(), true /* onTop */) } + ?.let { homeTask -> wct.reorder(homeTask.getToken(), toTop /* onTop */) } } private fun addWallpaperActivity(wct: WindowContainerTransaction) { - KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: addWallpaper") + ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: addWallpaper") val intent = Intent(context, DesktopWallpaperActivity::class.java) val options = ActivityOptions.makeBasic().apply { @@ -818,7 +835,7 @@ class DesktopTasksController( private fun removeWallpaperActivity(wct: WindowContainerTransaction) { desktopModeTaskRepository.wallpaperActivityToken?.let { token -> - KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: removeWallpaper") + ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: removeWallpaper") wct.removeTask(token) } } @@ -856,7 +873,7 @@ class DesktopTasksController( transition: IBinder, request: TransitionRequestInfo ): WindowContainerTransaction? { - KtProtoLog.v( + ProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleRequest request=%s", request @@ -870,8 +887,8 @@ class DesktopTasksController( reason = "recents animation is running" false } - // Handle back navigation for the last window if wallpaper available - shouldRemoveWallpaper(request) -> true + // Handle task closing for the last window if wallpaper is available + shouldHandleTaskClosing(request) -> true // Only handle open or to front transitions request.type != TRANSIT_OPEN && request.type != TRANSIT_TO_FRONT -> { reason = "transition type not handled (${request.type})" @@ -898,7 +915,7 @@ class DesktopTasksController( } if (!shouldHandleRequest) { - KtProtoLog.v( + ProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopTasksController: skipping handleRequest reason=%s", reason @@ -909,11 +926,10 @@ class DesktopTasksController( val result = triggerTask?.let { task -> when { - request.type == TRANSIT_TO_BACK -> handleBackNavigation(task) - // Check if the task has a top transparent activity - shouldLaunchAsModal(task) -> handleIncompatibleTaskLaunch(task) - // Check if the task has a top systemUI activity - isSystemUIApplication(task) -> handleIncompatibleTaskLaunch(task) + // Check if the closing task needs to be handled + TransitionUtil.isClosingType(request.type) -> handleTaskClosing(task) + // Check if the top task shouldn't be allowed to enter desktop mode + isIncompatibleTask(task) -> handleIncompatibleTaskLaunch(task) // Check if fullscreen task should be updated task.isFullscreen -> handleFullscreenTaskLaunch(task, transition) // Check if freeform task should be updated @@ -923,7 +939,7 @@ class DesktopTasksController( } } } - KtProtoLog.v( + ProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleRequest result=%s", result ?: "null" @@ -947,28 +963,31 @@ class DesktopTasksController( .forEach { finishTransaction.setCornerRadius(it.leash, cornerRadius) } } - // TODO(b/347289970): Consider replacing with API - private fun shouldLaunchAsModal(task: TaskInfo) = - Flags.enableDesktopWindowingModalsPolicy() && isSingleTopActivityTranslucent(task) + private fun isIncompatibleTask(task: TaskInfo) = + Flags.enableDesktopWindowingModalsPolicy() + && isTopActivityExemptFromDesktopWindowing(context, task) - private fun shouldRemoveWallpaper(request: TransitionRequestInfo): Boolean { + private fun shouldHandleTaskClosing(request: TransitionRequestInfo): Boolean { return Flags.enableDesktopWindowingWallpaperActivity() && - request.type == TRANSIT_TO_BACK && - request.triggerTask?.let { task -> - desktopModeTaskRepository.isOnlyActiveTask(task.taskId) - } - ?: false + TransitionUtil.isClosingType(request.type) && + request.triggerTask != null } private fun handleFreeformTaskLaunch( task: RunningTaskInfo, transition: IBinder ): WindowContainerTransaction? { - KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFreeformTaskLaunch") + ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFreeformTaskLaunch") + if (keyguardManager.isKeyguardLocked) { + // Do NOT handle freeform task launch when locked. + // It will be launched in fullscreen windowing mode (Details: b/160925539) + ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: skip keyguard is locked") + return null + } if (!desktopModeTaskRepository.isDesktopModeShowing(task.displayId)) { - KtProtoLog.d( + ProtoLog.d( WM_SHELL_DESKTOP_MODE, - "DesktopTasksController: switch freeform task to fullscreen oon transition" + + "DesktopTasksController: bring desktop tasks to front on transition" + " taskId=%d", task.taskId ) @@ -995,9 +1014,9 @@ class DesktopTasksController( task: RunningTaskInfo, transition: IBinder ): WindowContainerTransaction? { - KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFullscreenTaskLaunch") + ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFullscreenTaskLaunch") if (desktopModeTaskRepository.isDesktopModeShowing(task.displayId)) { - KtProtoLog.d( + ProtoLog.d( WM_SHELL_DESKTOP_MODE, "DesktopTasksController: switch fullscreen task to freeform on transition" + " taskId=%d", @@ -1005,6 +1024,9 @@ class DesktopTasksController( ) return WindowContainerTransaction().also { wct -> addMoveToDesktopChanges(wct, task) + // In some launches home task is moved behind new task being launched. Make sure + // that's not the case for launches in desktop. + moveHomeTask(wct, toTop = false) // Desktop Mode is already showing and we're launching a new Task - we might need to // minimize another Task. val taskToMinimize = addAndGetMinimizeChangesIfNeeded(task.displayId, wct, task) @@ -1024,17 +1046,26 @@ class DesktopTasksController( return WindowContainerTransaction().also { wct -> addMoveToFullscreenChanges(wct, task) } } - /** Handle back navigation by removing wallpaper activity if it's the last active task */ - private fun handleBackNavigation(task: RunningTaskInfo): WindowContainerTransaction? { - if ( - desktopModeTaskRepository.isOnlyActiveTask(task.taskId) && + /** Handle task closing by removing wallpaper activity if it's the last active task */ + private fun handleTaskClosing(task: RunningTaskInfo): WindowContainerTransaction? { + val wct = if ( + desktopModeTaskRepository.isOnlyVisibleNonClosingTask(task.taskId) && desktopModeTaskRepository.wallpaperActivityToken != null ) { // Remove wallpaper activity when the last active task is removed - return WindowContainerTransaction().also { wct -> removeWallpaperActivity(wct) } + WindowContainerTransaction().also { wct -> removeWallpaperActivity(wct) } } else { - return null + null + } + if (!desktopModeTaskRepository.addClosingTask(task.displayId, task.taskId)) { + // Could happen if the task hasn't been removed from closing list after it disappeared + ProtoLog.w( + WM_SHELL_DESKTOP_MODE, + "DesktopTasksController: the task with taskId=%d is already closing!", + task.taskId + ) } + return wct } private fun addMoveToDesktopChanges( @@ -1367,7 +1398,7 @@ class DesktopTasksController( if (!multiInstanceHelper.supportsMultiInstanceSplit(launchComponent)) { // TODO(b/320797628): Should only return early if there is an existing running task, and // notify the user as well. But for now, just ignore the drop. - KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "Dropped intent does not support multi-instance") + ProtoLog.v(WM_SHELL_DESKTOP_MODE, "Dropped intent does not support multi-instance") return false } @@ -1458,7 +1489,7 @@ class DesktopTasksController( private val listener: VisibleTasksListener = object : VisibleTasksListener { override fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) { - KtProtoLog.v( + ProtoLog.v( WM_SHELL_DESKTOP_MODE, "IDesktopModeImpl: onVisibilityChanged display=%d visible=%d", displayId, @@ -1503,11 +1534,11 @@ class DesktopTasksController( } override fun stashDesktopApps(displayId: Int) { - KtProtoLog.w(WM_SHELL_DESKTOP_MODE, "IDesktopModeImpl: stashDesktopApps is deprecated") + ProtoLog.w(WM_SHELL_DESKTOP_MODE, "IDesktopModeImpl: stashDesktopApps is deprecated") } override fun hideStashedDesktopApps(displayId: Int) { - KtProtoLog.w( + ProtoLog.w( WM_SHELL_DESKTOP_MODE, "IDesktopModeImpl: hideStashedDesktopApps is deprecated" ) @@ -1534,7 +1565,7 @@ class DesktopTasksController( } override fun setTaskListener(listener: IDesktopTaskListener?) { - KtProtoLog.v( + ProtoLog.v( WM_SHELL_DESKTOP_MODE, "IDesktopModeImpl: set task listener=%s", listener ?: "null" diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt index 0f88384ec2ac..534cc22ada47 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt @@ -23,12 +23,12 @@ import android.view.WindowManager.TRANSIT_TO_BACK import android.window.TransitionInfo import android.window.WindowContainerTransaction import androidx.annotation.VisibleForTesting +import com.android.internal.protolog.ProtoLog import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.protolog.ShellProtoLogGroup -import com.android.wm.shell.shared.DesktopModeStatus +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.TransitionObserver -import com.android.wm.shell.util.KtProtoLog /** * Limits the number of tasks shown in Desktop Mode. @@ -42,9 +42,12 @@ class DesktopTasksLimiter ( private val shellTaskOrganizer: ShellTaskOrganizer, ) { private val minimizeTransitionObserver = MinimizeTransitionObserver() + @VisibleForTesting + val leftoverMinimizedTasksRemover = LeftoverMinimizedTasksRemover() init { transitions.registerObserver(minimizeTransitionObserver) + taskRepository.addActiveTaskListener(leftoverMinimizedTasksRemover) } private data class TaskDetails (val displayId: Int, val taskId: Int) @@ -68,7 +71,7 @@ class DesktopTasksLimiter ( if (!taskRepository.isActiveTask(taskToMinimize.taskId)) return if (!isTaskReorderedToBackOrInvisible(info, taskToMinimize)) { - KtProtoLog.v( + ProtoLog.v( ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, "DesktopTasksLimiter: task %d is not reordered to back nor invis", taskToMinimize.taskId) @@ -106,19 +109,48 @@ class DesktopTasksLimiter ( } override fun onTransitionFinished(transition: IBinder, aborted: Boolean) { - KtProtoLog.v( + ProtoLog.v( ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, "DesktopTasksLimiter: transition %s finished", transition) mPendingTransitionTokensAndTasks.remove(transition) } } + @VisibleForTesting + inner class LeftoverMinimizedTasksRemover : DesktopModeTaskRepository.ActiveTasksListener { + override fun onActiveTasksChanged(displayId: Int) { + val wct = WindowContainerTransaction() + removeLeftoverMinimizedTasks(displayId, wct) + shellTaskOrganizer.applyTransaction(wct) + } + + fun removeLeftoverMinimizedTasks(displayId: Int, wct: WindowContainerTransaction) { + if (taskRepository + .getActiveNonMinimizedTasksOrderedFrontToBack(displayId).isNotEmpty()) { + return + } + val remainingMinimizedTasks = taskRepository.getMinimizedTasks(displayId) + if (remainingMinimizedTasks.isEmpty()) { + return + } + ProtoLog.v( + ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, + "DesktopTasksLimiter: removing leftover minimized tasks: $remainingMinimizedTasks") + remainingMinimizedTasks.forEach { taskIdToRemove -> + val taskToRemove = shellTaskOrganizer.getRunningTaskInfo(taskIdToRemove) + if (taskToRemove != null) { + wct.removeTask(taskToRemove.token) + } + } + } + } + /** * Mark a task as minimized, this should only be done after the corresponding transition has * finished so we don't minimize the task if the transition fails. */ private fun markTaskMinimized(displayId: Int, taskId: Int) { - KtProtoLog.v( + ProtoLog.v( ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, "DesktopTasksLimiter: marking %d as minimized", taskId) taskRepository.minimizeTask(displayId, taskId) @@ -137,7 +169,7 @@ class DesktopTasksLimiter ( wct: WindowContainerTransaction, newFrontTaskInfo: RunningTaskInfo, ): RunningTaskInfo? { - KtProtoLog.v( + ProtoLog.v( ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, "DesktopTasksLimiter: addMinimizeBackTaskChangesIfNeeded, newFrontTask=%d", newFrontTaskInfo.taskId) @@ -185,7 +217,7 @@ class DesktopTasksLimiter ( visibleFreeformTaskIdsOrderedFrontToBack: List<Int> ): RunningTaskInfo? { if (visibleFreeformTaskIdsOrderedFrontToBack.size <= getMaxTaskLimit()) { - KtProtoLog.v( + ProtoLog.v( ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, "DesktopTasksLimiter: no need to minimize; tasks below limit") // No need to minimize anything @@ -195,7 +227,7 @@ class DesktopTasksLimiter ( shellTaskOrganizer.getRunningTaskInfo( visibleFreeformTaskIdsOrderedFrontToBack.last()) if (taskToMinimize == null) { - KtProtoLog.e( + ProtoLog.e( ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, "DesktopTasksLimiter: taskToMinimize == null") return null diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt index dae75f90e3ae..246fd9281975 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt @@ -21,35 +21,38 @@ import android.os.IBinder import android.view.SurfaceControl import android.view.WindowManager import android.window.TransitionInfo +import android.window.WindowContainerTransaction +import com.android.internal.protolog.ProtoLog import com.android.window.flags.Flags.enableDesktopWindowingWallpaperActivity +import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE -import com.android.wm.shell.shared.DesktopModeStatus +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions -import com.android.wm.shell.util.KtProtoLog /** - * A [Transitions.TransitionObserver] that observes shell transitions and updates - * the [DesktopModeTaskRepository] state TODO: b/332682201 - * This observes transitions related to desktop mode - * and other transitions that originate both within and outside shell. + * A [Transitions.TransitionObserver] that observes shell transitions and updates the + * [DesktopModeTaskRepository] state TODO: b/332682201 This observes transitions related to desktop + * mode and other transitions that originate both within and outside shell. */ class DesktopTasksTransitionObserver( context: Context, private val desktopModeTaskRepository: DesktopModeTaskRepository, private val transitions: Transitions, + private val shellTaskOrganizer: ShellTaskOrganizer, shellInit: ShellInit ) : Transitions.TransitionObserver { init { - if (Transitions.ENABLE_SHELL_TRANSITIONS && - DesktopModeStatus.canEnterDesktopMode(context)) { + if ( + Transitions.ENABLE_SHELL_TRANSITIONS && DesktopModeStatus.canEnterDesktopMode(context) + ) { shellInit.addInitCallback(::onInit, this) } } fun onInit() { - KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopTasksTransitionObserver: onInit") + ProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopTasksTransitionObserver: onInit") transitions.registerObserver(this) } @@ -83,8 +86,16 @@ class DesktopTasksTransitionObserver( change.taskInfo?.let { taskInfo -> if (DesktopWallpaperActivity.isWallpaperTask(taskInfo)) { when (change.mode) { - WindowManager.TRANSIT_OPEN -> + WindowManager.TRANSIT_OPEN -> { desktopModeTaskRepository.wallpaperActivityToken = taskInfo.token + // After the task for the wallpaper is created, set it non-trimmable. + // This is important to prevent recents from trimming and removing the + // task. + shellTaskOrganizer.applyTransaction( + WindowContainerTransaction() + .setTaskTrimmableFromRecents(taskInfo.token, false) + ) + } WindowManager.TRANSIT_CLOSE -> desktopModeTaskRepository.wallpaperActivityToken = null else -> {} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt index c4a4474689fa..1c2415c236ad 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopWallpaperActivity.kt @@ -21,8 +21,8 @@ import android.app.ActivityManager import android.content.ComponentName import android.os.Bundle import android.view.WindowManager +import com.android.internal.protolog.ProtoLog import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE -import com.android.wm.shell.util.KtProtoLog /** * A transparent activity used in the desktop mode to show the wallpaper under the freeform windows. @@ -36,7 +36,7 @@ import com.android.wm.shell.util.KtProtoLog class DesktopWallpaperActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { - KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopWallpaperActivity: onCreate") + ProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopWallpaperActivity: onCreate") super.onCreate(savedInstanceState) window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt index d99b724c936f..ddee8fac8f44 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt @@ -29,6 +29,7 @@ import android.window.TransitionInfo.Change import android.window.TransitionRequestInfo import android.window.WindowContainerToken import android.window.WindowContainerTransaction +import com.android.internal.protolog.ProtoLog import com.android.wm.shell.RootTaskDisplayAreaOrganizer 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 @@ -42,7 +43,6 @@ import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_CANCEL_D import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP import com.android.wm.shell.transition.Transitions.TransitionHandler -import com.android.wm.shell.util.KtProtoLog import com.android.wm.shell.windowdecor.MoveToDesktopAnimator import com.android.wm.shell.windowdecor.MoveToDesktopAnimator.Companion.DRAG_FREEFORM_SCALE import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener @@ -114,7 +114,7 @@ class DragToDesktopTransitionHandler( dragToDesktopAnimator: MoveToDesktopAnimator, ) { if (inProgress) { - KtProtoLog.v( + ProtoLog.v( ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, "DragToDesktop: Drag to desktop transition already in progress." ) @@ -599,7 +599,7 @@ class DragToDesktopTransitionHandler( ) { val state = transitionState ?: return if (aborted && state.startTransitionToken == transition) { - KtProtoLog.v( + ProtoLog.v( ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, "DragToDesktop: onTransitionConsumed() start transition aborted" ) 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 891f75cfdbda..171378f9a164 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 @@ -42,6 +42,8 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.jank.Cuj; +import com.android.internal.jank.InteractionJankMonitor; import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource; import com.android.wm.shell.transition.Transitions; @@ -60,6 +62,7 @@ public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionH private final Context mContext; private final Transitions mTransitions; + private final InteractionJankMonitor mInteractionJankMonitor; private final List<IBinder> mPendingTransitionTokens = new ArrayList<>(); private Consumer<SurfaceControl.Transaction> mOnAnimationFinishedCallback; private final Supplier<SurfaceControl.Transaction> mTransactionSupplier; @@ -67,17 +70,21 @@ public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionH public ExitDesktopTaskTransitionHandler( Transitions transitions, - Context context) { - this(transitions, SurfaceControl.Transaction::new, context); + Context context, + InteractionJankMonitor interactionJankMonitor + ) { + this(transitions, SurfaceControl.Transaction::new, context, interactionJankMonitor); } private ExitDesktopTaskTransitionHandler( Transitions transitions, Supplier<SurfaceControl.Transaction> supplier, - Context context) { + Context context, + InteractionJankMonitor interactionJankMonitor) { mTransitions = transitions; mTransactionSupplier = supplier; mContext = context; + mInteractionJankMonitor = interactionJankMonitor; } /** @@ -146,6 +153,8 @@ public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionH final int screenHeight = metrics.heightPixels; final SurfaceControl sc = change.getLeash(); final Rect endBounds = change.getEndAbsBounds(); + mInteractionJankMonitor + .begin(sc, mContext, Cuj.CUJ_DESKTOP_MODE_EXIT_MODE); // Hide the first (fullscreen) frame because the animation will start from the freeform // size. startT.hide(sc) @@ -175,6 +184,7 @@ public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionH if (mOnAnimationFinishedCallback != null) { mOnAnimationFinishedCallback.accept(finishT); } + mInteractionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_EXIT_MODE); mTransitions.getMainExecutor().execute( () -> finishCallback.onTransitionFinished(null)); } 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 index 88d0554669b7..c35d77a1d74a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt @@ -27,6 +27,8 @@ import android.window.TransitionInfo import android.window.TransitionRequestInfo import android.window.WindowContainerTransaction import androidx.core.animation.addListener +import com.android.internal.jank.Cuj +import com.android.internal.jank.InteractionJankMonitor 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.OnTaskResizeAnimationListener @@ -35,7 +37,8 @@ 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> + private val transactionSupplier: Supplier<SurfaceControl.Transaction>, + private val interactionJankMonitor: InteractionJankMonitor ) : Transitions.TransitionHandler { private val rectEvaluator = RectEvaluator(Rect()) @@ -44,8 +47,9 @@ class ToggleResizeDesktopTaskTransitionHandler( private var boundsAnimator: Animator? = null constructor( - transitions: Transitions - ) : this(transitions, Supplier { SurfaceControl.Transaction() }) + transitions: Transitions, + interactionJankMonitor: InteractionJankMonitor + ) : this(transitions, Supplier { SurfaceControl.Transaction() }, interactionJankMonitor) /** Starts a quick resize transition. */ fun startTransition(wct: WindowContainerTransaction) { @@ -103,6 +107,8 @@ class ToggleResizeDesktopTaskTransitionHandler( onTaskResizeAnimationListener.onAnimationEnd(taskId) finishCallback.onTransitionFinished(null) boundsAnimator = null + interactionJankMonitor.end( + Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW) } ) addUpdateListener { anim -> 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 438aa768165e..3572d161f5b9 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 @@ -73,7 +73,7 @@ stack traces when specific surface transaction calls are made, which is possible following system properties for example: ```shell # Enabling -adb shell setprop persist.wm.debug.sc.tx.log_match_call setAlpha # matches the name of the SurfaceControlTransaction method +adb shell setprop persist.wm.debug.sc.tx.log_match_call setAlpha,setPosition # matches the name of the SurfaceControlTransaction methods adb shell setprop persist.wm.debug.sc.tx.log_match_name com.android.systemui # matches the name of the surface adb reboot adb logcat -s "SurfaceControlRegistry" @@ -87,7 +87,17 @@ adb reboot It is not necessary to set both `log_match_call` and `log_match_name`, but note logs can be quite noisy if unfiltered. -## Tracing activity starts in the app process +It can sometimes be useful to trace specific logs and when they are applied (sometimes we build +transactions that can be applied later). You can do this by adding the "merge" and "apply" calls to +the set of requested calls: +```shell +# Enabling +adb shell setprop persist.wm.debug.sc.tx.log_match_call setAlpha,merge,apply # apply will dump logs of each setAlpha or merge call on that tx +adb reboot +adb logcat -s "SurfaceControlRegistry" +``` + +## Tracing activity starts & finishes in the app process It's sometimes useful to know when to see a stack trace of when an activity starts in the app code (ie. if you are repro'ing a bug related to activity starts). You can enable this system property to @@ -103,6 +113,19 @@ adb shell setprop persist.wm.debug.start_activity \"\" adb reboot ``` +Likewise, to trace where a finish() call may be made in the app process, you can enable this system +property: +```shell +# Enabling +adb shell setprop persist.wm.debug.finish_activity true +adb reboot +adb logcat -s "Instrumentation" + +# Disabling +adb shell setprop persist.wm.debug.finish_activity \"\" +adb reboot +``` + ## Dumps Because the Shell library is built as a part of SystemUI, dumping the state is currently done as a 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 c374eb8e8f03..e00353d6ac82 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 @@ -52,6 +52,7 @@ import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.widget.FrameLayout; +import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import androidx.annotation.BinderThread; @@ -59,9 +60,10 @@ import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import com.android.internal.logging.UiEventLogger; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.launcher3.icons.IconProvider; 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.ExternalInterfaceBinder; import com.android.wm.shell.common.RemoteCallable; @@ -85,6 +87,7 @@ import java.util.function.Function; public class DragAndDropController implements RemoteCallable<DragAndDropController>, GlobalDragListener.GlobalDragListenerCallback, DisplayController.OnDisplaysChangedListener, + ShellTaskOrganizer.TaskVanishedListener, View.OnDragListener, ComponentCallbacks2 { private static final String TAG = DragAndDropController.class.getSimpleName(); @@ -92,6 +95,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll private final Context mContext; private final ShellController mShellController; private final ShellCommandHandler mShellCommandHandler; + private final ShellTaskOrganizer mShellTaskOrganizer; private final DisplayController mDisplayController; private final DragAndDropEventLogger mLogger; private final IconProvider mIconProvider; @@ -133,6 +137,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll ShellInit shellInit, ShellController shellController, ShellCommandHandler shellCommandHandler, + ShellTaskOrganizer shellTaskOrganizer, DisplayController displayController, UiEventLogger uiEventLogger, IconProvider iconProvider, @@ -142,6 +147,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll mContext = context; mShellController = shellController; mShellCommandHandler = shellCommandHandler; + mShellTaskOrganizer = shellTaskOrganizer; mDisplayController = displayController; mLogger = new DragAndDropEventLogger(uiEventLogger); mIconProvider = iconProvider; @@ -163,6 +169,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll }, 0); mShellController.addExternalInterface(KEY_EXTRA_SHELL_DRAG_AND_DROP, this::createExternalInterface, this); + mShellTaskOrganizer.addTaskVanishedListener(this); mShellCommandHandler.addDumpCallback(this::dump, this); mGlobalDragListener.setListener(this); } @@ -281,6 +288,34 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll } @Override + public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { + if (taskInfo.baseIntent == null) { + // Invalid info + return; + } + // Find the active drag + PerDisplay pd = null; + for (int i = 0; i < mDisplayDropTargets.size(); i++) { + final PerDisplay iPd = mDisplayDropTargets.valueAt(i); + if (iPd.isHandlingDrag) { + pd = iPd; + break; + } + } + if (pd == null || pd.activeDragCount <= 0 || !pd.isHandlingDrag) { + // Not currently dragging + return; + } + + // Update the drag session + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, + "Handling vanished task: id=%d component=%s", taskInfo.taskId, + taskInfo.baseIntent.getComponent()); + pd.dragSession.updateRunningTask(); + pd.dragLayout.updateSession(pd.dragSession); + } + + @Override public boolean onDrag(View target, DragEvent event) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Drag event: action=%s x=%f y=%f xOffset=%f yOffset=%f", @@ -298,9 +333,10 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll mActiveDragDisplay = displayId; pd.isHandlingDrag = DragUtils.canHandleDrag(event); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, - "Clip description: handlingDrag=%b itemCount=%d mimeTypes=%s", + "Clip description: handlingDrag=%b itemCount=%d mimeTypes=%s flags=%s", pd.isHandlingDrag, event.getClipData().getItemCount(), - DragUtils.getMimeTypesConcatenated(description)); + DragUtils.getMimeTypesConcatenated(description), + DragUtils.dragFlagsToString(event.getDragFlags())); } if (!pd.isHandlingDrag) { @@ -313,13 +349,18 @@ 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 pd.dragSession = new DragSession(ActivityTaskManager.getInstance(), mDisplayController.getDisplayLayout(displayId), event.getClipData(), event.getDragFlags()); - pd.dragSession.update(); + pd.dragSession.initialize(); pd.activeDragCount++; pd.dragLayout.prepare(pd.dragSession, mLogger.logStart(pd.dragSession)); + if (pd.dragSession.hideDragSourceTaskId != -1) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, + "Hiding task surface: taskId=%d", pd.dragSession.hideDragSourceTaskId); + mShellTaskOrganizer.setTaskSurfaceVisibility( + pd.dragSession.hideDragSourceTaskId, false /* visible */); + } setDropTargetWindowVisibility(pd, View.VISIBLE); notifyListeners(l -> { l.onDragStarted(); @@ -349,6 +390,13 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll if (pd.dragLayout.hasDropped()) { mLogger.logDrop(); } else { + if (pd.dragSession.hideDragSourceTaskId != -1) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, + "Re-showing task surface: taskId=%d", + pd.dragSession.hideDragSourceTaskId); + mShellTaskOrganizer.setTaskSurfaceVisibility( + pd.dragSession.hideDragSourceTaskId, true /* visible */); + } pd.activeDragCount--; pd.dragLayout.hide(event, () -> { if (pd.activeDragCount == 0) { @@ -402,7 +450,16 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll private boolean handleDrop(DragEvent event, PerDisplay pd) { final SurfaceControl dragSurface = event.getDragSurface(); pd.activeDragCount--; - return pd.dragLayout.drop(event, dragSurface, () -> { + // Find the token of the task to hide as a part of entering split + WindowContainerToken hideTaskToken = null; + if (pd.dragSession.hideDragSourceTaskId != -1) { + ActivityManager.RunningTaskInfo info = mShellTaskOrganizer.getRunningTaskInfo( + pd.dragSession.hideDragSourceTaskId); + if (info != null) { + hideTaskToken = info.token; + } + } + return pd.dragLayout.drop(event, dragSurface, hideTaskToken, () -> { if (pd.activeDragCount == 0) { // Hide the window if another drag hasn't been started while animating the drop setDropTargetWindowVisibility(pd, View.INVISIBLE); 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 a42ca1905ee7..95fe8b6f1f4e 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 @@ -59,6 +59,7 @@ import android.os.RemoteException; import android.os.UserHandle; import android.util.Log; import android.util.Slog; +import android.window.WindowContainerToken; import androidx.annotation.IntDef; import androidx.annotation.NonNull; @@ -66,7 +67,7 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.internal.logging.InstanceId; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; import com.android.wm.shell.protolog.ShellProtoLogGroup; @@ -84,7 +85,10 @@ public class DragAndDropPolicy { private static final String TAG = DragAndDropPolicy.class.getSimpleName(); private final Context mContext; - private final Starter mStarter; + // Used only for launching a fullscreen task (or as a fallback if there is no split starter) + private final Starter mFullscreenStarter; + // Used for launching tasks into splitscreen + private final Starter mSplitscreenStarter; private final SplitScreenController mSplitScreen; private final ArrayList<DragAndDropPolicy.Target> mTargets = new ArrayList<>(); private final RectF mDisallowHitRegion = new RectF(); @@ -97,10 +101,12 @@ public class DragAndDropPolicy { } @VisibleForTesting - DragAndDropPolicy(Context context, SplitScreenController splitScreen, Starter starter) { + DragAndDropPolicy(Context context, SplitScreenController splitScreen, + Starter fullscreenStarter) { mContext = context; mSplitScreen = splitScreen; - mStarter = mSplitScreen != null ? mSplitScreen : starter; + mFullscreenStarter = fullscreenStarter; + mSplitscreenStarter = splitScreen; } /** @@ -229,8 +235,13 @@ public class DragAndDropPolicy { return null; } + /** + * Handles the drop on a given {@param target}. If a {@param hideTaskToken} is set, then the + * handling of the drop will attempt to hide the given task as a part of the same window + * container transaction if possible. + */ @VisibleForTesting - void handleDrop(Target target) { + void handleDrop(Target target, @Nullable WindowContainerToken hideTaskToken) { if (target == null || !mTargets.contains(target)) { return; } @@ -245,17 +256,21 @@ public class DragAndDropPolicy { mSplitScreen.onDroppedToSplit(position, mLoggerSessionId); } + final Starter starter = target.type == TYPE_FULLSCREEN + ? mFullscreenStarter + : mSplitscreenStarter; if (mSession.appData != null) { - launchApp(mSession, position); + launchApp(mSession, starter, position, hideTaskToken); } else { - launchIntent(mSession, position); + launchIntent(mSession, starter, position, hideTaskToken); } } /** * Launches an app provided by SysUI. */ - private void launchApp(DragSession session, @SplitPosition int position) { + private void launchApp(DragSession session, Starter starter, @SplitPosition int position, + @Nullable WindowContainerToken hideTaskToken) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Launching app data at position=%d", position); final ClipDescription description = session.getClipDescription(); @@ -275,11 +290,15 @@ public class DragAndDropPolicy { if (isTask) { final int taskId = session.appData.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID); - mStarter.startTask(taskId, position, opts); + starter.startTask(taskId, position, opts, hideTaskToken); } else if (isShortcut) { + if (hideTaskToken != null) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, + "Can not hide task token with starting shortcut"); + } final String packageName = session.appData.getStringExtra(EXTRA_PACKAGE_NAME); final String id = session.appData.getStringExtra(EXTRA_SHORTCUT_ID); - mStarter.startShortcut(packageName, id, position, opts, user); + starter.startShortcut(packageName, id, position, opts, user); } else { final PendingIntent launchIntent = session.appData.getParcelableExtra(EXTRA_PENDING_INTENT); @@ -288,15 +307,16 @@ public class DragAndDropPolicy { Log.e(TAG, "Expected app intent's EXTRA_USER to match pending intent user"); } } - mStarter.startIntent(launchIntent, user.getIdentifier(), null /* fillIntent */, - position, opts); + starter.startIntent(launchIntent, user.getIdentifier(), null /* fillIntent */, + position, opts, hideTaskToken); } } /** * Launches an intent sender provided by an application. */ - private void launchIntent(DragSession session, @SplitPosition int position) { + private void launchIntent(DragSession session, Starter starter, @SplitPosition int position, + @Nullable WindowContainerToken hideTaskToken) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Launching intent at position=%d", position); final ActivityOptions baseActivityOpts = ActivityOptions.makeBasic(); @@ -309,20 +329,22 @@ public class DragAndDropPolicy { | FLAG_ACTIVITY_MULTIPLE_TASK); final Bundle opts = baseActivityOpts.toBundle(); - mStarter.startIntent(session.launchableIntent, + starter.startIntent(session.launchableIntent, session.launchableIntent.getCreatorUserHandle().getIdentifier(), - null /* fillIntent */, position, opts); + null /* fillIntent */, position, opts, hideTaskToken); } /** * Interface for actually committing the task launches. */ public interface Starter { - void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options); + void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options, + @Nullable WindowContainerToken hideTaskToken); void startShortcut(String packageName, String shortcutId, @SplitPosition int position, @Nullable Bundle options, UserHandle user); void startIntent(PendingIntent intent, int userId, Intent fillInIntent, - @SplitPosition int position, @Nullable Bundle options); + @SplitPosition int position, @Nullable Bundle options, + @Nullable WindowContainerToken hideTaskToken); void enterSplitScreen(int taskId, boolean leftOrTop); /** @@ -344,7 +366,12 @@ public class DragAndDropPolicy { } @Override - public void startTask(int taskId, int position, @Nullable Bundle options) { + public void startTask(int taskId, int position, @Nullable Bundle options, + @Nullable WindowContainerToken hideTaskToken) { + if (hideTaskToken != null) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, + "Default starter does not support hide task token"); + } try { ActivityTaskManager.getService().startActivityFromRecents(taskId, options); } catch (RemoteException e) { @@ -367,7 +394,12 @@ public class DragAndDropPolicy { @Override public void startIntent(PendingIntent intent, int userId, @Nullable Intent fillInIntent, - int position, @Nullable Bundle options) { + int position, @Nullable Bundle options, + @Nullable WindowContainerToken hideTaskToken) { + if (hideTaskToken != null) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, + "Default starter does not support hide task token"); + } try { intent.send(mContext, 0, fillInIntent, null, null, null, options); } catch (PendingIntent.CanceledException e) { @@ -420,7 +452,7 @@ public class DragAndDropPolicy { @Override public String toString() { - return "Target {hit=" + hitRegion + " draw=" + drawRegion + "}"; + return "Target {type=" + type + " hit=" + hitRegion + " draw=" + drawRegion + "}"; } } } 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 4bb10dfdf8c6..e4aa115347eb 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 @@ -20,7 +20,6 @@ import static android.app.StatusBarManager.DISABLE_NONE; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.content.pm.ActivityInfo.CONFIG_ASSETS_PATHS; import static android.content.pm.ActivityInfo.CONFIG_UI_MODE; -import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION; @@ -42,6 +41,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.Rect; import android.graphics.Region; import android.graphics.drawable.Drawable; @@ -51,11 +51,13 @@ import android.view.ViewTreeObserver; import android.view.WindowInsets; import android.view.WindowInsets.Type; import android.widget.LinearLayout; +import android.window.WindowContainerToken; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.android.internal.logging.InstanceId; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.R; import com.android.wm.shell.animation.Interpolators; @@ -102,6 +104,8 @@ public class DragLayout extends LinearLayout private boolean mIsShowing; private boolean mHasDropped; private DragSession mSession; + // The last position that was handled by the drag layout + private final Point mLastPosition = new Point(); @SuppressLint("WrongConstant") public DragLayout(Context context, SplitScreenController splitScreenController, @@ -265,6 +269,15 @@ public class DragLayout extends LinearLayout */ public void prepare(DragSession session, InstanceId loggerSessionId) { mPolicy.start(session, loggerSessionId); + updateSession(session); + } + + /** + * Updates the drag layout based on the diven drag session. + */ + public void updateSession(DragSession session) { + // Note: The policy currently just keeps a reference to the session + boolean updatingExistingSession = mSession != null; mSession = session; mHasDropped = false; mCurrentTarget = null; @@ -280,6 +293,8 @@ public class DragLayout extends LinearLayout int bgColor1 = getResizingBackgroundColor(taskInfo1).toArgb(); mDropZoneView1.setAppInfo(bgColor1, icon1); mDropZoneView2.setAppInfo(bgColor1, icon1); + mDropZoneView1.setForceIgnoreBottomMargin(false); + mDropZoneView2.setForceIgnoreBottomMargin(false); updateDropZoneSizes(null, null); // passing null splits the views evenly } else { // We use the first drop zone to show the fullscreen highlight, and don't need @@ -312,6 +327,11 @@ public class DragLayout extends LinearLayout updateDropZoneSizes(topOrLeftBounds, bottomOrRightBounds); } requestLayout(); + if (updatingExistingSession) { + // Update targets if we are already currently dragging + recomputeDropTargets(); + update(mLastPosition.x, mLastPosition.y); + } } private void updateDropZoneSizesForSingleTask() { @@ -359,6 +379,9 @@ public class DragLayout extends LinearLayout mDropZoneView2.setLayoutParams(dropZoneView2); } + /** + * Shows the drag layout. + */ public void show() { mIsShowing = true; recomputeDropTargets(); @@ -384,13 +407,19 @@ public class DragLayout extends LinearLayout * Updates the visible drop target as the user drags. */ public void update(DragEvent event) { + update((int) event.getX(), (int) event.getY()); + } + + /** + * Updates the visible drop target as the user drags to the given coordinates. + */ + private void update(int x, int y) { if (mHasDropped) { return; } // Find containing region, if the same as mCurrentRegion, then skip, otherwise, animate the // visibility of the current region - DragAndDropPolicy.Target target = mPolicy.getTargetAtLocation( - (int) event.getX(), (int) event.getY()); + DragAndDropPolicy.Target target = mPolicy.getTargetAtLocation(x, y); if (mCurrentTarget != target) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Current target: %s", target); if (target == null) { @@ -429,6 +458,7 @@ public class DragLayout extends LinearLayout } mCurrentTarget = target; } + mLastPosition.set(x, y); } /** @@ -436,6 +466,7 @@ public class DragLayout extends LinearLayout */ public void hide(DragEvent event, Runnable hideCompleteCallback) { mIsShowing = false; + mLastPosition.set(-1, -1); animateSplitContainers(false, () -> { if (hideCompleteCallback != null) { hideCompleteCallback.run(); @@ -456,13 +487,13 @@ public class DragLayout extends LinearLayout /** * Handles the drop onto a target and animates out the visible drop targets. */ - public boolean drop(DragEvent event, SurfaceControl dragSurface, - Runnable dropCompleteCallback) { + public boolean drop(DragEvent event, @NonNull SurfaceControl dragSurface, + @Nullable WindowContainerToken hideTaskToken, Runnable dropCompleteCallback) { final boolean handledDrop = mCurrentTarget != null; mHasDropped = true; // Process the drop - mPolicy.handleDrop(mCurrentTarget); + mPolicy.handleDrop(mCurrentTarget, hideTaskToken); // Start animating the drop UI out with the drag surface hide(event, dropCompleteCallback); @@ -472,7 +503,7 @@ public class DragLayout extends LinearLayout return handledDrop; } - private void hideDragSurface(SurfaceControl dragSurface) { + private void hideDragSurface(@NonNull SurfaceControl dragSurface) { final SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); final ValueAnimator dragSurfaceAnimator = ValueAnimator.ofFloat(0f, 1f); // Currently the splash icon animation runs with the default ValueAnimator duration of diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java index 0addd432aff0..dcbdfa349687 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java @@ -18,6 +18,7 @@ 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_HIDE_DRAG_SOURCE_TASK_ID; import android.app.ActivityManager; import android.app.ActivityTaskManager; @@ -27,10 +28,13 @@ import android.content.ClipData; import android.content.ClipDescription; import android.content.Intent; import android.content.pm.ActivityInfo; +import android.os.PersistableBundle; import androidx.annotation.Nullable; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.protolog.ShellProtoLogGroup; import java.util.List; @@ -61,6 +65,7 @@ public class DragSession { @WindowConfiguration.ActivityType int runningTaskActType = ACTIVITY_TYPE_STANDARD; boolean dragItemSupportsSplitscreen; + int hideDragSourceTaskId = -1; DragSession(ActivityTaskManager activityTaskManager, DisplayLayout dispLayout, ClipData data, int dragFlags) { @@ -68,6 +73,11 @@ public class DragSession { mInitialDragData = data; mInitialDragFlags = dragFlags; displayLayout = dispLayout; + hideDragSourceTaskId = data.getDescription().getExtras() != null + ? data.getDescription().getExtras().getInt(EXTRA_HIDE_DRAG_SOURCE_TASK_ID, -1) + : -1; + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, + "Extracting drag source taskId: taskId=%d", hideDragSourceTaskId); } /** @@ -79,17 +89,38 @@ public class DragSession { } /** - * Updates the session data based on the current state of the system. + * Updates the running task for this drag session. */ - void update() { - List<ActivityManager.RunningTaskInfo> tasks = - mActivityTaskManager.getTasks(1, false /* filterOnlyVisibleRecents */); + void updateRunningTask() { + final boolean hideDragSourceTask = hideDragSourceTaskId != -1; + final List<ActivityManager.RunningTaskInfo> tasks = + mActivityTaskManager.getTasks(hideDragSourceTask ? 2 : 1, + false /* filterOnlyVisibleRecents */); if (!tasks.isEmpty()) { - final ActivityManager.RunningTaskInfo task = tasks.get(0); - runningTaskInfo = task; - runningTaskWinMode = task.getWindowingMode(); - runningTaskActType = task.getActivityType(); + for (int i = tasks.size() - 1; i >= 0; i--) { + final ActivityManager.RunningTaskInfo task = tasks.get(i); + if (hideDragSourceTask && hideDragSourceTaskId == task.taskId) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, + "Skipping running task: id=%d component=%s", task.taskId, + task.baseIntent != null ? task.baseIntent.getComponent() : "null"); + continue; + } + runningTaskInfo = task; + runningTaskWinMode = task.getWindowingMode(); + runningTaskActType = task.getActivityType(); + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, + "Running task: id=%d component=%s", task.taskId, + task.baseIntent != null ? task.baseIntent.getComponent() : "null"); + break; + } } + } + + /** + * Updates the session data based on the current state of the system at the start of the drag. + */ + void initialize() { + updateRunningTask(); activityInfo = mInitialDragData.getItemAt(0).getActivityInfo(); // TODO: This should technically check & respect config_supportsNonResizableMultiWindow 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 index e215870f1894..22cfa328bfda 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java @@ -19,16 +19,28 @@ 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.View.DRAG_FLAG_ACCESSIBILITY_ACTION; +import static android.view.View.DRAG_FLAG_GLOBAL; +import static android.view.View.DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION; +import static android.view.View.DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION; +import static android.view.View.DRAG_FLAG_GLOBAL_SAME_APPLICATION; +import static android.view.View.DRAG_FLAG_GLOBAL_URI_READ; +import static android.view.View.DRAG_FLAG_GLOBAL_URI_WRITE; +import static android.view.View.DRAG_FLAG_HIDE_CALLING_TASK_ON_DRAG_START; +import static android.view.View.DRAG_FLAG_OPAQUE; +import static android.view.View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION; +import static android.view.View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG; import android.app.PendingIntent; import android.content.ClipData; import android.content.ClipDescription; import android.view.DragEvent; -import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import java.util.StringJoiner; + /** Collection of utility classes for handling drag and drop. */ public class DragUtils { private static final String TAG = "DragUtils"; @@ -76,7 +88,7 @@ public class DragUtils { */ @Nullable public static PendingIntent getLaunchIntent(@NonNull ClipData data, int dragFlags) { - if ((dragFlags & View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG) == 0) { + if ((dragFlags & DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG) == 0) { // Disallow launching the intent if the app does not want to delegate it to the system return null; } @@ -105,4 +117,35 @@ public class DragUtils { } return mimeTypes; } + + /** + * Returns the string description of the given {@param dragFlags}. + */ + public static String dragFlagsToString(int dragFlags) { + StringJoiner str = new StringJoiner("|"); + if ((dragFlags & DRAG_FLAG_GLOBAL) != 0) { + str.add("GLOBAL"); + } else if ((dragFlags & DRAG_FLAG_GLOBAL_URI_READ) != 0) { + str.add("GLOBAL_URI_READ"); + } else if ((dragFlags & DRAG_FLAG_GLOBAL_URI_WRITE) != 0) { + str.add("GLOBAL_URI_WRITE"); + } else if ((dragFlags & DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION) != 0) { + str.add("GLOBAL_PERSISTABLE_URI_PERMISSION"); + } else if ((dragFlags & DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION) != 0) { + str.add("GLOBAL_PREFIX_URI_PERMISSION"); + } else if ((dragFlags & DRAG_FLAG_OPAQUE) != 0) { + str.add("OPAQUE"); + } else if ((dragFlags & DRAG_FLAG_ACCESSIBILITY_ACTION) != 0) { + str.add("ACCESSIBILITY_ACTION"); + } else if ((dragFlags & DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION) != 0) { + str.add("REQUEST_SURFACE_FOR_RETURN_ANIMATION"); + } else if ((dragFlags & DRAG_FLAG_GLOBAL_SAME_APPLICATION) != 0) { + str.add("GLOBAL_SAME_APPLICATION"); + } else if ((dragFlags & DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG) != 0) { + str.add("START_INTENT_SENDER_ON_UNHANDLED_DRAG"); + } else if ((dragFlags & DRAG_FLAG_HIDE_CALLING_TASK_ON_DRAG_START) != 0) { + str.add("HIDE_CALLING_TASK_ON_DRAG_START"); + } + return str.toString(); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java index 724a130ef52d..18cd2d84d32d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java @@ -19,6 +19,7 @@ package com.android.wm.shell.draganddrop; import static com.android.wm.shell.animation.Interpolators.FAST_OUT_SLOW_IN; import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.content.Context; import android.graphics.Canvas; @@ -37,13 +38,16 @@ import android.widget.ImageView; import androidx.annotation.Nullable; import com.android.internal.policy.ScreenDecorationsUtils; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; +import com.android.wm.shell.protolog.ShellProtoLogGroup; /** * Renders a drop zone area for items being dragged. */ public class DropZoneView extends FrameLayout { + private static final boolean DEBUG_LAYOUT = false; private static final float SPLASHSCREEN_ALPHA = 0.90f; private static final float HIGHLIGHT_ALPHA = 1f; private static final int MARGIN_ANIMATION_ENTER_DURATION = 400; @@ -77,6 +81,7 @@ public class DropZoneView extends FrameLayout { private int mHighlightColor; private ObjectAnimator mBackgroundAnimator; + private int mTargetBackgroundColor; private ObjectAnimator mMarginAnimator; private float mMarginPercent; @@ -146,6 +151,10 @@ public class DropZoneView extends FrameLayout { /** Ignores the bottom margin provided by the insets. */ public void setForceIgnoreBottomMargin(boolean ignoreBottomMargin) { + if (DEBUG_LAYOUT) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, + "setForceIgnoreBottomMargin: ignore=%b", ignoreBottomMargin); + } mIgnoreBottomMargin = ignoreBottomMargin; if (mMarginPercent > 0) { mMarginView.invalidate(); @@ -154,8 +163,14 @@ public class DropZoneView extends FrameLayout { /** Sets the bottom inset so the drop zones are above bottom navigation. */ public void setBottomInset(float bottom) { + if (DEBUG_LAYOUT) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "setBottomInset: inset=%f", + bottom); + } mBottomInset = bottom; - ((LayoutParams) mSplashScreenView.getLayoutParams()).bottomMargin = (int) bottom; + final LayoutParams lp = (LayoutParams) mSplashScreenView.getLayoutParams(); + lp.bottomMargin = (int) bottom; + mSplashScreenView.setLayoutParams(lp); if (mMarginPercent > 0) { mMarginView.invalidate(); } @@ -181,6 +196,9 @@ public class DropZoneView extends FrameLayout { /** Animates between highlight and splashscreen depending on current state. */ public void animateSwitch() { + if (DEBUG_LAYOUT) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "animateSwitch"); + } mShowingHighlight = !mShowingHighlight; mShowingSplash = !mShowingHighlight; final int newColor = mShowingHighlight ? mHighlightColor : mSplashScreenColor; @@ -190,6 +208,10 @@ public class DropZoneView extends FrameLayout { /** Animates the highlight indicating the zone is hovered on or not. */ public void setShowingHighlight(boolean showingHighlight) { + if (DEBUG_LAYOUT) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "setShowingHighlight: showing=%b", + showingHighlight); + } mShowingHighlight = showingHighlight; mShowingSplash = !mShowingHighlight; final int newColor = mShowingHighlight ? mHighlightColor : mSplashScreenColor; @@ -199,6 +221,10 @@ public class DropZoneView extends FrameLayout { /** Animates the margins around the drop zone to show or hide. */ public void setShowingMargin(boolean visible) { + if (DEBUG_LAYOUT) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "setShowingMargin: visible=%b", + visible); + } if (mShowingMargin != visible) { mShowingMargin = visible; animateMarginToState(); @@ -212,6 +238,15 @@ public class DropZoneView extends FrameLayout { } private void animateBackground(int startColor, int endColor) { + if (DEBUG_LAYOUT) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, + "animateBackground: start=%s end=%s", + Integer.toHexString(startColor), Integer.toHexString(endColor)); + } + if (endColor == mTargetBackgroundColor) { + // Already at, or animating to, that background color + return; + } if (mBackgroundAnimator != null) { mBackgroundAnimator.cancel(); } @@ -223,6 +258,7 @@ public class DropZoneView extends FrameLayout { mBackgroundAnimator.setInterpolator(FAST_OUT_SLOW_IN); } mBackgroundAnimator.start(); + mTargetBackgroundColor = endColor; } private void animateSplashScreenIcon() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/GlobalDragListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/GlobalDragListener.kt index 31214eba8dd0..ffcfe6447e2d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/GlobalDragListener.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/GlobalDragListener.kt @@ -25,7 +25,7 @@ import android.view.IWindowManager import android.window.IGlobalDragListener import android.window.IUnhandledDragCallback import androidx.annotation.VisibleForTesting -import com.android.internal.protolog.common.ProtoLog +import com.android.internal.protolog.ProtoLog import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.protolog.ShellProtoLogGroup import java.util.function.Consumer diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java index 7d2aa275a684..4531967d6f93 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java @@ -25,11 +25,11 @@ import android.content.Context; import android.util.SparseArray; import android.view.SurfaceControl; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.protolog.ShellProtoLogGroup; -import com.android.wm.shell.shared.DesktopModeStatus; +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.windowdecor.WindowDecorViewModel; @@ -150,6 +150,10 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, "Adding active freeform task: #%d", taskInfo.taskId); } + } else if (repository.isClosingTask(taskInfo.taskId) + && repository.removeClosingTask(taskInfo.taskId)) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, + "Removing closing freeform task: #%d", taskInfo.taskId); } repository.updateVisibleFreeformTasks(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java index 2626e7380163..d2ceb67030fc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java @@ -27,7 +27,7 @@ import android.view.SurfaceControl; import androidx.annotation.NonNull; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.protolog.ShellProtoLogGroup; 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 cd478e5bd567..333c75f92ffc 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 @@ -49,7 +49,7 @@ import android.window.TransitionRequestInfo; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java index ce98458c0575..93ede7a8b7aa 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java @@ -16,7 +16,6 @@ package com.android.wm.shell.pip; -import android.content.ComponentName; import android.os.RemoteException; import android.view.IPinnedTaskListener; import android.view.WindowManagerGlobal; @@ -70,12 +69,6 @@ public class PinnedStackListenerForwarder { } } - private void onActivityHidden(ComponentName componentName) { - for (PinnedTaskListener listener : mListeners) { - listener.onActivityHidden(componentName); - } - } - @BinderThread private class PinnedTaskListenerImpl extends IPinnedTaskListener.Stub { @Override @@ -91,13 +84,6 @@ public class PinnedStackListenerForwarder { PinnedStackListenerForwarder.this.onImeVisibilityChanged(imeVisible, imeHeight); }); } - - @Override - public void onActivityHidden(ComponentName componentName) { - mMainExecutor.execute(() -> { - PinnedStackListenerForwarder.this.onActivityHidden(componentName); - }); - } } /** @@ -108,7 +94,5 @@ public class PinnedStackListenerForwarder { public void onMovementBoundsChanged(boolean fromImeAdjustment) {} public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {} - - public void onActivityHidden(ComponentName componentName) {} } } 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 a749019046f8..b27c428f1693 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 @@ -16,10 +16,12 @@ package com.android.wm.shell.pip; +import android.annotation.NonNull; import android.graphics.Rect; import com.android.wm.shell.shared.annotations.ExternalThread; +import java.util.concurrent.Executor; import java.util.function.Consumer; /** @@ -69,9 +71,10 @@ public interface Pip { default void removePipExclusionBoundsChangeListener(Consumer<Rect> listener) { } /** - * @return {@link PipTransitionController} instance. + * Register {@link PipTransitionController.PipTransitionCallback} to listen on PiP transition + * started / finished callbacks. */ - default PipTransitionController getPipTransitionController() { - return null; - } + default void registerPipTransitionCallback( + @NonNull PipTransitionController.PipTransitionCallback callback, + @NonNull Executor executor) { } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java index 0a3c15b6057f..a8346a9b3b48 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java @@ -17,6 +17,7 @@ package com.android.wm.shell.pip; import static android.util.RotationUtils.rotateBounds; +import static android.view.Surface.ROTATION_0; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; @@ -37,7 +38,7 @@ import android.window.TaskSnapshot; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.SfVsyncFrameCallbackProvider; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.animation.Interpolators; import com.android.wm.shell.common.pip.PipUtils; @@ -625,6 +626,14 @@ public class PipAnimationController { } } else { adjustedSourceRectHint.set(sourceRectHint); + if (isInPipDirection(direction) + && rotationDelta == ROTATION_0 + && taskInfo.displayCutoutInsets != null) { + // TODO: this is to special case the issues on Foldable device + // with display cutout. This aligns with what's in SwipePipToHomeAnimator. + adjustedSourceRectHint.offset(taskInfo.displayCutoutInsets.left, + taskInfo.displayCutoutInsets.top); + } } final Rect sourceHintRectInsets = new Rect(); if (!adjustedSourceRectHint.isEmpty()) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java index 202f60dad842..3d1994cac534 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java @@ -137,7 +137,7 @@ public class PipSurfaceTransactionHelper { mTmpDestinationRect.inset(insets); // Scale to the bounds no smaller than the destination and offset such that the top/left // of the scaled inset source rect aligns with the top/left of the destination bounds - final float scale, left, top; + final float scale; if (isInPipDirection && sourceRectHint != null && sourceRectHint.width() < sourceBounds.width()) { // scale by sourceRectHint if it's not edge-to-edge, for entering PiP transition only. @@ -148,14 +148,17 @@ public class PipSurfaceTransactionHelper { ? (float) destinationBounds.width() / sourceBounds.width() : (float) destinationBounds.height() / sourceBounds.height(); scale = (1 - fraction) * startScale + fraction * endScale; - left = destinationBounds.left - insets.left * scale; - top = destinationBounds.top - insets.top * scale; } else { scale = Math.max((float) destinationBounds.width() / sourceBounds.width(), (float) destinationBounds.height() / sourceBounds.height()); - // Work around the rounding error by fix the position at very beginning. - left = scale == 1 ? 0 : destinationBounds.left - insets.left * scale; - top = scale == 1 ? 0 : destinationBounds.top - insets.top * scale; + } + float left = destinationBounds.left - insets.left * scale; + float top = destinationBounds.top - insets.top * scale; + if (scale == 1) { + // Work around the 1 pixel off error by rounding the position down at very beginning. + // We noticed such error from flicker tests, not visually. + left = sourceBounds.left; + top = sourceBounds.top; } mTmpTransform.setScale(scale, scale); tx.setMatrix(leash, mTmpTransform, mTmpFloat9) 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 e4420d73886e..6e1e44b7ede0 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.res.Configuration; import android.graphics.Rect; import android.os.RemoteException; import android.os.SystemProperties; -import android.util.Rational; import android.view.Choreographer; import android.view.Display; import android.view.Surface; @@ -74,7 +73,7 @@ import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.animation.Interpolators; @@ -128,8 +127,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, SystemProperties.getInt( "persist.wm.debug.extra_content_overlay_fade_out_delay_ms", 400); - private static final float PIP_ASPECT_RATIO_MISMATCH_THRESHOLD = 0.005f; - private final Context mContext; private final SyncTransactionQueue mSyncTransactionQueue; private final PipBoundsState mPipBoundsState; @@ -426,7 +423,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, }); mPipTransitionController.setPipOrganizer(this); displayController.addDisplayWindowListener(this); - pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback); + pipTransitionController.registerPipTransitionCallback( + mPipTransitionCallback, mMainExecutor); } } @@ -498,7 +496,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "startSwipePipToHome: %s, state=%s", componentName, mPipTransitionState); mPipTransitionState.setInSwipePipToHomeTransition(true); - sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP); + if (!ENABLE_SHELL_TRANSITIONS) { + sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP); + } setBoundsStateForEntry(componentName, pictureInPictureParams, activityInfo); return mPipBoundsAlgorithm.getEntryDestinationBounds(); } @@ -608,6 +608,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, public void exitPip(int animationDurationMs, boolean requestEnterSplit) { if (!mPipTransitionState.isInPip() || mPipTransitionState.getTransitionState() == PipTransitionState.EXITING_PIP + || mPipTransitionState.getInSwipePipToHomeTransition() || mToken == null) { ProtoLog.wtf(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: Not allowed to exitPip in current state" @@ -637,6 +638,13 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, return; } + // bail early if leash is null + if (mLeash == null) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "exitPip: leash is null"); + return; + } + final Rect destinationBounds = new Rect(getExitDestinationBounds()); final int direction = syncWithSplitScreenBounds(destinationBounds, requestEnterSplit) ? TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN @@ -821,37 +829,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mPictureInPictureParams.getTitle()); mPipParamsChangedForwarder.notifySubtitleChanged( mPictureInPictureParams.getSubtitle()); - - if (mPictureInPictureParams.hasSourceBoundsHint() - && mPictureInPictureParams.hasSetAspectRatio()) { - Rational sourceRectHintAspectRatio = new Rational( - mPictureInPictureParams.getSourceRectHint().width(), - mPictureInPictureParams.getSourceRectHint().height()); - if (sourceRectHintAspectRatio.compareTo( - mPictureInPictureParams.getAspectRatio()) != 0) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "Aspect ratio of source rect hint (%d/%d) does not match the provided " - + "aspect ratio value (%d/%d). Consider matching them for " - + "improved animation. Future releases might override the " - + "value to match.", - mPictureInPictureParams.getSourceRectHint().width(), - mPictureInPictureParams.getSourceRectHint().height(), - mPictureInPictureParams.getAspectRatio().getNumerator(), - mPictureInPictureParams.getAspectRatio().getDenominator()); - } - if (Math.abs(sourceRectHintAspectRatio.floatValue() - - mPictureInPictureParams.getAspectRatioFloat()) - > PIP_ASPECT_RATIO_MISMATCH_THRESHOLD) { - ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "Aspect ratio of source rect hint (%f) does not match the provided " - + "aspect ratio value (%f) and is above threshold of %f. " - + "Consider matching them for improved animation. Future " - + "releases might override the value to match.", - sourceRectHintAspectRatio.floatValue(), - mPictureInPictureParams.getAspectRatioFloat(), - PIP_ASPECT_RATIO_MISMATCH_THRESHOLD); - } - } } mPipUiEventLoggerLogger.setTaskInfo(mTaskInfo); 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 3cae72d89ecc..87692ac52908 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 @@ -24,6 +24,7 @@ import static android.util.RotationUtils.rotateBounds; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; import static android.view.WindowManager.TRANSIT_CHANGE; +import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_PIP; import static android.view.WindowManager.TRANSIT_TO_BACK; @@ -62,7 +63,7 @@ import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.pip.PipBoundsAlgorithm; @@ -300,6 +301,10 @@ public class PipTransition extends PipTransitionController { finishTransaction); } + if (isCurrentPipActivityClosed(info)) { + mPipBoundsState.setLastPipComponentName(null /* componentName */); + } + return false; } @@ -322,6 +327,21 @@ public class PipTransition extends PipTransitionController { return true; } + private boolean isCurrentPipActivityClosed(TransitionInfo info) { + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + TransitionInfo.Change change = info.getChanges().get(i); + boolean isTaskChange = change.getTaskInfo() != null; + boolean hasComponentNameOfPip = change.getActivityComponent() != null + && change.getActivityComponent().equals( + mPipBoundsState.getLastPipComponentName()); + if (!isTaskChange && change.getMode() == TRANSIT_CLOSE && hasComponentNameOfPip) { + return true; + } + } + return false; + } + + @Override public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, @@ -1154,6 +1174,7 @@ public class PipTransition extends PipTransitionController { } final Rect sourceBounds = pipTaskInfo.configuration.windowConfiguration.getBounds(); + sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP); final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController.getAnimator(pipTaskInfo, leash, sourceBounds, sourceBounds, destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java index 6eefdcfc4d93..8d36db97c511 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 @@ -41,7 +41,7 @@ import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.pip.PipBoundsAlgorithm; import com.android.wm.shell.common.pip.PipBoundsState; @@ -53,8 +53,9 @@ import com.android.wm.shell.transition.DefaultMixedHandler; import com.android.wm.shell.transition.Transitions; import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.List; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Executor; /** * Responsible supplying PiP Transitions. @@ -66,7 +67,7 @@ public abstract class PipTransitionController implements Transitions.TransitionH protected final ShellTaskOrganizer mShellTaskOrganizer; protected final PipMenuController mPipMenuController; protected final Transitions mTransitions; - private final List<PipTransitionCallback> mPipTransitionCallbacks = new ArrayList<>(); + private final Map<PipTransitionCallback, Executor> mPipTransitionCallbacks = new HashMap<>(); protected PipTaskOrganizer mPipOrganizer; protected DefaultMixedHandler mMixedHandler; @@ -126,8 +127,10 @@ public abstract class PipTransitionController implements Transitions.TransitionH /** * Called when the Shell wants to start resizing Pip transition/animation. + * + * @param duration the suggested duration for resize animation. */ - public void startResizeTransition(WindowContainerTransaction wct) { + public void startResizeTransition(WindowContainerTransaction wct, int duration) { // Default implementation does nothing. } @@ -181,16 +184,18 @@ public abstract class PipTransitionController implements Transitions.TransitionH /** * Registers {@link PipTransitionCallback} to receive transition callbacks. */ - public void registerPipTransitionCallback(PipTransitionCallback callback) { - mPipTransitionCallbacks.add(callback); + public void registerPipTransitionCallback( + @NonNull PipTransitionCallback callback, @NonNull Executor executor) { + mPipTransitionCallbacks.put(callback, executor); } protected void sendOnPipTransitionStarted( @PipAnimationController.TransitionDirection int direction) { final Rect pipBounds = mPipBoundsState.getBounds(); - for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) { - final PipTransitionCallback callback = mPipTransitionCallbacks.get(i); - callback.onPipTransitionStarted(direction, pipBounds); + for (Map.Entry<PipTransitionCallback, Executor> entry + : mPipTransitionCallbacks.entrySet()) { + entry.getValue().execute( + () -> entry.getKey().onPipTransitionStarted(direction, pipBounds)); } if (isInPipDirection(direction) && Flags.enablePipUiStateCallbackOnEntering()) { try { @@ -207,9 +212,10 @@ public abstract class PipTransitionController implements Transitions.TransitionH protected void sendOnPipTransitionFinished( @PipAnimationController.TransitionDirection int direction) { - for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) { - final PipTransitionCallback callback = mPipTransitionCallbacks.get(i); - callback.onPipTransitionFinished(direction); + for (Map.Entry<PipTransitionCallback, Executor> entry + : mPipTransitionCallbacks.entrySet()) { + entry.getValue().execute( + () -> entry.getKey().onPipTransitionFinished(direction)); } if (isInPipDirection(direction) && Flags.enablePipUiStateCallbackOnEntering()) { try { @@ -226,9 +232,10 @@ public abstract class PipTransitionController implements Transitions.TransitionH protected void sendOnPipTransitionCancelled( @PipAnimationController.TransitionDirection int direction) { - for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) { - final PipTransitionCallback callback = mPipTransitionCallbacks.get(i); - callback.onPipTransitionCanceled(direction); + for (Map.Entry<PipTransitionCallback, Executor> entry + : mPipTransitionCallbacks.entrySet()) { + entry.getValue().execute( + () -> entry.getKey().onPipTransitionCanceled(direction)); } } 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 0169e8c40f45..c7369a3cd347 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 @@ -32,7 +32,7 @@ import android.view.View; import android.view.ViewRootImpl; import android.view.WindowManagerGlobal; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.pip.PipBoundsState; 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 8c4bf7620068..d1d82755d12c 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 @@ -59,7 +59,7 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.jank.InteractionJankMonitor; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.common.DisplayChangeController; @@ -106,6 +106,7 @@ import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.concurrent.Executor; import java.util.function.Consumer; /** @@ -367,15 +368,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb false /* fromRotation */, fromImeAdjustment, false /* fromShelfAdjustment */, null /* windowContainerTransaction */); } - - @Override - public void onActivityHidden(ComponentName componentName) { - if (componentName.equals(mPipBoundsState.getLastPipComponentName())) { - // The activity was removed, we don't want to restore to the reentry state - // saved for this component anymore. - mPipBoundsState.setLastPipComponentName(null); - } - } } /** @@ -487,7 +479,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb mShellCommandHandler.addDumpCallback(this::dump, this); mPipInputConsumer = new PipInputConsumer(WindowManagerGlobal.getWindowManagerService(), INPUT_CONSUMER_PIP, mMainExecutor); - mPipTransitionController.registerPipTransitionCallback(this); + mPipTransitionController.registerPipTransitionCallback(this, mMainExecutor); mPipTaskOrganizer.registerOnDisplayIdChangeCallback((int displayId) -> { mPipDisplayLayoutState.setDisplayId(displayId); onDisplayChanged(mDisplayController.getDisplayLayout(displayId), @@ -1229,8 +1221,11 @@ public class PipController implements PipTransitionController.PipTransitionCallb } @Override - public PipTransitionController getPipTransitionController() { - return mPipTransitionController; + public void registerPipTransitionCallback( + PipTransitionController.PipTransitionCallback callback, + Executor executor) { + mMainExecutor.execute(() -> mPipTransitionController.registerPipTransitionCallback( + callback, executor)); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java index f6cab485fa2a..d1978c30eecb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java @@ -28,7 +28,7 @@ import android.view.IWindowManager; import android.view.InputChannel; import android.view.InputEvent; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.protolog.ShellProtoLogGroup; 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 15342be0e8b7..c18964240f98 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 @@ -59,7 +59,7 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; import android.widget.LinearLayout; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.animation.Interpolators; import com.android.wm.shell.common.ShellExecutor; 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 ef468434db6a..df3803d54d9d 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 @@ -34,10 +34,11 @@ import android.graphics.PointF; import android.graphics.Rect; import android.os.Debug; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.animation.FloatProperties; import com.android.wm.shell.common.FloatingContentCoordinator; +import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.magnetictarget.MagnetizedObject; import com.android.wm.shell.common.pip.PipAppOpsListener; import com.android.wm.shell.common.pip.PipBoundsState; @@ -47,6 +48,7 @@ import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.shared.animation.PhysicsAnimator; +import com.android.wm.shell.shared.annotations.ShellMainThread; import kotlin.Unit; import kotlin.jvm.functions.Function0; @@ -171,7 +173,9 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, public void onPipTransitionCanceled(int direction) {} }; - public PipMotionHelper(Context context, @NonNull PipBoundsState pipBoundsState, + public PipMotionHelper(Context context, + @ShellMainThread ShellExecutor mainExecutor, + @NonNull PipBoundsState pipBoundsState, PipTaskOrganizer pipTaskOrganizer, PhonePipMenuController menuController, PipSnapAlgorithm snapAlgorithm, PipTransitionController pipTransitionController, FloatingContentCoordinator floatingContentCoordinator, @@ -183,7 +187,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, mSnapAlgorithm = snapAlgorithm; mFloatingContentCoordinator = floatingContentCoordinator; mPipPerfHintController = pipPerfHintControllerOptional.orElse(null); - pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback); + pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback, mainExecutor); mResizePipUpdateListener = (target, values) -> { if (mPipBoundsState.getMotionBoundsState().isInMotion()) { mPipTaskOrganizer.scheduleUserResizePip(getBounds(), 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 d8ac8e948a97..9c4e723efc23 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 @@ -48,7 +48,7 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityWindowInfo; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.ShellExecutor; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java index 5d858fa9aa3f..cb82db630715 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java @@ -23,7 +23,7 @@ import android.view.VelocityTracker; import android.view.ViewConfiguration; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.protolog.ShellProtoLogGroup; 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 6b890c49b713..50d22ad00b11 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 @@ -33,7 +33,7 @@ import android.app.RemoteAction; import android.content.Context; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.common.pip.PipMediaController; import com.android.wm.shell.common.pip.PipUtils; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBackgroundView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBackgroundView.java index 0221db836dda..eb7a10cc9dfb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBackgroundView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBackgroundView.java @@ -28,7 +28,7 @@ import android.widget.FrameLayout; import androidx.annotation.NonNull; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.protolog.ShellProtoLogGroup; 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 72c0cd71f198..188c35ff3562 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 @@ -33,7 +33,7 @@ import android.view.Gravity; import androidx.annotation.NonNull; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.pip.PipBoundsAlgorithm; 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 8a215b4b2e25..1afb470c5e9b 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 @@ -26,7 +26,7 @@ import android.graphics.Rect; import android.os.Handler; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement; import com.android.wm.shell.protolog.ShellProtoLogGroup; 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 3d286461ef79..0ed5079b7fba 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 @@ -39,7 +39,7 @@ import android.view.Gravity; import androidx.annotation.NonNull; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.common.DisplayController; @@ -257,7 +257,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal } private void onInit() { - mPipTransitionController.registerPipTransitionCallback(this); + mPipTransitionController.registerPipTransitionCallback(this, mMainExecutor); reloadResources(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java index 977aad4a898a..327ceef00e6a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java @@ -27,7 +27,7 @@ import android.content.Context; import android.os.Bundle; import android.os.Handler; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.TvWindowMenuActionButton; import com.android.wm.shell.protolog.ShellProtoLogGroup; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java index 6b5bdd2299e1..e74870d4d139 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java @@ -37,7 +37,7 @@ import android.window.SurfaceSyncGroup; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.pip.PipMenuController; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java index adc03cf5c4d7..eabf1b0b3063 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java @@ -39,7 +39,7 @@ import android.widget.TextView; import androidx.annotation.NonNull; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import java.util.Arrays; 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 4a767ef2a113..c7704f0b4eed 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 @@ -50,7 +50,7 @@ import android.widget.ImageView; import androidx.annotation.NonNull; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.widget.LinearLayoutManager; import com.android.internal.widget.RecyclerView; import com.android.wm.shell.R; 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 54e162bba2f3..ce5079227b61 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 @@ -33,7 +33,7 @@ import android.os.Bundle; import android.text.TextUtils; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.ImageUtils; import com.android.wm.shell.R; import com.android.wm.shell.common.pip.PipMediaController; 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 ca0d61f8fc9b..7a0e6694cb51 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 @@ -62,7 +62,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.graphics.SfVsyncFrameCallbackProvider; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.pip.PipDisplayLayoutState; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java index 5c561fed89c7..88f9e4c740e3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java @@ -47,9 +47,17 @@ public class PipResizeAnimator extends ValueAnimator @Nullable private Runnable mAnimationEndCallback; private RectEvaluator mRectEvaluator; + + // Bounds relative to which scaling/cropping must be done. private final Rect mBaseBounds = new Rect(); + + // Bounds to animate from. private final Rect mStartBounds = new Rect(); + + // Target bounds. private final Rect mEndBounds = new Rect(); + + // Bounds updated by the evaluator as animator is running. private final Rect mAnimatedRect = new Rect(); private final float mDelta; @@ -84,7 +92,6 @@ public class PipResizeAnimator extends ValueAnimator addListener(this); addUpdateListener(this); setEvaluator(mRectEvaluator); - // TODO: change this setDuration(duration); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java index 6e36a32ac931..9cfe1620a2ff 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java @@ -32,7 +32,7 @@ import android.view.View; import android.view.ViewRootImpl; import android.view.WindowManagerGlobal; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.pip.PipBoundsState; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java index fc0d36d13b2e..06adad626e9f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java @@ -36,7 +36,7 @@ import androidx.annotation.BinderThread; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.Preconditions; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; @@ -352,6 +352,7 @@ public class PipController implements ConfigurationChangeListener, mPipBoundsAlgorithm.dump(pw, innerPrefix); mPipBoundsState.dump(pw, innerPrefix); mPipDisplayLayoutState.dump(pw, innerPrefix); + mPipTransitionState.dump(pw, innerPrefix); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java index b757b00f16dd..ffda56d89276 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java @@ -28,7 +28,7 @@ import android.view.IWindowManager; import android.view.InputChannel; import android.view.InputEvent; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.protolog.ShellProtoLogGroup; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuView.java index 42b8e9f5a3ad..c54e4cd90f57 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuView.java @@ -59,7 +59,7 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; import android.widget.LinearLayout; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.animation.Interpolators; import com.android.wm.shell.common.ShellExecutor; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java index 495cd0075494..ea02de9d9704 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java @@ -16,6 +16,7 @@ package com.android.wm.shell.pip2.phone; +import static androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY; import static androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_NO_BOUNCY; import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW; import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_MEDIUM; @@ -25,6 +26,7 @@ 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.pip2.phone.PipMenuView.ANIM_TYPE_DISMISS; import static com.android.wm.shell.pip2.phone.PipMenuView.ANIM_TYPE_NONE; +import static com.android.wm.shell.pip2.phone.PipTransition.ANIMATING_BOUNDS_CHANGE_DURATION; import android.annotation.NonNull; import android.annotation.Nullable; @@ -35,7 +37,8 @@ import android.os.Bundle; import android.os.Debug; import android.view.SurfaceControl; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; +import com.android.internal.util.Preconditions; import com.android.wm.shell.R; import com.android.wm.shell.animation.FloatProperties; import com.android.wm.shell.common.FloatingContentCoordinator; @@ -45,6 +48,7 @@ import com.android.wm.shell.common.pip.PipBoundsAlgorithm; import com.android.wm.shell.common.pip.PipBoundsState; import com.android.wm.shell.common.pip.PipPerfHintController; import com.android.wm.shell.common.pip.PipSnapAlgorithm; +import com.android.wm.shell.pip2.animation.PipResizeAnimator; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.shared.animation.PhysicsAnimator; @@ -62,6 +66,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, PipTransitionState.PipTransitionStateChangedListener { private static final String TAG = "PipMotionHelper"; private static final String FLING_BOUNDS_CHANGE = "fling_bounds_change"; + private static final String ANIMATING_BOUNDS_CHANGE = "animating_bounds_change"; private static final boolean DEBUG = false; private static final int SHRINK_STACK_FROM_MENU_DURATION = 250; @@ -113,7 +118,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, /** SpringConfig to use for fling-then-spring animations. */ private final PhysicsAnimator.SpringConfig mSpringConfig = - new PhysicsAnimator.SpringConfig(700f, DAMPING_RATIO_NO_BOUNCY); + new PhysicsAnimator.SpringConfig(300f, DAMPING_RATIO_LOW_BOUNCY); /** SpringConfig used for animating into the dismiss region, matches the one in * {@link MagnetizedObject}. */ @@ -152,9 +157,16 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, private boolean mDismissalPending = false; /** - * Set to true if bounds change transition has been scheduled from PipMotionHelper. + * Set to true if bounds change transition has been scheduled from PipMotionHelper + * after animating is over. */ - private boolean mWaitingForBoundsChangeTransition = false; + private boolean mWaitingForFlingTransition = false; + + /** + * Set to true if bounds change transition has been scheduled from PipMotionHelper, + * and if the animation is supposed to run while transition is playing. + */ + private boolean mWaitingToPlayBoundsChangeTransition = false; /** * Gets set in {@link #animateToExpandedState(Rect, Rect, Rect, Runnable)}, this callback is @@ -634,6 +646,9 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, // The physics animation ended, though we may not necessarily be done animating, such as // when we're still dragging after moving out of the magnetic target. if (!mDismissalPending && !mSpringingToTouch && !mMagnetizedPip.getObjectStuckToTarget()) { + // Update the earlier estimate on bounds we are animating towards, since physics + // animator is non-deterministic. + setAnimatingToBounds(mPipBoundsState.getMotionBoundsState().getBoundsInMotion()); // do not schedule resize if PiP is dismissing, which may cause app re-open to // mBounds instead of its normal bounds. Bundle extra = new Bundle(); @@ -673,6 +688,11 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, * Directly resizes the PiP to the given {@param bounds}. */ private void resizeAndAnimatePipUnchecked(Rect toBounds, int duration) { + if (mPipBoundsState.getMotionBoundsState().isInMotion()) { + // Do not carry out any resizing if we are dragging or physics animator is running. + return; + } + if (DEBUG) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: resizeAndAnimatePipUnchecked: toBounds=%s" @@ -682,10 +702,11 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, // Intentionally resize here even if the current bounds match the destination bounds. // This is so all the proper callbacks are performed. - - // mPipTaskOrganizer.scheduleAnimateResizePip(toBounds, duration, - // TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND, null /* updateBoundsCallback */); - // setAnimatingToBounds(toBounds); + setAnimatingToBounds(toBounds); + Bundle extra = new Bundle(); + extra.putBoolean(ANIMATING_BOUNDS_CHANGE, true); + extra.putInt(ANIMATING_BOUNDS_CHANGE_DURATION, duration); + mPipTransitionState.setState(PipTransitionState.SCHEDULED_BOUNDS_CHANGE, extra); } @Override @@ -694,7 +715,11 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, @Nullable Bundle extra) { switch (newState) { case PipTransitionState.SCHEDULED_BOUNDS_CHANGE: - if (!extra.getBoolean(FLING_BOUNDS_CHANGE)) break; + mWaitingForFlingTransition = extra.getBoolean(FLING_BOUNDS_CHANGE); + mWaitingToPlayBoundsChangeTransition = extra.getBoolean(ANIMATING_BOUNDS_CHANGE); + if (!mWaitingForFlingTransition && !mWaitingToPlayBoundsChangeTransition) { + break; + } if (mPipBoundsState.getBounds().equals( mPipBoundsState.getMotionBoundsState().getBoundsInMotion())) { @@ -709,30 +734,30 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, break; } - // If touch is turned off and we are in a fling animation, schedule a transition. - mWaitingForBoundsChangeTransition = true; + // Delay config until the end, if we are animating after scheduling the transition. mPipScheduler.scheduleAnimateResizePip( - mPipBoundsState.getMotionBoundsState().getBoundsInMotion()); + mPipBoundsState.getMotionBoundsState().getAnimatingToBounds(), + mWaitingToPlayBoundsChangeTransition, + extra.getInt(ANIMATING_BOUNDS_CHANGE_DURATION, + PipTransition.BOUNDS_CHANGE_JUMPCUT_DURATION)); break; case PipTransitionState.CHANGING_PIP_BOUNDS: - if (!mWaitingForBoundsChangeTransition) break; - - // If bounds change transition was scheduled from this class, handle leash updates. - mWaitingForBoundsChangeTransition = false; SurfaceControl.Transaction startTx = extra.getParcelable( PipTransition.PIP_START_TX, SurfaceControl.Transaction.class); + SurfaceControl.Transaction finishTx = extra.getParcelable( + PipTransition.PIP_FINISH_TX, SurfaceControl.Transaction.class); Rect destinationBounds = extra.getParcelable( PipTransition.PIP_DESTINATION_BOUNDS, Rect.class); - startTx.setPosition(mPipTransitionState.mPinnedTaskLeash, - destinationBounds.left, destinationBounds.top); - startTx.apply(); - - // All motion operations have actually finished, so make bounds cache updates. - settlePipBoundsAfterPhysicsAnimation(false /* animatingAfter */); - cleanUpHighPerfSessionMaybe(); - - // Signal that the transition is done - should update transition state by default. - mPipScheduler.scheduleFinishResizePip(false /* configAtEnd */); + final int duration = extra.getInt(ANIMATING_BOUNDS_CHANGE_DURATION, + PipTransition.BOUNDS_CHANGE_JUMPCUT_DURATION); + + if (mWaitingForFlingTransition) { + mWaitingForFlingTransition = false; + handleFlingTransition(startTx, finishTx, destinationBounds); + } else if (mWaitingToPlayBoundsChangeTransition) { + mWaitingToPlayBoundsChangeTransition = false; + startResizeAnimation(startTx, finishTx, destinationBounds, duration); + } break; case PipTransitionState.EXITING_PIP: // We need to force finish any local animators if about to leave PiP, to avoid @@ -740,9 +765,46 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, if (!mPipBoundsState.getMotionBoundsState().isInMotion()) break; cancelPhysicsAnimation(); settlePipBoundsAfterPhysicsAnimation(false /* animatingAfter */); + break; } } + private void handleFlingTransition(SurfaceControl.Transaction startTx, + SurfaceControl.Transaction finishTx, Rect destinationBounds) { + startTx.setPosition(mPipTransitionState.mPinnedTaskLeash, + destinationBounds.left, destinationBounds.top); + startTx.apply(); + + // All motion operations have actually finished, so make bounds cache updates. + settlePipBoundsAfterPhysicsAnimation(false /* animatingAfter */); + cleanUpHighPerfSessionMaybe(); + + // Signal that the transition is done - should update transition state by default. + mPipScheduler.scheduleFinishResizePip(false /* configAtEnd */); + } + + private void startResizeAnimation(SurfaceControl.Transaction startTx, + SurfaceControl.Transaction finishTx, Rect destinationBounds, int duration) { + SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash; + Preconditions.checkState(pipLeash != null, + "No leash cached by mPipTransitionState=" + mPipTransitionState); + + startTx.setWindowCrop(pipLeash, mPipBoundsState.getBounds().width(), + mPipBoundsState.getBounds().height()); + + PipResizeAnimator animator = new PipResizeAnimator(mContext, pipLeash, + startTx, finishTx, mPipBoundsState.getBounds(), mPipBoundsState.getBounds(), + destinationBounds, duration, 0f /* angle */); + animator.setAnimationEndCallback(() -> { + mPipBoundsState.setBounds(destinationBounds); + // All motion operations have actually finished, so make bounds cache updates. + cleanUpHighPerfSessionMaybe(); + // Signal that we are done with resize transition + mPipScheduler.scheduleFinishResizePip(true /* configAtEnd */); + }); + animator.start(); + } + private void settlePipBoundsAfterPhysicsAnimation(boolean animatingAfter) { if (!animatingAfter) { // The physics animation ended, though we may not necessarily be done animating, such as @@ -754,6 +816,26 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, mPipBoundsState.getMotionBoundsState().onPhysicsAnimationEnded(); mSpringingToTouch = false; mDismissalPending = false; + + // Check whether new bounds after fling imply we need to update stash state too. + stashEndActionIfNeeded(); + } + + private void stashEndActionIfNeeded() { + boolean isStashing = mPipBoundsState.getBounds().right > mPipBoundsState + .getDisplayBounds().width() || mPipBoundsState.getBounds().left < 0; + if (!isStashing) { + return; + } + + if (mPipBoundsState.getBounds().left < 0 + && mPipBoundsState.getStashedState() != STASH_TYPE_LEFT) { + mPipBoundsState.setStashed(STASH_TYPE_LEFT); + } else if (mPipBoundsState.getBounds().left >= 0 + && mPipBoundsState.getStashedState() != STASH_TYPE_RIGHT) { + mPipBoundsState.setStashed(STASH_TYPE_RIGHT); + } + mMenuController.hideMenu(); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java index 33e80bd80988..5b0ca1837a1c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java @@ -16,6 +16,7 @@ package com.android.wm.shell.pip2.phone; import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE; +import static com.android.wm.shell.pip2.phone.PipTransition.ANIMATING_BOUNDS_CHANGE_DURATION; import android.annotation.Nullable; import android.content.Context; @@ -535,7 +536,8 @@ public class PipResizeGestureHandler implements mWaitingForBoundsChangeTransition = true; // Schedule PiP resize transition, but delay any config updates until very end. - mPipScheduler.scheduleAnimateResizePip(mLastResizeBounds, true /* configAtEnd */); + mPipScheduler.scheduleAnimateResizePip(mLastResizeBounds, + true /* configAtEnd */, PINCH_RESIZE_SNAP_DURATION); break; case PipTransitionState.CHANGING_PIP_BOUNDS: if (!mWaitingForBoundsChangeTransition) break; @@ -550,12 +552,15 @@ public class PipResizeGestureHandler implements PipTransition.PIP_START_TX, SurfaceControl.Transaction.class); SurfaceControl.Transaction finishTx = extra.getParcelable( PipTransition.PIP_FINISH_TX, SurfaceControl.Transaction.class); + final int duration = extra.getInt(ANIMATING_BOUNDS_CHANGE_DURATION, + PipTransition.BOUNDS_CHANGE_JUMPCUT_DURATION); + startTx.setWindowCrop(pipLeash, mPipBoundsState.getBounds().width(), mPipBoundsState.getBounds().height()); PipResizeAnimator animator = new PipResizeAnimator(mContext, pipLeash, startTx, finishTx, mPipBoundsState.getBounds(), mStartBoundsAfterRelease, - mLastResizeBounds, PINCH_RESIZE_SNAP_DURATION, mAngle); + mLastResizeBounds, duration, mAngle); animator.setAnimationEndCallback(() -> { // All motion operations have actually finished, so make bounds cache updates. mUpdateResizeBoundsCallback.accept(mLastResizeBounds); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java index 9c1e321a1273..ac670cf3f828 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java @@ -33,7 +33,7 @@ import androidx.annotation.IntDef; import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.pip.PipBoundsState; import com.android.wm.shell.common.pip.PipUtils; @@ -162,6 +162,18 @@ public class PipScheduler { * @param configAtEnd true if we are delaying config updates until the transition ends. */ public void scheduleAnimateResizePip(Rect toBounds, boolean configAtEnd) { + scheduleAnimateResizePip(toBounds, configAtEnd, + PipTransition.BOUNDS_CHANGE_JUMPCUT_DURATION); + } + + /** + * Animates resizing of the pinned stack given the duration. + * + * @param configAtEnd true if we are delaying config updates until the transition ends. + * @param duration the suggested duration to run the animation; the component responsible + * for running the animator will get this as an extra. + */ + public void scheduleAnimateResizePip(Rect toBounds, boolean configAtEnd, int duration) { if (mPipTransitionState.mPipTaskToken == null || !mPipTransitionState.isInPip()) { return; } @@ -170,7 +182,7 @@ public class PipScheduler { if (configAtEnd) { wct.deferConfigToTransitionEnd(mPipTransitionState.mPipTaskToken); } - mPipTransitionController.startResizeTransition(wct); + mPipTransitionController.startResizeTransition(wct, duration); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java index 56a465a4889a..53b80e8b7542 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java @@ -51,7 +51,7 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityWindowInfo; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.ShellExecutor; @@ -106,9 +106,6 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha private float mStashVelocityThreshold; - // The reference inset bounds, used to determine the dismiss fraction - private final Rect mInsetBounds = new Rect(); - // Used to workaround an issue where the WM rotation happens before we are notified, allowing // us to send stale bounds private int mDeferResizeToNormalBoundsUntilRotation = -1; @@ -206,17 +203,10 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha mMotionHelper, mainExecutor); mTouchState = new PipTouchState(ViewConfiguration.get(context), () -> { - if (mPipBoundsState.isStashed()) { - animateToUnStashedState(); - mPipUiEventLogger.log( - PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_UNSTASHED); - mPipBoundsState.setStashed(STASH_TYPE_NONE); - } else { - mMenuController.showMenuWithPossibleDelay(MENU_STATE_FULL, - mPipBoundsState.getBounds(), true /* allowMenuTimeout */, - willResizeMenu(), - shouldShowResizeHandle()); - } + mMenuController.showMenuWithPossibleDelay(MENU_STATE_FULL, + mPipBoundsState.getBounds(), true /* allowMenuTimeout */, + willResizeMenu(), + shouldShowResizeHandle()); }, menuController::hideMenu, mainExecutor); @@ -438,7 +428,6 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha mPipBoundsState.setNormalMovementBounds(normalMovementBounds); mPipBoundsState.setExpandedMovementBounds(expandedMovementBounds); mDisplayRotation = displayRotation; - mInsetBounds.set(insetBounds); updateMovementBounds(); mMovementBoundsExtraOffsets = extraOffset; @@ -713,15 +702,13 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha } } - private void animateToMaximizedState(Runnable callback) { - Rect maxMovementBounds = new Rect(); + private void animateToMaximizedState() { Rect maxBounds = new Rect(0, 0, mPipBoundsState.getMaxSize().x, mPipBoundsState.getMaxSize().y); - mPipBoundsAlgorithm.getMovementBounds(maxBounds, mInsetBounds, maxMovementBounds, - mIsImeShowing ? mImeHeight : 0); + mSavedSnapFraction = mMotionHelper.animateToExpandedState(maxBounds, - mPipBoundsState.getMovementBounds(), maxMovementBounds, - callback); + getMovementBounds(mPipBoundsState.getBounds()), + getMovementBounds(maxBounds), null /* callback */); } private void animateToNormalSize(Runnable callback) { @@ -729,22 +716,20 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds()); final Size minMenuSize = mMenuController.getEstimatedMinMenuSize(); - final Rect normalBounds = mPipBoundsState.getNormalBounds(); - final Rect destBounds = mPipBoundsAlgorithm.adjustNormalBoundsToFitMenu(normalBounds, - minMenuSize); - Rect restoredMovementBounds = new Rect(); - mPipBoundsAlgorithm.getMovementBounds(destBounds, - mInsetBounds, restoredMovementBounds, mIsImeShowing ? mImeHeight : 0); - mSavedSnapFraction = mMotionHelper.animateToExpandedState(destBounds, - mPipBoundsState.getMovementBounds(), restoredMovementBounds, callback); + final Size defaultSize = mSizeSpecSource.getDefaultSize(mPipBoundsState.getAspectRatio()); + final Rect normalBounds = new Rect(0, 0, defaultSize.getWidth(), defaultSize.getHeight()); + final Rect adjustedNormalBounds = mPipBoundsAlgorithm.adjustNormalBoundsToFitMenu( + normalBounds, minMenuSize); + + mSavedSnapFraction = mMotionHelper.animateToExpandedState(adjustedNormalBounds, + getMovementBounds(mPipBoundsState.getBounds()), + getMovementBounds(adjustedNormalBounds), callback /* callback */); } private void animateToUnexpandedState(Rect restoreBounds) { - Rect restoredMovementBounds = new Rect(); - mPipBoundsAlgorithm.getMovementBounds(restoreBounds, - mInsetBounds, restoredMovementBounds, mIsImeShowing ? mImeHeight : 0); mMotionHelper.animateToUnexpandedState(restoreBounds, mSavedSnapFraction, - restoredMovementBounds, mPipBoundsState.getMovementBounds(), false /* immediate */); + getMovementBounds(restoreBounds), + getMovementBounds(mPipBoundsState.getBounds()), false /* immediate */); mSavedSnapFraction = -1f; } @@ -752,10 +737,13 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha final Rect pipBounds = mPipBoundsState.getBounds(); final boolean onLeftEdge = pipBounds.left < mPipBoundsState.getDisplayBounds().left; final Rect unStashedBounds = new Rect(0, pipBounds.top, 0, pipBounds.bottom); - unStashedBounds.left = onLeftEdge ? mInsetBounds.left - : mInsetBounds.right - pipBounds.width(); - unStashedBounds.right = onLeftEdge ? mInsetBounds.left + pipBounds.width() - : mInsetBounds.right; + + Rect insetBounds = new Rect(); + mPipBoundsAlgorithm.getInsetBounds(insetBounds); + unStashedBounds.left = onLeftEdge ? insetBounds.left + : insetBounds.right - pipBounds.width(); + unStashedBounds.right = onLeftEdge ? insetBounds.left + pipBounds.width() + : insetBounds.right; mMotionHelper.animateToUnStashedBounds(unStashedBounds); } @@ -903,8 +891,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha // Reset the touch state on up before the fling settles mTouchState.reset(); if (mEnableStash && shouldStash(vel, getPossiblyMotionBounds())) { - // mMotionHelper.stashToEdge(vel.x, vel.y, - // this::stashEndAction /* endAction */); + mMotionHelper.stashToEdge(vel.x, vel.y, null /* endAction */); } else { if (mPipBoundsState.isStashed()) { // Reset stashed state if previously stashed @@ -919,10 +906,6 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha && mMenuState != MENU_STATE_FULL) { // If using pinch to zoom, double-tap functions as resizing between max/min size if (mPipResizeGestureHandler.isUsingPinchToZoom()) { - final boolean toExpand = mPipBoundsState.getBounds().width() - < mPipBoundsState.getMaxSize().x - && mPipBoundsState.getBounds().height() - < mPipBoundsState.getMaxSize().y; if (mMenuController.isMenuVisible()) { mMenuController.hideMenu(ANIM_TYPE_NONE, false /* resize */); } @@ -934,7 +917,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha // actually toggle to the size chosen if (nextSize == PipDoubleTapHelper.SIZE_SPEC_MAX) { mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds()); - animateToMaximizedState(null); + animateToMaximizedState(); } else if (nextSize == PipDoubleTapHelper.SIZE_SPEC_DEFAULT) { mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds()); animateToNormalSize(null); @@ -1038,14 +1021,18 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha * resized. */ private void updateMovementBounds() { + Rect insetBounds = new Rect(); + mPipBoundsAlgorithm.getInsetBounds(insetBounds); mPipBoundsAlgorithm.getMovementBounds(mPipBoundsState.getBounds(), - mInsetBounds, mPipBoundsState.getMovementBounds(), mIsImeShowing ? mImeHeight : 0); + insetBounds, mPipBoundsState.getMovementBounds(), mIsImeShowing ? mImeHeight : 0); mMotionHelper.onMovementBoundsChanged(); } private Rect getMovementBounds(Rect curBounds) { Rect movementBounds = new Rect(); - mPipBoundsAlgorithm.getMovementBounds(curBounds, mInsetBounds, + Rect insetBounds = new Rect(); + mPipBoundsAlgorithm.getInsetBounds(insetBounds); + mPipBoundsAlgorithm.getMovementBounds(curBounds, insetBounds, movementBounds, mIsImeShowing ? mImeHeight : 0); return movementBounds; } @@ -1091,6 +1078,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha case PipTransitionState.ENTERED_PIP: onActivityPinned(); mTouchState.setAllowInputEvents(true); + mTouchState.reset(); break; case PipTransitionState.EXITED_PIP: mTouchState.setAllowInputEvents(false); @@ -1101,6 +1089,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha break; case PipTransitionState.CHANGED_PIP_BOUNDS: mTouchState.setAllowInputEvents(true); + mTouchState.reset(); break; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchState.java index d093f1e5ccc1..bb8d4ee9c80f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchState.java @@ -23,7 +23,7 @@ import android.view.VelocityTracker; import android.view.ViewConfiguration; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.protolog.ShellProtoLogGroup; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java index 57dc5f92b2b6..683d30d59b33 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java @@ -71,6 +71,9 @@ public class PipTransition extends PipTransitionController implements static final String PIP_START_TX = "pip_start_tx"; static final String PIP_FINISH_TX = "pip_finish_tx"; static final String PIP_DESTINATION_BOUNDS = "pip_dest_bounds"; + static final String ANIMATING_BOUNDS_CHANGE_DURATION = + "animating_bounds_change_duration"; + static final int BOUNDS_CHANGE_JUMPCUT_DURATION = 0; /** * The fixed start delay in ms when fading out the content overlay from bounds animation. @@ -87,7 +90,7 @@ public class PipTransition extends PipTransitionController implements private final PipTransitionState mPipTransitionState; // - // Transition tokens + // Transition caches // @Nullable @@ -96,6 +99,8 @@ public class PipTransition extends PipTransitionController implements private IBinder mExitViaExpandTransition; @Nullable private IBinder mResizeTransition; + private int mBoundsChangeDuration = BOUNDS_CHANGE_JUMPCUT_DURATION; + // // Internal state and relevant cached info @@ -152,11 +157,12 @@ public class PipTransition extends PipTransitionController implements } @Override - public void startResizeTransition(WindowContainerTransaction wct) { + public void startResizeTransition(WindowContainerTransaction wct, int duration) { if (wct == null) { return; } mResizeTransition = mTransitions.startTransition(TRANSIT_RESIZE_PIP, wct, this); + mBoundsChangeDuration = duration; } @Nullable @@ -272,6 +278,10 @@ public class PipTransition extends PipTransitionController implements extra.putParcelable(PIP_START_TX, startTransaction); extra.putParcelable(PIP_FINISH_TX, finishTransaction); extra.putParcelable(PIP_DESTINATION_BOUNDS, pipChange.getEndAbsBounds()); + if (mBoundsChangeDuration > BOUNDS_CHANGE_JUMPCUT_DURATION) { + extra.putInt(ANIMATING_BOUNDS_CHANGE_DURATION, mBoundsChangeDuration); + mBoundsChangeDuration = BOUNDS_CHANGE_JUMPCUT_DURATION; + } mFinishCallback = finishCallback; mPipTransitionState.setState(PipTransitionState.CHANGING_PIP_BOUNDS, extra); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java index 9d599caf13dd..29272be6e9bd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java @@ -29,6 +29,7 @@ import androidx.annotation.Nullable; import com.android.internal.util.Preconditions; import com.android.wm.shell.shared.annotations.ShellMainThread; +import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -62,6 +63,8 @@ import java.util.List; * and throw an <code>IllegalStateException</code> otherwise.</p> */ public class PipTransitionState { + private static final String TAG = PipTransitionState.class.getSimpleName(); + public static final int UNDEFINED = 0; // State for Launcher animating the swipe PiP to home animation. @@ -190,8 +193,9 @@ public class PipTransitionState { "No extra bundle for " + stateToString(state) + " state."); } if (mState != state) { - dispatchPipTransitionStateChanged(mState, state, extra); + final int prevState = mState; mState = state; + dispatchPipTransitionStateChanged(prevState, mState, extra); } } @@ -319,4 +323,11 @@ public class PipTransitionState { return String.format("PipTransitionState(mState=%s, mInSwipePipToHomeTransition=%b)", stateToString(mState), mInSwipePipToHomeTransition); } + + /** Dumps internal state. */ + public void dump(PrintWriter pw, String prefix) { + final String innerPrefix = prefix + " "; + pw.println(prefix + TAG); + pw.println(innerPrefix + "mState=" + stateToString(mState)); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index 03c8cf8cc795..9539a456502f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -37,13 +37,14 @@ import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; import android.view.IRecentsAnimationRunner; +import android.window.WindowContainerToken; import androidx.annotation.BinderThread; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.ExternalInterfaceBinder; import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; @@ -52,9 +53,9 @@ import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.protolog.ShellProtoLogGroup; -import com.android.wm.shell.shared.DesktopModeStatus; import com.android.wm.shell.shared.annotations.ExternalThread; import com.android.wm.shell.shared.annotations.ShellMainThread; +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; @@ -65,9 +66,11 @@ import com.android.wm.shell.util.SplitBounds; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.concurrent.Executor; import java.util.function.Consumer; @@ -394,6 +397,7 @@ public class RecentTasksController implements TaskStackListenerCallback, } ArrayList<ActivityManager.RecentTaskInfo> freeformTasks = new ArrayList<>(); + Set<Integer> minimizedFreeformTasks = new HashSet<>(); int mostRecentFreeformTaskIndex = Integer.MAX_VALUE; @@ -409,15 +413,14 @@ public class RecentTasksController implements TaskStackListenerCallback, if (DesktopModeStatus.canEnterDesktopMode(mContext) && mDesktopModeTaskRepository.isPresent() && mDesktopModeTaskRepository.get().isActiveTask(taskInfo.taskId)) { - if (mDesktopModeTaskRepository.get().isMinimizedTask(taskInfo.taskId)) { - // Minimized freeform tasks should not be shown at all. - continue; - } // Freeform tasks will be added as a separate entry if (mostRecentFreeformTaskIndex == Integer.MAX_VALUE) { mostRecentFreeformTaskIndex = recentTasks.size(); } freeformTasks.add(taskInfo); + if (mDesktopModeTaskRepository.get().isMinimizedTask(taskInfo.taskId)) { + minimizedFreeformTasks.add(taskInfo.taskId); + } continue; } @@ -435,8 +438,10 @@ public class RecentTasksController implements TaskStackListenerCallback, // Add a special entry for freeform tasks if (!freeformTasks.isEmpty()) { - recentTasks.add(mostRecentFreeformTaskIndex, GroupedRecentTaskInfo.forFreeformTasks( - freeformTasks.toArray(new ActivityManager.RecentTaskInfo[0]))); + recentTasks.add(mostRecentFreeformTaskIndex, + GroupedRecentTaskInfo.forFreeformTasks( + freeformTasks.toArray(new ActivityManager.RecentTaskInfo[0]), + minimizedFreeformTasks)); } return recentTasks; @@ -453,11 +458,31 @@ public class RecentTasksController implements TaskStackListenerCallback, } /** - * Find the background task that match the given component. + * Returns the top running leaf task ignoring {@param ignoreTaskToken} if it is specified. + * NOTE: This path currently makes assumptions that ignoreTaskToken is for the top task. + */ + @Nullable + public ActivityManager.RunningTaskInfo getTopRunningTask( + @Nullable WindowContainerToken ignoreTaskToken) { + List<ActivityManager.RunningTaskInfo> tasks = mActivityTaskManager.getTasks(2, + false /* filterOnlyVisibleRecents */); + for (int i = tasks.size() - 1; i >= 0; i--) { + final ActivityManager.RunningTaskInfo task = tasks.get(i); + if (task.token.equals(ignoreTaskToken)) { + continue; + } + return task; + } + return null; + } + + /** + * Find the background task that match the given component. Ignores tasks match + * {@param ignoreTaskToken} if it is non-null. */ @Nullable public ActivityManager.RecentTaskInfo findTaskInBackground(ComponentName componentName, - int userId) { + int userId, @Nullable WindowContainerToken ignoreTaskToken) { if (componentName == null) { return null; } @@ -469,6 +494,9 @@ public class RecentTasksController implements TaskStackListenerCallback, if (task.isVisible) { continue; } + if (task.token.equals(ignoreTaskToken)) { + continue; + } if (componentName.equals(task.baseIntent.getComponent()) && userId == task.userId) { return task; } @@ -636,7 +664,7 @@ public class RecentTasksController implements TaskStackListenerCallback, @Override public ActivityManager.RunningTaskInfo[] getRunningTasks(int maxNum) { final ActivityManager.RunningTaskInfo[][] tasks = - new ActivityManager.RunningTaskInfo[][] {null}; + new ActivityManager.RunningTaskInfo[][]{null}; executeRemoteCallWithTaskPermission(mController, "getRunningTasks", (controller) -> tasks[0] = ActivityTaskManager.getInstance().getTasks(maxNum) .toArray(new ActivityManager.RunningTaskInfo[0]), diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index 3a266d9bb3ef..234b4d0f86db 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 @@ -63,7 +63,7 @@ import androidx.annotation.NonNull; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.IResultReceiver; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.protolog.ShellProtoLogGroup; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt index 7c5f10a5bcca..8ee72b499e5a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt @@ -76,21 +76,40 @@ class TaskStackTransitionObserver( continue } + // Filter out changes that we care about if (change.mode == WindowManager.TRANSIT_OPEN) { change.taskInfo?.let { taskInfoList.add(it) } transitionTypeList.add(change.mode) } } - transitionToTransitionChanges.put( - transition, - TransitionChanges(taskInfoList, transitionTypeList) - ) + // Only add the transition to map if it has a change we care about + if (taskInfoList.isNotEmpty()) { + transitionToTransitionChanges.put( + transition, + TransitionChanges(taskInfoList, transitionTypeList) + ) + } } } override fun onTransitionStarting(transition: IBinder) {} - override fun onTransitionMerged(merged: IBinder, playing: IBinder) {} + override fun onTransitionMerged(merged: IBinder, playing: IBinder) { + val mergedTransitionChanges = + transitionToTransitionChanges.get(merged) + ?: + // We are adding changes of the merged transition to changes of the playing + // transition so if there is no changes nothing to do. + return + + transitionToTransitionChanges.remove(merged) + val playingTransitionChanges = transitionToTransitionChanges.get(playing) + if (playingTransitionChanges != null) { + playingTransitionChanges.merge(mergedTransitionChanges) + } else { + transitionToTransitionChanges.put(playing, mergedTransitionChanges) + } + } override fun onTransitionFinished(transition: IBinder, aborted: Boolean) { val taskInfoList = @@ -138,6 +157,11 @@ class TaskStackTransitionObserver( private data class TransitionChanges( val taskInfoList: MutableList<RunningTaskInfo> = ArrayList(), - val transitionTypeList: MutableList<Int> = ArrayList() - ) + val transitionTypeList: MutableList<Int> = ArrayList(), + ) { + fun merge(transitionChanges: TransitionChanges) { + taskInfoList.addAll(transitionChanges.taskInfoList) + transitionTypeList.addAll(transitionChanges.transitionTypeList) + } + } } 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 64e26dbd70be..1cbb8bbe5f75 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 @@ -23,7 +23,7 @@ import android.view.SurfaceSession; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.SyncTransactionQueue; 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 f5fbae55960a..27fd309c09b0 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 @@ -24,7 +24,7 @@ import android.view.SurfaceSession; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.SyncTransactionQueue; 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 dd219d32bbaa..e659151fee7f 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 @@ -64,6 +64,7 @@ import android.view.SurfaceSession; import android.view.WindowManager; import android.widget.Toast; import android.window.RemoteTransition; +import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import androidx.annotation.BinderThread; @@ -73,7 +74,7 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.InstanceId; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.R; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; @@ -526,7 +527,15 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, mStageCoordinator.requestEnterSplitSelect(taskInfo, wct, splitPosition, taskBounds); } - public void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) { + /** + * Starts an existing task into split. + * TODO(b/351900580): We should remove this path and use StageCoordinator#startTask() instead + * @param hideTaskToken is not supported. + */ + public void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options, + @Nullable WindowContainerToken hideTaskToken) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, + "Legacy startTask does not support hide task token"); final int[] result = new int[1]; IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() { @Override @@ -584,8 +593,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, if (options == null) options = new Bundle(); final ActivityOptions activityOptions = ActivityOptions.fromBundle(options); - if (samePackage(packageName, getPackageName(reverseSplitPosition(position)), - user.getIdentifier(), getUserId(reverseSplitPosition(position)))) { + if (samePackage(packageName, getPackageName(reverseSplitPosition(position), null), + user.getIdentifier(), getUserId(reverseSplitPosition(position), null))) { if (mMultiInstanceHelpher.supportsMultiInstanceSplit( getShortcutComponent(packageName, shortcutId, user, mLauncherApps))) { activityOptions.setApplyMultipleTaskFlagForShortcut(true); @@ -676,10 +685,11 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, * See {@link #startIntent(PendingIntent, int, Intent, int, Bundle)} * @param instanceId to be used by {@link SplitscreenEventLogger} */ - public void startIntent(PendingIntent intent, int userId, @Nullable Intent fillInIntent, - @SplitPosition int position, @Nullable Bundle options, @NonNull InstanceId instanceId) { + public void startIntentWithInstanceId(PendingIntent intent, int userId, + @Nullable Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options, + @NonNull InstanceId instanceId) { mStageCoordinator.onRequestToSplit(instanceId, ENTER_REASON_LAUNCHER); - startIntent(intent, userId, fillInIntent, position, options); + startIntent(intent, userId, fillInIntent, position, options, null /* hideTaskToken */); } private void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, int userId1, @@ -825,9 +835,15 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, instanceId); } + /** + * Starts the given intent into split. + * @param hideTaskToken If non-null, a task matching this token will be moved to back in the + * same window container transaction as the starting of the intent. + */ @Override public void startIntent(PendingIntent intent, int userId1, @Nullable Intent fillInIntent, - @SplitPosition int position, @Nullable Bundle options) { + @SplitPosition int position, @Nullable Bundle options, + @Nullable WindowContainerToken hideTaskToken) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "startIntent(): intent=%s user=%d fillInIntent=%s position=%d", intent, userId1, fillInIntent, position); @@ -838,23 +854,24 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION); final String packageName1 = SplitScreenUtils.getPackageName(intent); - final String packageName2 = getPackageName(reverseSplitPosition(position)); - final int userId2 = getUserId(reverseSplitPosition(position)); + final String packageName2 = getPackageName(reverseSplitPosition(position), hideTaskToken); + final int userId2 = getUserId(reverseSplitPosition(position), hideTaskToken); final ComponentName component = intent.getIntent().getComponent(); // To prevent accumulating large number of instances in the background, reuse task // in the background. If we don't explicitly reuse, new may be created even if the app // isn't multi-instance because WM won't automatically remove/reuse the previous instance final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional - .map(recentTasks -> recentTasks.findTaskInBackground(component, userId1)) + .map(recentTasks -> recentTasks.findTaskInBackground(component, userId1, + hideTaskToken)) .orElse(null); if (taskInfo != null) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Found suitable background task=%s", taskInfo); if (ENABLE_SHELL_TRANSITIONS) { - mStageCoordinator.startTask(taskInfo.taskId, position, options); + mStageCoordinator.startTask(taskInfo.taskId, position, options, hideTaskToken); } else { - startTask(taskInfo.taskId, position, options); + startTask(taskInfo.taskId, position, options, hideTaskToken); } ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Start task in background"); return; @@ -879,19 +896,23 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } } - mStageCoordinator.startIntent(intent, fillInIntent, position, options); + mStageCoordinator.startIntent(intent, fillInIntent, position, options, hideTaskToken); } - /** Retrieve package name of a specific split position if split screen is activated, otherwise - * returns the package name of the top running task. */ + /** + * Retrieve package name of a specific split position if split screen is activated, otherwise + * returns the package name of the top running task. + * TODO(b/351900580): Merge this with getUserId() so we don't make multiple binder calls + */ @Nullable - private String getPackageName(@SplitPosition int position) { + private String getPackageName(@SplitPosition int position, + @Nullable WindowContainerToken ignoreTaskToken) { ActivityManager.RunningTaskInfo taskInfo; if (isSplitScreenVisible()) { taskInfo = getTaskInfo(position); } else { taskInfo = mRecentTasksOptional - .map(recentTasks -> recentTasks.getTopRunningTask()) + .map(recentTasks -> recentTasks.getTopRunningTask(ignoreTaskToken)) .orElse(null); if (!isValidToSplit(taskInfo)) { return null; @@ -901,15 +922,19 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, return taskInfo != null ? SplitScreenUtils.getPackageName(taskInfo.baseIntent) : null; } - /** Retrieve user id of a specific split position if split screen is activated, otherwise - * returns the user id of the top running task. */ - private int getUserId(@SplitPosition int position) { + /** + * Retrieve user id of a specific split position if split screen is activated, otherwise + * returns the user id of the top running task. + * TODO: Merge this with getPackageName() so we don't make multiple binder calls + */ + private int getUserId(@SplitPosition int position, + @Nullable WindowContainerToken ignoreTaskToken) { ActivityManager.RunningTaskInfo taskInfo; if (isSplitScreenVisible()) { taskInfo = getTaskInfo(position); } else { taskInfo = mRecentTasksOptional - .map(recentTasks -> recentTasks.getTopRunningTask()) + .map(recentTasks -> recentTasks.getTopRunningTask(ignoreTaskToken)) .orElse(null); if (!isValidToSplit(taskInfo)) { return -1; @@ -1290,7 +1315,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, @Override public void startTask(int taskId, int position, @Nullable Bundle options) { executeRemoteCallWithTaskPermission(mController, "startTask", - (controller) -> controller.startTask(taskId, position, options)); + (controller) -> controller.startTask(taskId, position, options, + null /* hideTaskToken */)); } @Override @@ -1402,8 +1428,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, public void startIntent(PendingIntent intent, int userId, Intent fillInIntent, int position, @Nullable Bundle options, InstanceId instanceId) { executeRemoteCallWithTaskPermission(mController, "startIntent", - (controller) -> controller.startIntent(intent, userId, fillInIntent, position, - options, instanceId)); + (controller) -> controller.startIntentWithInstanceId(intent, userId, + fillInIntent, position, options, instanceId)); } @Override 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 0541a0287179..b3dab8527617 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 @@ -46,7 +46,7 @@ import android.window.TransitionInfo; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.split.SplitDecorManager; import com.android.wm.shell.protolog.ShellProtoLogGroup; 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 b6a18e537600..41042344fd3a 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 @@ -119,7 +119,7 @@ import android.window.WindowContainerTransaction; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.InstanceId; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.ArrayUtils; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.R; @@ -592,12 +592,21 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } - /** Use this method to launch an existing Task via a taskId */ - void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) { + /** + * Use this method to launch an existing Task via a taskId. + * @param hideTaskToken If non-null, a task matching this token will be moved to back in the + * same window container transaction as the starting of the intent. + */ + void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options, + @Nullable WindowContainerToken hideTaskToken) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startTask: task=%d position=%d", taskId, position); mSplitRequest = new SplitRequest(taskId, position); final WindowContainerTransaction wct = new WindowContainerTransaction(); options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */); + if (hideTaskToken != null) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Reordering hide-task to bottom"); + wct.reorder(hideTaskToken, false /* onTop */); + } wct.startTask(taskId, options); // If this should be mixed, send the task to avoid split handle transition directly. if (mMixedHandler != null && mMixedHandler.isTaskInPip(taskId, mTaskOrganizer)) { @@ -623,9 +632,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, extraTransitType, !mIsDropEntering); } - /** Launches an activity into split. */ + /** + * Launches an activity into split. + * @param hideTaskToken If non-null, a task matching this token will be moved to back in the + * same window container transaction as the starting of the intent. + */ void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position, - @Nullable Bundle options) { + @Nullable Bundle options, @Nullable WindowContainerToken hideTaskToken) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startIntent: intent=%s position=%d", intent.getIntent(), position); mSplitRequest = new SplitRequest(intent.getIntent(), position); @@ -636,6 +649,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, final WindowContainerTransaction wct = new WindowContainerTransaction(); options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */); + if (hideTaskToken != null) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Reordering hide-task to bottom"); + wct.reorder(hideTaskToken, false /* onTop */); + } wct.sendPendingIntent(intent, fillInIntent, options); // If this should be mixed, just send the intent to avoid split handle transition directly. @@ -2649,7 +2666,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @Nullable TransitionRequestInfo request) { final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask(); if (triggerTask == null) { - if (isSplitActive()) { + if (isSplitScreenVisible()) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "handleRequest: transition=%d display rotation", request.getDebugId()); // Check if the display is rotating. 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 0f3d6cade95a..1076eca60369 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 @@ -45,7 +45,7 @@ import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.ArrayUtils; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.ShellTaskOrganizer; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuController.java index 1d8a8d506c5c..e0f63940663a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuController.java @@ -36,7 +36,7 @@ import android.view.LayoutInflater; import android.view.WindowManager; import android.view.WindowManagerGlobal; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.split.SplitScreenConstants; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java index 6325c686a682..ea8c0eb30061 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java @@ -390,7 +390,8 @@ public class SplashScreenExitAnimationUtils { SurfaceControl firstWindowSurface, ViewGroup splashScreenView, TransactionPool transactionPool, Rect firstWindowFrame, int mainWindowShiftLength, float roundedCornerRadius) { - mFromYDelta = fromYDelta - Math.max(firstWindowFrame.top, roundedCornerRadius); + mFromYDelta = firstWindowFrame.top + - Math.max(firstWindowFrame.top, roundedCornerRadius); mToYDelta = toYDelta; mOccludeHoleView = occludeHoleView; mApplier = new SyncRtSurfaceTransactionApplier(occludeHoleView); 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 2b12a22f907d..90eb22f369df 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 @@ -74,7 +74,7 @@ import com.android.internal.graphics.palette.Palette; import com.android.internal.graphics.palette.Quantizer; import com.android.internal.graphics.palette.VariationalKMeansQuantizer; import com.android.internal.policy.PhoneWindow; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.launcher3.icons.BaseIconFactory; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.common.TransactionPool; 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 e552e6cdacf3..08211ab5df9c 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 @@ -53,7 +53,7 @@ import android.window.SplashScreenView; import android.window.StartingWindowInfo; import android.window.StartingWindowRemovalInfo; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.ContrastColorUtil; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.protolog.ShellProtoLogGroup; 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 3353c7bd81c2..97a695f34cf7 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 @@ -43,7 +43,7 @@ import android.window.StartingWindowRemovalInfo; import android.window.TaskSnapshot; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TransactionPool; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java index 66b3553bea09..6e084d6e05a4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java @@ -21,8 +21,6 @@ import static android.graphics.Color.WHITE; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; -import static com.android.window.flags.Flags.windowSessionRelayoutInfo; - import android.annotation.BinderThread; import android.annotation.NonNull; import android.annotation.Nullable; @@ -30,7 +28,6 @@ import android.app.ActivityManager; import android.app.ActivityManager.TaskDescription; import android.graphics.Paint; import android.graphics.Rect; -import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.os.Trace; @@ -51,7 +48,7 @@ import android.window.SnapshotDrawerUtils; import android.window.StartingWindowInfo; import android.window.TaskSnapshot; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.view.BaseIWindow; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.protolog.ShellProtoLogGroup; @@ -139,16 +136,10 @@ public class TaskSnapshotWindow { } try { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#relayout"); - if (windowSessionRelayoutInfo()) { - final WindowRelayoutResult outRelayoutResult = new WindowRelayoutResult(tmpFrames, - tmpMergedConfiguration, surfaceControl, tmpInsetsState, tmpControls); - session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0, 0, 0, - outRelayoutResult); - } else { - session.relayoutLegacy(window, layoutParams, -1, -1, View.VISIBLE, 0, 0, 0, - tmpFrames, tmpMergedConfiguration, surfaceControl, tmpInsetsState, - tmpControls, new Bundle()); - } + final WindowRelayoutResult outRelayoutResult = new WindowRelayoutResult(tmpFrames, + tmpMergedConfiguration, surfaceControl, tmpInsetsState, tmpControls); + session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0, 0, 0, + outRelayoutResult); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } catch (RemoteException e) { snapshotSurface.clearWindowSynced(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java index 72fc8686f648..2036d9c13f0c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java @@ -35,7 +35,7 @@ import static android.window.StartingWindowInfo.TYPE_PARAMETER_WINDOWLESS; import android.window.StartingWindowInfo; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.startingsurface.StartingWindowTypeAlgorithm; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java index 2e6ddc363906..aa9f15c37531 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java @@ -18,7 +18,7 @@ package com.android.wm.shell.sysui; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_INIT; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import java.io.PrintWriter; import java.util.Arrays; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java index 5ced1fb41a41..0202b6cf3eab 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java @@ -40,7 +40,7 @@ import android.view.SurfaceControlRegistry; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener; import com.android.wm.shell.common.ExternalInterfaceBinder; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java index 2e2f569a52b8..dd4595a70211 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java @@ -25,8 +25,9 @@ import android.view.SurfaceControl; import androidx.annotation.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.protolog.ShellProtoLogGroup; import java.util.ArrayList; @@ -75,6 +76,7 @@ public class ShellInit { */ @VisibleForTesting public void init() { + ProtoLog.registerGroups(ShellProtoLogGroup.values()); ProtoLog.v(WM_SHELL_INIT, "Initializing Shell Components: %d", mInitCallbacks.size()); SurfaceControl.setDebugUsageAfterRelease(true); // Init in order of registration 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 a126cbe41b00..9750d3ec99f5 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 @@ -535,7 +535,8 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { WindowContainerTransaction wct = new WindowContainerTransaction(); if (mCaptionInsets != null) { wct.addInsetsSource(mTaskToken, mCaptionInsetsOwner, 0, - WindowInsets.Type.captionBar(), mCaptionInsets, null /* boundingRects */); + WindowInsets.Type.captionBar(), mCaptionInsets, null /* boundingRects */, + 0 /* flags */); } else { wct.removeInsetsSource(mTaskToken, mCaptionInsetsOwner, 0, WindowInsets.Type.captionBar()); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java index b03daaafd70c..35427b93acea 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/CounterRotatorHelper.java @@ -94,6 +94,11 @@ public class CounterRotatorHelper { return rotatedBounds; } + /** Returns true if the change is put on a surface in previous rotation. */ + public boolean isRotated(@NonNull TransitionInfo.Change change) { + return mLastRotationDelta != 0 && mRotatorMap.containsKey(change.getParent()); + } + /** * Removes the counter rotation surface in the finish transaction. No need to reparent the * children as the finish transaction should have already taken care of that. 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 8ee1efa90a30..4f4b8097cfac 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 @@ -39,7 +39,7 @@ import android.window.TransitionRequestInfo; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.activityembedding.ActivityEmbeddingController; import com.android.wm.shell.common.split.SplitScreenUtils; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java index c33fb80fdefc..c8921d256d7f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java @@ -28,7 +28,7 @@ import android.os.IBinder; import android.view.SurfaceControl; import android.window.TransitionInfo; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.activityembedding.ActivityEmbeddingController; import com.android.wm.shell.keyguard.KeyguardTransitionHandler; import com.android.wm.shell.pip.PipTransitionController; 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 018c9044e2f7..f33a57399c22 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 @@ -19,6 +19,7 @@ package com.android.wm.shell.transition; import static android.app.ActivityOptions.ANIM_CLIP_REVEAL; import static android.app.ActivityOptions.ANIM_CUSTOM; import static android.app.ActivityOptions.ANIM_NONE; +import static android.app.ActivityOptions.ANIM_FROM_STYLE; import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS; import static android.app.ActivityOptions.ANIM_SCALE_UP; import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION; @@ -53,6 +54,7 @@ import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER; import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; import static android.window.TransitionInfo.FLAG_TRANSLUCENT; +import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CHANGE; import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CLOSE; import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_CLOSE; import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_OPEN; @@ -102,7 +104,7 @@ import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.ScreenDecorationsUtils; import com.android.internal.policy.TransitionAnimation; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.window.flags.Flags; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.common.DisplayController; @@ -517,7 +519,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { animRelOffset.y = Math.max(animRelOffset.y, change.getEndRelOffset().y); } - if (change.getActivityComponent() != null && !isActivityLevel) { + if (change.getActivityComponent() != null && !isActivityLevel + && !mRotator.isRotated(change)) { // At this point, this is an independent activity change in a non-activity // transition. This means that an activity transition got erroneously combined // with another ongoing transition. This then means that the animation root may @@ -943,12 +946,15 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } private static int getWallpaperTransitType(TransitionInfo info) { + boolean hasWallpaper = false; boolean hasOpenWallpaper = false; boolean hasCloseWallpaper = false; for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); - if ((change.getFlags() & FLAG_SHOW_WALLPAPER) != 0) { + if ((change.getFlags() & FLAG_SHOW_WALLPAPER) != 0 + || (change.getFlags() & FLAG_IS_WALLPAPER) != 0) { + hasWallpaper = true; if (TransitionUtil.isOpeningType(change.getMode())) { hasOpenWallpaper = true; } else if (TransitionUtil.isClosingType(change.getMode())) { @@ -964,6 +970,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { return WALLPAPER_TRANSITION_OPEN; } else if (hasCloseWallpaper) { return WALLPAPER_TRANSITION_CLOSE; + } else if (hasWallpaper) { + return WALLPAPER_TRANSITION_CHANGE; } else { return WALLPAPER_TRANSITION_NONE; } @@ -978,7 +986,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { final int animType = options.getType(); return animType == ANIM_CUSTOM || animType == ANIM_SCALE_UP || animType == ANIM_THUMBNAIL_SCALE_UP || animType == ANIM_THUMBNAIL_SCALE_DOWN - || animType == ANIM_CLIP_REVEAL || animType == ANIM_OPEN_CROSS_PROFILE_APPS; + || animType == ANIM_CLIP_REVEAL || animType == ANIM_OPEN_CROSS_PROFILE_APPS + || animType == ANIM_FROM_STYLE; } private static void applyTransformation(long time, SurfaceControl.Transaction t, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java index 89b0e25b306b..978b8da2eb6d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java @@ -28,7 +28,7 @@ import android.view.SurfaceControl; import android.view.WindowManager; import android.window.IWindowContainerTransactionCallback; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; /** * Utilities and interfaces for transition-like usage on top of the legacy app-transition and diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java index e8b01b5880fb..8cc7f212af25 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java @@ -35,7 +35,7 @@ import android.annotation.Nullable; import android.view.SurfaceControl; import android.window.TransitionInfo; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.keyguard.KeyguardTransitionHandler; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.protolog.ShellProtoLogGroup; @@ -184,7 +184,8 @@ public class MixedTransitionHelper { for (int i = info.getChanges().size() - 1; i >= 0; --i) { TransitionInfo.Change change = info.getChanges().get(i); - if (change == pipChange || !isOpeningMode(change.getMode())) { + if (change == pipChange || !isOpeningMode(change.getMode()) || + change.getTaskInfo() == null) { // Ignore the change/task that's going into Pip or not opening continue; } 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 69c41675e989..c5dc668582bc 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 @@ -30,7 +30,7 @@ import android.window.TransitionRequestInfo; import android.window.WindowAnimationState; import android.window.WindowContainerTransaction; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.protolog.ShellProtoLogGroup; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java index 9fc6702562bb..391c5fe3473c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java @@ -30,7 +30,7 @@ import android.view.SurfaceControl; import android.window.TransitionInfo; import android.window.WindowContainerTransaction; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.keyguard.KeyguardTransitionHandler; import com.android.wm.shell.pip.PipTransitionController; 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 d6860464d055..6013a1ea1d9d 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 @@ -39,7 +39,7 @@ import android.window.WindowContainerTransaction; import androidx.annotation.BinderThread; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.shared.TransitionUtil; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java index 2047b5a88604..a5f071af6973 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java @@ -54,7 +54,7 @@ import android.window.TransitionInfo; import com.android.internal.R; import com.android.internal.policy.TransitionAnimation; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.window.flags.Flags; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.shared.TransitionUtil; 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 f257e207673d..5b4208a10907 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 @@ -28,12 +28,17 @@ 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.view.WindowManager.transitTypeToString; +import static android.window.TransitionInfo.FLAGS_IS_NON_APP_WINDOW; import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED; +import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; 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_NO_ANIMATION; import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; +import static com.android.window.flags.Flags.ensureWallpaperInTransitions; import static com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary; import static com.android.wm.shell.shared.TransitionUtil.isClosingType; import static com.android.wm.shell.shared.TransitionUtil.isOpeningType; @@ -72,7 +77,8 @@ import androidx.annotation.BinderThread; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; +import com.android.window.flags.Flags; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; @@ -512,12 +518,17 @@ public class Transitions implements RemoteCallable<Transitions>, boolean isOpening = isOpeningType(info.getType()); for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); - if (change.hasFlags(TransitionInfo.FLAGS_IS_NON_APP_WINDOW)) { + if (change.hasFlags(FLAGS_IS_NON_APP_WINDOW & ~FLAG_IS_WALLPAPER)) { // Currently system windows are controlled by WindowState, so don't change their // surfaces. Otherwise their surfaces could be hidden or cropped unexpectedly. - // This includes Wallpaper (always z-ordered at bottom) and IME (associated with - // app), because there may not be a transition associated with their visibility - // changes, and currently they don't need transition animation. + // This includes IME (associated with app), because there may not be a transition + // associated with their visibility changes, and currently they don't need a + // transition animation. + continue; + } + if (change.hasFlags(FLAG_IS_WALLPAPER) && !ensureWallpaperInTransitions()) { + // Wallpaper is always z-ordered at bottom, and historically is not animated by + // transition handlers. continue; } final SurfaceControl leash = change.getLeash(); @@ -570,6 +581,14 @@ public class Transitions implements RemoteCallable<Transitions>, final boolean isOpening = isOpeningType(transitType); final boolean isClosing = isClosingType(transitType); final int mode = change.getMode(); + // Ensure wallpapers stay in the back + if (change.hasFlags(FLAG_IS_WALLPAPER) && Flags.ensureWallpaperInTransitions()) { + if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) { + return -zSplitLine + numChanges - i; + } else { + return -zSplitLine - i; + } + } // Put all the OPEN/SHOW on top if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) { if (isOpening) { @@ -788,14 +807,16 @@ public class Transitions implements RemoteCallable<Transitions>, final int changeSize = info.getChanges().size(); boolean taskChange = false; boolean transferStartingWindow = false; - int noAnimationBehindStartingWindow = 0; + int animBehindStartingWindow = 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.hasAllFlags(FLAG_IS_BEHIND_STARTING_WINDOW | FLAG_NO_ANIMATION) + || change.hasAllFlags( + FLAG_IS_BEHIND_STARTING_WINDOW | FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) { + animBehindStartingWindow++; } if (!change.hasFlags(FLAG_IS_OCCLUDED)) { allOccluded = false; @@ -813,11 +834,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 - // 1. neither are tasks, and + // transitions with changes where + // 1. none are tasks, and // 2. one is a starting-window recipient, or all change is behind starting window. - if (!taskChange && (transferStartingWindow || noAnimationBehindStartingWindow == changeSize) - && changeSize == 2 + if (!taskChange && (transferStartingWindow || animBehindStartingWindow == changeSize) + && changeSize >= 1 // 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) @@ -1186,7 +1207,7 @@ public class Transitions implements RemoteCallable<Transitions>, public IBinder startTransition(@WindowManager.TransitionType int type, @NonNull WindowContainerTransaction wct, @Nullable TransitionHandler handler) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Directly starting a new transition " - + "type=%d wct=%s handler=%s", type, wct, handler); + + "type=%s wct=%s handler=%s", transitTypeToString(type), wct, handler); final ActiveTransition active = new ActiveTransition(mOrganizer.startNewTransition(type, wct)); active.mHandler = handler; 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 7c2ba455c0c9..88bfebf9331e 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 @@ -33,7 +33,7 @@ import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.shared.TransitionUtil; import com.android.wm.shell.sysui.ShellInit; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java index c045cebdf4e0..a2d2b9aff597 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java @@ -27,6 +27,7 @@ import androidx.annotation.Nullable; import java.util.Arrays; import java.util.List; +import java.util.Set; /** * Simple container for recent tasks. May contain either a single or pair of tasks. @@ -50,6 +51,9 @@ public class GroupedRecentTaskInfo implements Parcelable { private final SplitBounds mSplitBounds; @GroupType private final int mType; + // TODO(b/348332802): move isMinimized inside each Task object instead once we have a + // replacement for RecentTaskInfo + private final int[] mMinimizedTaskIds; /** * Create new for a single task @@ -57,7 +61,7 @@ public class GroupedRecentTaskInfo implements Parcelable { public static GroupedRecentTaskInfo forSingleTask( @NonNull ActivityManager.RecentTaskInfo task) { return new GroupedRecentTaskInfo(new ActivityManager.RecentTaskInfo[]{task}, null, - TYPE_SINGLE); + TYPE_SINGLE, null /* minimizedFreeformTasks */); } /** @@ -66,28 +70,51 @@ public class GroupedRecentTaskInfo implements Parcelable { public static GroupedRecentTaskInfo forSplitTasks(@NonNull ActivityManager.RecentTaskInfo task1, @NonNull ActivityManager.RecentTaskInfo task2, @Nullable SplitBounds splitBounds) { return new GroupedRecentTaskInfo(new ActivityManager.RecentTaskInfo[]{task1, task2}, - splitBounds, TYPE_SPLIT); + splitBounds, TYPE_SPLIT, null /* minimizedFreeformTasks */); } /** * Create new for a group of freeform tasks */ public static GroupedRecentTaskInfo forFreeformTasks( - @NonNull ActivityManager.RecentTaskInfo... tasks) { - return new GroupedRecentTaskInfo(tasks, null, TYPE_FREEFORM); + @NonNull ActivityManager.RecentTaskInfo[] tasks, + @NonNull Set<Integer> minimizedFreeformTasks) { + return new GroupedRecentTaskInfo( + tasks, + null /* splitBounds */, + TYPE_FREEFORM, + minimizedFreeformTasks.stream().mapToInt(i -> i).toArray()); } - private GroupedRecentTaskInfo(@NonNull ActivityManager.RecentTaskInfo[] tasks, - @Nullable SplitBounds splitBounds, @GroupType int type) { + private GroupedRecentTaskInfo( + @NonNull ActivityManager.RecentTaskInfo[] tasks, + @Nullable SplitBounds splitBounds, + @GroupType int type, + @Nullable int[] minimizedFreeformTaskIds) { mTasks = tasks; mSplitBounds = splitBounds; mType = type; + mMinimizedTaskIds = minimizedFreeformTaskIds; + ensureAllMinimizedIdsPresent(tasks, minimizedFreeformTaskIds); + } + + private static void ensureAllMinimizedIdsPresent( + @NonNull ActivityManager.RecentTaskInfo[] tasks, + @Nullable int[] minimizedFreeformTaskIds) { + if (minimizedFreeformTaskIds == null) { + return; + } + if (!Arrays.stream(minimizedFreeformTaskIds).allMatch( + taskId -> Arrays.stream(tasks).anyMatch(task -> task.taskId == taskId))) { + throw new IllegalArgumentException("Minimized task IDs contain non-existent Task ID."); + } } GroupedRecentTaskInfo(Parcel parcel) { mTasks = parcel.createTypedArray(ActivityManager.RecentTaskInfo.CREATOR); mSplitBounds = parcel.readTypedObject(SplitBounds.CREATOR); mType = parcel.readInt(); + mMinimizedTaskIds = parcel.createIntArray(); } /** @@ -135,6 +162,10 @@ public class GroupedRecentTaskInfo implements Parcelable { return mType; } + public int[] getMinimizedTaskIds() { + return mMinimizedTaskIds; + } + @Override public String toString() { StringBuilder taskString = new StringBuilder(); @@ -161,6 +192,8 @@ public class GroupedRecentTaskInfo implements Parcelable { taskString.append("TYPE_FREEFORM"); break; } + taskString.append(", Minimized Task IDs: "); + taskString.append(Arrays.toString(mMinimizedTaskIds)); return taskString.toString(); } @@ -181,6 +214,7 @@ public class GroupedRecentTaskInfo implements Parcelable { parcel.writeTypedArray(mTasks, flags); parcel.writeTypedObject(mSplitBounds, flags); parcel.writeInt(mType); + parcel.writeIntArray(mMinimizedTaskIds); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt deleted file mode 100644 index 564e716c7378..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt +++ /dev/null @@ -1,74 +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.util - -import android.util.Log -import com.android.internal.protolog.common.IProtoLogGroup -import com.android.internal.protolog.common.ProtoLog - -/** - * Log messages using an API similar to [com.android.internal.protolog.common.ProtoLog]. Useful for - * logging from Kotlin classes as ProtoLog does not have support for Kotlin. - * - * All messages are logged to logcat if logging is enabled for that [IProtoLogGroup]. - */ -// TODO(b/168581922): remove once ProtoLog adds support for Kotlin -class KtProtoLog { - companion object { - /** @see [com.android.internal.protolog.common.ProtoLog.d] */ - fun d(group: IProtoLogGroup, messageString: String, vararg args: Any) { - if (group.isLogToLogcat) { - Log.d(group.tag, String.format(messageString, *args)) - } - } - - /** @see [com.android.internal.protolog.common.ProtoLog.v] */ - fun v(group: IProtoLogGroup, messageString: String, vararg args: Any) { - if (group.isLogToLogcat) { - Log.v(group.tag, String.format(messageString, *args)) - } - } - - /** @see [com.android.internal.protolog.common.ProtoLog.i] */ - fun i(group: IProtoLogGroup, messageString: String, vararg args: Any) { - if (group.isLogToLogcat) { - Log.i(group.tag, String.format(messageString, *args)) - } - } - - /** @see [com.android.internal.protolog.common.ProtoLog.w] */ - fun w(group: IProtoLogGroup, messageString: String, vararg args: Any) { - if (group.isLogToLogcat) { - Log.w(group.tag, String.format(messageString, *args)) - } - } - - /** @see [com.android.internal.protolog.common.ProtoLog.e] */ - fun e(group: IProtoLogGroup, messageString: String, vararg args: Any) { - if (group.isLogToLogcat) { - Log.e(group.tag, String.format(messageString, *args)) - } - } - - /** @see [com.android.internal.protolog.common.ProtoLog.wtf] */ - fun wtf(group: IProtoLogGroup, messageString: String, vararg args: Any) { - if (group.isLogToLogcat) { - Log.wtf(group.tag, String.format(messageString, *args)) - } - } - } -} 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 21b6db29143a..faf6a627febe 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 @@ -22,18 +22,18 @@ 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 android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.view.InputDevice.SOURCE_TOUCHSCREEN; import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_HOVER_ENTER; import static android.view.MotionEvent.ACTION_HOVER_EXIT; -import static android.view.MotionEvent.ACTION_HOVER_MOVE; import static android.view.MotionEvent.ACTION_MOVE; import static android.view.MotionEvent.ACTION_UP; import static android.view.WindowInsets.Type.statusBars; 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.compatui.AppCompatUtils.isSingleTopActivityTranslucent; +import static com.android.wm.shell.compatui.AppCompatUtils.isTopActivityExemptFromDesktopWindowing; import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; @@ -42,12 +42,17 @@ import android.annotation.NonNull; import android.app.ActivityManager; import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityTaskManager; +import android.content.ComponentName; import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.Region; import android.hardware.input.InputManager; +import android.net.Uri; import android.os.Handler; import android.os.Looper; import android.os.RemoteException; @@ -73,7 +78,9 @@ import android.window.WindowContainerTransaction; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.jank.Cuj; +import com.android.internal.jank.InteractionJankMonitor; +import com.android.internal.protolog.ProtoLog; import com.android.window.flags.Flags; import com.android.wm.shell.R; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; @@ -90,7 +97,7 @@ import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition; import com.android.wm.shell.desktopmode.DesktopWallpaperActivity; import com.android.wm.shell.freeform.FreeformTaskTransitionStarter; -import com.android.wm.shell.shared.DesktopModeStatus; +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.splitscreen.SplitScreen.StageType; import com.android.wm.shell.splitscreen.SplitScreenController; @@ -101,9 +108,9 @@ import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.ExclusionRegionListener; import com.android.wm.shell.windowdecor.extension.TaskInfoKt; +import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder; import java.io.PrintWriter; -import java.util.Objects; import java.util.Optional; import java.util.function.Supplier; @@ -129,6 +136,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private final SyncTransactionQueue mSyncQueue; private final DesktopTasksController mDesktopTasksController; private final InputManager mInputManager; + private final InteractionJankMonitor mInteractionJankMonitor; private boolean mTransitionDragActive; private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>(); @@ -184,7 +192,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { SyncTransactionQueue syncQueue, Transitions transitions, Optional<DesktopTasksController> desktopTasksController, - RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer + RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, + InteractionJankMonitor interactionJankMonitor ) { this( context, @@ -205,7 +214,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { new InputMonitorFactory(), SurfaceControl.Transaction::new, rootTaskDisplayAreaOrganizer, - new SparseArray<>()); + new SparseArray<>(), + interactionJankMonitor); } @VisibleForTesting @@ -228,7 +238,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { InputMonitorFactory inputMonitorFactory, Supplier<SurfaceControl.Transaction> transactionFactory, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, - SparseArray<DesktopModeWindowDecoration> windowDecorByTaskId) { + SparseArray<DesktopModeWindowDecoration> windowDecorByTaskId, + InteractionJankMonitor interactionJankMonitor) { mContext = context; mMainExecutor = shellExecutor; mMainHandler = mainHandler; @@ -251,6 +262,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mWindowDecorByTaskId = windowDecorByTaskId; mSysUIPackageName = mContext.getResources().getString( com.android.internal.R.string.config_systemUi); + mInteractionJankMonitor = interactionJankMonitor; shellInit.addInitCallback(this::onInit, this); } @@ -381,10 +393,52 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mWindowDecorByTaskId.remove(taskInfo.taskId); } + private void onMaximizeOrRestore(int taskId, String tag) { + final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId); + if (decoration == null) { + return; + } + mInteractionJankMonitor.begin( + decoration.mTaskSurface, mContext, Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, tag); + mDesktopTasksController.toggleDesktopTaskSize(decoration.mTaskInfo); + decoration.closeHandleMenu(); + decoration.closeMaximizeMenu(); + } + + private void onSnapResize(int taskId, boolean left) { + final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId); + if (decoration == null) { + return; + } + mDesktopTasksController.snapToHalfScreen(decoration.mTaskInfo, + left ? SnapPosition.LEFT : SnapPosition.RIGHT); + decoration.closeHandleMenu(); + decoration.closeMaximizeMenu(); + } + + private void onOpenInBrowser(@NonNull DesktopModeWindowDecoration decor, @NonNull Uri uri) { + openInBrowser(uri); + decor.closeHandleMenu(); + decor.closeMaximizeMenu(); + } + + private void openInBrowser(Uri uri) { + final Intent intent = new Intent(Intent.ACTION_VIEW, uri) + .setComponent(getDefaultBrowser()) + .addFlags(FLAG_ACTIVITY_NEW_TASK); + mContext.startActivity(intent); + } + + private ComponentName getDefaultBrowser() { + final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://")); + final ResolveInfo info = mContext.getPackageManager() + .resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY); + return info.getComponentInfo().getComponentName(); + } + private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener implements View.OnClickListener, View.OnTouchListener, View.OnLongClickListener, View.OnGenericMotionListener, DragDetector.MotionEventHandler { - private static final int CLOSE_MAXIMIZE_MENU_DELAY_MS = 150; private final int mTaskId; private final WindowContainerToken mTaskToken; @@ -403,7 +457,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private boolean mTouchscreenInUse; private boolean mHasLongClicked; private int mDragPointerId = -1; - private final Runnable mCloseMaximizeWindowRunnable; private DesktopModeTouchEventListener( RunningTaskInfo taskInfo, @@ -414,11 +467,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mDragDetector = new DragDetector(this); mGestureDetector = new GestureDetector(mContext, this); mDisplayId = taskInfo.displayId; - mCloseMaximizeWindowRunnable = () -> { - final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId); - if (decoration == null) return; - decoration.closeMaximizeMenu(); - }; } @Override @@ -435,7 +483,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { SplitScreenController.EXIT_REASON_DESKTOP_MODE); } else { WindowContainerTransaction wct = new WindowContainerTransaction(); - mDesktopTasksController.onDesktopWindowClose(wct, mTaskId); + mDesktopTasksController.onDesktopWindowClose(wct, mDisplayId, mTaskId); mTaskOperations.closeTask(mTaskToken, wct); } } else if (id == R.id.back_button) { @@ -467,28 +515,19 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } else if (id == R.id.split_screen_button) { decoration.closeHandleMenu(); mDesktopTasksController.requestSplit(decoration.mTaskInfo); + } else if (id == R.id.open_in_browser_button) { + // TODO(b/346441962): let the decoration handle the click gesture and only call back + // to the ViewModel via #setOpenInBrowserClickListener + decoration.onOpenInBrowserClick(); } else if (id == R.id.collapse_menu_button) { decoration.closeHandleMenu(); } else if (id == R.id.maximize_window) { - final RunningTaskInfo taskInfo = decoration.mTaskInfo; - decoration.closeHandleMenu(); - decoration.closeMaximizeMenu(); - mDesktopTasksController.toggleDesktopTaskSize(taskInfo); - } else if (id == R.id.maximize_menu_maximize_button) { - final RunningTaskInfo taskInfo = decoration.mTaskInfo; - mDesktopTasksController.toggleDesktopTaskSize(taskInfo); - decoration.closeHandleMenu(); - decoration.closeMaximizeMenu(); - } else if (id == R.id.maximize_menu_snap_left_button) { - final RunningTaskInfo taskInfo = decoration.mTaskInfo; - mDesktopTasksController.snapToHalfScreen(taskInfo, SnapPosition.LEFT); - decoration.closeHandleMenu(); - decoration.closeMaximizeMenu(); - } else if (id == R.id.maximize_menu_snap_right_button) { - final RunningTaskInfo taskInfo = decoration.mTaskInfo; - mDesktopTasksController.snapToHalfScreen(taskInfo, SnapPosition.RIGHT); - decoration.closeHandleMenu(); - decoration.closeMaximizeMenu(); + // TODO(b/346441962): move click detection logic into the decor's + // {@link AppHeaderViewHolder}. Let it encapsulate the that and have it report + // back to the decoration using + // {@link DesktopModeWindowDecoration#setOnMaximizeOrRestoreClickListener}, which + // should shared with the maximize menu's maximize/restore actions. + onMaximizeOrRestore(decoration.mTaskInfo.taskId, "caption_bar_button"); } } @@ -570,40 +609,26 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { return false; } + /** + * TODO(b/346441962): move this hover detection logic into the decor's + * {@link AppHeaderViewHolder}. + */ @Override public boolean onGenericMotion(View v, MotionEvent ev) { final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId); final int id = v.getId(); - if (ev.getAction() == ACTION_HOVER_ENTER) { - if (!decoration.isMaximizeMenuActive() && id == R.id.maximize_window) { - decoration.onMaximizeWindowHoverEnter(); - } else if (id == R.id.maximize_window - || MaximizeMenu.Companion.isMaximizeMenuView(id)) { - // Re-hovering over any of the maximize menu views should keep the menu open by - // cancelling any attempts to close the menu. - mMainHandler.removeCallbacks(mCloseMaximizeWindowRunnable); - if (id != R.id.maximize_window) { - decoration.onMaximizeMenuHoverEnter(id, ev); - } + if (ev.getAction() == ACTION_HOVER_ENTER && id == R.id.maximize_window) { + decoration.setAppHeaderMaximizeButtonHovered(true); + if (!decoration.isMaximizeMenuActive()) { + decoration.onMaximizeButtonHoverEnter(); } return true; - } else if (ev.getAction() == ACTION_HOVER_MOVE - && MaximizeMenu.Companion.isMaximizeMenuView(id)) { - decoration.onMaximizeMenuHoverMove(id, ev); - mMainHandler.removeCallbacks(mCloseMaximizeWindowRunnable); - } else if (ev.getAction() == ACTION_HOVER_EXIT) { - if (!decoration.isMaximizeMenuActive() && id == R.id.maximize_window) { - decoration.onMaximizeWindowHoverExit(); - } else if (id == R.id.maximize_window - || MaximizeMenu.Companion.isMaximizeMenuView(id)) { - // Close menu if not hovering over maximize menu or maximize button after a - // delay to give user a chance to re-enter view or to move from one maximize - // menu view to another. - mMainHandler.postDelayed(mCloseMaximizeWindowRunnable, - CLOSE_MAXIMIZE_MENU_DELAY_MS); - if (id != R.id.maximize_window) { - decoration.onMaximizeMenuHoverExit(id, ev); - } + } + if (ev.getAction() == ACTION_HOVER_EXIT && id == R.id.maximize_window) { + decoration.setAppHeaderMaximizeButtonHovered(false); + decoration.onMaximizeHoverStateChanged(); + if (!decoration.isMaximizeMenuActive()) { + decoration.onMaximizeButtonHoverExit(); } return true; } @@ -711,8 +736,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { && action != MotionEvent.ACTION_CANCEL)) { return false; } - final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId); - mDesktopTasksController.toggleDesktopTaskSize(decoration.mTaskInfo); + onMaximizeOrRestore(mTaskId, "double_tap"); return true; } } @@ -1039,12 +1063,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { && taskInfo.isFocused) { return false; } - // TODO(b/347289970): Consider replacing with API if (Flags.enableDesktopWindowingModalsPolicy() - && isSingleTopActivityTranslucent(taskInfo)) { - return false; - } - if (isSystemUIApplication(taskInfo)) { + && isTopActivityExemptFromDesktopWindowing(mContext, taskInfo)) { return false; } return DesktopModeStatus.canEnterDesktopMode(mContext) @@ -1087,14 +1107,21 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } else { dragPositioningCallback = new VeiledResizeTaskPositioner( mTaskOrganizer, windowDecoration, mDisplayController, - mDragStartListener, mTransitions); + mDragStartListener, mTransitions, mInteractionJankMonitor); windowDecoration.setTaskDragResizer( (VeiledResizeTaskPositioner) dragPositioningCallback); } final DesktopModeTouchEventListener touchEventListener = new DesktopModeTouchEventListener(taskInfo, dragPositioningCallback); - + windowDecoration.setOnMaximizeOrRestoreClickListener(this::onMaximizeOrRestore); + windowDecoration.setOnLeftSnapClickListener((taskId, tag) -> { + onSnapResize(taskId, true /* isLeft */); + }); + windowDecoration.setOnRightSnapClickListener((taskId, tag) -> { + onSnapResize(taskId, false /* isLeft */); + }); + windowDecoration.setOpenInBrowserClickListener(this::onOpenInBrowser); windowDecoration.setCaptionListeners( touchEventListener, touchEventListener, touchEventListener, touchEventListener); windowDecoration.setExclusionRegionListener(mExclusionRegionListener); @@ -1117,14 +1144,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { && mSplitScreenController.isTaskInSplitScreen(taskId); } - // TODO(b/347289970): Consider replacing with API - private boolean isSystemUIApplication(RunningTaskInfo taskInfo) { - if (taskInfo.baseActivity != null) { - return (Objects.equals(taskInfo.baseActivity.getPackageName(), mSysUIPackageName)); - } - return false; - } - private void dump(PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; pw.println(prefix + "DesktopModeWindowDecorViewModel"); 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 4d597cac889e..5ffd883a7ceb 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 @@ -31,6 +31,7 @@ import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResiz import android.annotation.NonNull; import android.app.ActivityManager; import android.app.WindowConfiguration.WindowingMode; +import android.content.ComponentName; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; @@ -43,6 +44,7 @@ import android.graphics.PointF; import android.graphics.Rect; import android.graphics.Region; import android.graphics.drawable.Drawable; +import android.net.Uri; import android.os.Handler; import android.os.Trace; import android.util.Log; @@ -67,8 +69,9 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; -import com.android.wm.shell.shared.DesktopModeStatus; +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.splitscreen.SplitScreenController; +import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener; import com.android.wm.shell.windowdecor.extension.TaskInfoKt; import com.android.wm.shell.windowdecor.viewholder.AppHandleViewHolder; import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder; @@ -86,6 +89,10 @@ import java.util.function.Supplier; */ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLinearLayout> { private static final String TAG = "DesktopModeWindowDecoration"; + private static final int CAPTURED_LINK_TIMEOUT_MS = 7000; + + @VisibleForTesting + static final long CLOSE_MAXIMIZE_MENU_DELAY_MS = 150L; private final Handler mHandler; private final Choreographer mChoreographer; @@ -96,6 +103,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private View.OnTouchListener mOnCaptionTouchListener; private View.OnLongClickListener mOnCaptionLongClickListener; private View.OnGenericMotionListener mOnCaptionGenericMotionListener; + private OnTaskActionClickListener mOnMaximizeOrRestoreClickListener; + private OnTaskActionClickListener mOnLeftSnapClickListener; + private OnTaskActionClickListener mOnRightSnapClickListener; private DragPositioningCallback mDragPositioningCallback; private DragResizeInputListener mDragResizeListener; private DragDetector mDragDetector; @@ -116,10 +126,23 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private Bitmap mResizeVeilBitmap; private CharSequence mAppName; + private CapturedLink mCapturedLink; + private OpenInBrowserClickListener mOpenInBrowserClickListener; private ExclusionRegionListener mExclusionRegionListener; private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; + private final MaximizeMenuFactory mMaximizeMenuFactory; + + // Hover state for the maximize menu and button. The menu will remain open as long as either of + // these is true. See {@link #onMaximizeHoverStateChanged()}. + private boolean mIsAppHeaderMaximizeButtonHovered = false; + private boolean mIsMaximizeMenuHovered = false; + // Used to schedule the closing of the maximize menu when neither of the button or menu are + // being hovered. There's a small delay after stopping the hover, to allow a quick reentry + // to cancel the close. + private final Runnable mCloseMaximizeWindowRunnable = this::closeMaximizeMenu; + private final Runnable mCapturedLinkExpiredRunnable = this::onCapturedLinkExpired; DesktopModeWindowDecoration( Context context, @@ -135,7 +158,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin handler, choreographer, syncQueue, rootTaskDisplayAreaOrganizer, SurfaceControl.Builder::new, SurfaceControl.Transaction::new, WindowContainerTransaction::new, SurfaceControl::new, - new SurfaceControlViewHostFactory() {}); + new SurfaceControlViewHostFactory() {}, DefaultMaximizeMenuFactory.INSTANCE); } DesktopModeWindowDecoration( @@ -152,7 +175,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier, Supplier<WindowContainerTransaction> windowContainerTransactionSupplier, Supplier<SurfaceControl> surfaceControlSupplier, - SurfaceControlViewHostFactory surfaceControlViewHostFactory) { + SurfaceControlViewHostFactory surfaceControlViewHostFactory, + MaximizeMenuFactory maximizeMenuFactory) { super(context, displayController, taskOrganizer, taskInfo, taskSurface, surfaceControlBuilderSupplier, surfaceControlTransactionSupplier, windowContainerTransactionSupplier, surfaceControlSupplier, @@ -161,6 +185,31 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mChoreographer = choreographer; mSyncQueue = syncQueue; mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer; + mMaximizeMenuFactory = maximizeMenuFactory; + } + + /** + * Register a listener to be called back when one of the tasks' maximize/restore action is + * triggered. + * TODO(b/346441962): hook this up to double-tap and the header's maximize button, instead of + * having the ViewModel deal with parsing motion events. + */ + void setOnMaximizeOrRestoreClickListener(OnTaskActionClickListener listener) { + mOnMaximizeOrRestoreClickListener = listener; + } + + /** + * Register a listener to be called back when one of the tasks snap-left action is triggered. + */ + void setOnLeftSnapClickListener(OnTaskActionClickListener listener) { + mOnLeftSnapClickListener = listener; + } + + /** + * Register a listener to be called back when one of the tasks' snap-right action is triggered. + */ + void setOnRightSnapClickListener(OnTaskActionClickListener listener) { + mOnRightSnapClickListener = listener; } void setCaptionListeners( @@ -187,6 +236,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mDragDetector.setTouchSlop(ViewConfiguration.get(mContext).getScaledTouchSlop()); } + void setOpenInBrowserClickListener(OpenInBrowserClickListener listener) { + mOpenInBrowserClickListener = listener; + } + @Override void relayout(ActivityManager.RunningTaskInfo taskInfo) { final SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get(); @@ -278,6 +331,11 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop) { Trace.beginSection("DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces"); + + if (Flags.enableDesktopWindowingAppToWeb()) { + setCapturedLink(taskInfo.capturedLink, taskInfo.capturedLinkTimestamp); + } + if (isHandleMenuActive()) { mHandleMenu.relayout(startT); } @@ -322,6 +380,28 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin Trace.endSection(); // DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces } + private void setCapturedLink(Uri capturedLink, long timeStamp) { + if (capturedLink == null + || (mCapturedLink != null && mCapturedLink.mTimeStamp == timeStamp)) { + return; + } + mCapturedLink = new CapturedLink(capturedLink, timeStamp); + mHandler.postDelayed(mCapturedLinkExpiredRunnable, CAPTURED_LINK_TIMEOUT_MS); + } + + private void onCapturedLinkExpired() { + mHandler.removeCallbacks(mCapturedLinkExpiredRunnable); + if (mCapturedLink != null) { + mCapturedLink.setExpired(); + } + } + + void onOpenInBrowserClick() { + if (mOpenInBrowserClickListener == null || mCapturedLink == null) return; + mOpenInBrowserClickListener.onClick(this, mCapturedLink.mUri); + onCapturedLinkExpired(); + } + private void updateDragResizeListener(SurfaceControl oldDecorationSurface) { if (!isDragResizable(mTaskInfo)) { if (!mTaskInfo.positionInParent.equals(mPositionInParent)) { @@ -556,12 +636,13 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin if (mAppIconBitmap != null && mAppName != null) { return; } - final ActivityInfo activityInfo = mTaskInfo.topActivityInfo; - if (activityInfo == null) { - Log.e(TAG, "Top activity info not found in task"); + final ComponentName baseActivity = mTaskInfo.baseActivity; + if (baseActivity == null) { + Log.e(TAG, "Base activity component not found in task"); return; } - PackageManager pm = mContext.getApplicationContext().getPackageManager(); + final PackageManager pm = mContext.getApplicationContext().getPackageManager(); + final ActivityInfo activityInfo = pm.getActivityInfo(baseActivity, 0 /* flags */); final IconProvider provider = new IconProvider(mContext); final Drawable appIconDrawable = provider.getIcon(activityInfo); final BaseIconFactory headerIconFactory = createIconFactory(mContext, @@ -575,6 +656,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin final ApplicationInfo applicationInfo = activityInfo.applicationInfo; mAppName = pm.getApplicationLabel(applicationInfo); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Base activity's component name cannot be found on the system"); } finally { Trace.endSection(); } @@ -714,11 +797,41 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin * Create and display maximize menu window */ void createMaximizeMenu() { - mMaximizeMenu = new MaximizeMenu(mSyncQueue, mRootTaskDisplayAreaOrganizer, - mDisplayController, mTaskInfo, mOnCaptionButtonClickListener, - mOnCaptionGenericMotionListener, mOnCaptionTouchListener, mContext, + mMaximizeMenu = mMaximizeMenuFactory.create(mSyncQueue, mRootTaskDisplayAreaOrganizer, + mDisplayController, mTaskInfo, mContext, calculateMaximizeMenuPosition(), mSurfaceControlTransactionSupplier); - mMaximizeMenu.show(); + mMaximizeMenu.show( + mOnMaximizeOrRestoreClickListener, + mOnLeftSnapClickListener, + mOnRightSnapClickListener, + hovered -> { + mIsMaximizeMenuHovered = hovered; + onMaximizeHoverStateChanged(); + return null; + } + ); + } + + /** Set whether the app header's maximize button is hovered. */ + void setAppHeaderMaximizeButtonHovered(boolean hovered) { + mIsAppHeaderMaximizeButtonHovered = hovered; + onMaximizeHoverStateChanged(); + } + + /** + * Called when either one of the maximize button in the app header or the maximize menu has + * changed its hover state. + */ + void onMaximizeHoverStateChanged() { + if (!mIsMaximizeMenuHovered && !mIsAppHeaderMaximizeButtonHovered) { + // Neither is hovered, close the menu. + if (isMaximizeMenuActive()) { + mHandler.postDelayed(mCloseMaximizeWindowRunnable, CLOSE_MAXIMIZE_MENU_DELAY_MS); + } + return; + } + // At least one of the two is hovered, cancel the close if needed. + mHandler.removeCallbacks(mCloseMaximizeWindowRunnable); } /** @@ -749,11 +862,17 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin .setCaptionHeight(mResult.mCaptionHeight) .setDisplayController(mDisplayController) .setSplitScreenController(splitScreenController) + .setBrowserLinkAvailable(browserLinkAvailable()) .build(); mWindowDecorViewHolder.onHandleMenuOpened(); mHandleMenu.show(); } + @VisibleForTesting + boolean browserLinkAvailable() { + return mCapturedLink != null && !mCapturedLink.mExpired; + } + /** * Close the handle menu window. */ @@ -992,34 +1111,22 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin .setAnimatingTaskResize(animatingTaskResize); } - /** Called when there is a {@Link ACTION_HOVER_EXIT} on the maximize window button. */ - void onMaximizeWindowHoverExit() { + /** + * Called when there is a {@link MotionEvent#ACTION_HOVER_EXIT} on the maximize window button. + */ + void onMaximizeButtonHoverExit() { ((AppHeaderViewHolder) mWindowDecorViewHolder) .onMaximizeWindowHoverExit(); } - /** Called when there is a {@Link ACTION_HOVER_ENTER} on the maximize window button. */ - void onMaximizeWindowHoverEnter() { + /** + * Called when there is a {@link MotionEvent#ACTION_HOVER_ENTER} on the maximize window button. + */ + void onMaximizeButtonHoverEnter() { ((AppHeaderViewHolder) mWindowDecorViewHolder) .onMaximizeWindowHoverEnter(); } - /** Called when there is a {@Link ACTION_HOVER_ENTER} on a view in the maximize menu. */ - void onMaximizeMenuHoverEnter(int id, MotionEvent ev) { - mMaximizeMenu.onMaximizeMenuHoverEnter(id, ev); - } - - /** Called when there is a {@Link ACTION_HOVER_MOVE} on a view in the maximize menu. */ - void onMaximizeMenuHoverMove(int id, MotionEvent ev) { - mMaximizeMenu.onMaximizeMenuHoverMove(id, ev); - } - - /** Called when there is a {@Link ACTION_HOVER_EXIT} on a view in the maximize menu. */ - void onMaximizeMenuHoverExit(int id, MotionEvent ev) { - mMaximizeMenu.onMaximizeMenuHoverExit(id, ev); - } - - @Override public String toString() { return "{" @@ -1055,6 +1162,31 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin } } + @VisibleForTesting + static class CapturedLink { + private final long mTimeStamp; + private final Uri mUri; + private boolean mExpired; + + CapturedLink(@NonNull Uri uri, long timeStamp) { + mUri = uri; + mTimeStamp = timeStamp; + mExpired = false; + } + + void setExpired() { + mExpired = true; + } + } + + + /** Listener for the handle menu's "Open in browser" button */ + interface OpenInBrowserClickListener { + + /** Inform the implementing class that the "Open in browser" button has been clicked */ + void onClick(DesktopModeWindowDecoration decoration, Uri uri); + } + interface ExclusionRegionListener { /** Inform the implementing class of this task's change in region resize handles */ void onExclusionRegionChanged(int taskId, Region region); 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 fe1c9c3cce66..2fd3eaa37ec3 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 @@ -28,10 +28,12 @@ import android.graphics.Rect; import android.util.DisplayMetrics; import android.view.SurfaceControl; +import androidx.annotation.NonNull; + import com.android.window.flags.Flags; import com.android.wm.shell.R; import com.android.wm.shell.common.DisplayController; -import com.android.wm.shell.shared.DesktopModeStatus; +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; /** * Utility class that contains logic common to classes implementing {@link DragPositioningCallback} @@ -106,13 +108,15 @@ public class DragPositioningCallbackUtility { repositionTaskBounds.bottom = (candidateBottom < stableBounds.bottom) ? candidateBottom : oldBottom; } - // If width or height are negative or less than the minimum width or height, revert the + // If width or height are negative or exceeding the width or height constraints, revert the // respective bounds to use previous bound dimensions. - if (repositionTaskBounds.width() < getMinWidth(displayController, windowDecoration)) { + if (isExceedingWidthConstraint(repositionTaskBounds, stableBounds, displayController, + windowDecoration)) { repositionTaskBounds.right = oldRight; repositionTaskBounds.left = oldLeft; } - if (repositionTaskBounds.height() < getMinHeight(displayController, windowDecoration)) { + if (isExceedingHeightConstraint(repositionTaskBounds, stableBounds, displayController, + windowDecoration)) { repositionTaskBounds.top = oldTop; repositionTaskBounds.bottom = oldBottom; } @@ -174,6 +178,30 @@ public class DragPositioningCallbackUtility { return result; } + private static boolean isExceedingWidthConstraint(@NonNull Rect repositionTaskBounds, + Rect maxResizeBounds, DisplayController displayController, + WindowDecoration windowDecoration) { + // Check if width is less than the minimum width constraint. + if (repositionTaskBounds.width() < getMinWidth(displayController, windowDecoration)) { + return true; + } + // Check if width is more than the maximum resize bounds on desktop windowing mode. + return isSizeConstraintForDesktopModeEnabled(windowDecoration.mDecorWindowContext) + && repositionTaskBounds.width() > maxResizeBounds.width(); + } + + private static boolean isExceedingHeightConstraint(@NonNull Rect repositionTaskBounds, + Rect maxResizeBounds, DisplayController displayController, + WindowDecoration windowDecoration) { + // Check if height is less than the minimum height constraint. + if (repositionTaskBounds.height() < getMinHeight(displayController, windowDecoration)) { + return true; + } + // Check if height is more than the maximum resize bounds on desktop windowing mode. + return isSizeConstraintForDesktopModeEnabled(windowDecoration.mDecorWindowContext) + && repositionTaskBounds.height() > maxResizeBounds.height(); + } + private static float getMinWidth(DisplayController displayController, WindowDecoration windowDecoration) { return windowDecoration.mTaskInfo.minWidth < 0 ? getDefaultMinWidth(displayController, @@ -210,7 +238,7 @@ public class DragPositioningCallbackUtility { private static float getDefaultMinSize(DisplayController displayController, WindowDecoration windowDecoration) { - float density = displayController.getDisplayLayout(windowDecoration.mTaskInfo.displayId) + float density = displayController.getDisplayLayout(windowDecoration.mTaskInfo.displayId) .densityDpi() * DisplayMetrics.DENSITY_DEFAULT_SCALE; return windowDecoration.mTaskInfo.defaultMinSize * density; } 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 d902444d4b15..32df8b3b2c7c 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 @@ -55,7 +55,7 @@ import android.view.ViewConfiguration; import android.view.WindowManagerGlobal; import android.window.InputTransferToken; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; @@ -468,8 +468,7 @@ class DragResizeInputListener implements AutoCloseable { case MotionEvent.ACTION_HOVER_ENTER: case MotionEvent.ACTION_HOVER_MOVE: { updateCursorType(e.getDisplayId(), e.getDeviceId(), - e.getPointerId(/*pointerIndex=*/0), e.getXCursorPosition(), - e.getYCursorPosition()); + e.getPointerId(/*pointerIndex=*/0), e.getX(), e.getY()); result = true; break; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java index b5d1d4a76342..ba5f0791a010 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java @@ -33,9 +33,6 @@ import android.graphics.Region; import android.util.Size; import android.view.MotionEvent; -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; - import com.android.wm.shell.R; import java.util.Objects; @@ -44,11 +41,6 @@ import java.util.Objects; * Geometry for a drag resize region for a particular window. */ final class DragResizeWindowGeometry { - // TODO(b/337264971) clean up when no longer needed - @VisibleForTesting static final boolean DEBUG = true; - // The additional width to apply to edge resize bounds just for logging when a touch is - // close. - @VisibleForTesting static final int EDGE_DEBUG_BUFFER = 15; private final int mTaskCornerRadius; private final Size mTaskSize; // The size of the handle applied to the edges of the window, for the user to drag resize. @@ -60,8 +52,6 @@ final class DragResizeWindowGeometry { private final @NonNull TaskCorners mFineTaskCorners; // The bounds for each edge drag region, which can resize the task in one direction. private final @NonNull TaskEdges mTaskEdges; - // Extra-large edge bounds for logging to help debug when an edge resize is ignored. - private final @Nullable TaskEdges mDebugTaskEdges; DragResizeWindowGeometry(int taskCornerRadius, @NonNull Size taskSize, int resizeHandleThickness, int fineCornerSize, int largeCornerSize) { @@ -74,11 +64,6 @@ final class DragResizeWindowGeometry { // Save touch areas for each edge. mTaskEdges = new TaskEdges(mTaskSize, mResizeHandleThickness); - if (DEBUG) { - mDebugTaskEdges = new TaskEdges(mTaskSize, mResizeHandleThickness + EDGE_DEBUG_BUFFER); - } else { - mDebugTaskEdges = null; - } } /** @@ -120,13 +105,7 @@ final class DragResizeWindowGeometry { */ void union(@NonNull Region region) { // Apply the edge resize regions. - if (inDebugMode()) { - // Use the larger edge sizes if we are debugging, to be able to log if we ignored a - // touch due to the size of the edge region. - mDebugTaskEdges.union(region); - } else { - mTaskEdges.union(region); - } + mTaskEdges.union(region); if (enableWindowingEdgeDragResize()) { // Apply the corners as well for the larger corners, to ensure we capture all possible @@ -219,10 +198,6 @@ final class DragResizeWindowGeometry { @DragPositioningCallback.CtrlType private int calculateEdgeResizeCtrlType(float x, float y) { - if (inDebugMode() && (mDebugTaskEdges.contains((int) x, (int) y) - && !mTaskEdges.contains((int) x, (int) y))) { - return CTRL_TYPE_UNDEFINED; - } int ctrlType = CTRL_TYPE_UNDEFINED; // 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. @@ -313,9 +288,7 @@ final class DragResizeWindowGeometry { && this.mResizeHandleThickness == other.mResizeHandleThickness && this.mFineTaskCorners.equals(other.mFineTaskCorners) && this.mLargeTaskCorners.equals(other.mLargeTaskCorners) - && (inDebugMode() - ? this.mDebugTaskEdges.equals(other.mDebugTaskEdges) - : this.mTaskEdges.equals(other.mTaskEdges)); + && this.mTaskEdges.equals(other.mTaskEdges); } @Override @@ -326,11 +299,7 @@ final class DragResizeWindowGeometry { mResizeHandleThickness, mFineTaskCorners, mLargeTaskCorners, - (inDebugMode() ? mDebugTaskEdges : mTaskEdges)); - } - - private boolean inDebugMode() { - return DEBUG && mDebugTaskEdges != null; + mTaskEdges); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java index df0836c1121d..7e44f32bcbeb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java @@ -42,6 +42,7 @@ import android.graphics.Rect; import android.view.MotionEvent; import android.view.SurfaceControl; import android.view.View; +import android.widget.Button; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; @@ -81,6 +82,7 @@ class HandleMenu { // those as well. final Point mGlobalMenuPosition = new Point(); private final boolean mShouldShowWindowingPill; + private final boolean mShouldShowBrowserPill; private final Bitmap mAppIconBitmap; private final CharSequence mAppName; private final View.OnClickListener mOnClickListener; @@ -101,7 +103,7 @@ class HandleMenu { View.OnClickListener onClickListener, View.OnTouchListener onTouchListener, Bitmap appIcon, CharSequence appName, DisplayController displayController, SplitScreenController splitScreenController, boolean shouldShowWindowingPill, - int captionHeight) { + boolean shouldShowBrowserPill, int captionHeight) { mParentDecor = parentDecor; mContext = mParentDecor.mDecorWindowContext; mTaskInfo = mParentDecor.mTaskInfo; @@ -113,6 +115,7 @@ class HandleMenu { mAppIconBitmap = appIcon; mAppName = appName; mShouldShowWindowingPill = shouldShowWindowingPill; + mShouldShowBrowserPill = shouldShowBrowserPill; mCaptionHeight = captionHeight; loadHandleMenuDimensions(); updateHandleMenuPillPositions(); @@ -170,6 +173,7 @@ class HandleMenu { setupWindowingPill(handleMenu); } setupMoreActionsPill(handleMenu); + setupOpenInBrowserPill(handleMenu); } /** @@ -228,6 +232,15 @@ class HandleMenu { } } + private void setupOpenInBrowserPill(View handleMenu) { + if (!mShouldShowBrowserPill) { + handleMenu.findViewById(R.id.open_in_browser_pill).setVisibility(View.GONE); + return; + } + final Button browserButton = handleMenu.findViewById(R.id.open_in_browser_button); + browserButton.setOnClickListener(mOnClickListener); + } + /** * Returns array of windowing icon color based on current UI theme. First element of the * array is for inactive icons and the second is for active icons. @@ -423,6 +436,10 @@ class HandleMenu { menuHeight -= loadDimensionPixelSize(resources, R.dimen.desktop_mode_handle_menu_more_actions_pill_height); } + if (!mShouldShowBrowserPill) { + menuHeight -= loadDimensionPixelSize(resources, + R.dimen.desktop_mode_handle_menu_open_in_browser_pill_height); + } return menuHeight; } @@ -457,6 +474,7 @@ class HandleMenu { private int mCaptionHeight; private DisplayController mDisplayController; private SplitScreenController mSplitScreenController; + private boolean mShowBrowserPill; Builder(@NonNull DesktopModeWindowDecoration parent) { mParent = parent; @@ -507,10 +525,15 @@ class HandleMenu { return this; } + Builder setBrowserLinkAvailable(Boolean showBrowserPill) { + mShowBrowserPill = showBrowserPill; + return this; + } + HandleMenu build() { return new HandleMenu(mParent, mLayoutId, mOnClickListener, mOnTouchListener, mAppIcon, mName, mDisplayController, mSplitScreenController, - mShowWindowingPill, mCaptionHeight); + mShowWindowingPill, mShowBrowserPill, mCaptionHeight); } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt index 8c5d4a2c2ffb..25a829b44448 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt @@ -26,6 +26,7 @@ import android.view.View.SCALE_Y import android.view.View.TRANSLATION_Y import android.view.View.TRANSLATION_Z import android.view.ViewGroup +import android.widget.Button import androidx.core.animation.doOnEnd import androidx.core.view.children import com.android.wm.shell.R @@ -72,6 +73,7 @@ class HandleMenuAnimator( private val appInfoPill: ViewGroup = handleMenu.requireViewById(R.id.app_info_pill) private val windowingPill: ViewGroup = handleMenu.requireViewById(R.id.windowing_pill) private val moreActionsPill: ViewGroup = handleMenu.requireViewById(R.id.more_actions_pill) + private val openInBrowserPill: ViewGroup = handleMenu.requireViewById(R.id.open_in_browser_pill) /** Animates the opening of the handle menu. */ fun animateOpen() { @@ -80,6 +82,7 @@ class HandleMenuAnimator( animateAppInfoPillOpen() animateWindowingPillOpen() animateMoreActionsPillOpen() + animateOpenInBrowserPill() runAnimations() } @@ -94,6 +97,7 @@ class HandleMenuAnimator( animateAppInfoPillOpen() animateWindowingPillOpen() animateMoreActionsPillOpen() + animateOpenInBrowserPill() runAnimations() } @@ -109,6 +113,7 @@ class HandleMenuAnimator( animateAppInfoPillFadeOut() windowingPillClose() moreActionsPillClose() + openInBrowserPillClose() runAnimations(after) } @@ -125,6 +130,7 @@ class HandleMenuAnimator( animateAppInfoPillFadeOut() windowingPillClose() moreActionsPillClose() + openInBrowserPillClose() runAnimations(after) } @@ -137,6 +143,7 @@ class HandleMenuAnimator( appInfoPill.children.forEach { it.alpha = 0f } windowingPill.alpha = 0f moreActionsPill.alpha = 0f + openInBrowserPill.alpha = 0f // Setup pivots. handleMenu.pivotX = menuWidth / 2f @@ -147,6 +154,9 @@ class HandleMenuAnimator( moreActionsPill.pivotX = menuWidth / 2f moreActionsPill.pivotY = appInfoPill.measuredHeight.toFloat() + + openInBrowserPill.pivotX = menuWidth / 2f + openInBrowserPill.pivotY = appInfoPill.measuredHeight.toFloat() } private fun animateAppInfoPillOpen() { @@ -268,12 +278,50 @@ class HandleMenuAnimator( // More Actions Content Opacity Animation moreActionsPill.children.forEach { animators += - ObjectAnimator.ofFloat(it, ALPHA, 1f).apply { + ObjectAnimator.ofFloat(it, ALPHA, 1f).apply { + startDelay = BODY_ALPHA_OPEN_DELAY + duration = BODY_CONTENT_ALPHA_OPEN_DURATION + interpolator = Interpolators.FAST_OUT_SLOW_IN + } + } + } + + private fun animateOpenInBrowserPill() { + // Open in Browser X & Y Scaling Animation + animators += + ObjectAnimator.ofFloat(openInBrowserPill, SCALE_X, HALF_INITIAL_SCALE, 1f).apply { + startDelay = BODY_SCALE_OPEN_DELAY + duration = BODY_SCALE_OPEN_DURATION + } + + animators += + ObjectAnimator.ofFloat(openInBrowserPill, SCALE_Y, HALF_INITIAL_SCALE, 1f).apply { + startDelay = BODY_SCALE_OPEN_DELAY + duration = BODY_SCALE_OPEN_DURATION + } + + // Open in Browser Opacity Animation + animators += + ObjectAnimator.ofFloat(openInBrowserPill, ALPHA, 1f).apply { + startDelay = BODY_ALPHA_OPEN_DELAY + duration = BODY_ALPHA_OPEN_DURATION + } + + // Open in Browser Elevation Animation + animators += + ObjectAnimator.ofFloat(openInBrowserPill, TRANSLATION_Z, 1f).apply { + startDelay = ELEVATION_OPEN_DELAY + duration = BODY_ELEVATION_OPEN_DURATION + } + + // Open in Browser Button Opacity Animation + val button = openInBrowserPill.requireViewById<Button>(R.id.open_in_browser_button) + animators += + ObjectAnimator.ofFloat(button, ALPHA, 1f).apply { startDelay = BODY_ALPHA_OPEN_DELAY duration = BODY_CONTENT_ALPHA_OPEN_DURATION interpolator = Interpolators.FAST_OUT_SLOW_IN } - } } private fun appInfoPillCollapse() { @@ -379,6 +427,37 @@ class HandleMenuAnimator( } } + private fun openInBrowserPillClose() { + // Open in Browser X & Y Scaling Animation + animators += + ObjectAnimator.ofFloat(openInBrowserPill, SCALE_X, HALF_INITIAL_SCALE).apply { + duration = BODY_CLOSE_DURATION + } + + animators += + ObjectAnimator.ofFloat(openInBrowserPill, SCALE_Y, HALF_INITIAL_SCALE).apply { + duration = BODY_CLOSE_DURATION + } + + // Open in Browser Opacity Animation + animators += + ObjectAnimator.ofFloat(openInBrowserPill, ALPHA, 0f).apply { + duration = BODY_CLOSE_DURATION + } + + animators += + ObjectAnimator.ofFloat(openInBrowserPill, ALPHA, 0f).apply { + duration = BODY_CLOSE_DURATION + } + + // Upward Open in Browser y-translation Animation + val yStart: Float = -captionHeight / 2 + animators += + ObjectAnimator.ofFloat(openInBrowserPill, TRANSLATION_Y, yStart).apply { + duration = BODY_CLOSE_DURATION + } + } + /** * Runs the list of hide animators concurrently. * diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt index 0470367015ea..5f9f8d6d1764 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt @@ -20,7 +20,6 @@ import android.animation.AnimatorSet import android.animation.ObjectAnimator import android.animation.ValueAnimator import android.annotation.ColorInt -import android.annotation.IdRes import android.app.ActivityManager.RunningTaskInfo import android.content.Context import android.content.res.ColorStateList @@ -28,6 +27,7 @@ import android.content.res.Resources import android.graphics.Paint import android.graphics.PixelFormat import android.graphics.PointF +import android.graphics.Rect import android.graphics.drawable.Drawable import android.graphics.drawable.GradientDrawable import android.graphics.drawable.LayerDrawable @@ -37,16 +37,17 @@ import android.graphics.drawable.shapes.RoundRectShape import android.util.StateSet import android.view.LayoutInflater import android.view.MotionEvent +import android.view.MotionEvent.ACTION_HOVER_ENTER +import android.view.MotionEvent.ACTION_HOVER_EXIT +import android.view.MotionEvent.ACTION_HOVER_MOVE import android.view.SurfaceControl import android.view.SurfaceControl.Transaction import android.view.SurfaceControlViewHost import android.view.View -import android.view.View.OnClickListener -import android.view.View.OnGenericMotionListener -import android.view.View.OnTouchListener import android.view.View.SCALE_Y import android.view.View.TRANSLATION_Y import android.view.View.TRANSLATION_Z +import android.view.ViewGroup import android.view.WindowManager import android.view.WindowlessWindowManager import android.widget.Button @@ -64,10 +65,10 @@ import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHo import com.android.wm.shell.windowdecor.common.DecorThemeUtil import com.android.wm.shell.windowdecor.common.OPACITY_12 import com.android.wm.shell.windowdecor.common.OPACITY_40 +import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener import com.android.wm.shell.windowdecor.common.withAlpha import java.util.function.Supplier - /** * Menu that appears when user long clicks the maximize button. Gives the user the option to * maximize the task or snap the task to the right or left half of the screen. @@ -77,9 +78,6 @@ class MaximizeMenu( private val rootTdaOrganizer: RootTaskDisplayAreaOrganizer, private val displayController: DisplayController, private val taskInfo: RunningTaskInfo, - private val onClickListener: OnClickListener, - private val onGenericMotionListener: OnGenericMotionListener, - private val onTouchListener: OnTouchListener, private val decorWindowContext: Context, private val menuPosition: PointF, private val transactionSupplier: Supplier<Transaction> = Supplier { Transaction() } @@ -102,9 +100,19 @@ class MaximizeMenu( } /** Creates and shows the maximize window. */ - fun show() { + fun show( + onMaximizeClickListener: OnTaskActionClickListener, + onLeftSnapClickListener: OnTaskActionClickListener, + onRightSnapClickListener: OnTaskActionClickListener, + onHoverListener: (Boolean) -> Unit + ) { if (maximizeMenu != null) return - createMaximizeMenu() + createMaximizeMenu( + onMaximizeClickListener = onMaximizeClickListener, + onLeftSnapClickListener = onLeftSnapClickListener, + onRightSnapClickListener = onRightSnapClickListener, + onHoverListener = onHoverListener + ) maximizeMenuView?.animateOpenMenu() } @@ -117,7 +125,12 @@ class MaximizeMenu( } /** Create a maximize menu that is attached to the display area. */ - private fun createMaximizeMenu() { + private fun createMaximizeMenu( + onMaximizeClickListener: OnTaskActionClickListener, + onLeftSnapClickListener: OnTaskActionClickListener, + onRightSnapClickListener: OnTaskActionClickListener, + onHoverListener: (Boolean) -> Unit + ) { val t = transactionSupplier.get() val builder = SurfaceControl.Builder() rootTdaOrganizer.attachToDisplayArea(taskInfo.displayId, builder) @@ -146,11 +159,19 @@ class MaximizeMenu( context = decorWindowContext, menuHeight = menuHeight, menuPadding = menuPadding, - onClickListener = onClickListener, - onTouchListener = onTouchListener, - onGenericMotionListener = onGenericMotionListener, ).also { menuView -> + val taskId = taskInfo.taskId menuView.bind(taskInfo) + menuView.onMaximizeClickListener = { + onMaximizeClickListener.onClick(taskId, "maximize_menu_option") + } + menuView.onLeftSnapClickListener = { + onLeftSnapClickListener.onClick(taskId, "left_snap_option") + } + menuView.onRightSnapClickListener = { + onRightSnapClickListener.onClick(taskId, "right_snap_option") + } + menuView.onMenuHoverListener = onHoverListener viewHost.setView(menuView.rootView, lp) } @@ -198,56 +219,6 @@ class MaximizeMenu( } /** - * Called when a [MotionEvent.ACTION_HOVER_ENTER] is triggered on any of the menu's views. - * - * TODO(b/346440693): this is only needed for the left/right snap options that don't support - * selector states to manage its hover state. Look into whether that can be added to avoid - * manually tracking hover enter/exit motion events. Also because those button colors/states - * aren't updating correctly for pressed, focused and selected states. - * See also [onMaximizeMenuHoverMove] and [onMaximizeMenuHoverExit]. - */ - fun onMaximizeMenuHoverEnter(viewId: Int, ev: MotionEvent) { - setSnapButtonsColorOnHover(viewId, ev) - } - - /** Called when a [MotionEvent.ACTION_HOVER_MOVE] is triggered on any of the menu's views. */ - fun onMaximizeMenuHoverMove(viewId: Int, ev: MotionEvent) { - setSnapButtonsColorOnHover(viewId, ev) - } - - /** Called when a [MotionEvent.ACTION_HOVER_EXIT] is triggered on any of the menu's views. */ - fun onMaximizeMenuHoverExit(id: Int, ev: MotionEvent) { - val snapOptionsWidth = maximizeMenuView?.snapOptionsWidth ?: return - val snapOptionsHeight = maximizeMenuView?.snapOptionsHeight ?: return - val inSnapMenuBounds = ev.x >= 0 && ev.x <= snapOptionsWidth && - ev.y >= 0 && ev.y <= snapOptionsHeight - - if (id == R.id.maximize_menu_snap_menu_layout && !inSnapMenuBounds) { - // After exiting the snap menu layout area, checks to see that user is not still - // hovering within the snap menu layout bounds which would indicate that the user is - // hovering over a snap button within the snap menu layout rather than having exited. - maximizeMenuView?.updateSplitSnapSelection(MaximizeMenuView.SnapToHalfSelection.NONE) - } - } - - private fun setSnapButtonsColorOnHover(viewId: Int, ev: MotionEvent) { - val snapOptionsWidth = maximizeMenuView?.snapOptionsWidth ?: return - val snapMenuCenter = snapOptionsWidth / 2 - when { - viewId == R.id.maximize_menu_snap_left_button || - (viewId == R.id.maximize_menu_snap_menu_layout && ev.x <= snapMenuCenter) -> { - maximizeMenuView - ?.updateSplitSnapSelection(MaximizeMenuView.SnapToHalfSelection.LEFT) - } - viewId == R.id.maximize_menu_snap_right_button || - (viewId == R.id.maximize_menu_snap_menu_layout && ev.x > snapMenuCenter) -> { - maximizeMenuView - ?.updateSplitSnapSelection(MaximizeMenuView.SnapToHalfSelection.RIGHT) - } - } - } - - /** * The view within the Maximize Menu, presents maximize, restore and snap-to-side options for * resizing a Task. */ @@ -255,12 +226,11 @@ class MaximizeMenu( context: Context, private val menuHeight: Int, private val menuPadding: Int, - onClickListener: OnClickListener, - onTouchListener: OnTouchListener, - onGenericMotionListener: OnGenericMotionListener, ) { - val rootView: View = LayoutInflater.from(context) - .inflate(R.layout.desktop_mode_window_decor_maximize_menu, null /* root */) + val rootView = LayoutInflater.from(context) + .inflate(R.layout.desktop_mode_window_decor_maximize_menu, null /* root */) as ViewGroup + private val container = requireViewById(R.id.container) + private val overlay = requireViewById(R.id.maximize_menu_overlay) private val maximizeText = requireViewById(R.id.maximize_menu_maximize_window_text) as TextView private val maximizeButton = @@ -285,30 +255,63 @@ class MaximizeMenu( private val fillRadius = context.resources .getDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_buttons_fill_radius) + private val hoverTempRect = Rect() private val openMenuAnimatorSet = AnimatorSet() private lateinit var taskInfo: RunningTaskInfo private lateinit var style: MenuStyle - /** The width of the snap menu option view, including both left and right snaps. */ - val snapOptionsWidth: Int - get() = snapButtonsLayout.width - /** The height of the snap menu option view, including both left and right snaps .*/ - val snapOptionsHeight: Int - get() = snapButtonsLayout.height + /** Invoked when the maximize or restore option is clicked. */ + var onMaximizeClickListener: (() -> Unit)? = null + /** Invoked when the left snap option is clicked. */ + var onLeftSnapClickListener: (() -> Unit)? = null + /** Invoked when the right snap option is clicked. */ + var onRightSnapClickListener: (() -> Unit)? = null + /** Invoked whenever the hover state of the menu changes. */ + var onMenuHoverListener: ((Boolean) -> Unit)? = null init { - // TODO(b/346441962): encapsulate menu hover enter/exit logic inside this class and - // expose only what is actually relevant to outside classes so that specific checks - // against resource IDs aren't needed outside this class. - rootView.setOnGenericMotionListener(onGenericMotionListener) - rootView.setOnTouchListener(onTouchListener) - maximizeButton.setOnClickListener(onClickListener) - maximizeButton.setOnGenericMotionListener(onGenericMotionListener) - snapRightButton.setOnClickListener(onClickListener) - snapRightButton.setOnGenericMotionListener(onGenericMotionListener) - snapLeftButton.setOnClickListener(onClickListener) - snapLeftButton.setOnGenericMotionListener(onGenericMotionListener) - snapButtonsLayout.setOnGenericMotionListener(onGenericMotionListener) + overlay.setOnHoverListener { _, event -> + // The overlay covers the entire menu, so it's a convenient way to monitor whether + // the menu is hovered as a whole or not. + when (event.action) { + ACTION_HOVER_ENTER -> onMenuHoverListener?.invoke(true) + ACTION_HOVER_EXIT -> onMenuHoverListener?.invoke(false) + } + + // Also check if the hover falls within the snap options layout, to manually + // set the left/right state based on the event's position. + // TODO(b/346440693): this manual hover tracking is needed for left/right snap + // because its view/background(s) don't support selector states. Look into whether + // that can be added to avoid manual tracking. Also because these button + // colors/state logic is only being applied on hover events, but there's pressed, + // focused and selected states that should be responsive too. + val snapLayoutBoundsRelToOverlay = hoverTempRect.also { rect -> + snapButtonsLayout.getDrawingRect(rect) + rootView.offsetDescendantRectToMyCoords(snapButtonsLayout, rect) + } + if (event.action == ACTION_HOVER_ENTER || event.action == ACTION_HOVER_MOVE) { + if (snapLayoutBoundsRelToOverlay.contains(event.x.toInt(), event.y.toInt())) { + // Hover is inside the snap layout, anything left of center is the left + // snap, and anything right of center is right snap. + val layoutCenter = snapLayoutBoundsRelToOverlay.centerX() + if (event.x < layoutCenter) { + updateSplitSnapSelection(SnapToHalfSelection.LEFT) + } else { + updateSplitSnapSelection(SnapToHalfSelection.RIGHT) + } + } else { + // Any other hover is outside the snap layout, so neither is selected. + updateSplitSnapSelection(SnapToHalfSelection.NONE) + } + } + + // Don't consume the event to allow child views to receive the event too. + return@setOnHoverListener false + } + + maximizeButton.setOnClickListener { onMaximizeClickListener?.invoke() } + snapRightButton.setOnClickListener { onRightSnapClickListener?.invoke() } + snapLeftButton.setOnClickListener { onLeftSnapClickListener?.invoke() } // To prevent aliasing. maximizeButton.setLayerType(View.LAYER_TYPE_SOFTWARE, null) @@ -351,7 +354,7 @@ class MaximizeMenu( val value = animatedValue as Float val topPadding = menuPadding - ((1 - value) * menuHeight).toInt() - rootView.setPadding(menuPadding, topPadding, + container.setPadding(menuPadding, topPadding, menuPadding, menuPadding) } }, @@ -410,7 +413,7 @@ class MaximizeMenu( } /** Update the view state to a new snap to half selection. */ - fun updateSplitSnapSelection(selection: SnapToHalfSelection) { + private fun updateSplitSnapSelection(selection: SnapToHalfSelection) { when (selection) { SnapToHalfSelection.NONE -> deactivateSnapOptions() SnapToHalfSelection.LEFT -> activateSnapOption(activateLeft = true) @@ -638,13 +641,41 @@ class MaximizeMenu( private const val ELEVATION_ANIMATION_DURATION_MS = 50L private const val CONTROLS_ALPHA_ANIMATION_DELAY_MS = 33L private const val MENU_Z_TRANSLATION = 1f - fun isMaximizeMenuView(@IdRes viewId: Int): Boolean { - return viewId == R.id.maximize_menu || - viewId == R.id.maximize_menu_maximize_button || - viewId == R.id.maximize_menu_snap_left_button || - viewId == R.id.maximize_menu_snap_right_button || - viewId == R.id.maximize_menu_snap_menu_layout || - viewId == R.id.maximize_menu_snap_menu_layout - } + } +} + +/** A factory interface to create a [MaximizeMenu]. */ +interface MaximizeMenuFactory { + fun create( + syncQueue: SyncTransactionQueue, + rootTdaOrganizer: RootTaskDisplayAreaOrganizer, + displayController: DisplayController, + taskInfo: RunningTaskInfo, + decorWindowContext: Context, + menuPosition: PointF, + transactionSupplier: Supplier<Transaction> + ): MaximizeMenu +} + +/** A [MaximizeMenuFactory] implementation that creates a [MaximizeMenu]. */ +object DefaultMaximizeMenuFactory : MaximizeMenuFactory { + override fun create( + syncQueue: SyncTransactionQueue, + rootTdaOrganizer: RootTaskDisplayAreaOrganizer, + displayController: DisplayController, + taskInfo: RunningTaskInfo, + decorWindowContext: Context, + menuPosition: PointF, + transactionSupplier: Supplier<Transaction> + ): MaximizeMenu { + return MaximizeMenu( + syncQueue, + rootTdaOrganizer, + displayController, + taskInfo, + decorWindowContext, + menuPosition, + transactionSupplier + ) } } 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 5fce5d228d71..153221150992 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 @@ -18,6 +18,8 @@ package com.android.wm.shell.windowdecor; import static android.view.WindowManager.TRANSIT_CHANGE; +import static com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_RESIZE_WINDOW; + import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; @@ -31,6 +33,7 @@ import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.internal.jank.InteractionJankMonitor; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.transition.Transitions; @@ -56,6 +59,7 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback, private final PointF mRepositionStartPoint = new PointF(); private final Rect mRepositionTaskBounds = new Rect(); private final Supplier<SurfaceControl.Transaction> mTransactionSupplier; + private final InteractionJankMonitor mInteractionJankMonitor; private int mCtrlType; private boolean mIsResizingOrAnimatingResize; @Surface.Rotation private int mRotation; @@ -64,22 +68,24 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback, DesktopModeWindowDecoration windowDecoration, DisplayController displayController, DragPositioningCallbackUtility.DragStartListener dragStartListener, - Transitions transitions) { + Transitions transitions, InteractionJankMonitor interactionJankMonitor) { this(taskOrganizer, windowDecoration, displayController, dragStartListener, - SurfaceControl.Transaction::new, transitions); + SurfaceControl.Transaction::new, transitions, interactionJankMonitor); } public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, DesktopModeWindowDecoration windowDecoration, DisplayController displayController, DragPositioningCallbackUtility.DragStartListener dragStartListener, - Supplier<SurfaceControl.Transaction> supplier, Transitions transitions) { + Supplier<SurfaceControl.Transaction> supplier, Transitions transitions, + InteractionJankMonitor interactionJankMonitor) { mDesktopWindowDecoration = windowDecoration; mTaskOrganizer = taskOrganizer; mDisplayController = displayController; mDragStartListener = dragStartListener; mTransactionSupplier = supplier; mTransitions = transitions; + mInteractionJankMonitor = interactionJankMonitor; } @Override @@ -89,6 +95,9 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback, mDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration.getBounds()); mRepositionStartPoint.set(x, y); if (isResizing()) { + // Capture CUJ for re-sizing window in DW mode. + mInteractionJankMonitor.begin(mDesktopWindowDecoration.mTaskSurface, + mDesktopWindowDecoration.mContext, CUJ_DESKTOP_MODE_RESIZE_WINDOW); if (!mDesktopWindowDecoration.mTaskInfo.isFocused) { WindowContainerTransaction wct = new WindowContainerTransaction(); wct.reorder(mDesktopWindowDecoration.mTaskInfo.token, true); @@ -146,6 +155,7 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback, // won't be called. resetVeilIfVisible(); } + mInteractionJankMonitor.end(CUJ_DESKTOP_MODE_RESIZE_WINDOW); } else { final WindowContainerTransaction wct = new WindowContainerTransaction(); DragPositioningCallbackUtility.updateTaskBounds(mRepositionTaskBounds, 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 216990c35247..e86f6a162c2d 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 @@ -19,6 +19,7 @@ package com.android.wm.shell.windowdecor; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.content.res.Configuration.DENSITY_DPI_UNDEFINED; +import static android.view.InsetsSource.FLAG_FORCE_CONSUMING; import static android.view.WindowInsets.Type.captionBar; import static android.view.WindowInsets.Type.mandatorySystemGestures; import static android.view.WindowInsets.Type.statusBars; @@ -53,9 +54,10 @@ import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import com.android.internal.annotations.VisibleForTesting; +import com.android.window.flags.Flags; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; -import com.android.wm.shell.shared.DesktopModeStatus; +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams.OccludingCaptionElement; import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer; @@ -759,9 +761,12 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> } void addOrUpdate(WindowContainerTransaction wct) { - wct.addInsetsSource(mToken, mOwner, INDEX, captionBar(), mFrame, mBoundingRects); + final @InsetsSource.Flags int captionSourceFlags = + Flags.enableCaptionCompatInsetForceConsumption() ? FLAG_FORCE_CONSUMING : 0; + wct.addInsetsSource(mToken, mOwner, INDEX, captionBar(), mFrame, mBoundingRects, + captionSourceFlags); wct.addInsetsSource(mToken, mOwner, INDEX, mandatorySystemGestures(), mFrame, - mBoundingRects); + mBoundingRects, 0 /* flags */); } void remove(WindowContainerTransaction wct) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/OnTaskActionClickListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/OnTaskActionClickListener.kt new file mode 100644 index 000000000000..14b9e7f71622 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/OnTaskActionClickListener.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.wm.shell.windowdecor.common + +/** A callback to be invoked when a Task's window decor element is clicked. */ +fun interface OnTaskActionClickListener { + /** + * Called when a task's decor element has been clicked. + * + * @param taskId the id of the task. + * @param tag a readable identifier for the element. + */ + fun onClick(taskId: Int, tag: String) +} diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt index a0a61fe2cf72..d0e8215e662e 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt @@ -117,12 +117,10 @@ class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(fli /** * Checks that all parts of the screen are covered at the start and end of the transition - * - * TODO b/197726599 Prevents all states from being checked */ @Presubmit @Test - fun entireScreenCoveredAtStartAndEnd() = flicker.entireScreenCovered(allStates = false) + fun entireScreenCoveredAtStartAndEnd() = flicker.entireScreenCovered() /** Checks [pipApp] window remains visible and on top throughout the transition */ @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt index 35ed8de3a464..7873a85d515d 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt @@ -65,7 +65,7 @@ open class YouTubeEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransit setup { standardAppHelper.launchViaIntent( wmHelper, - YouTubeAppHelper.getYoutubeVideoIntent("HPcEAtoXXLA"), + YouTubeAppHelper.getYoutubeVideoIntent("3KtWfp0UopM"), ComponentNameMatcher(YouTubeAppHelper.PACKAGE_NAME, "") ) standardAppHelper.waitForVideoPlaying() diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt index 879034f32514..5c539a5e8a03 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipToOtherOrientationTest.kt @@ -73,7 +73,7 @@ open class YouTubeEnterPipToOtherOrientationTest(flicker: LegacyFlickerTest) : setup { standardAppHelper.launchViaIntent( wmHelper, - YouTubeAppHelper.getYoutubeVideoIntent("HPcEAtoXXLA"), + YouTubeAppHelper.getYoutubeVideoIntent("3KtWfp0UopM"), ComponentNameMatcher(YouTubeAppHelper.PACKAGE_NAME, "") ) standardAppHelper.enterFullscreen() diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt index d485b82f5ddb..430f80b9a927 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt +++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt @@ -20,18 +20,23 @@ import android.tools.flicker.AssertionInvocationGroup import android.tools.flicker.assertors.assertions.AppLayerIsInvisibleAtEnd import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAlways import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAtStart +import android.tools.flicker.assertors.assertions.AppWindowBecomesVisible import android.tools.flicker.assertors.assertions.AppWindowHasDesktopModeInitialBoundsAtTheEnd -import android.tools.flicker.assertors.assertions.AppWindowIsVisibleAlways +import android.tools.flicker.assertors.assertions.AppWindowHasSizeOfAtLeast +import android.tools.flicker.assertors.assertions.AppWindowIsInvisibleAtEnd import android.tools.flicker.assertors.assertions.AppWindowOnTopAtEnd import android.tools.flicker.assertors.assertions.AppWindowOnTopAtStart -import android.tools.flicker.assertors.assertions.AppWindowRemainInsideDisplayBounds -import android.tools.flicker.assertors.assertions.LauncherWindowMovesToTop +import android.tools.flicker.assertors.assertions.LauncherWindowReplacesAppAsTopWindow import android.tools.flicker.config.AssertionTemplates import android.tools.flicker.config.FlickerConfigEntry import android.tools.flicker.config.ScenarioId import android.tools.flicker.config.desktopmode.Components +import android.tools.flicker.config.desktopmode.Components.DESKTOP_WALLPAPER import android.tools.flicker.extractors.ITransitionMatcher import android.tools.flicker.extractors.ShellTransitionScenarioExtractor +import android.tools.flicker.extractors.TaggedCujTransitionMatcher +import android.tools.flicker.extractors.TaggedScenarioExtractorBuilder +import android.tools.traces.events.CujType import android.tools.traces.wm.Transition import android.tools.traces.wm.TransitionType @@ -48,6 +53,7 @@ class DesktopModeFlickerScenarios { transitions: Collection<Transition> ): Collection<Transition> { return transitions.filter { + // TODO(351168217) Use jank CUJ to extract a longer trace it.type == TransitionType.DESKTOP_MODE_END_DRAG_TO_DESKTOP } } @@ -60,11 +66,14 @@ class DesktopModeFlickerScenarios { AppWindowOnTopAtEnd(Components.DESKTOP_MODE_APP), AppWindowHasDesktopModeInitialBoundsAtTheEnd( Components.DESKTOP_MODE_APP - ) + ), + AppWindowBecomesVisible(DESKTOP_WALLPAPER) ) .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }), ) + // Use this scenario for closing an app in desktop windowing, except the last app. For the + // last app use CLOSE_LAST_APP scenario val CLOSE_APP = FlickerConfigEntry( scenarioId = ScenarioId("CLOSE_APP"), @@ -75,7 +84,13 @@ class DesktopModeFlickerScenarios { override fun findAll( transitions: Collection<Transition> ): Collection<Transition> { - return transitions.filter { it.type == TransitionType.CLOSE } + // In case there are multiple windows closing, filter out the + // last window closing. It should use the CLOSE_LAST_APP + // scenario below. + return transitions + .filter { it.type == TransitionType.CLOSE } + .sortedByDescending { it.id } + .drop(1) } } ), @@ -100,19 +115,19 @@ class DesktopModeFlickerScenarios { transitions: Collection<Transition> ): Collection<Transition> { val lastTransition = - transitions.findLast { it.type == TransitionType.CLOSE } - return if (lastTransition != null) listOf(lastTransition) - else emptyList() + transitions + .filter { it.type == TransitionType.CLOSE } + .maxByOrNull { it.id }!! + return listOf(lastTransition) } } ), assertions = AssertionTemplates.COMMON_ASSERTIONS + listOf( - AppWindowOnTopAtStart(Components.DESKTOP_MODE_APP), - AppLayerIsVisibleAtStart(Components.DESKTOP_MODE_APP), - AppLayerIsInvisibleAtEnd(Components.DESKTOP_MODE_APP), - LauncherWindowMovesToTop() + AppWindowIsInvisibleAtEnd(Components.DESKTOP_MODE_APP), + LauncherWindowReplacesAppAsTopWindow(Components.DESKTOP_MODE_APP), + AppWindowIsInvisibleAtEnd(DESKTOP_WALLPAPER) ) .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }), ) @@ -121,24 +136,29 @@ class DesktopModeFlickerScenarios { FlickerConfigEntry( scenarioId = ScenarioId("CORNER_RESIZE"), extractor = - ShellTransitionScenarioExtractor( - transitionMatcher = - object : ITransitionMatcher { - override fun findAll( - transitions: Collection<Transition> - ): Collection<Transition> { - return transitions.filter { - it.type == TransitionType.CHANGE - } - } - } - ), + TaggedScenarioExtractorBuilder() + .setTargetTag(CujType.CUJ_DESKTOP_MODE_RESIZE_WINDOW) + .setTransitionMatcher( + TaggedCujTransitionMatcher(associatedTransitionRequired = false) + ) + .build(), + assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS + ) + + val CORNER_RESIZE_TO_MINIMUM_SIZE = + FlickerConfigEntry( + scenarioId = ScenarioId("CORNER_RESIZE_TO_MINIMUM_SIZE"), + extractor = + TaggedScenarioExtractorBuilder() + .setTargetTag(CujType.CUJ_DESKTOP_MODE_RESIZE_WINDOW) + .setTransitionMatcher( + TaggedCujTransitionMatcher(associatedTransitionRequired = false) + ) + .build(), assertions = - listOf( - AppWindowIsVisibleAlways(Components.DESKTOP_MODE_APP), - AppWindowOnTopAtEnd(Components.DESKTOP_MODE_APP), - AppWindowRemainInsideDisplayBounds(Components.DESKTOP_MODE_APP), - ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }), + AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS + + listOf(AppWindowHasSizeOfAtLeast(Components.DESKTOP_MODE_APP, 770, 700)) + .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }), ) } } diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppToMinimumWindowSizeLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppToMinimumWindowSizeLandscape.kt new file mode 100644 index 000000000000..6319cf74ed8f --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppToMinimumWindowSizeLandscape.kt @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.desktopmode.flicker + +import android.tools.Rotation +import android.tools.flicker.FlickerConfig +import android.tools.flicker.annotation.ExpectedScenarios +import android.tools.flicker.annotation.FlickerConfigProvider +import android.tools.flicker.config.FlickerConfig +import android.tools.flicker.config.FlickerServiceConfig +import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.desktopmode.flicker.DesktopModeFlickerScenarios.Companion.CORNER_RESIZE_TO_MINIMUM_SIZE +import com.android.wm.shell.flicker.service.desktopmode.scenarios.ResizeAppWithCornerResize +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Resize app window using corner resize to the smallest possible height and width in + * landscape mode. + * + * Assert that the minimum window size constraint is maintained. + */ +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class ResizeAppToMinimumWindowSizeLandscape : ResizeAppWithCornerResize( + rotation = Rotation.ROTATION_90, + horizontalChange = -1500, + verticalChange = 1500) { + @ExpectedScenarios(["CORNER_RESIZE_TO_MINIMUM_SIZE"]) + @Test + override fun resizeAppWithCornerResize() = super.resizeAppWithCornerResize() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(CORNER_RESIZE_TO_MINIMUM_SIZE) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppToMinimumWindowSizePortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppToMinimumWindowSizePortrait.kt new file mode 100644 index 000000000000..431f6e3d3ea2 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppToMinimumWindowSizePortrait.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.desktopmode.flicker + +import android.tools.flicker.FlickerConfig +import android.tools.flicker.annotation.ExpectedScenarios +import android.tools.flicker.annotation.FlickerConfigProvider +import android.tools.flicker.config.FlickerConfig +import android.tools.flicker.config.FlickerServiceConfig +import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.service.desktopmode.flicker.DesktopModeFlickerScenarios.Companion.CORNER_RESIZE_TO_MINIMUM_SIZE +import com.android.wm.shell.flicker.service.desktopmode.scenarios.ResizeAppWithCornerResize +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Resize app window using corner resize to the smallest possible height and width in portrait mode. + * + * Assert that the minimum window size constraint is maintained. + */ +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class ResizeAppToMinimumWindowSizePortrait : ResizeAppWithCornerResize(horizontalChange = -1500, + verticalChange = 1500) { + @ExpectedScenarios(["CORNER_RESIZE_TO_MINIMUM_SIZE"]) + @Test + override fun resizeAppWithCornerResize() = super.resizeAppWithCornerResize() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(CORNER_RESIZE_TO_MINIMUM_SIZE) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/MaximizeAppWindow.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/MaximizeAppWindow.kt new file mode 100644 index 000000000000..20e2167c28f2 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/MaximizeAppWindow.kt @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.desktopmode.scenarios + +import android.app.Instrumentation +import android.tools.NavBar +import android.tools.Rotation +import android.tools.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.DesktopModeAppHelper +import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.window.flags.Flags +import com.android.wm.shell.flicker.service.common.Utils +import org.junit.After +import org.junit.Assume +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +/** Base scenario test for maximize app window CUJ in desktop mode. */ +@Ignore("Base Test Class") +abstract class MaximizeAppWindow +{ + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation)) + + @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, + Rotation.ROTATION_0) + + @Before + fun setup() { + Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet) + testApp.enterDesktopWithDrag(wmHelper, device) + } + + @Test + open fun maximizeAppWindow() { + testApp.maximiseDesktopApp(wmHelper, device) + } + + @After + fun teardown() { + testApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ResizeAppWithCornerResize.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ResizeAppWithCornerResize.kt index ac9089a5c1bd..136cf378aa09 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ResizeAppWithCornerResize.kt +++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ResizeAppWithCornerResize.kt @@ -38,7 +38,9 @@ import org.junit.Test @Ignore("Base Test Class") abstract class ResizeAppWithCornerResize @JvmOverloads -constructor(val rotation: Rotation = Rotation.ROTATION_0) { +constructor(val rotation: Rotation = Rotation.ROTATION_0, + val horizontalChange: Int = 50, + val verticalChange: Int = -50) { private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() private val tapl = LauncherInstrumentation() @@ -46,7 +48,9 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) { private val device = UiDevice.getInstance(instrumentation) private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation)) - @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) + @Rule + @JvmField + val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) @Before fun setup() { @@ -58,7 +62,11 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) { @Test open fun resizeAppWithCornerResize() { - testApp.cornerResize(wmHelper, device, DesktopModeAppHelper.Corners.RIGHT_TOP, 50, -50) + testApp.cornerResize(wmHelper, + device, + DesktopModeAppHelper.Corners.RIGHT_TOP, + horizontalChange, + verticalChange) } @After diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt index c671fbe39ac5..b5a6d83afd05 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt +++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt @@ -48,6 +48,8 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) { fun setup() { tapl.setEnableRotation(true) tapl.setExpectedRotation(rotation.value) + // TODO: b/349075982 - Remove once launcher rotation and checks are stable. + tapl.setExpectedRotationCheckEnabled(false) SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp, rotation) } diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp b/libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp index 0fe7a16be851..35b2f56bca92 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp +++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp @@ -37,17 +37,51 @@ filegroup { "src/**/B*.kt", "src/**/C*.kt", "src/**/D*.kt", - "src/**/E*.kt", ], } filegroup { name: "WMShellFlickerTestsSplitScreenGroup2-src", srcs: [ + "src/**/E*.kt", + ], +} + +filegroup { + name: "WMShellFlickerTestsSplitScreenGroup3-src", + srcs: [ + "src/**/S*.kt", + ], +} + +filegroup { + name: "WMShellFlickerTestsSplitScreenGroupOther-src", + srcs: [ "src/**/*.kt", ], } +java_library { + name: "WMShellFlickerTestsSplitScreenBase", + srcs: [ + ":WMShellFlickerTestsSplitScreenBase-src", + ], + static_libs: [ + "WMShellFlickerTestsBase", + "wm-shell-flicker-utils", + "androidx.test.ext.junit", + "flickertestapplib", + "flickerlib", + "flickerlib-helpers", + "flickerlib-trace_processor_shell", + "platform-test-annotations", + "wm-flicker-common-app-helpers", + "wm-flicker-common-assertions", + "launcher-helper-lib", + "launcher-aosp-tapl", + ], +} + android_test { name: "WMShellFlickerTestsSplitScreenGroup1", defaults: ["WMShellFlickerTestsDefault"], @@ -56,10 +90,12 @@ android_test { instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen", test_config_template: "AndroidTestTemplate.xml", srcs: [ - ":WMShellFlickerTestsSplitScreenBase-src", ":WMShellFlickerTestsSplitScreenGroup1-src", ], - static_libs: ["WMShellFlickerTestsBase"], + static_libs: [ + "WMShellFlickerTestsBase", + "WMShellFlickerTestsSplitScreenBase", + ], data: ["trace_config/*"], } @@ -71,12 +107,50 @@ android_test { instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen", test_config_template: "AndroidTestTemplate.xml", srcs: [ - ":WMShellFlickerTestsSplitScreenBase-src", ":WMShellFlickerTestsSplitScreenGroup2-src", ], + static_libs: [ + "WMShellFlickerTestsBase", + "WMShellFlickerTestsSplitScreenBase", + ], + data: ["trace_config/*"], +} + +android_test { + name: "WMShellFlickerTestsSplitScreenGroup3", + defaults: ["WMShellFlickerTestsDefault"], + manifest: "AndroidManifest.xml", + package_name: "com.android.wm.shell.flicker.splitscreen", + instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen", + test_config_template: "AndroidTestTemplate.xml", + srcs: [ + ":WMShellFlickerTestsSplitScreenGroup3-src", + ], + static_libs: [ + "WMShellFlickerTestsBase", + "WMShellFlickerTestsSplitScreenBase", + ], + data: ["trace_config/*"], +} + +android_test { + name: "WMShellFlickerTestsSplitScreenGroupOther", + defaults: ["WMShellFlickerTestsDefault"], + manifest: "AndroidManifest.xml", + package_name: "com.android.wm.shell.flicker.splitscreen", + instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen", + test_config_template: "AndroidTestTemplate.xml", + srcs: [ + ":WMShellFlickerTestsSplitScreenGroupOther-src", + ], exclude_srcs: [ ":WMShellFlickerTestsSplitScreenGroup1-src", + ":WMShellFlickerTestsSplitScreenGroup2-src", + ":WMShellFlickerTestsSplitScreenGroup3-src", + ], + static_libs: [ + "WMShellFlickerTestsBase", + "WMShellFlickerTestsSplitScreenBase", ], - static_libs: ["WMShellFlickerTestsBase"], data: ["trace_config/*"], } diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp index 13f95ccea640..6b6954289a34 100644 --- a/libs/WindowManager/Shell/tests/unittest/Android.bp +++ b/libs/WindowManager/Shell/tests/unittest/Android.bp @@ -46,6 +46,7 @@ android_test { "androidx.dynamicanimation_dynamicanimation", "dagger2", "frameworks-base-testutils", + "kotlin-test", "kotlinx-coroutines-android", "kotlinx-coroutines-core", "mockito-kotlin2", @@ -58,6 +59,7 @@ android_test { "guava-android-testlib", "com.android.window.flags.window-aconfig-java", "platform-test-annotations", + "flag-junit", ], libs: [ diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java index f9b4108bc8c2..e91828be4ebe 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java @@ -45,6 +45,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.app.ActivityManager.RunningTaskInfo; +import android.app.TaskInfo; import android.content.LocusId; import android.content.pm.ParceledListSlice; import android.os.Binder; @@ -63,6 +64,8 @@ import androidx.test.filters.SmallTest; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.compatui.CompatUIController; +import com.android.wm.shell.compatui.api.CompatUIInfo; +import com.android.wm.shell.compatui.impl.CompatUIEvents; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellInit; @@ -70,6 +73,7 @@ import com.android.wm.shell.sysui.ShellInit; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -371,7 +375,7 @@ public class ShellTaskOrganizerTests extends ShellTestCase { mOrganizer.onTaskAppeared(taskInfo1, /* leash= */ null); // sizeCompatActivity is null if top activity is not in size compat. - verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */); + verifyOnCompatInfoChangedInvokedWith(taskInfo1, null /* taskListener */); // sizeCompatActivity is non-null if top activity is in size compat. clearInvocations(mCompatUI); @@ -381,7 +385,7 @@ public class ShellTaskOrganizerTests extends ShellTestCase { taskInfo2.appCompatTaskInfo.topActivityInSizeCompat = true; taskInfo2.isVisible = true; mOrganizer.onTaskInfoChanged(taskInfo2); - verify(mCompatUI).onCompatInfoChanged(taskInfo2, taskListener); + verifyOnCompatInfoChangedInvokedWith(taskInfo2, taskListener); // Not show size compat UI if task is not visible. clearInvocations(mCompatUI); @@ -391,11 +395,11 @@ public class ShellTaskOrganizerTests extends ShellTestCase { taskInfo3.appCompatTaskInfo.topActivityInSizeCompat = true; taskInfo3.isVisible = false; mOrganizer.onTaskInfoChanged(taskInfo3); - verify(mCompatUI).onCompatInfoChanged(taskInfo3, null /* taskListener */); + verifyOnCompatInfoChangedInvokedWith(taskInfo3, null /* taskListener */); clearInvocations(mCompatUI); mOrganizer.onTaskVanished(taskInfo1); - verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */); + verifyOnCompatInfoChangedInvokedWith(taskInfo1, null /* taskListener */); } @Test @@ -410,7 +414,7 @@ public class ShellTaskOrganizerTests extends ShellTestCase { // Task listener sent to compat UI is null if top activity isn't eligible for letterbox // education. - verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */); + verifyOnCompatInfoChangedInvokedWith(taskInfo1, null /* taskListener */); // Task listener is non-null if top activity is eligible for letterbox education and task // is visible. @@ -421,7 +425,7 @@ public class ShellTaskOrganizerTests extends ShellTestCase { taskInfo2.appCompatTaskInfo.topActivityEligibleForLetterboxEducation = true; taskInfo2.isVisible = true; mOrganizer.onTaskInfoChanged(taskInfo2); - verify(mCompatUI).onCompatInfoChanged(taskInfo2, taskListener); + verifyOnCompatInfoChangedInvokedWith(taskInfo2, taskListener); // Task listener is null if task is invisible. clearInvocations(mCompatUI); @@ -431,11 +435,11 @@ public class ShellTaskOrganizerTests extends ShellTestCase { taskInfo3.appCompatTaskInfo.topActivityEligibleForLetterboxEducation = true; taskInfo3.isVisible = false; mOrganizer.onTaskInfoChanged(taskInfo3); - verify(mCompatUI).onCompatInfoChanged(taskInfo3, null /* taskListener */); + verifyOnCompatInfoChangedInvokedWith(taskInfo3, null /* taskListener */); clearInvocations(mCompatUI); mOrganizer.onTaskVanished(taskInfo1); - verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */); + verifyOnCompatInfoChangedInvokedWith(taskInfo1, null /* taskListener */); } @Test @@ -451,7 +455,7 @@ public class ShellTaskOrganizerTests extends ShellTestCase { // Task listener sent to compat UI is null if top activity doesn't request a camera // compat control. - verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */); + verifyOnCompatInfoChangedInvokedWith(taskInfo1, null /* taskListener */); // Task listener is non-null when request a camera compat control for a visible task. clearInvocations(mCompatUI); @@ -462,7 +466,7 @@ public class ShellTaskOrganizerTests extends ShellTestCase { CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED; taskInfo2.isVisible = true; mOrganizer.onTaskInfoChanged(taskInfo2); - verify(mCompatUI).onCompatInfoChanged(taskInfo2, taskListener); + verifyOnCompatInfoChangedInvokedWith(taskInfo2, taskListener); // CompatUIController#onCompatInfoChanged is called when requested state for a camera // compat control changes for a visible task. @@ -474,7 +478,7 @@ public class ShellTaskOrganizerTests extends ShellTestCase { CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED; taskInfo3.isVisible = true; mOrganizer.onTaskInfoChanged(taskInfo3); - verify(mCompatUI).onCompatInfoChanged(taskInfo3, taskListener); + verifyOnCompatInfoChangedInvokedWith(taskInfo3, taskListener); // CompatUIController#onCompatInfoChanged is called when a top activity goes in size compat // mode for a visible task that has a compat control. @@ -487,7 +491,7 @@ public class ShellTaskOrganizerTests extends ShellTestCase { CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED; taskInfo4.isVisible = true; mOrganizer.onTaskInfoChanged(taskInfo4); - verify(mCompatUI).onCompatInfoChanged(taskInfo4, taskListener); + verifyOnCompatInfoChangedInvokedWith(taskInfo4, taskListener); // Task linster is null when a camera compat control is dimissed for a visible task. clearInvocations(mCompatUI); @@ -498,7 +502,7 @@ public class ShellTaskOrganizerTests extends ShellTestCase { CAMERA_COMPAT_CONTROL_DISMISSED; taskInfo5.isVisible = true; mOrganizer.onTaskInfoChanged(taskInfo5); - verify(mCompatUI).onCompatInfoChanged(taskInfo5, null /* taskListener */); + verifyOnCompatInfoChangedInvokedWith(taskInfo5, null /* taskListener */); // Task linster is null when request a camera compat control for a invisible task. clearInvocations(mCompatUI); @@ -509,11 +513,11 @@ public class ShellTaskOrganizerTests extends ShellTestCase { CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED; taskInfo6.isVisible = false; mOrganizer.onTaskInfoChanged(taskInfo6); - verify(mCompatUI).onCompatInfoChanged(taskInfo6, null /* taskListener */); + verifyOnCompatInfoChangedInvokedWith(taskInfo6, null /* taskListener */); clearInvocations(mCompatUI); mOrganizer.onTaskVanished(taskInfo1); - verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */); + verifyOnCompatInfoChangedInvokedWith(taskInfo1, null /* taskListener */); } @Test @@ -640,7 +644,8 @@ public class ShellTaskOrganizerTests extends ShellTestCase { mOrganizer.onTaskAppeared(task1, /* leash= */ null); - mOrganizer.onSizeCompatRestartButtonClicked(task1.taskId); + mOrganizer.onSizeCompatRestartButtonClicked( + new CompatUIEvents.SizeCompatRestartButtonClicked(task1.taskId)); verify(mTaskOrganizerController).restartTaskTopActivityProcessIfVisible(task1.token); } @@ -687,6 +692,25 @@ public class ShellTaskOrganizerTests extends ShellTestCase { verify(mRecentTasksController).onTaskRunningInfoChanged(task2); } + @Test + public void testTaskVanishedCallback() { + RunningTaskInfo task1 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FULLSCREEN); + mOrganizer.onTaskAppeared(task1, /* leash= */ null); + + RunningTaskInfo[] vanishedTasks = new RunningTaskInfo[1]; + ShellTaskOrganizer.TaskVanishedListener listener = + new ShellTaskOrganizer.TaskVanishedListener() { + @Override + public void onTaskVanished(RunningTaskInfo taskInfo) { + vanishedTasks[0] = taskInfo; + } + }; + mOrganizer.addTaskVanishedListener(listener); + mOrganizer.onTaskVanished(task1); + + assertEquals(vanishedTasks[0], task1); + } + private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode) { RunningTaskInfo taskInfo = new RunningTaskInfo(); taskInfo.taskId = taskId; @@ -694,4 +718,13 @@ public class ShellTaskOrganizerTests extends ShellTestCase { taskInfo.isVisible = true; return taskInfo; } + + private void verifyOnCompatInfoChangedInvokedWith(TaskInfo taskInfo, + ShellTaskOrganizer.TaskListener listener) { + final ArgumentCaptor<CompatUIInfo> capture = ArgumentCaptor.forClass(CompatUIInfo.class); + verify(mCompatUI).onCompatInfoChanged(capture.capture()); + final CompatUIInfo captureValue = capture.getValue(); + assertEquals(captureValue.getTaskInfo(), taskInfo); + assertEquals(captureValue.getListener(), listener); + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java index 51a20ee9d090..a2df22c5468f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java @@ -26,7 +26,7 @@ import android.testing.TestableContext; import androidx.test.platform.app.InstrumentationRegistry; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import org.junit.After; import org.junit.Before; 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 bd20c1143262..55b6bd278f20 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 @@ -20,9 +20,13 @@ import static android.view.WindowManager.TRANSIT_OPEN; import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW; +import static com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationRunner.calculateParentBounds; +import static com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationRunner.shouldUseJumpCutForAnimation; import static com.android.wm.shell.transition.Transitions.TRANSIT_TASK_FRAGMENT_DRAG_RESIZE; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; @@ -32,9 +36,14 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import android.animation.Animator; +import android.annotation.NonNull; +import android.graphics.Point; +import android.graphics.Rect; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; import android.window.TransitionInfo; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -130,7 +139,7 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim @Test public void testInvalidCustomAnimation_disableAnimationOptionsPerChange() { final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0) - .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) + .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY, TRANSIT_OPEN)) .build(); info.setAnimationOptions(TransitionInfo.AnimationOptions .makeCustomAnimOptions("packageName", 0 /* enterResId */, 0 /* exitResId */, @@ -148,7 +157,7 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim @Test public void testInvalidCustomAnimation_enableAnimationOptionsPerChange() { final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0) - .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) + .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY, TRANSIT_OPEN)) .build(); info.getChanges().getFirst().setAnimationOptions(TransitionInfo.AnimationOptions .makeCustomAnimOptions("packageName", 0 /* enterResId */, 0 /* exitResId */, @@ -161,4 +170,140 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim // An invalid custom animation is equivalent to jump-cut. assertEquals(0, animator.getDuration()); } + + @DisableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG) + @Test + public void testCalculateParentBounds_flagDisabled() { + final Rect parentBounds = new Rect(0, 0, 2000, 2000); + final Rect primaryBounds = new Rect(); + final Rect secondaryBounds = new Rect(); + parentBounds.splitVertically(primaryBounds, secondaryBounds); + + final TransitionInfo.Change change = createChange(0 /* flags */); + change.setStartAbsBounds(secondaryBounds); + + final TransitionInfo.Change boundsAnimationChange = createChange(0 /* flags */); + boundsAnimationChange.setStartAbsBounds(primaryBounds); + boundsAnimationChange.setEndAbsBounds(primaryBounds); + final Rect actualParentBounds = new Rect(); + + calculateParentBounds(change, boundsAnimationChange, actualParentBounds); + + assertEquals(parentBounds, actualParentBounds); + + actualParentBounds.setEmpty(); + + boundsAnimationChange.setStartAbsBounds(secondaryBounds); + boundsAnimationChange.setEndAbsBounds(primaryBounds); + + calculateParentBounds(boundsAnimationChange, boundsAnimationChange, actualParentBounds); + + assertEquals(parentBounds, actualParentBounds); + } + + // TODO(b/243518738): Rewrite with TestParameter + @EnableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG) + @Test + public void testCalculateParentBounds_flagEnabled() { + TransitionInfo.Change change; + final TransitionInfo.Change stubChange = createChange(0 /* flags */); + final Rect actualParentBounds = new Rect(); + Rect parentBounds = new Rect(0, 0, 2000, 2000); + Rect endAbsBounds = new Rect(0, 0, 2000, 2000); + change = prepareChangeForParentBoundsCalculationTest( + new Point(0, 0) /* endRelOffset */, + endAbsBounds, + new Point() /* endParentSize */ + ); + + calculateParentBounds(change, stubChange, actualParentBounds); + + assertTrue("Parent bounds must be empty because end parent size is not set.", + actualParentBounds.isEmpty()); + + String testString = "Parent start with (0, 0)"; + change = prepareChangeForParentBoundsCalculationTest( + new Point(endAbsBounds.left - parentBounds.left, + endAbsBounds.top - parentBounds.top), + endAbsBounds, new Point(parentBounds.width(), parentBounds.height())); + + calculateParentBounds(change, stubChange, actualParentBounds); + + assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds, + actualParentBounds); + + testString = "Container not start with (0, 0)"; + parentBounds = new Rect(0, 0, 2000, 2000); + endAbsBounds = new Rect(1000, 500, 2000, 1500); + change = prepareChangeForParentBoundsCalculationTest( + new Point(endAbsBounds.left - parentBounds.left, + endAbsBounds.top - parentBounds.top), + endAbsBounds, new Point(parentBounds.width(), parentBounds.height())); + + calculateParentBounds(change, stubChange, actualParentBounds); + + assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds, + actualParentBounds); + + testString = "Parent container on the right"; + parentBounds = new Rect(1000, 0, 2000, 2000); + endAbsBounds = new Rect(1000, 500, 1500, 1500); + change = prepareChangeForParentBoundsCalculationTest( + new Point(endAbsBounds.left - parentBounds.left, + endAbsBounds.top - parentBounds.top), + endAbsBounds, new Point(parentBounds.width(), parentBounds.height())); + + calculateParentBounds(change, stubChange, actualParentBounds); + + assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds, + actualParentBounds); + + testString = "Parent container on the bottom"; + parentBounds = new Rect(0, 1000, 2000, 2000); + endAbsBounds = new Rect(500, 1500, 1500, 2000); + change = prepareChangeForParentBoundsCalculationTest( + new Point(endAbsBounds.left - parentBounds.left, + endAbsBounds.top - parentBounds.top), + endAbsBounds, new Point(parentBounds.width(), parentBounds.height())); + + calculateParentBounds(change, stubChange, actualParentBounds); + + assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds, + actualParentBounds); + + testString = "Parent container in the middle"; + parentBounds = new Rect(500, 500, 1500, 1500); + endAbsBounds = new Rect(1000, 500, 1500, 1000); + change = prepareChangeForParentBoundsCalculationTest( + new Point(endAbsBounds.left - parentBounds.left, + endAbsBounds.top - parentBounds.top), + endAbsBounds, new Point(parentBounds.width(), parentBounds.height())); + + calculateParentBounds(change, stubChange, actualParentBounds); + + assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds, + actualParentBounds); + } + + @Test + public void testShouldUseJumpCutForAnimation() { + final Animation noopAnimation = new AlphaAnimation(0f, 1f); + assertTrue("Animation without duration should use jump cut.", + shouldUseJumpCutForAnimation(noopAnimation)); + + final Animation alphaAnimation = new AlphaAnimation(0f, 1f); + alphaAnimation.setDuration(100); + assertFalse("Animation with duration should not use jump cut.", + shouldUseJumpCutForAnimation(alphaAnimation)); + } + + @NonNull + private static TransitionInfo.Change prepareChangeForParentBoundsCalculationTest( + @NonNull Point endRelOffset, @NonNull Rect endAbsBounds, @NonNull Point endParentSize) { + final TransitionInfo.Change change = createChange(0 /* flags */); + change.setEndRelOffset(endRelOffset.x, endRelOffset.y); + change.setEndAbsBounds(endAbsBounds); + change.setEndParentSize(endParentSize.x, endParentSize.y); + return change; + } } 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 0b2265d4ce9c..c18d7ec821b6 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 @@ -16,6 +16,7 @@ package com.android.wm.shell.activityembedding; +import static android.view.WindowManager.TRANSIT_NONE; import static android.window.TransitionInfo.FLAG_FILLS_TASK; import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; @@ -31,6 +32,7 @@ import android.annotation.NonNull; import android.graphics.Rect; import android.os.IBinder; import android.view.SurfaceControl; +import android.view.WindowManager; import android.window.TransitionInfo; import android.window.WindowContainerToken; @@ -82,11 +84,27 @@ abstract class ActivityEmbeddingAnimationTestBase extends ShellTestCase { spyOn(mFinishCallback); } - /** Creates a mock {@link TransitionInfo.Change}. */ + /** + * Creates a mock {@link TransitionInfo.Change}. + * + * @param flags the {@link TransitionInfo.ChangeFlags} of the change + */ static TransitionInfo.Change createChange(@TransitionInfo.ChangeFlags int flags) { + return createChange(flags, TRANSIT_NONE); + } + + /** + * Creates a mock {@link TransitionInfo.Change}. + * + * @param flags the {@link TransitionInfo.ChangeFlags} of the change + * @param mode the transition mode of the change + */ + static TransitionInfo.Change createChange(@TransitionInfo.ChangeFlags int flags, + @WindowManager.TransitionType int mode) { TransitionInfo.Change c = new TransitionInfo.Change(mock(WindowContainerToken.class), mock(SurfaceControl.class)); c.setFlags(flags); + c.setMode(mode); return c; } 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 index 636c6326d213..f5847cc27071 100644 --- 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 @@ -70,7 +70,7 @@ public class DividerViewTest extends ShellTestCase { mContext, configuration, mCallbacks); splitWindowManager.init(mSplitLayout, new InsetsState(), false /* isRestoring */); - mDividerView = spy((DividerView) splitWindowManager.getDividerView()); + mDividerView = spy(splitWindowManager.getDividerView()); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt index 4cd2a366f5eb..ecaf970ae389 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt @@ -16,8 +16,10 @@ package com.android.wm.shell.compatui +import android.content.ComponentName import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest +import com.android.internal.R import com.android.wm.shell.ShellTestCase import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask import org.junit.Assert.assertFalse @@ -34,26 +36,55 @@ import org.junit.runner.RunWith @RunWith(AndroidTestingRunner::class) @SmallTest class AppCompatUtilsTest : ShellTestCase() { - @Test - fun testIsSingleTopActivityTranslucent() { - assertTrue(isSingleTopActivityTranslucent( + fun testIsTopActivityExemptFromDesktopWindowing_topActivityTransparent() { + assertTrue(isTopActivityExemptFromDesktopWindowing(mContext, createFreeformTask(/* displayId */ 0) .apply { isTopActivityTransparent = true numActivities = 1 })) - assertFalse(isSingleTopActivityTranslucent( + assertFalse(isTopActivityExemptFromDesktopWindowing(mContext, createFreeformTask(/* displayId */ 0) .apply { isTopActivityTransparent = true numActivities = 0 })) - assertFalse(isSingleTopActivityTranslucent( + } + + @Test + fun testIsTopActivityExemptFromDesktopWindowing_singleTopActivity() { + assertTrue(isTopActivityExemptFromDesktopWindowing(mContext, + createFreeformTask(/* displayId */ 0) + .apply { + isTopActivityTransparent = true + numActivities = 1 + })) + assertFalse(isTopActivityExemptFromDesktopWindowing(mContext, createFreeformTask(/* displayId */ 0) .apply { isTopActivityTransparent = false numActivities = 1 })) } -}
\ No newline at end of file + + @Test + fun testIsTopActivityExemptFromDesktopWindowing__topActivityStyleFloating() { + assertFalse(isTopActivityExemptFromDesktopWindowing(mContext, + createFreeformTask(/* displayId */ 0) + .apply { + isTopActivityStyleFloating = true + })) + } + + @Test + fun testIsTopActivityExemptFromDesktopWindowing_systemUiTask() { + val systemUIPackageName = context.resources.getString(R.string.config_systemUi) + val baseComponent = ComponentName(systemUIPackageName, /* class */ "") + assertTrue(isTopActivityExemptFromDesktopWindowing(mContext, + createFreeformTask(/* displayId */ 0) + .apply { + baseActivity = baseComponent + })) + } +} 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 9c008647104a..fc7a7770b8ca 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 @@ -38,6 +38,9 @@ import android.app.CameraCompatTaskInfo.CameraCompatControlState; import android.app.TaskInfo; import android.content.Context; import android.content.res.Configuration; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.testing.AndroidTestingRunner; import android.view.InsetsSource; import android.view.InsetsState; @@ -45,6 +48,7 @@ import android.view.accessibility.AccessibilityManager; import androidx.test.filters.SmallTest; +import com.android.window.flags.Flags; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.DisplayController; @@ -55,6 +59,7 @@ 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.api.CompatUIInfo; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; @@ -63,6 +68,7 @@ import dagger.Lazy; import org.junit.Assert; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -82,6 +88,10 @@ public class CompatUIControllerTest extends ShellTestCase { private static final int DISPLAY_ID = 0; private static final int TASK_ID = 12; + @Rule + public final CheckFlagsRule mCheckFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); + private CompatUIController mController; private ShellInit mShellInit; @Mock @@ -168,28 +178,32 @@ public class CompatUIControllerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK) public void instantiateController_addInitCallback() { verify(mShellInit, times(1)).addInitCallback(any(), any()); } @Test + @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK) public void instantiateController_registerKeyguardChangeListener() { verify(mMockShellController, times(1)).addKeyguardChangeListener(any()); } @Test + @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK) public void testListenerRegistered() { verify(mMockDisplayController).addDisplayWindowListener(mController); verify(mMockImeController).addPositionProcessor(mController); } @Test + @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK) public void testOnCompatInfoChanged() { TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN); // Verify that the compat controls are added with non-null task listener. - mController.onCompatInfoChanged(taskInfo, mMockTaskListener); + mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener)); verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener)); verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo), @@ -202,7 +216,7 @@ public class CompatUIControllerTest extends ShellTestCase { clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController); taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED); - mController.onCompatInfoChanged(taskInfo, mMockTaskListener); + mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener)); verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */ true); @@ -213,9 +227,9 @@ public class CompatUIControllerTest extends ShellTestCase { // Verify that compat controls and letterbox education are removed with null task listener. clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController); - mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID, + mController.onCompatInfoChanged(new CompatUIInfo(createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), - /* taskListener= */ null); + /* taskListener= */ null)); verify(mMockCompatLayout).release(); verify(mMockLetterboxEduLayout).release(); @@ -223,6 +237,7 @@ public class CompatUIControllerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK) public void testOnCompatInfoChanged_createLayoutReturnsFalse() { doReturn(false).when(mMockCompatLayout).createLayout(anyBoolean()); doReturn(false).when(mMockLetterboxEduLayout).createLayout(anyBoolean()); @@ -230,7 +245,7 @@ public class CompatUIControllerTest extends ShellTestCase { TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN); - mController.onCompatInfoChanged(taskInfo, mMockTaskListener); + mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener)); verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener)); verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo), @@ -240,7 +255,7 @@ public class CompatUIControllerTest extends ShellTestCase { // Verify that the layout is created again. clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController); - mController.onCompatInfoChanged(taskInfo, mMockTaskListener); + mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener)); verify(mMockCompatLayout, never()).updateCompatInfo(any(), any(), anyBoolean()); verify(mMockLetterboxEduLayout, never()).updateCompatInfo(any(), any(), anyBoolean()); @@ -253,6 +268,7 @@ public class CompatUIControllerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK) public void testOnCompatInfoChanged_updateCompatInfoReturnsFalse() { doReturn(false).when(mMockCompatLayout).updateCompatInfo(any(), any(), anyBoolean()); doReturn(false).when(mMockLetterboxEduLayout).updateCompatInfo(any(), any(), anyBoolean()); @@ -260,7 +276,7 @@ public class CompatUIControllerTest extends ShellTestCase { TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN); - mController.onCompatInfoChanged(taskInfo, mMockTaskListener); + mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener)); verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener)); verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo), @@ -270,7 +286,7 @@ public class CompatUIControllerTest extends ShellTestCase { clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mMockRestartDialogLayout, mController); - mController.onCompatInfoChanged(taskInfo, mMockTaskListener); + mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener)); verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */ true); @@ -282,7 +298,7 @@ public class CompatUIControllerTest extends ShellTestCase { // Verify that the layout is created again. clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mMockRestartDialogLayout, mController); - mController.onCompatInfoChanged(taskInfo, mMockTaskListener); + mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener)); verify(mMockCompatLayout, never()).updateCompatInfo(any(), any(), anyBoolean()); verify(mMockLetterboxEduLayout, never()).updateCompatInfo(any(), any(), anyBoolean()); @@ -296,6 +312,7 @@ public class CompatUIControllerTest extends ShellTestCase { @Test + @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK) public void testOnDisplayAdded() { mController.onDisplayAdded(DISPLAY_ID); mController.onDisplayAdded(DISPLAY_ID + 1); @@ -305,11 +322,11 @@ public class CompatUIControllerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK) public void testOnDisplayRemoved() { mController.onDisplayAdded(DISPLAY_ID); - mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID, - /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), - mMockTaskListener); + mController.onCompatInfoChanged(new CompatUIInfo(createTaskInfo(DISPLAY_ID, TASK_ID, + /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener)); mController.onDisplayRemoved(DISPLAY_ID + 1); @@ -328,9 +345,10 @@ public class CompatUIControllerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK) public void testOnDisplayConfigurationChanged() { - mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID, - /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener); + mController.onCompatInfoChanged(new CompatUIInfo(createTaskInfo(DISPLAY_ID, TASK_ID, + /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener)); mController.onDisplayConfigurationChanged(DISPLAY_ID + 1, new Configuration()); @@ -346,10 +364,11 @@ public class CompatUIControllerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK) public void testInsetsChanged() { mController.onDisplayAdded(DISPLAY_ID); - mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID, - /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener); + mController.onCompatInfoChanged(new CompatUIInfo(createTaskInfo(DISPLAY_ID, TASK_ID, + /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener)); InsetsState insetsState = new InsetsState(); InsetsSource insetsSource = new InsetsSource( InsetsSource.createId(null, 0, navigationBars()), navigationBars()); @@ -373,9 +392,10 @@ public class CompatUIControllerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK) public void testChangeLayoutsVisibilityOnImeShowHide() { - mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID, - /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener); + mController.onCompatInfoChanged(new CompatUIInfo(createTaskInfo(DISPLAY_ID, TASK_ID, + /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener)); // Verify that the restart button is hidden after IME is showing. mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ true); @@ -387,7 +407,7 @@ public class CompatUIControllerTest extends ShellTestCase { // Verify button remains hidden while IME is showing. TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN); - mController.onCompatInfoChanged(taskInfo, mMockTaskListener); + mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener)); verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */ false); @@ -405,9 +425,10 @@ public class CompatUIControllerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK) public void testChangeLayoutsVisibilityOnKeyguardShowingChanged() { - mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID, - /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener); + mController.onCompatInfoChanged(new CompatUIInfo(createTaskInfo(DISPLAY_ID, TASK_ID, + /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener)); // Verify that the restart button is hidden after keyguard becomes showing. mController.onKeyguardVisibilityChanged(true, false, false); @@ -419,7 +440,7 @@ public class CompatUIControllerTest extends ShellTestCase { // Verify button remains hidden while keyguard is showing. TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN); - mController.onCompatInfoChanged(taskInfo, mMockTaskListener); + mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener)); verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */ false); @@ -437,9 +458,10 @@ public class CompatUIControllerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK) public void testLayoutsRemainHiddenOnKeyguardShowingFalseWhenImeIsShowing() { - mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID, - /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener); + mController.onCompatInfoChanged(new CompatUIInfo(createTaskInfo(DISPLAY_ID, TASK_ID, + /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener)); mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ true); mController.onKeyguardVisibilityChanged(true, false, false); @@ -466,9 +488,10 @@ public class CompatUIControllerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK) public void testLayoutsRemainHiddenOnImeHideWhenKeyguardIsShowing() { - mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID, - /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener); + mController.onCompatInfoChanged(new CompatUIInfo(createTaskInfo(DISPLAY_ID, TASK_ID, + /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener)); mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ true); mController.onKeyguardVisibilityChanged(true, false, false); @@ -495,6 +518,7 @@ public class CompatUIControllerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK) public void testRestartLayoutRecreatedIfNeeded() { final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN); @@ -502,14 +526,15 @@ public class CompatUIControllerTest extends ShellTestCase { .needsToBeRecreated(any(TaskInfo.class), any(ShellTaskOrganizer.TaskListener.class)); - mController.onCompatInfoChanged(taskInfo, mMockTaskListener); - mController.onCompatInfoChanged(taskInfo, mMockTaskListener); + mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener)); + mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener)); verify(mMockRestartDialogLayout, times(2)) .createLayout(anyBoolean()); } @Test + @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK) public void testRestartLayoutNotRecreatedIfNotNeeded() { final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN); @@ -517,14 +542,15 @@ public class CompatUIControllerTest extends ShellTestCase { .needsToBeRecreated(any(TaskInfo.class), any(ShellTaskOrganizer.TaskListener.class)); - mController.onCompatInfoChanged(taskInfo, mMockTaskListener); - mController.onCompatInfoChanged(taskInfo, mMockTaskListener); + mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener)); + mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener)); verify(mMockRestartDialogLayout, times(1)) .createLayout(anyBoolean()); } @Test + @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK) public void testUpdateActiveTaskInfo_newTask_visibleAndFocused_updated() { // Simulate user aspect ratio button being shown for previous task mController.setHasShownUserAspectRatioSettingsButton(true); @@ -545,6 +571,7 @@ public class CompatUIControllerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK) public void testUpdateActiveTaskInfo_newTask_notVisibleOrFocused_notUpdated() { // Create new task final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, @@ -606,6 +633,7 @@ public class CompatUIControllerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK) public void testUpdateActiveTaskInfo_sameTask_notUpdated() { // Create new task final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, @@ -634,6 +662,7 @@ public class CompatUIControllerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK) public void testUpdateActiveTaskInfo_transparentTask_notUpdated() { // Create new task final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, @@ -674,7 +703,7 @@ public class CompatUIControllerTest extends ShellTestCase { CAMERA_COMPAT_CONTROL_HIDDEN); taskInfo.appCompatTaskInfo.isLetterboxEducationEnabled = false; - mController.onCompatInfoChanged(taskInfo, mMockTaskListener); + mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener)); verify(mController, never()).createLetterboxEduWindowManager(any(), eq(taskInfo), eq(mMockTaskListener)); 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 cd3e8cb0e8e1..33d69f5c34c8 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 @@ -23,6 +23,7 @@ import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_S import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; @@ -31,6 +32,9 @@ import android.app.ActivityManager; import android.app.CameraCompatTaskInfo.CameraCompatControlState; import android.app.TaskInfo; import android.graphics.Rect; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.testing.AndroidTestingRunner; import android.util.Pair; import android.view.LayoutInflater; @@ -40,16 +44,20 @@ import android.widget.LinearLayout; import androidx.test.filters.SmallTest; +import com.android.window.flags.Flags; import com.android.wm.shell.R; 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.CompatUIController.CompatUIHintsState; +import com.android.wm.shell.compatui.api.CompatUIEvent; +import com.android.wm.shell.compatui.impl.CompatUIEvents; import junit.framework.Assert; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -70,8 +78,12 @@ public class CompatUILayoutTest extends ShellTestCase { private static final int TASK_ID = 1; + @Rule + public final CheckFlagsRule mCheckFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); + @Mock private SyncTransactionQueue mSyncTransactionQueue; - @Mock private CompatUIController.CompatUICallback mCallback; + @Mock private Consumer<CompatUIEvent> mCallback; @Mock private Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnRestartButtonClicked; @Mock private ShellTaskOrganizer.TaskListener mTaskListener; @Mock private SurfaceControlViewHost mViewHost; @@ -101,6 +113,7 @@ public class CompatUILayoutTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK) public void testOnClickForRestartButton() { final ImageButton button = mLayout.findViewById(R.id.size_compat_restart_button); button.performClick(); @@ -117,6 +130,7 @@ public class CompatUILayoutTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK) public void testOnLongClickForRestartButton() { doNothing().when(mWindowManager).onRestartButtonLongClicked(); @@ -127,6 +141,7 @@ public class CompatUILayoutTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK) public void testOnClickForSizeCompatHint() { mWindowManager.mHasSizeCompat = true; mWindowManager.createLayout(/* canShow= */ true); @@ -137,6 +152,7 @@ public class CompatUILayoutTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK) public void testUpdateCameraTreatmentButton_treatmentAppliedByDefault() { mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED; mWindowManager.createLayout(/* canShow= */ true); @@ -145,16 +161,17 @@ public class CompatUILayoutTest extends ShellTestCase { button.performClick(); verify(mWindowManager).onCameraTreatmentButtonClicked(); - verify(mCallback).onCameraControlStateUpdated( - TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED); + verifyOnCameraControlStateUpdatedInvokedWith(TASK_ID, + CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED); button.performClick(); - verify(mCallback).onCameraControlStateUpdated( - TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED); + verifyOnCameraControlStateUpdatedInvokedWith(TASK_ID, + CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED); } @Test + @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK) public void testUpdateCameraTreatmentButton_treatmentSuggestedByDefault() { mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED; mWindowManager.createLayout(/* canShow= */ true); @@ -163,16 +180,17 @@ public class CompatUILayoutTest extends ShellTestCase { button.performClick(); verify(mWindowManager).onCameraTreatmentButtonClicked(); - verify(mCallback).onCameraControlStateUpdated( - TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED); + verifyOnCameraControlStateUpdatedInvokedWith(TASK_ID, + CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED); button.performClick(); - verify(mCallback).onCameraControlStateUpdated( - TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED); + verifyOnCameraControlStateUpdatedInvokedWith(TASK_ID, + CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED); } @Test + @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK) public void testOnCameraDismissButtonClicked() { mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED; mWindowManager.createLayout(/* canShow= */ true); @@ -181,12 +199,12 @@ public class CompatUILayoutTest extends ShellTestCase { button.performClick(); verify(mWindowManager).onCameraDismissButtonClicked(); - verify(mCallback).onCameraControlStateUpdated( - TASK_ID, CAMERA_COMPAT_CONTROL_DISMISSED); + verifyOnCameraControlStateUpdatedInvokedWith(TASK_ID, CAMERA_COMPAT_CONTROL_DISMISSED); verify(mLayout).setCameraControlVisibility(/* show */ false); } @Test + @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK) public void testOnLongClickForCameraTreatmentButton() { doNothing().when(mWindowManager).onCameraButtonLongClicked(); @@ -198,6 +216,7 @@ public class CompatUILayoutTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK) public void testOnLongClickForCameraDismissButton() { doNothing().when(mWindowManager).onCameraButtonLongClicked(); @@ -208,6 +227,7 @@ public class CompatUILayoutTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK) public void testOnClickForCameraCompatHint() { mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED; mWindowManager.createLayout(/* canShow= */ true); @@ -229,4 +249,15 @@ public class CompatUILayoutTest extends ShellTestCase { taskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 2000, 2000)); return taskInfo; } + + private void verifyOnCameraControlStateUpdatedInvokedWith(int taskId, int state) { + final ArgumentCaptor<CompatUIEvent> captureValue = ArgumentCaptor.forClass( + CompatUIEvent.class); + verify(mCallback).accept(captureValue.capture()); + final CompatUIEvents.CameraControlStateUpdated compatUIEvent = + (CompatUIEvents.CameraControlStateUpdated) captureValue.getValue(); + Assert.assertEquals((compatUIEvent).getTaskId(), taskId); + Assert.assertEquals((compatUIEvent).getState(), state); + clearInvocations(mCallback); + } } 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 41a81c1a9921..eb3da8f65b5d 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 @@ -25,6 +25,7 @@ import static android.view.WindowInsets.Type.navigationBars; import static android.view.WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -42,6 +43,9 @@ import android.app.CameraCompatTaskInfo; import android.app.TaskInfo; import android.content.res.Configuration; import android.graphics.Rect; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.platform.test.flag.junit.SetFlagsRule; import android.testing.AndroidTestingRunner; import android.util.Pair; @@ -60,6 +64,8 @@ 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.CompatUIController.CompatUIHintsState; +import com.android.wm.shell.compatui.api.CompatUIEvent; +import com.android.wm.shell.compatui.impl.CompatUIEvents; import junit.framework.Assert; @@ -85,12 +91,16 @@ public class CompatUIWindowManagerTest extends ShellTestCase { @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT); + @Rule + public final CheckFlagsRule mCheckFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); + private static final int TASK_ID = 1; private static final int TASK_WIDTH = 2000; private static final int TASK_HEIGHT = 2000; @Mock private SyncTransactionQueue mSyncTransactionQueue; - @Mock private CompatUIController.CompatUICallback mCallback; + @Mock private Consumer<CompatUIEvent> mCallback; @Mock private Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnRestartButtonClicked; @Mock private ShellTaskOrganizer.TaskListener mTaskListener; @Mock private CompatUILayout mLayout; @@ -129,6 +139,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testCreateSizeCompatButton() { // Doesn't create layout if show is false. mWindowManager.mHasSizeCompat = true; @@ -174,6 +185,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testCreateCameraCompatControl() { // Doesn't create layout if show is false. mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED; @@ -212,6 +224,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testRelease() { mWindowManager.mHasSizeCompat = true; mWindowManager.createLayout(/* canShow= */ true); @@ -224,6 +237,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testUpdateCompatInfo() { mWindowManager.mHasSizeCompat = true; mWindowManager.createLayout(/* canShow= */ true); @@ -315,6 +329,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testUpdateCompatInfoLayoutNotInflatedYet() { mWindowManager.createLayout(/* canShow= */ false); @@ -347,6 +362,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testUpdateDisplayLayout() { final DisplayInfo displayInfo = new DisplayInfo(); displayInfo.logicalWidth = 1000; @@ -366,6 +382,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testUpdateDisplayLayoutInsets() { final DisplayInfo displayInfo = new DisplayInfo(); displayInfo.logicalWidth = 1000; @@ -390,6 +407,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testUpdateVisibility() { // Create button if it is not created. mWindowManager.mLayout = null; @@ -415,6 +433,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testAttachToParentSurface() { final SurfaceControl.Builder b = new SurfaceControl.Builder(); mWindowManager.attachToParentSurface(b); @@ -423,37 +442,38 @@ public class CompatUIWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testOnCameraDismissButtonClicked() { mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED; mWindowManager.createLayout(/* canShow= */ true); clearInvocations(mLayout); mWindowManager.onCameraDismissButtonClicked(); - verify(mCallback).onCameraControlStateUpdated(TASK_ID, CAMERA_COMPAT_CONTROL_DISMISSED); + verifyOnCameraControlStateUpdatedInvokedWith(TASK_ID, CAMERA_COMPAT_CONTROL_DISMISSED); verify(mLayout).setCameraControlVisibility(/* show= */ false); } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testOnCameraTreatmentButtonClicked() { mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED; mWindowManager.createLayout(/* canShow= */ true); clearInvocations(mLayout); mWindowManager.onCameraTreatmentButtonClicked(); - verify(mCallback).onCameraControlStateUpdated( - TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED); - verify(mLayout).updateCameraTreatmentButton( + verifyOnCameraControlStateUpdatedInvokedWith(TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED); + verify(mLayout).updateCameraTreatmentButton(CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED); mWindowManager.onCameraTreatmentButtonClicked(); - verify(mCallback).onCameraControlStateUpdated( - TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED); - verify(mLayout).updateCameraTreatmentButton( + verifyOnCameraControlStateUpdatedInvokedWith(TASK_ID, CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED); + verify(mLayout).updateCameraTreatmentButton(CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED); } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testOnRestartButtonClicked() { mWindowManager.onRestartButtonClicked(); @@ -468,8 +488,9 @@ public class CompatUIWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testOnRestartButtonLongClicked_showHint() { - // Not create hint popup. + // Not create hint popup. mWindowManager.mHasSizeCompat = true; mWindowManager.mCompatUIHintsState.mHasShownSizeCompatHint = true; mWindowManager.createLayout(/* canShow= */ true); @@ -483,6 +504,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testOnCameraControlLongClicked_showHint() { // Not create hint popup. mWindowManager.mCameraCompatControlState = CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED; @@ -498,6 +520,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testWhenDockedStateHasChanged_needsToBeRecreated() { ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo(); newTaskInfo.configuration.uiMode |= Configuration.UI_MODE_TYPE_DESK; @@ -506,6 +529,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testShouldShowSizeCompatRestartButton() { mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_HIDE_SCM_BUTTON); doReturn(85).when(mCompatUIConfiguration).getHideSizeCompatRestartButtonTolerance(); @@ -558,4 +582,15 @@ public class CompatUIWindowManagerTest extends ShellTestCase { taskInfo.configuration.smallestScreenWidthDp = LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP; return taskInfo; } + + private void verifyOnCameraControlStateUpdatedInvokedWith(int taskId, int state) { + final ArgumentCaptor<CompatUIEvent> captureValue = ArgumentCaptor.forClass( + CompatUIEvent.class); + verify(mCallback).accept(captureValue.capture()); + final CompatUIEvents.CameraControlStateUpdated compatUIEvent = + (CompatUIEvents.CameraControlStateUpdated) captureValue.getValue(); + Assert.assertEquals((compatUIEvent).getTaskId(), taskId); + Assert.assertEquals((compatUIEvent).getState(), state); + clearInvocations(mCallback); + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java index 172c263ab0f6..e8191db13084 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java @@ -16,12 +16,17 @@ package com.android.wm.shell.compatui; +import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.testing.AndroidTestingRunner; import android.view.LayoutInflater; import android.view.View; @@ -32,6 +37,7 @@ import com.android.wm.shell.R; import com.android.wm.shell.ShellTestCase; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -54,6 +60,10 @@ public class LetterboxEduDialogLayoutTest extends ShellTestCase { private View mDismissButton; private View mDialogContainer; + @Rule + public final CheckFlagsRule mCheckFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); + @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -66,6 +76,7 @@ public class LetterboxEduDialogLayoutTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testOnFinishInflate() { assertEquals(mLayout.getDialogContainerView(), mLayout.findViewById(R.id.letterbox_education_dialog_container)); @@ -76,6 +87,7 @@ public class LetterboxEduDialogLayoutTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testOnDismissButtonClicked() { assertTrue(mDismissButton.performClick()); @@ -83,6 +95,7 @@ public class LetterboxEduDialogLayoutTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testOnBackgroundClicked() { assertTrue(mLayout.performClick()); @@ -90,6 +103,7 @@ public class LetterboxEduDialogLayoutTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testOnDialogContainerClicked() { assertTrue(mDialogContainer.performClick()); @@ -97,6 +111,7 @@ public class LetterboxEduDialogLayoutTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testSetDismissOnClickListenerNull() { mLayout.setDismissOnClickListener(null); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java index a60a1cbb435f..b5664ac113fa 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java @@ -19,6 +19,7 @@ package com.android.wm.shell.compatui; import static android.content.res.Configuration.UI_MODE_NIGHT_YES; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK; import static com.google.common.truth.Truth.assertThat; @@ -37,6 +38,9 @@ import android.app.ActivityManager; import android.app.TaskInfo; import android.graphics.Insets; import android.graphics.Rect; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.testing.AndroidTestingRunner; import android.util.Pair; import android.view.DisplayCutout; @@ -61,6 +65,7 @@ import com.android.wm.shell.transition.Transitions; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -116,6 +121,10 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase { private CompatUIConfiguration mCompatUIConfiguration; private TestShellExecutor mExecutor; + @Rule + public final CheckFlagsRule mCheckFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); + @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -153,6 +162,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testCreateLayout_notEligible_doesNotCreateLayout() { LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ false); @@ -162,6 +172,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testCreateLayout_eligibleAndDocked_doesNotCreateLayout() { LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true, /* isDocked */ true); @@ -172,6 +183,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testCreateLayout_taskBarEducationIsShowing_doesNotCreateLayout() { LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true, USER_ID_1, /* isTaskbarEduShowing= */ true); @@ -182,6 +194,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testCreateLayout_canShowFalse_returnsTrueButDoesNotCreateLayout() { LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true); @@ -192,6 +205,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testCreateLayout_canShowTrue_createsLayoutCorrectly() { LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true); @@ -238,6 +252,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testCreateLayout_alreadyShownToUser_createsLayoutForOtherUserOnly() { LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true, USER_ID_1, /* isTaskbarEduShowing= */ false); @@ -271,6 +286,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testCreateLayout_windowManagerReleasedBeforeTransitionsIsIdle_doesNotStartAnim() { LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true); @@ -288,6 +304,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testUpdateCompatInfo_updatesLayoutCorrectly() { LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true); @@ -316,6 +333,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testUpdateCompatInfo_notEligibleUntilUpdate_createsLayoutAfterUpdate() { LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ false); @@ -329,6 +347,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testUpdateCompatInfo_canShowFalse_doesNothing() { LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true); @@ -343,6 +362,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testUpdateDisplayLayout_updatesLayoutCorrectly() { LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true); @@ -364,6 +384,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testRelease_animationIsCancelled() { LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true); @@ -374,6 +395,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testDeviceThemeChange_educationDialogUnseen_recreated() { LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true); ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduLayoutTest.java index 4f71b83179b1..0da14d673732 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduLayoutTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduLayoutTest.java @@ -16,6 +16,8 @@ package com.android.wm.shell.compatui; +import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK; + import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; @@ -23,6 +25,9 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import android.app.TaskInfo; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.LayoutInflater; @@ -34,6 +39,7 @@ import com.android.wm.shell.R; import com.android.wm.shell.ShellTestCase; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -62,6 +68,10 @@ public class ReachabilityEduLayoutTest extends ShellTestCase { @Mock private TaskInfo mTaskInfo; + @Rule + public final CheckFlagsRule mCheckFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); + @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -74,6 +84,7 @@ public class ReachabilityEduLayoutTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testOnFinishInflate() { assertNotNull(mMoveUpButton); assertNotNull(mMoveDownButton); @@ -82,6 +93,7 @@ public class ReachabilityEduLayoutTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void handleVisibility_educationNotEnabled_buttonsAreHidden() { mLayout.handleVisibility(/* horizontalEnabled */ false, /* verticalEnabled */ false, /* letterboxVerticalPosition */ @@ -94,6 +106,7 @@ public class ReachabilityEduLayoutTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void handleVisibility_horizontalEducationEnableduiConfigurationIsUpdated() { mLayout.handleVisibility(/* horizontalEnabled */ true, /* verticalEnabled */ false, /* letterboxVerticalPosition */ -1, /* letterboxHorizontalPosition */ @@ -106,6 +119,7 @@ public class ReachabilityEduLayoutTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void handleVisibility_verticalEducationEnabled_uiConfigurationIsUpdated() { mLayout.handleVisibility(/* horizontalEnabled */ false, /* verticalEnabled */ true, /* letterboxVerticalPosition */ 0, /* letterboxHorizontalPosition */ 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 5867a8553d53..eafb41470cda 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 @@ -16,12 +16,17 @@ package com.android.wm.shell.compatui; +import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK; + import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import android.app.ActivityManager; import android.app.TaskInfo; import android.content.res.Configuration; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.testing.AndroidTestingRunner; import androidx.test.filters.SmallTest; @@ -35,6 +40,7 @@ import com.android.wm.shell.common.SyncTransactionQueue; import junit.framework.Assert; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -65,6 +71,10 @@ public class ReachabilityEduWindowManagerTest extends ShellTestCase { private TaskInfo mTaskInfo; private ReachabilityEduWindowManager mWindowManager; + @Rule + public final CheckFlagsRule mCheckFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); + @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -80,6 +90,7 @@ public class ReachabilityEduWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testCreateLayout_notEligible_doesNotCreateLayout() { assertFalse(mWindowManager.createLayout(/* canShow= */ true)); @@ -87,6 +98,7 @@ public class ReachabilityEduWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testWhenDockedStateHasChanged_needsToBeRecreated() { ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo(); newTaskInfo.configuration.uiMode = @@ -97,6 +109,7 @@ public class ReachabilityEduWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testWhenDarkLightThemeHasChanged_needsToBeRecreated() { ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo(); mTaskInfo.configuration.uiMode = diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java index e2dcdb0e91b2..6b0c5dd2e1c7 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java @@ -16,6 +16,8 @@ package com.android.wm.shell.compatui; +import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -23,6 +25,9 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.testing.AndroidTestingRunner; import android.view.LayoutInflater; import android.view.View; @@ -34,6 +39,7 @@ import com.android.wm.shell.R; import com.android.wm.shell.ShellTestCase; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -60,6 +66,10 @@ public class RestartDialogLayoutTest extends ShellTestCase { private View mDialogContainer; private CheckBox mDontRepeatCheckBox; + @Rule + public final CheckFlagsRule mCheckFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); + @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -76,6 +86,7 @@ public class RestartDialogLayoutTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testOnFinishInflate() { assertEquals(mLayout.getDialogContainerView(), mLayout.findViewById(R.id.letterbox_restart_dialog_container)); @@ -86,6 +97,7 @@ public class RestartDialogLayoutTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testOnDismissButtonClicked() { assertTrue(mDismissButton.performClick()); @@ -93,6 +105,7 @@ public class RestartDialogLayoutTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testOnRestartButtonClickedWithoutCheckbox() { mDontRepeatCheckBox.setChecked(false); assertTrue(mRestartButton.performClick()); @@ -101,6 +114,7 @@ public class RestartDialogLayoutTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testOnRestartButtonClickedWithCheckbox() { mDontRepeatCheckBox.setChecked(true); assertTrue(mRestartButton.performClick()); @@ -109,6 +123,7 @@ public class RestartDialogLayoutTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testOnBackgroundClickedDoesntDismiss() { assertFalse(mLayout.performClick()); @@ -116,6 +131,7 @@ public class RestartDialogLayoutTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testOnDialogContainerClicked() { assertTrue(mDialogContainer.performClick()); @@ -124,6 +140,7 @@ public class RestartDialogLayoutTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testSetDismissOnClickListenerNull() { mLayout.setDismissOnClickListener(null); @@ -135,6 +152,7 @@ public class RestartDialogLayoutTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testSetRestartOnClickListenerNull() { mLayout.setRestartOnClickListener(null); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogWindowManagerTest.java index 9f109a1d0f50..cfeef90cb4b6 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogWindowManagerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogWindowManagerTest.java @@ -16,9 +16,14 @@ package com.android.wm.shell.compatui; +import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK; + import android.app.ActivityManager; import android.app.TaskInfo; import android.content.res.Configuration; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.testing.AndroidTestingRunner; import android.util.Pair; @@ -33,6 +38,7 @@ import com.android.wm.shell.transition.Transitions; import junit.framework.Assert; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -60,6 +66,10 @@ public class RestartDialogWindowManagerTest extends ShellTestCase { private RestartDialogWindowManager mWindowManager; private TaskInfo mTaskInfo; + @Rule + public final CheckFlagsRule mCheckFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); + @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -76,6 +86,7 @@ public class RestartDialogWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testWhenDockedStateHasChanged_needsToBeRecreated() { ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo(); newTaskInfo.configuration.uiMode = @@ -86,6 +97,7 @@ public class RestartDialogWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testWhenDarkLightThemeHasChanged_needsToBeRecreated() { ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo(); mTaskInfo.configuration.uiMode = 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 index 02316125bcc3..3fa21cee0d68 100644 --- 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 @@ -19,6 +19,7 @@ package com.android.wm.shell.compatui; import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; @@ -28,6 +29,9 @@ import android.app.ActivityManager; import android.app.CameraCompatTaskInfo.CameraCompatControlState; import android.app.TaskInfo; import android.content.ComponentName; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.testing.AndroidTestingRunner; import android.util.Pair; import android.view.LayoutInflater; @@ -47,6 +51,7 @@ import com.android.wm.shell.common.SyncTransactionQueue; import junit.framework.Assert; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -86,6 +91,10 @@ public class UserAspectRatioSettingsLayoutTest extends ShellTestCase { private UserAspectRatioSettingsLayout mLayout; private TaskInfo mTaskInfo; + @Rule + public final CheckFlagsRule mCheckFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); + @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -107,6 +116,7 @@ public class UserAspectRatioSettingsLayoutTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testOnClickForUserAspectRatioSettingsButton() { final ImageButton button = mLayout.findViewById(R.id.user_aspect_ratio_settings_button); button.performClick(); @@ -123,6 +133,7 @@ public class UserAspectRatioSettingsLayoutTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testOnLongClickForUserAspectRatioButton() { doNothing().when(mWindowManager).onUserAspectRatioSettingsButtonLongClicked(); @@ -133,6 +144,7 @@ public class UserAspectRatioSettingsLayoutTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testOnClickForUserAspectRatioSettingsHint() { mWindowManager.mHasUserAspectRatioSettingsButton = true; mWindowManager.createLayout(/* canShow= */ true); 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 index 94e168ed70ed..9f288cc4bd93 100644 --- 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 @@ -22,6 +22,7 @@ import static android.hardware.usb.UsbManager.ACTION_USB_STATE; import static android.view.WindowInsets.Type.navigationBars; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -39,6 +40,9 @@ import android.content.ComponentName; import android.content.Intent; import android.content.res.Configuration; import android.graphics.Rect; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; import android.util.Pair; @@ -61,6 +65,7 @@ import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState; import junit.framework.Assert; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -107,6 +112,10 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { private TestShellExecutor mExecutor; + @Rule + public final CheckFlagsRule mCheckFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); + @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -138,6 +147,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testCreateUserAspectRatioButton() { // Doesn't create layout if show is false. mWindowManager.mHasUserAspectRatioSettingsButton = true; @@ -178,6 +188,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testRelease() { mWindowManager.mHasUserAspectRatioSettingsButton = true; mWindowManager.createLayout(/* canShow= */ true); @@ -190,6 +201,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testUpdateCompatInfo() { mWindowManager.mHasUserAspectRatioSettingsButton = true; mWindowManager.createLayout(/* canShow= */ true); @@ -242,6 +254,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testUpdateCompatInfoLayoutNotInflatedYet() { mWindowManager.mHasUserAspectRatioSettingsButton = true; mWindowManager.createLayout(/* canShow= */ false); @@ -267,6 +280,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testEligibleButtonHiddenIfLetterboxBoundsEqualToStableBounds() { TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */ true, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER); @@ -292,6 +306,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testUserFullscreenOverrideEnabled_buttonAlwaysShown() { TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */ true, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER); @@ -310,6 +325,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testUpdateDisplayLayout() { final DisplayInfo displayInfo = new DisplayInfo(); displayInfo.logicalWidth = 1000; @@ -329,6 +345,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testUpdateDisplayLayoutInsets() { final DisplayInfo displayInfo = new DisplayInfo(); displayInfo.logicalWidth = 1000; @@ -353,6 +370,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testUpdateVisibility() { // Create button if it is not created. mWindowManager.removeLayout(); @@ -378,6 +396,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testLayoutHasUserAspectRatioSettingsButton() { clearInvocations(mWindowManager); spyOn(mWindowManager); @@ -411,6 +430,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testAttachToParentSurface() { final SurfaceControl.Builder b = new SurfaceControl.Builder(); mWindowManager.attachToParentSurface(b); @@ -419,6 +439,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testOnUserAspectRatioButtonClicked() { mWindowManager.onUserAspectRatioSettingsButtonClicked(); @@ -433,6 +454,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testOnUserAspectRatioButtonLongClicked_showHint() { // Not create hint popup. mWindowManager.mHasUserAspectRatioSettingsButton = true; @@ -448,6 +470,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { } @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testWhenDockedStateHasChanged_needsToBeRecreated() { ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo(); newTaskInfo.configuration.uiMode |= Configuration.UI_MODE_TYPE_DESK; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt index fb03f20f939c..d4596396840e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt @@ -19,6 +19,8 @@ import android.app.ActivityManager import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN import android.content.Context +import android.graphics.Point +import android.graphics.Rect import android.os.IBinder import android.testing.AndroidTestingRunner import android.view.SurfaceControl @@ -36,11 +38,12 @@ import android.window.TransitionInfo import android.window.TransitionInfo.Change import android.window.WindowContainerToken import androidx.test.filters.SmallTest -import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn import com.android.modules.utils.testing.ExtendedMockitoRule +import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT @@ -49,26 +52,27 @@ import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_ import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_UNKNOWN -import com.android.wm.shell.shared.DesktopModeStatus +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.TransitionInfoBuilder import com.android.wm.shell.transition.Transitions -import com.google.common.truth.Truth.assertThat +import junit.framework.Assert.assertNotNull +import junit.framework.Assert.assertNull import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor -import org.mockito.Mock -import org.mockito.Mockito -import org.mockito.Mockito.mock -import org.mockito.Mockito.verify import org.mockito.kotlin.any import org.mockito.kotlin.eq +import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.same +import org.mockito.kotlin.spy import org.mockito.kotlin.times +import org.mockito.kotlin.verify import org.mockito.kotlin.verifyZeroInteractions +import org.mockito.kotlin.whenever /** * Test class for {@link DesktopModeLoggerTransitionObserver} @@ -77,20 +81,17 @@ import org.mockito.kotlin.verifyZeroInteractions */ @SmallTest @RunWith(AndroidTestingRunner::class) -class DesktopModeLoggerTransitionObserverTest { +class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { @JvmField @Rule val extendedMockitoRule = - ExtendedMockitoRule.Builder(this) - .mockStatic(DesktopModeEventLogger::class.java) - .mockStatic(DesktopModeStatus::class.java) - .build()!! + ExtendedMockitoRule.Builder(this).mockStatic(DesktopModeStatus::class.java).build()!! - @Mock lateinit var testExecutor: ShellExecutor - @Mock private lateinit var mockShellInit: ShellInit - @Mock private lateinit var transitions: Transitions - @Mock private lateinit var context: Context + private val testExecutor = mock<ShellExecutor>() + private val mockShellInit = mock<ShellInit>() + private val transitions = mock<Transitions>() + private val context = mock<Context>() private lateinit var transitionObserver: DesktopModeLoggerTransitionObserver private lateinit var shellInit: ShellInit @@ -98,9 +99,9 @@ class DesktopModeLoggerTransitionObserverTest { @Before fun setup() { - doReturn(true).`when` { DesktopModeStatus.canEnterDesktopMode(any()) } - shellInit = Mockito.spy(ShellInit(testExecutor)) - desktopModeEventLogger = mock(DesktopModeEventLogger::class.java) + whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(true) + shellInit = spy(ShellInit(testExecutor)) + desktopModeEventLogger = mock<DesktopModeEventLogger>() transitionObserver = DesktopModeLoggerTransitionObserver( @@ -121,7 +122,7 @@ class DesktopModeLoggerTransitionObserverTest { @Test fun transitOpen_notFreeformWindow_doesNotLogTaskAddedOrSessionEnter() { - val change = createChange(TRANSIT_OPEN, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN)) + val change = createChange(TRANSIT_OPEN, createTaskInfo(WINDOWING_MODE_FULLSCREEN)) val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build() callOnTransitionReady(transitionInfo) @@ -132,22 +133,17 @@ class DesktopModeLoggerTransitionObserverTest { @Test fun transitOpen_logTaskAddedAndEnterReasonAppFreeformIntent() { - val change = createChange(TRANSIT_OPEN, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val change = createChange(TRANSIT_OPEN, createTaskInfo(WINDOWING_MODE_FREEFORM)) val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build() callOnTransitionReady(transitionInfo) - val sessionId = transitionObserver.getLoggerSessionId() - assertThat(sessionId).isNotNull() - verify(desktopModeEventLogger, times(1)) - .logSessionEnter(eq(sessionId!!), eq(EnterReason.APP_FREEFORM_INTENT)) - verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any()) - verifyZeroInteractions(desktopModeEventLogger) + verifyTaskAddedAndEnterLogging(EnterReason.APP_FREEFORM_INTENT, DEFAULT_TASK_UPDATE) } @Test fun transitEndDragToDesktop_logTaskAddedAndEnterReasonAppHandleDrag() { - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) // task change is finalised when drag ends val transitionInfo = TransitionInfoBuilder(Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, 0) @@ -155,82 +151,57 @@ class DesktopModeLoggerTransitionObserverTest { .build() callOnTransitionReady(transitionInfo) - val sessionId = transitionObserver.getLoggerSessionId() - assertThat(sessionId).isNotNull() - verify(desktopModeEventLogger, times(1)) - .logSessionEnter(eq(sessionId!!), eq(EnterReason.APP_HANDLE_DRAG)) - verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any()) - verifyZeroInteractions(desktopModeEventLogger) + verifyTaskAddedAndEnterLogging(EnterReason.APP_HANDLE_DRAG, DEFAULT_TASK_UPDATE) } @Test fun transitEnterDesktopByButtonTap_logTaskAddedAndEnterReasonButtonTap() { - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) val transitionInfo = TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON, 0) .addChange(change) .build() callOnTransitionReady(transitionInfo) - val sessionId = transitionObserver.getLoggerSessionId() - assertThat(sessionId).isNotNull() - verify(desktopModeEventLogger, times(1)) - .logSessionEnter(eq(sessionId!!), eq(EnterReason.APP_HANDLE_MENU_BUTTON)) - verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any()) - verifyZeroInteractions(desktopModeEventLogger) + verifyTaskAddedAndEnterLogging(EnterReason.APP_HANDLE_MENU_BUTTON, DEFAULT_TASK_UPDATE) } @Test fun transitEnterDesktopFromAppFromOverview_logTaskAddedAndEnterReasonAppFromOverview() { - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) val transitionInfo = TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW, 0) .addChange(change) .build() callOnTransitionReady(transitionInfo) - val sessionId = transitionObserver.getLoggerSessionId() - assertThat(sessionId).isNotNull() - verify(desktopModeEventLogger, times(1)) - .logSessionEnter(eq(sessionId!!), eq(EnterReason.APP_FROM_OVERVIEW)) - verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any()) - verifyZeroInteractions(desktopModeEventLogger) + verifyTaskAddedAndEnterLogging(EnterReason.APP_FROM_OVERVIEW, DEFAULT_TASK_UPDATE) } @Test fun transitEnterDesktopFromKeyboardShortcut_logTaskAddedAndEnterReasonKeyboardShortcut() { - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) val transitionInfo = TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT, 0) .addChange(change) .build() callOnTransitionReady(transitionInfo) - val sessionId = transitionObserver.getLoggerSessionId() - assertThat(sessionId).isNotNull() - verify(desktopModeEventLogger, times(1)) - .logSessionEnter(eq(sessionId!!), eq(EnterReason.KEYBOARD_SHORTCUT_ENTER)) - verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any()) - verifyZeroInteractions(desktopModeEventLogger) + verifyTaskAddedAndEnterLogging(EnterReason.KEYBOARD_SHORTCUT_ENTER, DEFAULT_TASK_UPDATE) } @Test fun transitToFront_logTaskAddedAndEnterReasonOverview() { - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) val transitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, 0).addChange(change).build() callOnTransitionReady(transitionInfo) - val sessionId = transitionObserver.getLoggerSessionId() - assertThat(sessionId).isNotNull() - verify(desktopModeEventLogger, times(1)) - .logSessionEnter(eq(sessionId!!), eq(EnterReason.OVERVIEW)) - verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any()) - verifyZeroInteractions(desktopModeEventLogger) + verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW, DEFAULT_TASK_UPDATE) } @Test @@ -238,35 +209,29 @@ class DesktopModeLoggerTransitionObserverTest { // previous exit to overview transition val previousSessionId = 1 // add a freeform task - transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM) + transitionObserver.addTaskInfosToCachedMap(previousTaskInfo) transitionObserver.setLoggerSessionId(previousSessionId) - val previousChange = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) val previousTransitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS) - .addChange(previousChange) + .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo)) .build() callOnTransitionReady(previousTransitionInfo) - verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(previousSessionId), any()) - verify(desktopModeEventLogger, times(1)) - .logSessionExit(eq(previousSessionId), eq(ExitReason.RETURN_HOME_OR_OVERVIEW)) + verifyTaskRemovedAndExitLogging( + previousSessionId, ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE) // Enter desktop mode from cancelled recents has no transition. Enter is detected on the // next transition involving freeform windows // TRANSIT_TO_FRONT - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) val transitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, 0).addChange(change).build() callOnTransitionReady(transitionInfo) - val sessionId = transitionObserver.getLoggerSessionId() - assertThat(sessionId).isNotNull() - verify(desktopModeEventLogger, times(1)) - .logSessionEnter(eq(sessionId!!), eq(EnterReason.OVERVIEW)) - verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any()) - verifyZeroInteractions(desktopModeEventLogger) + verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW, DEFAULT_TASK_UPDATE) } @Test @@ -274,35 +239,29 @@ class DesktopModeLoggerTransitionObserverTest { // previous exit to overview transition val previousSessionId = 1 // add a freeform task - transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM) + transitionObserver.addTaskInfosToCachedMap(previousTaskInfo) transitionObserver.setLoggerSessionId(previousSessionId) - val previousChange = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) val previousTransitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS) - .addChange(previousChange) + .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo)) .build() callOnTransitionReady(previousTransitionInfo) - verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(previousSessionId), any()) - verify(desktopModeEventLogger, times(1)) - .logSessionExit(eq(previousSessionId), eq(ExitReason.RETURN_HOME_OR_OVERVIEW)) + verifyTaskRemovedAndExitLogging( + previousSessionId, ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE) // Enter desktop mode from cancelled recents has no transition. Enter is detected on the // next transition involving freeform windows // TRANSIT_CHANGE - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) val transitionInfo = TransitionInfoBuilder(TRANSIT_CHANGE, 0).addChange(change).build() callOnTransitionReady(transitionInfo) - val sessionId = transitionObserver.getLoggerSessionId() - assertThat(sessionId).isNotNull() - verify(desktopModeEventLogger, times(1)) - .logSessionEnter(eq(sessionId!!), eq(EnterReason.OVERVIEW)) - verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any()) - verifyZeroInteractions(desktopModeEventLogger) + verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW, DEFAULT_TASK_UPDATE) } @Test @@ -310,35 +269,29 @@ class DesktopModeLoggerTransitionObserverTest { // previous exit to overview transition val previousSessionId = 1 // add a freeform task - transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM) + transitionObserver.addTaskInfosToCachedMap(previousTaskInfo) transitionObserver.setLoggerSessionId(previousSessionId) - val previousChange = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) val previousTransitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS) - .addChange(previousChange) + .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo)) .build() callOnTransitionReady(previousTransitionInfo) - verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(previousSessionId), any()) - verify(desktopModeEventLogger, times(1)) - .logSessionExit(eq(previousSessionId), eq(ExitReason.RETURN_HOME_OR_OVERVIEW)) + verifyTaskRemovedAndExitLogging( + previousSessionId, ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE) // Enter desktop mode from cancelled recents has no transition. Enter is detected on the // next transition involving freeform windows // TRANSIT_OPEN - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build() callOnTransitionReady(transitionInfo) - val sessionId = transitionObserver.getLoggerSessionId() - assertThat(sessionId).isNotNull() - verify(desktopModeEventLogger, times(1)) - .logSessionEnter(eq(sessionId!!), eq(EnterReason.OVERVIEW)) - verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any()) - verifyZeroInteractions(desktopModeEventLogger) + verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW, DEFAULT_TASK_UPDATE) } @Test @@ -349,286 +302,389 @@ class DesktopModeLoggerTransitionObserverTest { // previous exit to overview transition val previousSessionId = 1 // add a freeform task - transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM) + transitionObserver.addTaskInfosToCachedMap(previousTaskInfo) transitionObserver.setLoggerSessionId(previousSessionId) - val previousChange = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) val previousTransitionInfo = - TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS) - .addChange(previousChange) - .build() + TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS) + .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo)) + .build() callOnTransitionReady(previousTransitionInfo) - verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(previousSessionId), any()) - verify(desktopModeEventLogger, times(1)) - .logSessionExit(eq(previousSessionId), eq(ExitReason.RETURN_HOME_OR_OVERVIEW)) + verifyTaskRemovedAndExitLogging( + previousSessionId, ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE) // TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) val transitionInfo = - TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW, 0) - .addChange(change) - .build() + TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW, 0) + .addChange(change) + .build() callOnTransitionReady(transitionInfo) - val sessionId = transitionObserver.getLoggerSessionId() - assertThat(sessionId).isNotNull() - verify(desktopModeEventLogger, times(1)) - .logSessionEnter(eq(sessionId!!), eq(EnterReason.APP_FROM_OVERVIEW)) - verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any()) - verifyZeroInteractions(desktopModeEventLogger) + verifyTaskAddedAndEnterLogging(EnterReason.APP_FROM_OVERVIEW, DEFAULT_TASK_UPDATE) } @Test fun transitEnterDesktopFromUnknown_logTaskAddedAndEnterReasonUnknown() { - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) val transitionInfo = TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_UNKNOWN, 0).addChange(change).build() callOnTransitionReady(transitionInfo) - val sessionId = transitionObserver.getLoggerSessionId() - assertThat(sessionId).isNotNull() - verify(desktopModeEventLogger, times(1)) - .logSessionEnter(eq(sessionId!!), eq(EnterReason.UNKNOWN_ENTER)) - verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any()) - verifyZeroInteractions(desktopModeEventLogger) + verifyTaskAddedAndEnterLogging(EnterReason.UNKNOWN_ENTER, DEFAULT_TASK_UPDATE) } @Test fun transitWake_logTaskAddedAndEnterReasonScreenOn() { - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) val transitionInfo = TransitionInfoBuilder(TRANSIT_WAKE, 0).addChange(change).build() callOnTransitionReady(transitionInfo) - val sessionId = transitionObserver.getLoggerSessionId() - assertThat(sessionId).isNotNull() - verify(desktopModeEventLogger, times(1)) - .logSessionEnter(eq(sessionId!!), eq(EnterReason.SCREEN_ON)) - verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any()) - verifyZeroInteractions(desktopModeEventLogger) + verifyTaskAddedAndEnterLogging(EnterReason.SCREEN_ON, DEFAULT_TASK_UPDATE) } @Test - fun transitSleep_logTaskAddedAndExitReasonScreenOff_sessionIdNull() { + fun transitSleep_logTaskRemovedAndExitReasonScreenOff_sessionIdNull() { val sessionId = 1 // add a freeform task - transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) transitionObserver.setLoggerSessionId(sessionId) val transitionInfo = TransitionInfoBuilder(TRANSIT_SLEEP).build() callOnTransitionReady(transitionInfo) - verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any()) - verify(desktopModeEventLogger, times(1)) - .logSessionExit(eq(sessionId), eq(ExitReason.SCREEN_OFF)) - verifyZeroInteractions(desktopModeEventLogger) - assertThat(transitionObserver.getLoggerSessionId()).isNull() + verifyTaskRemovedAndExitLogging(sessionId, ExitReason.SCREEN_OFF, DEFAULT_TASK_UPDATE) } @Test fun transitExitDesktopTaskDrag_logTaskRemovedAndExitReasonDragToExit_sessionIdNull() { val sessionId = 1 // add a freeform task - transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) transitionObserver.setLoggerSessionId(sessionId) // window mode changing from FREEFORM to FULLSCREEN - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN)) + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN)) val transitionInfo = TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG).addChange(change).build() callOnTransitionReady(transitionInfo) - verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any()) - verify(desktopModeEventLogger, times(1)) - .logSessionExit(eq(sessionId), eq(ExitReason.DRAG_TO_EXIT)) - verifyZeroInteractions(desktopModeEventLogger) - assertThat(transitionObserver.getLoggerSessionId()).isNull() + verifyTaskRemovedAndExitLogging(sessionId, ExitReason.DRAG_TO_EXIT, DEFAULT_TASK_UPDATE) } @Test fun transitExitDesktopAppHandleButton_logTaskRemovedAndExitReasonButton_sessionIdNull() { val sessionId = 1 // add a freeform task - transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) transitionObserver.setLoggerSessionId(sessionId) // window mode changing from FREEFORM to FULLSCREEN - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN)) + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN)) val transitionInfo = TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_HANDLE_MENU_BUTTON) .addChange(change) .build() callOnTransitionReady(transitionInfo) - verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any()) - verify(desktopModeEventLogger, times(1)) - .logSessionExit(eq(sessionId), eq(ExitReason.APP_HANDLE_MENU_BUTTON_EXIT)) - verifyZeroInteractions(desktopModeEventLogger) - assertThat(transitionObserver.getLoggerSessionId()).isNull() + verifyTaskRemovedAndExitLogging( + sessionId, ExitReason.APP_HANDLE_MENU_BUTTON_EXIT, DEFAULT_TASK_UPDATE) } @Test fun transitExitDesktopUsingKeyboard_logTaskRemovedAndExitReasonKeyboard_sessionIdNull() { val sessionId = 1 // add a freeform task - transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) transitionObserver.setLoggerSessionId(sessionId) // window mode changing from FREEFORM to FULLSCREEN - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN)) + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN)) val transitionInfo = TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT).addChange(change).build() callOnTransitionReady(transitionInfo) - verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any()) - verify(desktopModeEventLogger, times(1)) - .logSessionExit(eq(sessionId), eq(ExitReason.KEYBOARD_SHORTCUT_EXIT)) - verifyZeroInteractions(desktopModeEventLogger) - assertThat(transitionObserver.getLoggerSessionId()).isNull() + verifyTaskRemovedAndExitLogging( + sessionId, ExitReason.KEYBOARD_SHORTCUT_EXIT, DEFAULT_TASK_UPDATE) } @Test fun transitExitDesktopUnknown_logTaskRemovedAndExitReasonUnknown_sessionIdNull() { val sessionId = 1 // add a freeform task - transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) transitionObserver.setLoggerSessionId(sessionId) // window mode changing from FREEFORM to FULLSCREEN - val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN)) + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN)) val transitionInfo = TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_UNKNOWN).addChange(change).build() callOnTransitionReady(transitionInfo) - verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any()) - verify(desktopModeEventLogger, times(1)) - .logSessionExit(eq(sessionId), eq(ExitReason.UNKNOWN_EXIT)) - verifyZeroInteractions(desktopModeEventLogger) - assertThat(transitionObserver.getLoggerSessionId()).isNull() + verifyTaskRemovedAndExitLogging(sessionId, ExitReason.UNKNOWN_EXIT, DEFAULT_TASK_UPDATE) } @Test fun transitToFrontWithFlagRecents_logTaskRemovedAndExitReasonOverview_sessionIdNull() { val sessionId = 1 // add a freeform task - transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) transitionObserver.setLoggerSessionId(sessionId) // recents transition - val change = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val change = createChange(TRANSIT_TO_BACK, createTaskInfo(WINDOWING_MODE_FREEFORM)) val transitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS).addChange(change).build() callOnTransitionReady(transitionInfo) - verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any()) - verify(desktopModeEventLogger, times(1)) - .logSessionExit(eq(sessionId), eq(ExitReason.RETURN_HOME_OR_OVERVIEW)) - verifyZeroInteractions(desktopModeEventLogger) - assertThat(transitionObserver.getLoggerSessionId()).isNull() + verifyTaskRemovedAndExitLogging( + sessionId, ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE) } @Test fun transitClose_logTaskRemovedAndExitReasonTaskFinished_sessionIdNull() { val sessionId = 1 // add a freeform task - transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) transitionObserver.setLoggerSessionId(sessionId) // task closing - val change = createChange(TRANSIT_CLOSE, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN)) + val change = createChange(TRANSIT_CLOSE, createTaskInfo(WINDOWING_MODE_FULLSCREEN)) val transitionInfo = TransitionInfoBuilder(TRANSIT_CLOSE).addChange(change).build() callOnTransitionReady(transitionInfo) - verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any()) - verify(desktopModeEventLogger, times(1)) - .logSessionExit(eq(sessionId), eq(ExitReason.TASK_FINISHED)) - verifyZeroInteractions(desktopModeEventLogger) - assertThat(transitionObserver.getLoggerSessionId()).isNull() + verifyTaskRemovedAndExitLogging(sessionId, ExitReason.TASK_FINISHED, DEFAULT_TASK_UPDATE) } @Test fun sessionExitByRecents_cancelledAnimation_sessionRestored() { val sessionId = 1 // add a freeform task to an existing session - transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val taskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM) + transitionObserver.addTaskInfosToCachedMap(taskInfo) transitionObserver.setLoggerSessionId(sessionId) // recents transition sent freeform window to back - val change = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val change = createChange(TRANSIT_TO_BACK, taskInfo) val transitionInfo1 = TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS).addChange(change).build() callOnTransitionReady(transitionInfo1) - verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any()) - verify(desktopModeEventLogger, times(1)) - .logSessionExit(eq(sessionId), eq(ExitReason.RETURN_HOME_OR_OVERVIEW)) - assertThat(transitionObserver.getLoggerSessionId()).isNull() + + verifyTaskRemovedAndExitLogging( + sessionId, ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE) val transitionInfo2 = TransitionInfoBuilder(TRANSIT_NONE).build() callOnTransitionReady(transitionInfo2) - verify(desktopModeEventLogger, times(1)).logSessionEnter(any(), any()) - verify(desktopModeEventLogger, times(1)).logTaskAdded(any(), any()) + verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW, DEFAULT_TASK_UPDATE) } @Test fun sessionAlreadyStarted_newFreeformTaskAdded_logsTaskAdded() { val sessionId = 1 // add an existing freeform task - transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) transitionObserver.setLoggerSessionId(sessionId) // new freeform task added - val change = createChange(TRANSIT_OPEN, createTaskInfo(2, WINDOWING_MODE_FREEFORM)) + val change = createChange(TRANSIT_OPEN, createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2)) val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build() callOnTransitionReady(transitionInfo) - verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any()) + verify(desktopModeEventLogger, times(1)) + .logTaskAdded(eq(sessionId), eq(DEFAULT_TASK_UPDATE.copy(instanceId = 2))) verify(desktopModeEventLogger, never()).logSessionEnter(any(), any()) } @Test + fun sessionAlreadyStarted_taskPositionChanged_logsTaskUpdate() { + val sessionId = 1 + // add an existing freeform task + val taskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM) + transitionObserver.addTaskInfosToCachedMap(taskInfo) + transitionObserver.setLoggerSessionId(sessionId) + + // task position changed + val newTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM, taskX = DEFAULT_TASK_X + 100) + val transitionInfo = + TransitionInfoBuilder(TRANSIT_CHANGE, 0) + .addChange(createChange(TRANSIT_CHANGE, newTaskInfo)) + .build() + callOnTransitionReady(transitionInfo) + + verify(desktopModeEventLogger, times(1)) + .logTaskInfoChanged( + eq(sessionId), eq(DEFAULT_TASK_UPDATE.copy(taskX = DEFAULT_TASK_X + 100))) + verifyZeroInteractions(desktopModeEventLogger) + } + + @Test + fun sessionAlreadyStarted_taskResized_logsTaskUpdate() { + val sessionId = 1 + // add an existing freeform task + val taskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM) + transitionObserver.addTaskInfosToCachedMap(taskInfo) + transitionObserver.setLoggerSessionId(sessionId) + + // task resized + val newTaskInfo = + createTaskInfo( + WINDOWING_MODE_FREEFORM, + taskWidth = DEFAULT_TASK_WIDTH + 100, + taskHeight = DEFAULT_TASK_HEIGHT - 100) + val transitionInfo = + TransitionInfoBuilder(TRANSIT_CHANGE, 0) + .addChange(createChange(TRANSIT_CHANGE, newTaskInfo)) + .build() + callOnTransitionReady(transitionInfo) + + verify(desktopModeEventLogger, times(1)) + .logTaskInfoChanged( + eq(sessionId), + eq( + DEFAULT_TASK_UPDATE.copy( + taskWidth = DEFAULT_TASK_WIDTH + 100, taskHeight = DEFAULT_TASK_HEIGHT - 100))) + verifyZeroInteractions(desktopModeEventLogger) + } + + @Test + fun sessionAlreadyStarted_multipleTasksUpdated_logsTaskUpdateForCorrectTask() { + val sessionId = 1 + // add 2 existing freeform task + val taskInfo1 = createTaskInfo(WINDOWING_MODE_FREEFORM) + val taskInfo2 = createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2) + transitionObserver.addTaskInfosToCachedMap(taskInfo1) + transitionObserver.addTaskInfosToCachedMap(taskInfo2) + transitionObserver.setLoggerSessionId(sessionId) + + // task 1 position update + val newTaskInfo1 = createTaskInfo(WINDOWING_MODE_FREEFORM, taskX = DEFAULT_TASK_X + 100) + val transitionInfo1 = + TransitionInfoBuilder(TRANSIT_CHANGE, 0) + .addChange(createChange(TRANSIT_CHANGE, newTaskInfo1)) + .build() + callOnTransitionReady(transitionInfo1) + + verify(desktopModeEventLogger, times(1)) + .logTaskInfoChanged( + eq(sessionId), eq(DEFAULT_TASK_UPDATE.copy(taskX = DEFAULT_TASK_X + 100))) + verifyZeroInteractions(desktopModeEventLogger) + + // task 2 resize + val newTaskInfo2 = + createTaskInfo( + WINDOWING_MODE_FREEFORM, + id = 2, + taskWidth = DEFAULT_TASK_WIDTH + 100, + taskHeight = DEFAULT_TASK_HEIGHT - 100) + val transitionInfo2 = + TransitionInfoBuilder(TRANSIT_CHANGE, 0) + .addChange(createChange(TRANSIT_CHANGE, newTaskInfo2)) + .build() + + callOnTransitionReady(transitionInfo2) + + verify(desktopModeEventLogger, times(1)) + .logTaskInfoChanged( + eq(sessionId), + eq( + DEFAULT_TASK_UPDATE.copy( + instanceId = 2, + taskWidth = DEFAULT_TASK_WIDTH + 100, + taskHeight = DEFAULT_TASK_HEIGHT - 100))) + verifyZeroInteractions(desktopModeEventLogger) + } + + @Test fun sessionAlreadyStarted_freeformTaskRemoved_logsTaskRemoved() { val sessionId = 1 // add two existing freeform tasks - transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM)) - transitionObserver.addTaskInfosToCachedMap(createTaskInfo(2, WINDOWING_MODE_FREEFORM)) + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2)) transitionObserver.setLoggerSessionId(sessionId) - // new freeform task added - val change = createChange(TRANSIT_CLOSE, createTaskInfo(2, WINDOWING_MODE_FREEFORM)) + // new freeform task closed + val change = createChange(TRANSIT_CLOSE, createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2)) val transitionInfo = TransitionInfoBuilder(TRANSIT_CLOSE, 0).addChange(change).build() callOnTransitionReady(transitionInfo) - verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any()) + verify(desktopModeEventLogger, times(1)) + .logTaskRemoved(eq(sessionId), eq(DEFAULT_TASK_UPDATE.copy(instanceId = 2))) verify(desktopModeEventLogger, never()).logSessionExit(any(), any()) } /** Simulate calling the onTransitionReady() method */ private fun callOnTransitionReady(transitionInfo: TransitionInfo) { - val transition = mock(IBinder::class.java) - val startT = mock(SurfaceControl.Transaction::class.java) - val finishT = mock(SurfaceControl.Transaction::class.java) + val transition = mock<IBinder>() + val startT = mock<SurfaceControl.Transaction>() + val finishT = mock<SurfaceControl.Transaction>() transitionObserver.onTransitionReady(transition, transitionInfo, startT, finishT) } - companion object { - fun createTaskInfo(taskId: Int, windowMode: Int): ActivityManager.RunningTaskInfo { - val taskInfo = ActivityManager.RunningTaskInfo() - taskInfo.taskId = taskId - taskInfo.configuration.windowConfiguration.windowingMode = windowMode + private fun verifyTaskAddedAndEnterLogging(enterReason: EnterReason, taskUpdate: TaskUpdate) { + val sessionId = transitionObserver.getLoggerSessionId() + assertNotNull(sessionId) + verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(sessionId!!), eq(enterReason)) + verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), eq(taskUpdate)) + verifyZeroInteractions(desktopModeEventLogger) + } - return taskInfo - } + private fun verifyTaskRemovedAndExitLogging( + sessionId: Int, + exitReason: ExitReason, + taskUpdate: TaskUpdate + ) { + verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), eq(taskUpdate)) + verify(desktopModeEventLogger, times(1)).logSessionExit(eq(sessionId), eq(exitReason)) + verifyZeroInteractions(desktopModeEventLogger) + assertNull(transitionObserver.getLoggerSessionId()) + } + + private companion object { + const val DEFAULT_TASK_ID = 1 + const val DEFAULT_TASK_UID = 2 + const val DEFAULT_TASK_HEIGHT = 100 + const val DEFAULT_TASK_WIDTH = 200 + const val DEFAULT_TASK_X = 30 + const val DEFAULT_TASK_Y = 70 + val DEFAULT_TASK_UPDATE = + TaskUpdate( + DEFAULT_TASK_ID, + DEFAULT_TASK_UID, + DEFAULT_TASK_HEIGHT, + DEFAULT_TASK_WIDTH, + DEFAULT_TASK_X, + DEFAULT_TASK_Y, + ) + + fun createTaskInfo( + windowMode: Int, + id: Int = DEFAULT_TASK_ID, + uid: Int = DEFAULT_TASK_UID, + taskHeight: Int = DEFAULT_TASK_HEIGHT, + taskWidth: Int = DEFAULT_TASK_WIDTH, + taskX: Int = DEFAULT_TASK_X, + taskY: Int = DEFAULT_TASK_Y, + ) = + ActivityManager.RunningTaskInfo().apply { + taskId = id + userId = uid + configuration.windowConfiguration.apply { + windowingMode = windowMode + positionInParent = Point(taskX, taskY) + bounds.set(Rect(taskX, taskY, taskX + taskWidth, taskY + taskHeight)) + } + } fun createChange(mode: Int, taskInfo: ActivityManager.RunningTaskInfo): Change { val change = - Change( - WindowContainerToken(mock(IWindowContainerToken::class.java)), - mock(SurfaceControl::class.java)) + Change(WindowContainerToken(mock<IWindowContainerToken>()), mock<SurfaceControl>()) change.mode = mode change.taskInfo = taskInfo return change 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 310ccc252469..6612aee0cd12 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 @@ -119,54 +119,91 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { } @Test - fun isOnlyActiveTask_noActiveTasks() { - // Not an active task - assertThat(repo.isOnlyActiveTask(1)).isFalse() + fun isOnlyVisibleNonClosingTask_noTasks() { + // No visible tasks + assertThat(repo.isOnlyVisibleNonClosingTask(1)).isFalse() + assertThat(repo.isClosingTask(1)).isFalse() } @Test - fun isOnlyActiveTask_singleActiveTask() { - repo.addActiveTask(DEFAULT_DISPLAY, 1) - // The only active task - assertThat(repo.isActiveTask(1)).isTrue() - assertThat(repo.isOnlyActiveTask(1)).isTrue() - // Not an active task - assertThat(repo.isActiveTask(99)).isFalse() - assertThat(repo.isOnlyActiveTask(99)).isFalse() + fun isOnlyVisibleNonClosingTask_singleVisibleNonClosingTask() { + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true) + + // The only visible task + assertThat(repo.isVisibleTask(1)).isTrue() + assertThat(repo.isClosingTask(1)).isFalse() + assertThat(repo.isOnlyVisibleNonClosingTask(1)).isTrue() + // Not a visible task + assertThat(repo.isVisibleTask(99)).isFalse() + assertThat(repo.isClosingTask(99)).isFalse() + assertThat(repo.isOnlyVisibleNonClosingTask(99)).isFalse() + } + + @Test + fun isOnlyVisibleNonClosingTask_singleVisibleClosingTask() { + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true) + repo.addClosingTask(DEFAULT_DISPLAY, 1) + + // A visible task that's closing + assertThat(repo.isVisibleTask(1)).isTrue() + assertThat(repo.isClosingTask(1)).isTrue() + assertThat(repo.isOnlyVisibleNonClosingTask(1)).isFalse() + // Not a visible task + assertThat(repo.isVisibleTask(99)).isFalse() + assertThat(repo.isOnlyVisibleNonClosingTask(99)).isFalse() + } + + @Test + fun isOnlyVisibleNonClosingTask_singleVisibleMinimizedTask() { + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true) + repo.minimizeTask(DEFAULT_DISPLAY, 1) + + // The visible task that's closing + assertThat(repo.isVisibleTask(1)).isTrue() + assertThat(repo.isMinimizedTask(1)).isTrue() + assertThat(repo.isOnlyVisibleNonClosingTask(1)).isFalse() + // Not a visible task + assertThat(repo.isVisibleTask(99)).isFalse() + assertThat(repo.isOnlyVisibleNonClosingTask(99)).isFalse() } @Test - fun isOnlyActiveTask_multipleActiveTasks() { - repo.addActiveTask(DEFAULT_DISPLAY, 1) - repo.addActiveTask(DEFAULT_DISPLAY, 2) + fun isOnlyVisibleNonClosingTask_multipleVisibleNonClosingTasks() { + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true) + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = true) + // Not the only task - assertThat(repo.isActiveTask(1)).isTrue() - assertThat(repo.isOnlyActiveTask(1)).isFalse() + assertThat(repo.isVisibleTask(1)).isTrue() + assertThat(repo.isClosingTask(1)).isFalse() + assertThat(repo.isOnlyVisibleNonClosingTask(1)).isFalse() // Not the only task - assertThat(repo.isActiveTask(2)).isTrue() - assertThat(repo.isOnlyActiveTask(2)).isFalse() - // Not an active task - assertThat(repo.isActiveTask(99)).isFalse() - assertThat(repo.isOnlyActiveTask(99)).isFalse() + assertThat(repo.isVisibleTask(2)).isTrue() + assertThat(repo.isClosingTask(2)).isFalse() + assertThat(repo.isOnlyVisibleNonClosingTask(2)).isFalse() + // Not a visible task + assertThat(repo.isVisibleTask(99)).isFalse() + assertThat(repo.isClosingTask(99)).isFalse() + assertThat(repo.isOnlyVisibleNonClosingTask(99)).isFalse() } @Test - fun isOnlyActiveTask_multipleDisplays() { - repo.addActiveTask(DEFAULT_DISPLAY, 1) - repo.addActiveTask(DEFAULT_DISPLAY, 2) - repo.addActiveTask(SECOND_DISPLAY, 3) + fun isOnlyVisibleNonClosingTask_multipleDisplays() { + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true) + repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = true) + repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 3, visible = true) + // Not the only task on DEFAULT_DISPLAY - assertThat(repo.isActiveTask(1)).isTrue() - assertThat(repo.isOnlyActiveTask(1)).isFalse() + assertThat(repo.isVisibleTask(1)).isTrue() + assertThat(repo.isOnlyVisibleNonClosingTask(1)).isFalse() // Not the only task on DEFAULT_DISPLAY - assertThat(repo.isActiveTask(2)).isTrue() - assertThat(repo.isOnlyActiveTask(2)).isFalse() - // The only active task on SECOND_DISPLAY - assertThat(repo.isActiveTask(3)).isTrue() - assertThat(repo.isOnlyActiveTask(3)).isTrue() - // Not an active task - assertThat(repo.isActiveTask(99)).isFalse() - assertThat(repo.isOnlyActiveTask(99)).isFalse() + assertThat(repo.isVisibleTask(2)).isTrue() + assertThat(repo.isOnlyVisibleNonClosingTask(2)).isFalse() + // The only visible task on SECOND_DISPLAY + assertThat(repo.isVisibleTask(3)).isTrue() + assertThat(repo.isOnlyVisibleNonClosingTask(3)).isTrue() + // Not a visible task + assertThat(repo.isVisibleTask(99)).isFalse() + assertThat(repo.isOnlyVisibleNonClosingTask(99)).isFalse() } @Test 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 e17f7f2f7b12..6cabbf90bbc2 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 @@ -18,7 +18,9 @@ package com.android.wm.shell.desktopmode import android.app.ActivityManager.RecentTaskInfo import android.app.ActivityManager.RunningTaskInfo +import android.app.KeyguardManager import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME +import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN @@ -45,6 +47,7 @@ import android.view.Display.DEFAULT_DISPLAY import android.view.SurfaceControl import android.view.WindowManager import android.view.WindowManager.TRANSIT_CHANGE +import android.view.WindowManager.TRANSIT_CLOSE import android.view.WindowManager.TRANSIT_OPEN import android.view.WindowManager.TRANSIT_TO_BACK import android.view.WindowManager.TRANSIT_TO_FRONT @@ -87,7 +90,7 @@ import com.android.wm.shell.draganddrop.DragAndDropController import com.android.wm.shell.recents.RecentTasksController import com.android.wm.shell.recents.RecentsTransitionHandler import com.android.wm.shell.recents.RecentsTransitionStateListener -import com.android.wm.shell.shared.DesktopModeStatus +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.splitscreen.SplitScreenController import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellController @@ -102,6 +105,8 @@ import com.google.common.truth.Truth.assertWithMessage import java.util.Optional import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue +import kotlin.test.assertNotNull +import kotlin.test.assertNull import org.junit.After import org.junit.Assume.assumeTrue import org.junit.Before @@ -133,6 +138,7 @@ import org.mockito.quality.Strictness */ @SmallTest @RunWith(AndroidTestingRunner::class) +@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) class DesktopTasksControllerTest : ShellTestCase() { @JvmField @Rule val setFlagsRule = SetFlagsRule() @@ -146,6 +152,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Mock lateinit var syncQueue: SyncTransactionQueue @Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer @Mock lateinit var transitions: Transitions + @Mock lateinit var keyguardManager: KeyguardManager @Mock lateinit var exitDesktopTransitionHandler: ExitDesktopTaskTransitionHandler @Mock lateinit var enterDesktopTransitionHandler: EnterDesktopTaskTransitionHandler @Mock @@ -188,7 +195,6 @@ class DesktopTasksControllerTest : ShellTestCase() { .strictness(Strictness.LENIENT) .spyStatic(DesktopModeStatus::class.java) .startMocking() - whenever(DesktopModeStatus.isEnabled()).thenReturn(true) doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } shellInit = spy(ShellInit(testExecutor)) @@ -230,6 +236,7 @@ class DesktopTasksControllerTest : ShellTestCase() { rootTaskDisplayAreaOrganizer, dragAndDropController, transitions, + keyguardManager, enterDesktopTransitionHandler, exitDesktopTransitionHandler, toggleResizeDesktopTaskTransitionHandler, @@ -257,8 +264,8 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - fun instantiate_flagOff_doNotAddInitCallback() { - whenever(DesktopModeStatus.isEnabled()).thenReturn(false) + fun instantiate_canNotEnterDesktopMode_doNotAddInitCallback() { + whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(false) clearInvocations(shellInit) createController() @@ -436,14 +443,15 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - fun showDesktopApps_dontReorderMinimizedTask() { + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun showDesktopApps_desktopWallpaperDisabled_dontReorderMinimizedTask() { val homeTask = setUpHomeTask() val freeformTask = setUpFreeformTask() val minimizedTask = setUpFreeformTask() + markTaskHidden(freeformTask) markTaskHidden(minimizedTask) desktopModeTaskRepository.minimizeTask(DEFAULT_DISPLAY, minimizedTask.taskId) - controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition())) val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java) @@ -454,6 +462,26 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun showDesktopApps_desktopWallpaperEnabled_dontReorderMinimizedTask() { + setUpHomeTask() + val freeformTask = setUpFreeformTask() + val minimizedTask = setUpFreeformTask() + + markTaskHidden(freeformTask) + markTaskHidden(minimizedTask) + desktopModeTaskRepository.minimizeTask(DEFAULT_DISPLAY, minimizedTask.taskId) + controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition())) + + val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java) + assertThat(wct.hierarchyOps).hasSize(2) + // Add desktop wallpaper activity + wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent) + // Reorder freeform task to top, don't reorder the minimized task + wct.assertReorderAt(index = 1, freeformTask, toTop = true) + } + + @Test fun getVisibleTaskCount_noTasks_returnsZero() { assertThat(controller.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0) } @@ -642,33 +670,68 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - fun moveToDesktop_nonRunningTask_launchesInFreeform() { + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun moveToDesktop_desktopWallpaperDisabled_nonRunningTask_launchesInFreeform() { + val task = createTaskInfo(1) whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null) + whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task) - val task = createTaskInfo(1) + controller.moveToDesktop(task.taskId, transitionSource = UNKNOWN) + + with(getLatestEnterDesktopWct()) { + assertLaunchTaskAt(0, task.taskId, WINDOWING_MODE_FREEFORM) + } + } + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun moveToDesktop_desktopWallpaperEnabled_nonRunningTask_launchesInFreeform() { + val task = createTaskInfo(1) + whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null) whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task) controller.moveToDesktop(task.taskId, transitionSource = UNKNOWN) + with(getLatestEnterDesktopWct()) { - assertLaunchTaskAt(0, task.taskId, WINDOWING_MODE_FREEFORM) + // Add desktop wallpaper activity + assertPendingIntentAt(index = 0, desktopWallpaperIntent) + // Launch task + assertLaunchTaskAt(index = 1, task.taskId, WINDOWING_MODE_FREEFORM) } } @Test - fun moveToDesktop_topActivityTranslucent_doesNothing() { - setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) + fun moveToDesktop_topActivityTranslucentWithStyleFloating_taskIsMovedToDesktop() { val task = - setUpFullscreenTask().apply { - isTopActivityTransparent = true - numActivities = 1 - } + setUpFullscreenTask().apply { + isTopActivityTransparent = true + isTopActivityStyleFloating = true + numActivities = 1 + } + + controller.moveToDesktop(task, transitionSource = UNKNOWN) + + val wct = getLatestEnterDesktopWct() + assertThat(wct.changes[task.token.asBinder()]?.windowingMode).isEqualTo(WINDOWING_MODE_FREEFORM) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) + fun moveToDesktop_topActivityTranslucentWithoutStyleFloating_doesNothing() { + val task = + setUpFullscreenTask().apply { + isTopActivityTransparent = true + isTopActivityStyleFloating = false + numActivities = 1 + } controller.moveToDesktop(task, transitionSource = UNKNOWN) verifyEnterDesktopWCTNotExecuted() } @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) fun moveToDesktop_systemUIActivity_doesNothing() { val task = setUpFullscreenTask() @@ -773,21 +836,44 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - fun moveToDesktop_bringsTasksOverLimit_dontShowBackTask() { + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun moveToDesktop_desktopWallpaperDisabled_bringsTasksOver_dontShowBackTask() { val taskLimit = desktopTasksLimiter.getMaxTaskLimit() - val homeTask = setUpHomeTask() val freeformTasks = (1..taskLimit).map { _ -> setUpFreeformTask() } val newTask = setUpFullscreenTask() + val homeTask = setUpHomeTask() controller.moveToDesktop(newTask, transitionSource = UNKNOWN) val wct = getLatestEnterDesktopWct() assertThat(wct.hierarchyOps.size).isEqualTo(taskLimit + 1) // visible tasks + home wct.assertReorderAt(0, homeTask) - for (i in 1..<taskLimit) { // Skipping freeformTasks[0] - wct.assertReorderAt(index = i, task = freeformTasks[i]) - } - wct.assertReorderAt(taskLimit, newTask) + wct.assertReorderSequenceInRange( + range = 1..<(taskLimit + 1), + *freeformTasks.drop(1).toTypedArray(), // Skipping freeformTasks[0] + newTask + ) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun moveToDesktop_desktopWallpaperEnabled_bringsTasksOverLimit_dontShowBackTask() { + val taskLimit = desktopTasksLimiter.getMaxTaskLimit() + val freeformTasks = (1..taskLimit).map { _ -> setUpFreeformTask() } + val newTask = setUpFullscreenTask() + setUpHomeTask() + + controller.moveToDesktop(newTask, transitionSource = UNKNOWN) + + val wct = getLatestEnterDesktopWct() + assertThat(wct.hierarchyOps.size).isEqualTo(taskLimit + 1) // visible tasks + wallpaper + // Add desktop wallpaper activity + wct.assertPendingIntentAt(0, desktopWallpaperIntent) + wct.assertReorderSequenceInRange( + range = 1..<(taskLimit + 1), + *freeformTasks.drop(1).toTypedArray(), // Skipping freeformTasks[0] + newTask + ) } @Test @@ -923,7 +1009,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test fun onDesktopWindowClose_noActiveTasks() { val wct = WindowContainerTransaction() - controller.onDesktopWindowClose(wct, 1 /* taskId */) + controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, taskId = 1) // Doesn't modify transaction assertThat(wct.hierarchyOps).isEmpty() } @@ -932,7 +1018,7 @@ class DesktopTasksControllerTest : ShellTestCase() { fun onDesktopWindowClose_singleActiveTask_noWallpaperActivityToken() { val task = setUpFreeformTask() val wct = WindowContainerTransaction() - controller.onDesktopWindowClose(wct, task.taskId) + controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, taskId = task.taskId) // Doesn't modify transaction assertThat(wct.hierarchyOps).isEmpty() } @@ -944,12 +1030,38 @@ class DesktopTasksControllerTest : ShellTestCase() { desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken val wct = WindowContainerTransaction() - controller.onDesktopWindowClose(wct, task.taskId) + controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, taskId = task.taskId) // Adds remove wallpaper operation wct.assertRemoveAt(index = 0, wallpaperToken) } @Test + fun onDesktopWindowClose_singleActiveTask_isClosing() { + val task = setUpFreeformTask() + val wallpaperToken = MockToken().token() + desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken + desktopModeTaskRepository.addClosingTask(DEFAULT_DISPLAY, task.taskId) + + val wct = WindowContainerTransaction() + controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, taskId = task.taskId) + // Doesn't modify transaction + assertThat(wct.hierarchyOps).isEmpty() + } + + @Test + fun onDesktopWindowClose_singleActiveTask_isMinimized() { + val task = setUpFreeformTask() + val wallpaperToken = MockToken().token() + desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken + desktopModeTaskRepository.minimizeTask(DEFAULT_DISPLAY, task.taskId) + + val wct = WindowContainerTransaction() + controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, taskId = task.taskId) + // Doesn't modify transaction + assertThat(wct.hierarchyOps).isEmpty() + } + + @Test fun onDesktopWindowClose_multipleActiveTasks() { val task1 = setUpFreeformTask() setUpFreeformTask() @@ -957,22 +1069,56 @@ class DesktopTasksControllerTest : ShellTestCase() { desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken val wct = WindowContainerTransaction() - controller.onDesktopWindowClose(wct, task1.taskId) + controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, taskId = task1.taskId) // Doesn't modify transaction assertThat(wct.hierarchyOps).isEmpty() } @Test + fun onDesktopWindowClose_multipleActiveTasks_isOnlyNonClosingTask() { + val task1 = setUpFreeformTask() + val task2 = setUpFreeformTask() + val wallpaperToken = MockToken().token() + desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken + desktopModeTaskRepository.addClosingTask(DEFAULT_DISPLAY, task2.taskId) + + val wct = WindowContainerTransaction() + controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, taskId = task1.taskId) + // Adds remove wallpaper operation + wct.assertRemoveAt(index = 0, wallpaperToken) + } + + @Test + fun onDesktopWindowClose_multipleActiveTasks_hasMinimized() { + val task1 = setUpFreeformTask() + val task2 = setUpFreeformTask() + val wallpaperToken = MockToken().token() + desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken + desktopModeTaskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId) + + val wct = WindowContainerTransaction() + controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, taskId = task1.taskId) + // Adds remove wallpaper operation + wct.assertRemoveAt(index = 0, wallpaperToken) + } + + @Test fun handleRequest_fullscreenTask_freeformVisible_returnSwitchToFreeformWCT() { assumeTrue(ENABLE_SHELL_TRANSITIONS) + val homeTask = setUpHomeTask() val freeformTask = setUpFreeformTask() markTaskVisible(freeformTask) val fullscreenTask = createFullscreenTask() - val result = controller.handleRequest(Binder(), createTransition(fullscreenTask)) - assertThat(result?.changes?.get(fullscreenTask.token.asBinder())?.windowingMode) + val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask)) + + assertNotNull(wct, "should handle request") + assertThat(wct.changes[fullscreenTask.token.asBinder()]?.windowingMode) .isEqualTo(WINDOWING_MODE_FREEFORM) + + assertThat(wct.hierarchyOps).hasSize(2) + wct.assertReorderAt(1, homeTask, toTop = false) } @Test @@ -1052,41 +1198,106 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - fun handleRequest_freeformTask_freeformNotVisible_reorderedToTop() { + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_freeformTask_desktopWallpaperDisabled_freeformNotVisible_reorderedToTop() { assumeTrue(ENABLE_SHELL_TRANSITIONS) val freeformTask1 = setUpFreeformTask() + val freeformTask2 = createFreeformTask() + markTaskHidden(freeformTask1) + val result = + controller.handleRequest(Binder(), createTransition(freeformTask2, type = TRANSIT_TO_FRONT)) + + assertNotNull(result, "Should handle request") + assertThat(result.hierarchyOps?.size).isEqualTo(2) + result.assertReorderAt(1, freeformTask2, toTop = true) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_freeformTask_desktopWallpaperEnabled_freeformNotVisible_reorderedToTop() { + assumeTrue(ENABLE_SHELL_TRANSITIONS) + val freeformTask1 = setUpFreeformTask() val freeformTask2 = createFreeformTask() + + markTaskHidden(freeformTask1) val result = - controller.handleRequest(Binder(), createTransition(freeformTask2, type = TRANSIT_TO_FRONT)) + controller.handleRequest(Binder(), createTransition(freeformTask2, type = TRANSIT_TO_FRONT)) - assertThat(result?.hierarchyOps?.size).isEqualTo(2) - result!!.assertReorderAt(1, freeformTask2, toTop = true) + assertNotNull(result, "Should handle request") + assertThat(result.hierarchyOps?.size).isEqualTo(3) + // Add desktop wallpaper activity + result.assertPendingIntentAt(0, desktopWallpaperIntent) + // Bring active desktop tasks to front + result.assertReorderAt(1, freeformTask1, toTop = true) + // Bring new task to front + result.assertReorderAt(2, freeformTask2, toTop = true) } @Test - fun handleRequest_freeformTask_noOtherTasks_reorderedToTop() { + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_freeformTask_desktopWallpaperDisabled_noOtherTasks_reorderedToTop() { assumeTrue(ENABLE_SHELL_TRANSITIONS) val task = createFreeformTask() val result = controller.handleRequest(Binder(), createTransition(task)) - assertThat(result?.hierarchyOps?.size).isEqualTo(1) - result!!.assertReorderAt(0, task, toTop = true) + assertNotNull(result, "Should handle request") + assertThat(result.hierarchyOps?.size).isEqualTo(1) + result.assertReorderAt(0, task, toTop = true) } @Test - fun handleRequest_freeformTask_freeformOnOtherDisplayOnly_reorderedToTop() { + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_freeformTask_desktopWallpaperEnabled_noOtherTasks_reorderedToTop() { + assumeTrue(ENABLE_SHELL_TRANSITIONS) + + val task = createFreeformTask() + val result = controller.handleRequest(Binder(), createTransition(task)) + + assertNotNull(result, "Should handle request") + assertThat(result.hierarchyOps?.size).isEqualTo(2) + // Add desktop wallpaper activity + result.assertPendingIntentAt(0, desktopWallpaperIntent) + // Bring new task to front + result.assertReorderAt(1, task, toTop = true) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_freeformTask_dskWallpaperDisabled_freeformOnOtherDisplayOnly_reorderedToTop() { assumeTrue(ENABLE_SHELL_TRANSITIONS) val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY) - val taskSecondDisplay = createFreeformTask(displayId = SECOND_DISPLAY) + // Second display task + createFreeformTask(displayId = SECOND_DISPLAY) val result = controller.handleRequest(Binder(), createTransition(taskDefaultDisplay)) - assertThat(result?.hierarchyOps?.size).isEqualTo(1) - result!!.assertReorderAt(0, taskDefaultDisplay, toTop = true) + + assertNotNull(result, "Should handle request") + assertThat(result.hierarchyOps?.size).isEqualTo(1) + result.assertReorderAt(0, taskDefaultDisplay, toTop = true) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_freeformTask_dskWallpaperEnabled_freeformOnOtherDisplayOnly_reorderedToTop() { + assumeTrue(ENABLE_SHELL_TRANSITIONS) + + val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY) + // Second display task + createFreeformTask(displayId = SECOND_DISPLAY) + + val result = controller.handleRequest(Binder(), createTransition(taskDefaultDisplay)) + + assertNotNull(result, "Should handle request") + assertThat(result.hierarchyOps?.size).isEqualTo(2) + // Add desktop wallpaper activity + result.assertPendingIntentAt(0, desktopWallpaperIntent) + // Bring new task to front + result.assertReorderAt(1, taskDefaultDisplay, toTop = true) } @Test @@ -1118,6 +1329,17 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun handleRequest_freeformTask_keyguardLocked_returnNull() { + assumeTrue(ENABLE_SHELL_TRANSITIONS) + whenever(keyguardManager.isKeyguardLocked).thenReturn(true) + val freeformTask = createFreeformTask(displayId = DEFAULT_DISPLAY) + + val result = controller.handleRequest(Binder(), createTransition(freeformTask)) + + assertNull(result, "Should NOT handle request") + } + + @Test fun handleRequest_notOpenOrToFrontTransition_returnNull() { assumeTrue(ENABLE_SHELL_TRANSITIONS) @@ -1171,20 +1393,40 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - fun handleRequest_shouldLaunchAsModal_returnSwitchToFullscreenWCT() { - setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) + fun handleRequest_topActivityTransparentWithStyleFloating_returnSwitchToFreeformWCT() { + val freeformTask = setUpFreeformTask() + markTaskVisible(freeformTask) + val task = - setUpFreeformTask().apply { - isTopActivityTransparent = true - numActivities = 1 - } + setUpFullscreenTask().apply { + isTopActivityTransparent = true + isTopActivityStyleFloating = true + numActivities = 1 + } val result = controller.handleRequest(Binder(), createTransition(task)) assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode) - .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN + .isEqualTo(WINDOWING_MODE_FREEFORM) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) + fun handleRequest_topActivityTransparentWithoutStyleFloating_returnSwitchToFullscreenWCT() { + val task = + setUpFreeformTask().apply { + isTopActivityTransparent = true + isTopActivityStyleFloating = false + numActivities = 1 + } + + val result = controller.handleRequest(Binder(), createTransition(task)) + assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode) + .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN } @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) fun handleRequest_systemUIActivity_returnSwitchToFullscreenWCT() { val task = setUpFreeformTask() @@ -1200,48 +1442,205 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_backTransition_singleActiveTaskNoTokenFlagDisabled_doesNotHandle() { + val task = setUpFreeformTask() + + val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK)) + + assertNull(result, "Should not handle request") + } + + @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_backTransition_singleActiveTask_noToken() { + fun handleRequest_backTransition_singleActiveTaskNoTokenFlagEnabled_doesNotHandle() { val task = setUpFreeformTask() + val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK)) - // Doesn't handle request - assertThat(result).isNull() + + assertNull(result, "Should not handle request") } @Test @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_backTransition_singleActiveTask_hasToken_desktopWallpaperDisabled() { + fun handleRequest_backTransition_singleActiveTaskWithTokenFlagDisabled_doesNotHandle() { + val task = setUpFreeformTask() + desktopModeTaskRepository.wallpaperActivityToken = MockToken().token() + val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK)) + + assertNull(result, "Should not handle request") + } + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_backTransition_singleActiveTaskWithTokenFlagEnabled_handlesRequest() { val task = setUpFreeformTask() + val wallpaperToken = MockToken().token() + + desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK)) - // Doesn't handle request - assertThat(result).isNull() + + assertNotNull(result, "Should handle request") + // Should create remove wallpaper transaction + .assertRemoveAt(index = 0, wallpaperToken) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_backTransition_multipleActiveTasksFlagDisabled_doesNotHandle() { + val task1 = setUpFreeformTask() + setUpFreeformTask() + + desktopModeTaskRepository.wallpaperActivityToken = MockToken().token() + val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK)) + + assertNull(result, "Should not handle request") } @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_backTransition_singleActiveTask_hasToken_desktopWallpaperEnabled() { + fun handleRequest_backTransition_multipleActiveTasksFlagEnabled_doesNotHandle() { + val task1 = setUpFreeformTask() + setUpFreeformTask() + + desktopModeTaskRepository.wallpaperActivityToken = MockToken().token() + val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK)) + + assertNull(result, "Should not handle request") + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_backTransition_multipleActiveTasksSingleNonClosing_handlesRequest() { + val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val wallpaperToken = MockToken().token() + desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken + desktopModeTaskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId) + val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK)) + + assertNotNull(result, "Should handle request") + // Should create remove wallpaper transaction + .assertRemoveAt(index = 0, wallpaperToken) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_backTransition_multipleActiveTasksSingleNonMinimized_handlesRequest() { + val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val wallpaperToken = MockToken().token() + + desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken + desktopModeTaskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId) + val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK)) + assertNotNull(result, "Should handle request") + // Should create remove wallpaper transaction + .assertRemoveAt(index = 0, wallpaperToken) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_closeTransition_singleActiveTaskNoTokenFlagDisabled_doesNotHandle() { val task = setUpFreeformTask() - val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK)) - assertThat(result).isNotNull() - // Creates remove wallpaper transaction - result!!.assertRemoveAt(index = 0, wallpaperToken) + + val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE)) + + assertNull(result, "Should not handle request") + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_closeTransition_singleActiveTaskNoTokenFlagEnabled_doesNotHandle() { + val task = setUpFreeformTask() + + val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE)) + + assertNull(result, "Should not handle request") + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_closeTransition_singleActiveTaskWithTokenFlagDisabled_doesNotHandle() { + val task = setUpFreeformTask() + + desktopModeTaskRepository.wallpaperActivityToken = MockToken().token() + val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE)) + + assertNull(result, "Should not handle request") } @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_backTransition_multipleActiveTasks() { + fun handleRequest_closeTransition_singleActiveTaskWithTokenFlagEnabled_handlesRequest() { + val task = setUpFreeformTask() + val wallpaperToken = MockToken().token() + + desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken + val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE)) + + assertNotNull(result, "Should handle request") + // Should create remove wallpaper transaction + .assertRemoveAt(index = 0, wallpaperToken) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_closeTransition_multipleActiveTasksFlagDisabled_doesNotHandle() { + val task1 = setUpFreeformTask() + setUpFreeformTask() + desktopModeTaskRepository.wallpaperActivityToken = MockToken().token() + val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE)) + + assertNull(result, "Should not handle request") + } + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_closeTransition_multipleActiveTasksFlagEnabled_doesNotHandle() { val task1 = setUpFreeformTask() setUpFreeformTask() - val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK)) - // Doesn't handle request - assertThat(result).isNull() + + desktopModeTaskRepository.wallpaperActivityToken = MockToken().token() + val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE)) + + assertNull(result, "Should not handle request") + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_closeTransition_multipleActiveTasksSingleNonClosing_handlesRequest() { + val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val wallpaperToken = MockToken().token() + + desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken + desktopModeTaskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId) + val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE)) + + assertNotNull(result, "Should handle request") + // Should create remove wallpaper transaction + .assertRemoveAt(index = 0, wallpaperToken) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun handleRequest_closeTransition_multipleActiveTasksSingleNonMinimized_handlesRequest() { + val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val wallpaperToken = MockToken().token() + + desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken + desktopModeTaskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId) + val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE)) + + assertNotNull(result, "Should handle request") + // Should create remove wallpaper transaction + .assertRemoveAt(index = 0, wallpaperToken) } @Test @@ -1562,6 +1961,26 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun toggleBounds_togglesToCalculatedBoundsForNonResizable() { + val bounds = Rect(0, 0, 200, 100) + val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds).apply { + topActivityInfo = ActivityInfo().apply { + screenOrientation = SCREEN_ORIENTATION_LANDSCAPE + configuration.windowConfiguration.appBounds = bounds + } + isResizeable = false + } + + // Bounds should be 1000 x 500, vertically centered in the 1000 x 1000 stable bounds + val expectedBounds = Rect(STABLE_BOUNDS.left, 250, STABLE_BOUNDS.right, 750) + + controller.toggleDesktopTaskSize(task) + // Assert bounds set to stable bounds + val wct = getLatestToggleResizeDesktopTaskWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(expectedBounds) + } + + @Test fun toggleBounds_lastBoundsBeforeMaximizeSaved() { val bounds = Rect(0, 0, 100, 100) val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds) @@ -1588,6 +2007,46 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun toggleBounds_togglesFromStableBoundsToLastBoundsBeforeMaximize_nonResizeableEqualWidth() { + val boundsBeforeMaximize = Rect(0, 0, 100, 100) + val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize).apply { + isResizeable = false + } + + // Maximize + controller.toggleDesktopTaskSize(task) + task.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS.left, + boundsBeforeMaximize.top, STABLE_BOUNDS.right, boundsBeforeMaximize.bottom) + + // Restore + controller.toggleDesktopTaskSize(task) + + // Assert bounds set to last bounds before maximize + val wct = getLatestToggleResizeDesktopTaskWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize) + } + + @Test + fun toggleBounds_togglesFromStableBoundsToLastBoundsBeforeMaximize_nonResizeableEqualHeight() { + val boundsBeforeMaximize = Rect(0, 0, 100, 100) + val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize).apply { + isResizeable = false + } + + // Maximize + controller.toggleDesktopTaskSize(task) + task.configuration.windowConfiguration.bounds.set(boundsBeforeMaximize.left, + STABLE_BOUNDS.top, boundsBeforeMaximize.right, STABLE_BOUNDS.bottom) + + // Restore + controller.toggleDesktopTaskSize(task) + + // Assert bounds set to last bounds before maximize + val wct = getLatestToggleResizeDesktopTaskWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize) + } + + @Test fun toggleBounds_removesLastBoundsBeforeMaximizeAfterRestoringBounds() { val boundsBeforeMaximize = Rect(0, 0, 100, 100) val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize) @@ -1615,6 +2074,7 @@ class DesktopTasksControllerTest : ShellTestCase() { task.topActivityInfo = activityInfo whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) desktopModeTaskRepository.addActiveTask(displayId, task.taskId) + desktopModeTaskRepository.updateVisibleFreeformTasks(displayId, task.taskId, visible = true) desktopModeTaskRepository.addOrMoveFreeformTaskToTop(displayId, task.taskId) runningTasks.add(task) return task @@ -1826,6 +2286,16 @@ private fun WindowContainerTransaction.assertReorderSequence(vararg tasks: Runni } } +/** Checks if the reorder hierarchy operations in [range] correspond to [tasks] list */ +private fun WindowContainerTransaction.assertReorderSequenceInRange( + range: IntRange, + vararg tasks: RunningTaskInfo +) { + assertThat(hierarchyOps.slice(range).map { it.type to it.container }) + .containsExactlyElementsIn(tasks.map { HIERARCHY_OP_TYPE_REORDER to it.token.asBinder() }) + .inOrder() +} + private fun WindowContainerTransaction.assertRemoveAt(index: Int, token: WindowContainerToken) { assertIndexInBounds(index) val op = hierarchyOps[index] diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt index 77f917cc28d8..8d9fd9166050 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt @@ -24,6 +24,7 @@ import android.view.Display.DEFAULT_DISPLAY import android.view.WindowManager.TRANSIT_OPEN import android.view.WindowManager.TRANSIT_TO_BACK import android.window.WindowContainerTransaction +import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_TASK import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER import androidx.test.filters.SmallTest import com.android.dx.mockito.inline.extended.ExtendedMockito @@ -32,7 +33,7 @@ import com.android.dx.mockito.inline.extended.StaticMockitoSession import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask -import com.android.wm.shell.shared.DesktopModeStatus +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.transition.TransitionInfoBuilder import com.android.wm.shell.transition.Transitions import com.android.wm.shell.util.StubTransaction @@ -205,6 +206,46 @@ class DesktopTasksLimiterTest : ShellTestCase() { } @Test + fun removeLeftoverMinimizedTasks_activeNonMinimizedTasksStillAround_doesNothing() { + desktopTaskRepo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 1) + desktopTaskRepo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 2) + desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = 2) + + val wct = WindowContainerTransaction() + desktopTasksLimiter.leftoverMinimizedTasksRemover.removeLeftoverMinimizedTasks( + DEFAULT_DISPLAY, wct) + + assertThat(wct.isEmpty).isTrue() + } + + @Test + fun removeLeftoverMinimizedTasks_noMinimizedTasks_doesNothing() { + val wct = WindowContainerTransaction() + desktopTasksLimiter.leftoverMinimizedTasksRemover.removeLeftoverMinimizedTasks( + DEFAULT_DISPLAY, wct) + + assertThat(wct.isEmpty).isTrue() + } + + @Test + fun removeLeftoverMinimizedTasks_onlyMinimizedTasksLeft_removesAllMinimizedTasks() { + val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task1.taskId) + desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId) + + val wct = WindowContainerTransaction() + desktopTasksLimiter.leftoverMinimizedTasksRemover.removeLeftoverMinimizedTasks( + DEFAULT_DISPLAY, wct) + + assertThat(wct.hierarchyOps).hasSize(2) + assertThat(wct.hierarchyOps[0].type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK) + assertThat(wct.hierarchyOps[0].container).isEqualTo(task1.token.asBinder()) + assertThat(wct.hierarchyOps[1].type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK) + assertThat(wct.hierarchyOps[1].container).isEqualTo(task2.token.asBinder()) + } + + @Test fun addAndGetMinimizeTaskChangesIfNeeded_tasksWithinLimit_noTaskMinimized() { val taskLimit = desktopTasksLimiter.getMaxTaskLimit() (1..<taskLimit).forEach { _ -> setUpFreeformTask() } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java index b2467e9a62cf..e5157c974e2d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java @@ -45,6 +45,7 @@ import android.window.WindowContainerTransaction; import androidx.test.filters.SmallTest; +import com.android.internal.jank.InteractionJankMonitor; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource; @@ -65,6 +66,8 @@ public class ExitDesktopTaskTransitionHandlerTest extends ShellTestCase { @Mock private Transitions mTransitions; @Mock + private InteractionJankMonitor mInteractionJankMonitor; + @Mock IBinder mToken; @Mock Supplier<SurfaceControl.Transaction> mTransactionFactory; @@ -94,7 +97,7 @@ public class ExitDesktopTaskTransitionHandlerTest extends ShellTestCase { .thenReturn(getContext().getResources().getDisplayMetrics()); mExitDesktopTaskTransitionHandler = new ExitDesktopTaskTransitionHandler(mTransitions, - mContext); + mContext, mInteractionJankMonitor); mPoint = new Point(0, 0); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java index a64ebd301c00..840126421c08 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java @@ -76,6 +76,8 @@ public class DragAndDropControllerTest extends ShellTestCase { @Mock private ShellCommandHandler mShellCommandHandler; @Mock + private ShellTaskOrganizer mShellTaskOrganizer; + @Mock private DisplayController mDisplayController; @Mock private UiEventLogger mUiEventLogger; @@ -96,8 +98,8 @@ public class DragAndDropControllerTest extends ShellTestCase { public void setUp() throws RemoteException { MockitoAnnotations.initMocks(this); mController = new DragAndDropController(mContext, mShellInit, mShellController, - mShellCommandHandler, mDisplayController, mUiEventLogger, mIconProvider, - mGlobalDragListener, mTransitions, mMainExecutor); + mShellCommandHandler, mShellTaskOrganizer, mDisplayController, mUiEventLogger, + mIconProvider, mGlobalDragListener, mTransitions, mMainExecutor); mController.onInit(); } 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 6e72e8df8d62..97fa8d6ceca9 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 @@ -65,8 +65,6 @@ import android.content.res.Resources; import android.graphics.Insets; import android.os.RemoteException; import android.view.DisplayInfo; -import android.view.DragEvent; -import android.view.View; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -76,7 +74,6 @@ import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.draganddrop.DragAndDropPolicy.Target; import com.android.wm.shell.splitscreen.SplitScreenController; -import com.android.wm.shell.startingsurface.TaskSnapshotWindow; import org.junit.After; import org.junit.Before; @@ -106,6 +103,8 @@ public class DragAndDropPolicyTest extends ShellTestCase { // Both the split-screen and start interface. @Mock private SplitScreenController mSplitScreenStarter; + @Mock + private DragAndDropPolicy.Starter mFullscreenStarter; @Mock private InstanceId mLoggerSessionId; @@ -151,7 +150,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, mSplitScreenStarter, mSplitScreenStarter)); + mPolicy = spy(new DragAndDropPolicy(mContext, mSplitScreenStarter, mFullscreenStarter)); mActivityClipData = createAppClipData(MIMETYPE_APPLICATION_ACTIVITY); mLaunchableIntentPendingIntent = mock(PendingIntent.class); when(mLaunchableIntentPendingIntent.getCreatorUserHandle()) @@ -285,14 +284,14 @@ public class DragAndDropPolicyTest extends ShellTestCase { setRunningTask(mHomeTask); DragSession dragSession = new DragSession(mActivityTaskManager, mLandscapeDisplayLayout, data, 0 /* dragFlags */); - dragSession.update(); + dragSession.initialize(); mPolicy.start(dragSession, mLoggerSessionId); ArrayList<Target> targets = assertExactTargetTypes( mPolicy.getTargets(mInsets), TYPE_FULLSCREEN); - mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN)); - verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(), - eq(SPLIT_POSITION_UNDEFINED), any()); + mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), null /* hideTaskToken */); + verify(mFullscreenStarter).startIntent(any(), anyInt(), any(), + eq(SPLIT_POSITION_UNDEFINED), any(), any()); } private void dragOverFullscreenApp_expectSplitScreenTargets(ClipData data) { @@ -300,19 +299,19 @@ public class DragAndDropPolicyTest extends ShellTestCase { setRunningTask(mFullscreenAppTask); DragSession dragSession = new DragSession(mActivityTaskManager, mLandscapeDisplayLayout, data, 0 /* dragFlags */); - dragSession.update(); + dragSession.initialize(); mPolicy.start(dragSession, mLoggerSessionId); ArrayList<Target> targets = assertExactTargetTypes( mPolicy.getTargets(mInsets), TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT); - mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_LEFT)); + mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_LEFT), null /* hideTaskToken */); verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(), - eq(SPLIT_POSITION_TOP_OR_LEFT), any()); + eq(SPLIT_POSITION_TOP_OR_LEFT), any(), any()); reset(mSplitScreenStarter); - mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT)); + mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), null /* hideTaskToken */); verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(), - eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any()); + eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any(), any()); } private void dragOverFullscreenAppPhone_expectVerticalSplitScreenTargets(ClipData data) { @@ -320,19 +319,20 @@ public class DragAndDropPolicyTest extends ShellTestCase { setRunningTask(mFullscreenAppTask); DragSession dragSession = new DragSession(mActivityTaskManager, mPortraitDisplayLayout, data, 0 /* dragFlags */); - dragSession.update(); + dragSession.initialize(); mPolicy.start(dragSession, mLoggerSessionId); ArrayList<Target> targets = assertExactTargetTypes( mPolicy.getTargets(mInsets), TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM); - mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_TOP)); + mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_TOP), null /* hideTaskToken */); verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(), - eq(SPLIT_POSITION_TOP_OR_LEFT), any()); + eq(SPLIT_POSITION_TOP_OR_LEFT), any(), any()); reset(mSplitScreenStarter); - mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM)); + mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM), + null /* hideTaskToken */); verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(), - eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any()); + eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any(), any()); } @Test @@ -340,7 +340,7 @@ public class DragAndDropPolicyTest extends ShellTestCase { setRunningTask(mFullscreenAppTask); DragSession dragSession = new DragSession(mActivityTaskManager, mLandscapeDisplayLayout, mActivityClipData, 0 /* dragFlags */); - dragSession.update(); + dragSession.initialize(); mPolicy.start(dragSession, mLoggerSessionId); ArrayList<Target> targets = mPolicy.getTargets(mInsets); for (Target t : targets) { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java index 3f3cafcf6375..6ec6bed860bd 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java @@ -36,7 +36,7 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestRunningTaskInfoBuilder; import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; -import com.android.wm.shell.shared.DesktopModeStatus; +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.windowdecor.WindowDecorViewModel; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java index 5880ffb0dce2..72950a8dc139 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java @@ -88,8 +88,11 @@ public class PipAnimationControllerTest extends ShellTestCase { @Test public void getAnimator_withBounds_returnBoundsAnimator() { + final Rect baseValue = new Rect(0, 0, 100, 100); + final Rect startValue = new Rect(0, 0, 100, 100); + final Rect endValue1 = new Rect(100, 100, 200, 200); final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController - .getAnimator(mTaskInfo, mLeash, new Rect(), new Rect(), new Rect(), null, + .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue1, null, TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0); assertEquals("Expect ANIM_TYPE_BOUNDS animation", 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 d38fc6cb6418..75d21457b60b 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 @@ -34,7 +34,6 @@ import static org.mockito.Mockito.when; import static java.lang.Integer.MAX_VALUE; -import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; import android.content.res.Configuration; @@ -183,7 +182,7 @@ public class PipControllerTest extends ShellTestCase { @Test public void instantiatePipController_registersPipTransitionCallback() { - verify(mMockPipTransitionController).registerPipTransitionCallback(any()); + verify(mMockPipTransitionController).registerPipTransitionCallback(any(), any()); } @Test @@ -235,27 +234,6 @@ public class PipControllerTest extends ShellTestCase { } @Test - public void onActivityHidden_isLastPipComponentName_clearLastPipComponent() { - final ComponentName component1 = new ComponentName(mContext, "component1"); - when(mMockPipBoundsState.getLastPipComponentName()).thenReturn(component1); - - mPipController.mPinnedTaskListener.onActivityHidden(component1); - - verify(mMockPipBoundsState).setLastPipComponentName(null); - } - - @Test - public void onActivityHidden_isNotLastPipComponentName_lastPipComponentNotCleared() { - final ComponentName component1 = new ComponentName(mContext, "component1"); - final ComponentName component2 = new ComponentName(mContext, "component2"); - when(mMockPipBoundsState.getLastPipComponentName()).thenReturn(component1); - - mPipController.mPinnedTaskListener.onActivityHidden(component2); - - verify(mMockPipBoundsState, never()).setLastPipComponentName(null); - } - - @Test public void saveReentryState_savesPipBoundsState() { final Rect bounds = new Rect(0, 0, 10, 10); when(mMockPipBoundsAlgorithm.getSnapFraction(bounds)).thenReturn(1.0f); 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 ace09a82d71c..66f8c0b9558d 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 @@ -114,8 +114,8 @@ public class PipResizeGestureHandlerTest extends ShellTestCase { final PipBoundsAlgorithm pipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, pipSnapAlgorithm, pipKeepClearAlgorithm, mPipDisplayLayoutState, mSizeSpecSource); - final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mPipBoundsState, - mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm, + final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mMainExecutor, + mPipBoundsState, mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm, mMockPipTransitionController, mFloatingContentCoordinator, Optional.empty() /* pipPerfHintControllerOptional */); 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 92762fa68550..6d18e3696f84 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 @@ -116,8 +116,8 @@ public class PipTouchHandlerTest extends ShellTestCase { mPipSnapAlgorithm = new PipSnapAlgorithm(); mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, mPipSnapAlgorithm, new PipKeepClearAlgorithmInterface() {}, mPipDisplayLayoutState, mSizeSpecSource); - PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mPipBoundsState, - mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm, + PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mMainExecutor, + mPipBoundsState, mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm, mMockPipTransitionController, mFloatingContentCoordinator, Optional.empty() /* pipPerfHintControllerOptional */); mPipTouchHandler = new PipTouchHandler(mContext, mShellInit, mPhonePipMenuController, 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 974539f23b80..aa2d6f09508f 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 @@ -241,16 +241,16 @@ public class TvPipGravityTest extends ShellTestCase { @Test public void updateGravity_move_expanded_valid() { - mTvPipBoundsState.setTvPipExpanded(true); - // Vertical expanded PiP. mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true); + mTvPipBoundsState.setTvPipExpanded(true); mTvPipBoundsState.setTvPipGravity(Gravity.CENTER_VERTICAL | Gravity.RIGHT); moveAndCheckGravity(KEYCODE_DPAD_LEFT, Gravity.CENTER_VERTICAL | Gravity.LEFT, true); moveAndCheckGravity(KEYCODE_DPAD_RIGHT, Gravity.CENTER_VERTICAL | Gravity.RIGHT, true); // Horizontal expanded PiP. mTvPipBoundsState.setDesiredTvExpandedAspectRatio(HORIZONTAL_EXPANDED_ASPECT_RATIO, true); + mTvPipBoundsState.setTvPipExpanded(true); mTvPipBoundsState.setTvPipGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL); moveAndCheckGravity(KEYCODE_DPAD_UP, Gravity.TOP | Gravity.CENTER_HORIZONTAL, true); moveAndCheckGravity(KEYCODE_DPAD_DOWN, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, true); @@ -281,10 +281,9 @@ public class TvPipGravityTest extends ShellTestCase { @Test public void updateGravity_move_expanded_invalid() { - mTvPipBoundsState.setTvPipExpanded(true); - // Vertical expanded PiP. mTvPipBoundsState.setDesiredTvExpandedAspectRatio(VERTICAL_EXPANDED_ASPECT_RATIO, true); + mTvPipBoundsState.setTvPipExpanded(true); mTvPipBoundsState.setTvPipGravity(Gravity.CENTER_VERTICAL | Gravity.RIGHT); moveAndCheckGravity(KEYCODE_DPAD_RIGHT, Gravity.CENTER_VERTICAL | Gravity.RIGHT, false); moveAndCheckGravity(KEYCODE_DPAD_UP, Gravity.CENTER_VERTICAL | Gravity.RIGHT, false); @@ -297,6 +296,7 @@ public class TvPipGravityTest extends ShellTestCase { // Horizontal expanded PiP. mTvPipBoundsState.setDesiredTvExpandedAspectRatio(HORIZONTAL_EXPANDED_ASPECT_RATIO, true); + mTvPipBoundsState.setTvPipExpanded(true); mTvPipBoundsState.setTvPipGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL); moveAndCheckGravity(KEYCODE_DPAD_DOWN, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, false); moveAndCheckGravity(KEYCODE_DPAD_LEFT, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, false); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt index bbd65be9abda..15b73c541ed8 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.recents import android.app.ActivityManager +import android.app.ActivityManager.RecentTaskInfo import android.graphics.Rect import android.os.Parcel import android.testing.AndroidTestingRunner @@ -33,6 +34,7 @@ import com.android.wm.shell.util.GroupedRecentTaskInfo.TYPE_SPLIT import com.android.wm.shell.util.SplitBounds import com.google.common.truth.Correspondence import com.google.common.truth.Truth.assertThat +import org.junit.Assert.assertThrows import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.mock @@ -86,12 +88,13 @@ class GroupedRecentTaskInfoTest : ShellTestCase() { @Test fun testFreeformTasks_hasCorrectType() { - assertThat(freeformTasksGroupInfo().type).isEqualTo(TYPE_FREEFORM) + assertThat(freeformTasksGroupInfo(freeformTaskIds = arrayOf(1)).type) + .isEqualTo(TYPE_FREEFORM) } @Test - fun testSplitTasks_taskInfoList_hasThreeTasks() { - val list = freeformTasksGroupInfo().taskInfoList + fun testCreateFreeformTasks_hasCorrectNumberOfTasks() { + val list = freeformTasksGroupInfo(freeformTaskIds = arrayOf(1, 2, 3)).taskInfoList assertThat(list).hasSize(3) assertThat(list[0].taskId).isEqualTo(1) assertThat(list[1].taskId).isEqualTo(2) @@ -99,6 +102,16 @@ class GroupedRecentTaskInfoTest : ShellTestCase() { } @Test + fun testCreateFreeformTasks_nonExistentMinimizedTaskId_throwsException() { + assertThrows(IllegalArgumentException::class.java) { + freeformTasksGroupInfo( + freeformTaskIds = arrayOf(1, 2, 3), + minimizedTaskIds = arrayOf(1, 4) + ) + } + } + + @Test fun testParcelling_singleTask() { val recentTaskInfo = singleTaskGroupInfo() val parcel = Parcel.obtain() @@ -129,7 +142,7 @@ class GroupedRecentTaskInfoTest : ShellTestCase() { @Test fun testParcelling_freeformTasks() { - val recentTaskInfo = freeformTasksGroupInfo() + val recentTaskInfo = freeformTasksGroupInfo(freeformTaskIds = arrayOf(1, 2, 3)) val parcel = Parcel.obtain() recentTaskInfo.writeToParcel(parcel, 0) parcel.setDataPosition(0) @@ -145,6 +158,21 @@ class GroupedRecentTaskInfoTest : ShellTestCase() { .containsExactly(1, 2, 3) } + @Test + fun testParcelling_freeformTasks_minimizedTasks() { + val recentTaskInfo = freeformTasksGroupInfo( + freeformTaskIds = arrayOf(1, 2, 3), minimizedTaskIds = arrayOf(2)) + + val parcel = Parcel.obtain() + recentTaskInfo.writeToParcel(parcel, 0) + parcel.setDataPosition(0) + + // Read the object back from the parcel + val recentTaskInfoParcel = CREATOR.createFromParcel(parcel) + assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_FREEFORM) + assertThat(recentTaskInfoParcel.minimizedTaskIds).isEqualTo(arrayOf(2).toIntArray()) + } + private fun createTaskInfo(id: Int) = ActivityManager.RecentTaskInfo().apply { taskId = id token = WindowContainerToken(mock(IWindowContainerToken::class.java)) @@ -162,10 +190,12 @@ class GroupedRecentTaskInfoTest : ShellTestCase() { return GroupedRecentTaskInfo.forSplitTasks(task1, task2, splitBounds) } - private fun freeformTasksGroupInfo(): GroupedRecentTaskInfo { - val task1 = createTaskInfo(id = 1) - val task2 = createTaskInfo(id = 2) - val task3 = createTaskInfo(id = 3) - return GroupedRecentTaskInfo.forFreeformTasks(task1, task2, task3) + private fun freeformTasksGroupInfo( + freeformTaskIds: Array<Int>, + minimizedTaskIds: Array<Int> = emptyArray() + ): GroupedRecentTaskInfo { + return GroupedRecentTaskInfo.forFreeformTasks( + freeformTaskIds.map { createTaskInfo(it) }.toTypedArray(), + minimizedTaskIds.toSet()) } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java index e291c0e1a151..a0aab2e6c8a4 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java @@ -68,7 +68,7 @@ import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; -import com.android.wm.shell.shared.DesktopModeStatus; +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; @@ -399,7 +399,7 @@ public class RecentTasksControllerTest extends ShellTestCase { } @Test - public void testGetRecentTasks_proto2Enabled_ignoresMinimizedFreeformTasks() { + public void testGetRecentTasks_proto2Enabled_includesMinimizedFreeformTasks() { ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); @@ -415,8 +415,7 @@ public class RecentTasksControllerTest extends ShellTestCase { ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); - // 2 freeform tasks should be grouped into one, 1 task should be skipped, 3 total recents - // entries + // 3 freeform tasks should be grouped into one, 2 single tasks, 3 total recents entries assertEquals(3, recentTasks.size()); GroupedRecentTaskInfo freeformGroup = recentTasks.get(0); GroupedRecentTaskInfo singleGroup1 = recentTasks.get(1); @@ -428,9 +427,10 @@ public class RecentTasksControllerTest extends ShellTestCase { assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup2.getType()); // Check freeform group entries - assertEquals(2, freeformGroup.getTaskInfoList().size()); + assertEquals(3, freeformGroup.getTaskInfoList().size()); assertEquals(t1, freeformGroup.getTaskInfoList().get(0)); - assertEquals(t5, freeformGroup.getTaskInfoList().get(1)); + assertEquals(t3, freeformGroup.getTaskInfoList().get(1)); + assertEquals(t5, freeformGroup.getTaskInfoList().get(2)); // Check single entries assertEquals(t2, singleGroup1.getTaskInfo1()); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt index f9599702e763..0e5efa650cc4 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt @@ -48,7 +48,6 @@ import org.mockito.kotlin.same import org.mockito.kotlin.verify import org.mockito.kotlin.whenever - /** * Test class for {@link TaskStackTransitionObserver} * @@ -168,6 +167,80 @@ class TaskStackTransitionObserverTest { .isEqualTo(freeformOpenChange.taskInfo?.windowingMode) } + @Test + @EnableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL) + fun transitionMerged_withChange_onlyOpenChangeIsNotified() { + val listener = TestListener() + val executor = TestShellExecutor() + transitionObserver.addTaskStackTransitionObserverListener(listener, executor) + + // Create open transition + val change = + createChange( + WindowManager.TRANSIT_OPEN, + createTaskInfo(1, WindowConfiguration.WINDOWING_MODE_FREEFORM) + ) + val transitionInfo = + TransitionInfoBuilder(WindowManager.TRANSIT_OPEN, 0).addChange(change).build() + + // create change transition to be merged to above transition + val mergedChange = + createChange( + WindowManager.TRANSIT_CHANGE, + createTaskInfo(2, WindowConfiguration.WINDOWING_MODE_FREEFORM) + ) + val mergedTransitionInfo = + TransitionInfoBuilder(WindowManager.TRANSIT_CHANGE, 0).addChange(mergedChange).build() + val mergedTransition = Mockito.mock(IBinder::class.java) + + callOnTransitionReady(transitionInfo) + callOnTransitionReady(mergedTransitionInfo, mergedTransition) + callOnTransitionMerged(mergedTransition) + callOnTransitionFinished() + executor.flushAll() + + assertThat(listener.taskInfoToBeNotified.taskId).isEqualTo(change.taskInfo?.taskId) + assertThat(listener.taskInfoToBeNotified.windowingMode) + .isEqualTo(change.taskInfo?.windowingMode) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL) + fun transitionMerged_withOpen_lastOpenChangeIsNotified() { + val listener = TestListener() + val executor = TestShellExecutor() + transitionObserver.addTaskStackTransitionObserverListener(listener, executor) + + // Create open transition + val change = + createChange( + WindowManager.TRANSIT_OPEN, + createTaskInfo(1, WindowConfiguration.WINDOWING_MODE_FREEFORM) + ) + val transitionInfo = + TransitionInfoBuilder(WindowManager.TRANSIT_OPEN, 0).addChange(change).build() + + // create change transition to be merged to above transition + val mergedChange = + createChange( + WindowManager.TRANSIT_OPEN, + createTaskInfo(2, WindowConfiguration.WINDOWING_MODE_FREEFORM) + ) + val mergedTransitionInfo = + TransitionInfoBuilder(WindowManager.TRANSIT_OPEN, 0).addChange(mergedChange).build() + val mergedTransition = Mockito.mock(IBinder::class.java) + + callOnTransitionReady(transitionInfo) + callOnTransitionReady(mergedTransitionInfo, mergedTransition) + callOnTransitionMerged(mergedTransition) + callOnTransitionFinished() + executor.flushAll() + + assertThat(listener.taskInfoToBeNotified.taskId).isEqualTo(mergedChange.taskInfo?.taskId) + assertThat(listener.taskInfoToBeNotified.windowingMode) + .isEqualTo(mergedChange.taskInfo?.windowingMode) + } + class TestListener : TaskStackTransitionObserver.TaskStackTransitionObserverListener { var taskInfoToBeNotified = ActivityManager.RunningTaskInfo() @@ -179,11 +252,14 @@ class TaskStackTransitionObserverTest { } /** Simulate calling the onTransitionReady() method */ - private fun callOnTransitionReady(transitionInfo: TransitionInfo) { + private fun callOnTransitionReady( + transitionInfo: TransitionInfo, + transition: IBinder = mockTransitionBinder + ) { val startT = Mockito.mock(SurfaceControl.Transaction::class.java) val finishT = Mockito.mock(SurfaceControl.Transaction::class.java) - transitionObserver.onTransitionReady(mockTransitionBinder, transitionInfo, startT, finishT) + transitionObserver.onTransitionReady(transition, transitionInfo, startT, finishT) } /** Simulate calling the onTransitionFinished() method */ @@ -191,6 +267,11 @@ class TaskStackTransitionObserverTest { transitionObserver.onTransitionFinished(mockTransitionBinder, false) } + /** Simulate calling the onTransitionMerged() method */ + private fun callOnTransitionMerged(merged: IBinder, playing: IBinder = mockTransitionBinder) { + transitionObserver.onTransitionMerged(merged, playing) + } + companion object { fun createTaskInfo(taskId: Int, windowingMode: Int): ActivityManager.RunningTaskInfo { val taskInfo = ActivityManager.RunningTaskInfo() diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt new file mode 100644 index 000000000000..b1d62f485a2a --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt @@ -0,0 +1,456 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.shared.desktopmode + +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule +import android.provider.Settings +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE +import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY +import com.android.window.flags.Flags.FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION +import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.Companion.convertToToggleOverrideWithFallback +import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.DESKTOP_WINDOWING_MODE +import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.ToggleOverride.OVERRIDE_OFF +import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.ToggleOverride.OVERRIDE_ON +import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.ToggleOverride.OVERRIDE_UNSET +import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.WALLPAPER_ACTIVITY +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Test class for [DesktopModeFlags] + * + * Usage: atest WMShellUnitTests:DesktopModeFlagsTest + */ +@SmallTest +@RunWith(AndroidTestingRunner::class) +class DesktopModeFlagsTest : ShellTestCase() { + + @JvmField @Rule val setFlagsRule = SetFlagsRule() + + @Before + fun setUp() { + resetCache() + } + + // TODO(b/348193756): Add tests + // isEnabled_flagNotOverridable_overrideOff_featureFlagOn_returnsTrue and + // isEnabled_flagNotOverridable_overrideOn_featureFlagOff_returnsFalse after adding non + // overridable flags to DesktopModeFlags. + + @Test + @DisableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) + @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + fun isEnabled_devOptionFlagDisabled_overrideOff_featureFlagOn_returnsTrue() { + setOverride(OVERRIDE_OFF.setting) + + // In absence of dev options, follow flag + assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue() + } + + @Test + @DisableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + fun isEnabled_devOptionFlagDisabled_overrideOn_featureFlagOff_returnsFalse() { + setOverride(OVERRIDE_ON.setting) + + // In absence of dev options, follow flag + assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse() + } + + @Test + @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + fun isEnabled_overrideUnset_featureFlagOn_returnsTrue() { + setOverride(OVERRIDE_UNSET.setting) + + // For overridableFlag, for unset overrides, follow flag + assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue() + } + + @Test + @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) + @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + fun isEnabled_overrideUnset_featureFlagOff_returnsFalse() { + setOverride(OVERRIDE_UNSET.setting) + + // For overridableFlag, for unset overrides, follow flag + assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse() + } + + @Test + @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + fun isEnabled_noOverride_featureFlagOn_returnsTrue() { + setOverride(null) + + // For overridableFlag, in absence of overrides, follow flag + assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue() + } + + @Test + @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) + @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + fun isEnabled_noOverride_featureFlagOff_returnsFalse() { + setOverride(null) + + // For overridableFlag, in absence of overrides, follow flag + assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse() + } + + @Test + @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + fun isEnabled_unrecognizableOverride_featureFlagOn_returnsTrue() { + setOverride(-2) + + // For overridableFlag, for recognizable overrides, follow flag + assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue() + } + + @Test + @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) + @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + fun isEnabled_unrecognizableOverride_featureFlagOff_returnsFalse() { + setOverride(-2) + + // For overridableFlag, for recognizable overrides, follow flag + assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse() + } + + @Test + @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + fun isEnabled_overrideOff_featureFlagOn_returnsFalse() { + setOverride(OVERRIDE_OFF.setting) + + // For overridableFlag, follow override if they exist + assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse() + } + + @Test + @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) + @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + fun isEnabled_overrideOn_featureFlagOff_returnsTrue() { + setOverride(OVERRIDE_ON.setting) + + // For overridableFlag, follow override if they exist + assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue() + } + + @Test + @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + fun isEnabled_overrideOffThenOn_featureFlagOn_returnsFalseAndFalse() { + setOverride(OVERRIDE_OFF.setting) + + // For overridableFlag, follow override if they exist + assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse() + + setOverride(OVERRIDE_ON.setting) + + // Keep overrides constant through the process + assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse() + } + + @Test + @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) + @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + fun isEnabled_overrideOnThenOff_featureFlagOff_returnsTrueAndTrue() { + setOverride(OVERRIDE_ON.setting) + + // For overridableFlag, follow override if they exist + assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue() + + setOverride(OVERRIDE_OFF.setting) + + // Keep overrides constant through the process + assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue() + } + + @Test + @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) + @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + fun isEnabled_noSystemProperty_overrideOn_featureFlagOff_returnsTrueAndStoresPropertyOn() { + System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY) + setOverride(OVERRIDE_ON.setting) + + assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue() + // Store System Property if not present + assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) + .isEqualTo(OVERRIDE_ON.setting.toString()) + } + + @Test + @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + fun isEnabled_noSystemProperty_overrideUnset_featureFlagOn_returnsTrueAndStoresPropertyUnset() { + System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY) + setOverride(OVERRIDE_UNSET.setting) + + assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue() + // Store System Property if not present + assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) + .isEqualTo(OVERRIDE_UNSET.setting.toString()) + } + + @Test + @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) + @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + fun isEnabled_noSystemProperty_overrideUnset_featureFlagOff_returnsFalseAndStoresPropertyUnset() { + System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY) + setOverride(OVERRIDE_UNSET.setting) + + assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse() + // Store System Property if not present + assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) + .isEqualTo(OVERRIDE_UNSET.setting.toString()) + } + + @Test + @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + @Suppress("ktlint:standard:max-line-length") + fun isEnabled_systemPropertyNotInteger_overrideOff_featureFlagOn_returnsFalseAndStoresPropertyOff() { + System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, "abc") + setOverride(OVERRIDE_OFF.setting) + + assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse() + // Store System Property if currently invalid + assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) + .isEqualTo(OVERRIDE_OFF.setting.toString()) + } + + @Test + @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + @Suppress("ktlint:standard:max-line-length") + fun isEnabled_systemPropertyInvalidInteger_overrideOff_featureFlagOn_returnsFalseAndStoresPropertyOff() { + System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, "-2") + setOverride(OVERRIDE_OFF.setting) + + assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse() + // Store System Property if currently invalid + assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) + .isEqualTo(OVERRIDE_OFF.setting.toString()) + } + + @Test + @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + fun isEnabled_systemPropertyOff_overrideOn_featureFlagOn_returnsFalseAndDoesNotUpdateProperty() { + System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, OVERRIDE_OFF.setting.toString()) + setOverride(OVERRIDE_ON.setting) + + // Have a consistent override until reboot + assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse() + assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) + .isEqualTo(OVERRIDE_OFF.setting.toString()) + } + + @Test + @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) + @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + fun isEnabled_systemPropertyOn_overrideOff_featureFlagOff_returnsTrueAndDoesNotUpdateProperty() { + System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, OVERRIDE_ON.setting.toString()) + setOverride(OVERRIDE_OFF.setting) + + // Have a consistent override until reboot + assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue() + assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) + .isEqualTo(OVERRIDE_ON.setting.toString()) + } + + @Test + @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + @Suppress("ktlint:standard:max-line-length") + fun isEnabled_systemPropertyUnset_overrideOff_featureFlagOn_returnsTrueAndDoesNotUpdateProperty() { + System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, OVERRIDE_UNSET.setting.toString()) + setOverride(OVERRIDE_OFF.setting) + + // Have a consistent override until reboot + assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue() + assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) + .isEqualTo(OVERRIDE_UNSET.setting.toString()) + } + + @Test + @EnableFlags( + FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, + FLAG_ENABLE_DESKTOP_WINDOWING_MODE, + FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun isEnabled_dwFlagEnabled_overrideUnset_featureFlagOn_returnsTrue() { + setOverride(OVERRIDE_UNSET.setting) + + // For unset overrides, follow flag + assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isTrue() + } + + @Test + @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun isEnabled_dwFlagEnabled_overrideUnset_featureFlagOff_returnsFalse() { + setOverride(OVERRIDE_UNSET.setting) + + // For unset overrides, follow flag + assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isFalse() + } + + @Test + @EnableFlags( + FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, + FLAG_ENABLE_DESKTOP_WINDOWING_MODE, + FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun isEnabled_dwFlagEnabled_overrideOn_featureFlagOn_returnsTrue() { + setOverride(OVERRIDE_ON.setting) + + // When toggle override matches its default state (dw flag), don't override flags + assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isTrue() + } + + @Test + @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun isEnabled_dwFlagEnabled_overrideOn_featureFlagOff_returnFalse() { + setOverride(OVERRIDE_ON.setting) + + // When toggle override matches its default state (dw flag), don't override flags + assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isFalse() + } + + @Test + @EnableFlags( + FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, + FLAG_ENABLE_DESKTOP_WINDOWING_MODE, + FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun isEnabled_dwFlagEnabled_overrideOff_featureFlagOn_returnsFalse() { + setOverride(OVERRIDE_OFF.setting) + + // Follow override if they exist, and is not equal to default toggle state (dw flag) + assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isFalse() + } + + @Test + @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun isEnabled_dwFlagEnabled_overrideOff_featureFlagOff_returnsFalse() { + setOverride(OVERRIDE_OFF.setting) + + // Follow override if they exist, and is not equal to default toggle state (dw flag) + assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isFalse() + } + + @Test + @EnableFlags( + FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + fun isEnabled_dwFlagDisabled_overrideUnset_featureFlagOn_returnsTrue() { + setOverride(OVERRIDE_UNSET.setting) + + // For unset overrides, follow flag + assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isTrue() + } + + @Test + @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) + @DisableFlags( + FLAG_ENABLE_DESKTOP_WINDOWING_MODE, FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun isEnabled_dwFlagDisabled_overrideUnset_featureFlagOff_returnsFalse() { + setOverride(OVERRIDE_UNSET.setting) + + // For unset overrides, follow flag + assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isFalse() + } + + @Test + @EnableFlags( + FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + fun isEnabled_dwFlagDisabled_overrideOn_featureFlagOn_returnsTrue() { + setOverride(OVERRIDE_ON.setting) + + // Follow override if they exist, and is not equal to default toggle state (dw flag) + assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isTrue() + } + + @Test + @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) + @DisableFlags( + FLAG_ENABLE_DESKTOP_WINDOWING_MODE, FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun isEnabled_dwFlagDisabled_overrideOn_featureFlagOff_returnTrue() { + setOverride(OVERRIDE_ON.setting) + + // Follow override if they exist, and is not equal to default toggle state (dw flag) + assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isTrue() + } + + @Test + @EnableFlags( + FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + fun isEnabled_dwFlagDisabled_overrideOff_featureFlagOn_returnsTrue() { + setOverride(OVERRIDE_OFF.setting) + + // When toggle override matches its default state (dw flag), don't override flags + assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isTrue() + } + + @Test + @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) + @DisableFlags( + FLAG_ENABLE_DESKTOP_WINDOWING_MODE, FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + fun isEnabled_dwFlagDisabled_overrideOff_featureFlagOff_returnsFalse() { + setOverride(OVERRIDE_OFF.setting) + + // When toggle override matches its default state (dw flag), don't override flags + assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isFalse() + } + + @Test + fun convertToToggleOverrideWithFallback_validInt_returnsToggleOverride() { + assertThat(convertToToggleOverrideWithFallback(0, OVERRIDE_UNSET)).isEqualTo(OVERRIDE_OFF) + assertThat(convertToToggleOverrideWithFallback(1, OVERRIDE_UNSET)).isEqualTo(OVERRIDE_ON) + assertThat(convertToToggleOverrideWithFallback(-1, OVERRIDE_ON)).isEqualTo(OVERRIDE_UNSET) + } + + @Test + fun convertToToggleOverrideWithFallback_invalidInt_returnsFallback() { + assertThat(convertToToggleOverrideWithFallback(2, OVERRIDE_ON)).isEqualTo(OVERRIDE_ON) + assertThat(convertToToggleOverrideWithFallback(-2, OVERRIDE_UNSET)).isEqualTo(OVERRIDE_UNSET) + } + + private fun setOverride(setting: Int?) { + val contentResolver = mContext.contentResolver + val key = Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES + if (setting == null) { + Settings.Global.putString(contentResolver, key, null) + } else { + Settings.Global.putInt(contentResolver, key, setting) + } + } + + private fun resetCache() { + val cachedToggleOverride = + DesktopModeFlags::class.java.getDeclaredField("cachedToggleOverride") + cachedToggleOverride.isAccessible = true + cachedToggleOverride.set(null, null) + + // Clear override cache stored in System property + System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY) + } + + private companion object { + const val SYSTEM_PROPERTY_OVERRIDE_KEY = "sys.wmshell.desktopmode.dev_toggle_override" + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/OWNERS b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/OWNERS new file mode 100644 index 000000000000..2fabd4a33586 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/OWNERS @@ -0,0 +1 @@ +file:/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
\ No newline at end of file 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 3c387f0d7c34..5b95b1588814 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java @@ -36,6 +36,7 @@ import static org.mockito.ArgumentMatchers.isA; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; @@ -49,6 +50,9 @@ import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; import android.os.Bundle; +import android.os.IBinder; +import android.window.IWindowContainerToken; +import android.window.WindowContainerToken; import androidx.test.annotation.UiThreadTest; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -195,10 +199,10 @@ public class SplitScreenControllerTests extends ShellTestCase { PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE); mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null, - SPLIT_POSITION_TOP_OR_LEFT, null); + SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */); verify(mStageCoordinator).startIntent(eq(pendingIntent), mIntentCaptor.capture(), - eq(SPLIT_POSITION_TOP_OR_LEFT), isNull()); + eq(SPLIT_POSITION_TOP_OR_LEFT), isNull(), isNull()); assertEquals(FLAG_ACTIVITY_NO_USER_ACTION, mIntentCaptor.getValue().getFlags() & FLAG_ACTIVITY_NO_USER_ACTION); } @@ -213,19 +217,20 @@ public class SplitScreenControllerTests extends ShellTestCase { ActivityManager.RunningTaskInfo topRunningTask = createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent); doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask(); + doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask(any()); mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null, - SPLIT_POSITION_TOP_OR_LEFT, null); + SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */); verify(mStageCoordinator).startIntent(eq(pendingIntent), mIntentCaptor.capture(), - eq(SPLIT_POSITION_TOP_OR_LEFT), isNull()); + eq(SPLIT_POSITION_TOP_OR_LEFT), isNull(), isNull()); assertEquals(FLAG_ACTIVITY_MULTIPLE_TASK, mIntentCaptor.getValue().getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK); } @Test public void startIntent_multiInstancesNotSupported_startTaskInBackgroundBeforeSplitActivated() { - doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any()); + doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any(), any()); Intent startIntent = createStartIntent("startActivity"); PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE); @@ -233,15 +238,16 @@ public class SplitScreenControllerTests extends ShellTestCase { ActivityManager.RunningTaskInfo topRunningTask = createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent); doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask(); + doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask(any()); // Put the same component into a task in the background ActivityManager.RecentTaskInfo sameTaskInfo = new ActivityManager.RecentTaskInfo(); - doReturn(sameTaskInfo).when(mRecentTasks).findTaskInBackground(any(), anyInt()); + doReturn(sameTaskInfo).when(mRecentTasks).findTaskInBackground(any(), anyInt(), any()); mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null, - SPLIT_POSITION_TOP_OR_LEFT, null); + SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */); verify(mStageCoordinator).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT), - isNull()); + isNull(), isNull()); verify(mMultiInstanceHelper, never()).supportsMultiInstanceSplit(any()); verify(mStageCoordinator, never()).switchSplitPosition(any()); } @@ -249,7 +255,7 @@ public class SplitScreenControllerTests extends ShellTestCase { @Test public void startIntent_multiInstancesSupported_startTaskInBackgroundAfterSplitActivated() { doReturn(true).when(mMultiInstanceHelper).supportsMultiInstanceSplit(any()); - doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any()); + doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any(), any()); Intent startIntent = createStartIntent("startActivity"); PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE); @@ -261,13 +267,13 @@ public class SplitScreenControllerTests extends ShellTestCase { SPLIT_POSITION_BOTTOM_OR_RIGHT); // Put the same component into a task in the background doReturn(new ActivityManager.RecentTaskInfo()).when(mRecentTasks) - .findTaskInBackground(any(), anyInt()); + .findTaskInBackground(any(), anyInt(), any()); mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null, - SPLIT_POSITION_TOP_OR_LEFT, null); + SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */); verify(mMultiInstanceHelper, never()).supportsMultiInstanceSplit(any()); verify(mStageCoordinator).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT), - isNull()); + isNull(), isNull()); } @Test @@ -284,7 +290,7 @@ public class SplitScreenControllerTests extends ShellTestCase { SPLIT_POSITION_BOTTOM_OR_RIGHT); mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null, - SPLIT_POSITION_TOP_OR_LEFT, null); + SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */); verify(mStageCoordinator).switchSplitPosition(anyString()); } @@ -312,6 +318,7 @@ public class SplitScreenControllerTests extends ShellTestCase { info.supportsMultiWindow = true; info.baseIntent = strIntent; info.baseActivity = strIntent.getComponent(); + info.token = new WindowContainerToken(mock(IWindowContainerToken.class)); ActivityInfo activityInfo = new ActivityInfo(); activityInfo.packageName = info.baseActivity.getPackageName(); activityInfo.name = info.baseActivity.getClassName(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ChangeBuilder.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ChangeBuilder.java new file mode 100644 index 000000000000..b54c3bf72110 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ChangeBuilder.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.transition; + +import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; + +import static org.mockito.Mockito.mock; + +import android.app.ActivityManager.RunningTaskInfo; +import android.view.Surface; +import android.view.SurfaceControl; +import android.view.WindowManager; +import android.window.TransitionInfo; + +public class ChangeBuilder { + final TransitionInfo.Change mChange; + + ChangeBuilder(@WindowManager.TransitionType int mode) { + mChange = new TransitionInfo.Change(null /* token */, createMockSurface(true)); + mChange.setMode(mode); + } + + ChangeBuilder setFlags(@TransitionInfo.ChangeFlags int flags) { + mChange.setFlags(flags); + return this; + } + + ChangeBuilder setTask(RunningTaskInfo taskInfo) { + mChange.setTaskInfo(taskInfo); + return this; + } + + ChangeBuilder setRotate(int anim) { + return setRotate(Surface.ROTATION_90, anim); + } + + ChangeBuilder setRotate() { + return setRotate(ROTATION_ANIMATION_UNSPECIFIED); + } + + ChangeBuilder setRotate(@Surface.Rotation int target, int anim) { + mChange.setRotation(Surface.ROTATION_0, target); + mChange.setRotationAnimation(anim); + return this; + } + + TransitionInfo.Change build() { + return mChange; + } + + private static SurfaceControl createMockSurface(boolean valid) { + SurfaceControl sc = mock(SurfaceControl.class); + doReturn(valid).when(sc).isValid(); + return sc; + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java new file mode 100644 index 000000000000..754a173ff069 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.transition; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +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.window.TransitionInfo.FLAG_SYNC; +import static android.window.TransitionInfo.FLAG_TRANSLUCENT; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.app.ActivityManager.RunningTaskInfo; +import android.content.Context; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.view.SurfaceControl; +import android.window.TransitionInfo; +import android.window.WindowContainerToken; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.wm.shell.RootTaskDisplayAreaOrganizer; +import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.TestShellExecutor; +import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.TransactionPool; +import com.android.wm.shell.sysui.ShellInit; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for the default animation handler that is used if no other special-purpose handler picks + * up an animation request. + * + * Build/Install/Run: + * atest WMShellUnitTests:DefaultTransitionHandlerTest + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class DefaultTransitionHandlerTest extends ShellTestCase { + + private final Context mContext = + InstrumentationRegistry.getInstrumentation().getTargetContext(); + + private final DisplayController mDisplayController = mock(DisplayController.class); + private final TransactionPool mTransactionPool = new MockTransactionPool(); + private final TestShellExecutor mMainExecutor = new TestShellExecutor(); + private final TestShellExecutor mAnimExecutor = new TestShellExecutor(); + private final Handler mMainHandler = new Handler(Looper.getMainLooper()); + + private ShellInit mShellInit; + private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; + private DefaultTransitionHandler mTransitionHandler; + + @Before + public void setUp() { + mShellInit = new ShellInit(mMainExecutor); + mRootTaskDisplayAreaOrganizer = new RootTaskDisplayAreaOrganizer( + mMainExecutor, + mContext, + mShellInit); + mTransitionHandler = new DefaultTransitionHandler( + mContext, mShellInit, mDisplayController, + mTransactionPool, mMainExecutor, mMainHandler, mAnimExecutor, + mRootTaskDisplayAreaOrganizer); + mShellInit.init(); + } + + @After + public void tearDown() { + flushHandlers(); + } + + private void flushHandlers() { + mMainHandler.runWithScissors(() -> { + mAnimExecutor.flushAll(); + mMainExecutor.flushAll(); + }, 1000L); + } + + @Test + public void testAnimationBackgroundCreatedForTaskTransition() { + final TransitionInfo.Change openTask = new ChangeBuilder(TRANSIT_OPEN) + .setTask(createTaskInfo(1)) + .build(); + final TransitionInfo.Change closeTask = new ChangeBuilder(TRANSIT_TO_BACK) + .setTask(createTaskInfo(2)) + .build(); + + final IBinder token = new Binder(); + final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN) + .addChange(openTask) + .addChange(closeTask) + .build(); + final SurfaceControl.Transaction startT = MockTransactionPool.create(); + final SurfaceControl.Transaction finishT = MockTransactionPool.create(); + + mTransitionHandler.startAnimation(token, info, startT, finishT, + mock(Transitions.TransitionFinishCallback.class)); + + mergeSync(mTransitionHandler, token); + flushHandlers(); + + verify(startT).setColor(any(), any()); + } + + @Test + public void testNoAnimationBackgroundForTranslucentTasks() { + final TransitionInfo.Change openTask = new ChangeBuilder(TRANSIT_OPEN) + .setTask(createTaskInfo(1)) + .setFlags(FLAG_TRANSLUCENT) + .build(); + final TransitionInfo.Change closeTask = new ChangeBuilder(TRANSIT_TO_BACK) + .setTask(createTaskInfo(2)) + .setFlags(FLAG_TRANSLUCENT) + .build(); + + final IBinder token = new Binder(); + final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN) + .addChange(openTask) + .addChange(closeTask) + .build(); + final SurfaceControl.Transaction startT = MockTransactionPool.create(); + final SurfaceControl.Transaction finishT = MockTransactionPool.create(); + + mTransitionHandler.startAnimation(token, info, startT, finishT, + mock(Transitions.TransitionFinishCallback.class)); + + mergeSync(mTransitionHandler, token); + flushHandlers(); + + verify(startT, never()).setColor(any(), any()); + } + + @Test + public void testNoAnimationBackgroundForWallpapers() { + final TransitionInfo.Change openWallpaper = new ChangeBuilder(TRANSIT_OPEN) + .setFlags(TransitionInfo.FLAG_IS_WALLPAPER) + .build(); + final TransitionInfo.Change closeWallpaper = new ChangeBuilder(TRANSIT_TO_BACK) + .setFlags(TransitionInfo.FLAG_IS_WALLPAPER) + .build(); + + final IBinder token = new Binder(); + final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN) + .addChange(openWallpaper) + .addChange(closeWallpaper) + .build(); + final SurfaceControl.Transaction startT = MockTransactionPool.create(); + final SurfaceControl.Transaction finishT = MockTransactionPool.create(); + + mTransitionHandler.startAnimation(token, info, startT, finishT, + mock(Transitions.TransitionFinishCallback.class)); + + mergeSync(mTransitionHandler, token); + flushHandlers(); + + verify(startT, never()).setColor(any(), any()); + } + + private static void mergeSync(Transitions.TransitionHandler handler, IBinder token) { + handler.mergeAnimation( + new Binder(), + new TransitionInfoBuilder(TRANSIT_SLEEP, FLAG_SYNC).build(), + MockTransactionPool.create(), + token, + mock(Transitions.TransitionFinishCallback.class)); + } + + private static RunningTaskInfo createTaskInfo(int taskId) { + RunningTaskInfo taskInfo = new RunningTaskInfo(); + taskInfo.taskId = taskId; + taskInfo.topActivityType = ACTIVITY_TYPE_STANDARD; + taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + taskInfo.configuration.windowConfiguration.setActivityType(taskInfo.topActivityType); + taskInfo.token = mock(WindowContainerToken.class); + return taskInfo; + } +} + diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/MockTransactionPool.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/MockTransactionPool.java new file mode 100644 index 000000000000..574a87ac4b17 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/MockTransactionPool.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.transition; + +import static org.mockito.Mockito.RETURNS_SELF; +import static org.mockito.Mockito.mock; + +import android.view.SurfaceControl; + +import com.android.wm.shell.common.TransactionPool; +import com.android.wm.shell.util.StubTransaction; + +public class MockTransactionPool extends TransactionPool { + + public static SurfaceControl.Transaction create() { + return mock(StubTransaction.class, RETURNS_SELF); + } + + @Override + public SurfaceControl.Transaction acquire() { + return create(); + } + + @Override + public void release(SurfaceControl.Transaction t) { + } +} 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 964d86e8bd35..409b87723e79 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 @@ -79,7 +79,6 @@ import android.util.Pair; import android.view.IRecentsAnimationRunner; import android.view.Surface; import android.view.SurfaceControl; -import android.view.WindowManager; import android.window.IRemoteTransition; import android.window.IRemoteTransitionFinishedCallback; import android.window.IWindowContainerToken; @@ -1614,43 +1613,6 @@ public class ShellTransitionTests extends ShellTestCase { eq(R.styleable.WindowAnimation_activityCloseEnterAnimation), anyBoolean()); } - class ChangeBuilder { - final TransitionInfo.Change mChange; - - ChangeBuilder(@WindowManager.TransitionType int mode) { - mChange = new TransitionInfo.Change(null /* token */, createMockSurface(true)); - mChange.setMode(mode); - } - - ChangeBuilder setFlags(@TransitionInfo.ChangeFlags int flags) { - mChange.setFlags(flags); - return this; - } - - ChangeBuilder setTask(RunningTaskInfo taskInfo) { - mChange.setTaskInfo(taskInfo); - return this; - } - - ChangeBuilder setRotate(int anim) { - return setRotate(Surface.ROTATION_90, anim); - } - - ChangeBuilder setRotate() { - return setRotate(ROTATION_ANIMATION_UNSPECIFIED); - } - - ChangeBuilder setRotate(@Surface.Rotation int target, int anim) { - mChange.setRotation(Surface.ROTATION_0, target); - mChange.setRotationAnimation(anim); - return this; - } - - TransitionInfo.Change build() { - return mChange; - } - } - class TestTransitionHandler implements Transitions.TransitionHandler { ArrayList<Pair<IBinder, Transitions.TransitionFinishCallback>> mFinishes = new ArrayList<>(); @@ -1739,12 +1701,6 @@ public class ShellTransitionTests extends ShellTestCase { .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build(); } - private static SurfaceControl createMockSurface(boolean valid) { - SurfaceControl sc = mock(SurfaceControl.class); - doReturn(valid).when(sc).isValid(); - return sc; - } - private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode, int activityType) { RunningTaskInfo taskInfo = new RunningTaskInfo(); taskInfo.taskId = taskId; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt index ca1e3f173e24..b1803e97b107 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt @@ -55,6 +55,7 @@ import androidx.test.filters.SmallTest import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession import com.android.dx.mockito.inline.extended.StaticMockitoSession +import com.android.internal.jank.InteractionJankMonitor import com.android.window.flags.Flags import com.android.wm.shell.R import com.android.wm.shell.RootTaskDisplayAreaOrganizer @@ -67,14 +68,16 @@ 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.desktopmode.DesktopTasksController +import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition import com.android.wm.shell.freeform.FreeformTaskTransitionStarter -import com.android.wm.shell.shared.DesktopModeStatus +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.sysui.KeyguardChangeListener 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.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener +import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener import java.util.Optional import java.util.function.Supplier import org.junit.Assert.assertEquals @@ -82,6 +85,7 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.anyInt @@ -131,6 +135,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { @Mock private lateinit var mockRootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer @Mock private lateinit var mockShellCommandHandler: ShellCommandHandler @Mock private lateinit var mockWindowManager: IWindowManager + @Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor private val transactionFactory = Supplier<SurfaceControl.Transaction> { SurfaceControl.Transaction() @@ -164,7 +169,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { mockInputMonitorFactory, transactionFactory, mockRootTaskDisplayAreaOrganizer, - windowDecorByTaskIdSpy + windowDecorByTaskIdSpy, mockInteractionJankMonitor ) whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout) @@ -343,10 +348,35 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { } @Test - fun testDecorationIsNotCreatedForTopTranslucentActivities() { - setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) + fun testDecorationIsCreatedForTopTranslucentActivitiesWithStyleFloating() { + val mockitoSession: StaticMockitoSession = mockitoSession() + .strictness(Strictness.LENIENT) + .spyStatic(DesktopModeStatus::class.java) + .startMocking() + try { + val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true).apply { + isTopActivityTransparent = true + isTopActivityStyleFloating = true + numActivities = 1 + } + doReturn(true).`when` { DesktopModeStatus.canEnterDesktopMode(any()) } + setUpMockDecorationsForTasks(task) + + onTaskOpening(task) + verify(mockDesktopModeWindowDecorFactory) + .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any()) + } finally { + mockitoSession.finishMocking() + } + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) + fun testDecorationIsNotCreatedForTopTranslucentActivitiesWithoutStyleFloating() { val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true).apply { isTopActivityTransparent = true + isTopActivityStyleFloating = false numActivities = 1 } onTaskOpening(task) @@ -356,6 +386,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { } @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) fun testDecorationIsNotCreatedForSystemUIActivities() { val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true) @@ -518,6 +549,99 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { } } + @Test + fun testOnDecorMaximizedOrRestored_togglesTaskSize() { + val decor = setUpMockDecorationForTask(createTask(windowingMode = WINDOWING_MODE_FREEFORM)) + onTaskOpening(decor.mTaskInfo) + val maxOrRestoreListener = ArgumentCaptor.forClass(OnTaskActionClickListener::class.java) + .let { captor -> + verify(decor).setOnMaximizeOrRestoreClickListener(captor.capture()) + return@let captor.value + } + + maxOrRestoreListener.onClick(decor.mTaskInfo.taskId, "test") + + verify(mockDesktopTasksController).toggleDesktopTaskSize(decor.mTaskInfo) + } + + @Test + fun testOnDecorMaximizedOrRestored_closesMenus() { + val decor = setUpMockDecorationForTask(createTask(windowingMode = WINDOWING_MODE_FREEFORM)) + onTaskOpening(decor.mTaskInfo) + val maxOrRestoreListener = ArgumentCaptor.forClass(OnTaskActionClickListener::class.java) + .let { captor -> + verify(decor).setOnMaximizeOrRestoreClickListener(captor.capture()) + return@let captor.value + } + + maxOrRestoreListener.onClick(decor.mTaskInfo.taskId, "test") + + verify(decor).closeHandleMenu() + verify(decor).closeMaximizeMenu() + } + + @Test + fun testOnDecorSnappedLeft_snapResizes() { + val decor = setUpMockDecorationForTask(createTask(windowingMode = WINDOWING_MODE_FREEFORM)) + onTaskOpening(decor.mTaskInfo) + val snapLeftListener = ArgumentCaptor.forClass(OnTaskActionClickListener::class.java) + .let { captor -> + verify(decor).setOnLeftSnapClickListener(captor.capture()) + return@let captor.value + } + + snapLeftListener.onClick(decor.mTaskInfo.taskId, "test") + + verify(mockDesktopTasksController).snapToHalfScreen(decor.mTaskInfo, SnapPosition.LEFT) + } + + @Test + fun testOnDecorSnappedLeft_closeMenus() { + val decor = setUpMockDecorationForTask(createTask(windowingMode = WINDOWING_MODE_FREEFORM)) + onTaskOpening(decor.mTaskInfo) + val snapLeftListener = ArgumentCaptor.forClass(OnTaskActionClickListener::class.java) + .let { captor -> + verify(decor).setOnLeftSnapClickListener(captor.capture()) + return@let captor.value + } + + snapLeftListener.onClick(decor.mTaskInfo.taskId, "test") + + verify(decor).closeHandleMenu() + verify(decor).closeMaximizeMenu() + } + + @Test + fun testOnDecorSnappedRight_snapResizes() { + val decor = setUpMockDecorationForTask(createTask(windowingMode = WINDOWING_MODE_FREEFORM)) + onTaskOpening(decor.mTaskInfo) + val snapLeftListener = ArgumentCaptor.forClass(OnTaskActionClickListener::class.java) + .let { captor -> + verify(decor).setOnRightSnapClickListener(captor.capture()) + return@let captor.value + } + + snapLeftListener.onClick(decor.mTaskInfo.taskId, "test") + + verify(mockDesktopTasksController).snapToHalfScreen(decor.mTaskInfo, SnapPosition.RIGHT) + } + + @Test + fun testOnDecorSnappedRight_closeMenus() { + val decor = setUpMockDecorationForTask(createTask(windowingMode = WINDOWING_MODE_FREEFORM)) + onTaskOpening(decor.mTaskInfo) + val snapLeftListener = ArgumentCaptor.forClass(OnTaskActionClickListener::class.java) + .let { captor -> + verify(decor).setOnRightSnapClickListener(captor.capture()) + return@let captor.value + } + + snapLeftListener.onClick(decor.mTaskInfo.taskId, "test") + + verify(decor).closeHandleMenu() + verify(decor).closeMaximizeMenu() + } + private fun onTaskOpening(task: RunningTaskInfo, leash: SurfaceControl = SurfaceControl()) { desktopModeWindowDecorViewModel.onTaskOpening( task, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java index 46c158908226..d8606093ac5c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java @@ -24,9 +24,15 @@ import static android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.wm.shell.MockSurfaceControlHelper.createMockSurfaceControlTransaction; +import static com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.CLOSE_MAXIMIZE_MENU_DELAY_MS; import static com.google.common.truth.Truth.assertThat; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.doReturn; @@ -38,11 +44,14 @@ import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.content.ComponentName; +import android.content.Context; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Resources; import android.content.res.TypedArray; +import android.graphics.PointF; +import android.net.Uri; import android.os.Handler; import android.os.SystemProperties; import android.platform.test.annotations.DisableFlags; @@ -62,6 +71,7 @@ import android.view.View; import android.view.WindowManager; import android.window.WindowContainerTransaction; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; @@ -74,8 +84,12 @@ import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestRunningTaskInfoBuilder; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.SyncTransactionQueue; -import com.android.wm.shell.shared.DesktopModeStatus; +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams; +import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener; + +import kotlin.Unit; +import kotlin.jvm.functions.Function1; import org.junit.After; import org.junit.Before; @@ -84,6 +98,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Mock; import org.mockito.quality.Strictness; @@ -105,6 +120,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { private static final String USE_ROUNDED_CORNERS_SYSPROP_KEY = "persist.wm.debug.desktop_use_rounded_corners"; + private static final Uri TEST_URI = Uri.parse("www.google.com"); + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT); @Mock @@ -112,8 +129,6 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { @Mock private ShellTaskOrganizer mMockShellTaskOrganizer; @Mock - private Handler mMockHandler; - @Mock private Choreographer mMockChoreographer; @Mock private SyncTransactionQueue mMockSyncQueue; @@ -131,13 +146,20 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { private WindowDecoration.SurfaceControlViewHostFactory mMockSurfaceControlViewHostFactory; @Mock private TypedArray mMockRoundedCornersRadiusArray; - @Mock private TestTouchEventListener mMockTouchEventListener; @Mock private DesktopModeWindowDecoration.ExclusionRegionListener mMockExclusionRegionListener; @Mock private PackageManager mMockPackageManager; + @Mock + private Handler mMockHandler; + @Mock + private DesktopModeWindowDecoration.OpenInBrowserClickListener mMockOpenInBrowserClickListener; + @Captor + private ArgumentCaptor<Function1<Boolean, Unit>> mOnMaxMenuHoverChangeListener; + @Captor + private ArgumentCaptor<Runnable> mCloseMaxMenuRunnable; private final InsetsState mInsetsState = new InsetsState(); private SurfaceControl.Transaction mMockTransaction; @@ -154,7 +176,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { } @Before - public void setUp() { + public void setUp() throws PackageManager.NameNotFoundException { mMockitoSession = mockitoSession() .strictness(Strictness.LENIENT) .spyStatic(DesktopModeStatus.class) @@ -170,6 +192,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { mTestableContext.ensureTestableResources(); mContext.setMockPackageManager(mMockPackageManager); when(mMockPackageManager.getApplicationLabel(any())).thenReturn("applicationLabel"); + final ActivityInfo activityInfo = new ActivityInfo(); + activityInfo.applicationInfo = new ApplicationInfo(); + when(mMockPackageManager.getActivityInfo(any(), anyInt())).thenReturn(activityInfo); final Display defaultDisplay = mock(Display.class); doReturn(defaultDisplay).when(mMockDisplayController).getDisplay(Display.DEFAULT_DISPLAY); doReturn(mInsetsState).when(mMockDisplayController).getInsetsState(anyInt()); @@ -459,6 +484,151 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { verify(mMockHandler).removeCallbacks(runnableArgument.getValue()); } + @Test + public void createMaximizeMenu_showsMenu() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); + final MaximizeMenu menu = mock(MaximizeMenu.class); + final DesktopModeWindowDecoration decoration = createWindowDecoration(taskInfo, + new FakeMaximizeMenuFactory(menu)); + assertFalse(decoration.isMaximizeMenuActive()); + + createMaximizeMenu(decoration, menu); + + assertTrue(decoration.isMaximizeMenuActive()); + } + + @Test + public void maximizeMenu_unHoversMenu_schedulesCloseMenu() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); + final MaximizeMenu menu = mock(MaximizeMenu.class); + final DesktopModeWindowDecoration decoration = createWindowDecoration(taskInfo, + new FakeMaximizeMenuFactory(menu)); + decoration.setAppHeaderMaximizeButtonHovered(false); + createMaximizeMenu(decoration, menu); + + mOnMaxMenuHoverChangeListener.getValue().invoke(false); + + verify(mMockHandler) + .postDelayed(mCloseMaxMenuRunnable.capture(), eq(CLOSE_MAXIMIZE_MENU_DELAY_MS)); + + mCloseMaxMenuRunnable.getValue().run(); + verify(menu).close(); + assertFalse(decoration.isMaximizeMenuActive()); + } + + @Test + public void maximizeMenu_unHoversButton_schedulesCloseMenu() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); + final MaximizeMenu menu = mock(MaximizeMenu.class); + final DesktopModeWindowDecoration decoration = createWindowDecoration(taskInfo, + new FakeMaximizeMenuFactory(menu)); + decoration.setAppHeaderMaximizeButtonHovered(true); + createMaximizeMenu(decoration, menu); + + decoration.setAppHeaderMaximizeButtonHovered(false); + + verify(mMockHandler) + .postDelayed(mCloseMaxMenuRunnable.capture(), eq(CLOSE_MAXIMIZE_MENU_DELAY_MS)); + + mCloseMaxMenuRunnable.getValue().run(); + verify(menu).close(); + assertFalse(decoration.isMaximizeMenuActive()); + } + + @Test + public void maximizeMenu_hoversMenu_cancelsCloseMenu() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); + final MaximizeMenu menu = mock(MaximizeMenu.class); + final DesktopModeWindowDecoration decoration = createWindowDecoration(taskInfo, + new FakeMaximizeMenuFactory(menu)); + createMaximizeMenu(decoration, menu); + + mOnMaxMenuHoverChangeListener.getValue().invoke(true); + + verify(mMockHandler).removeCallbacks(any()); + } + + @Test + public void maximizeMenu_hoversButton_cancelsCloseMenu() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); + final MaximizeMenu menu = mock(MaximizeMenu.class); + final DesktopModeWindowDecoration decoration = createWindowDecoration(taskInfo, + new FakeMaximizeMenuFactory(menu)); + createMaximizeMenu(decoration, menu); + + decoration.setAppHeaderMaximizeButtonHovered(true); + + verify(mMockHandler).removeCallbacks(any()); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB) + public void capturedLink_postsOnCapturedLinkExpiredRunnable() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); + final ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class); + final DesktopModeWindowDecoration decor = createWindowDecoration(taskInfo); + + decor.relayout(taskInfo); + // Assert captured link is set + assertTrue(decor.browserLinkAvailable()); + // Asset runnable posted to set captured link to expired + verify(mMockHandler).postDelayed(runnableArgument.capture(), anyLong()); + runnableArgument.getValue().run(); + assertFalse(decor.browserLinkAvailable()); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB) + public void capturedLink_capturedLinkNotResetToSameLink() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); + final DesktopModeWindowDecoration decor = createWindowDecoration(taskInfo); + final ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class); + + // Set captured link and run on captured link expired runnable + decor.relayout(taskInfo); + verify(mMockHandler).postDelayed(runnableArgument.capture(), anyLong()); + runnableArgument.getValue().run(); + + decor.relayout(taskInfo); + // Assert captured link not set to same value twice + assertFalse(decor.browserLinkAvailable()); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB) + public void capturedLink_capturedLinkExpiresAfterClick() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); + final DesktopModeWindowDecoration decor = createWindowDecoration(taskInfo); + + decor.relayout(taskInfo); + // Assert captured link is set + assertTrue(decor.browserLinkAvailable()); + decor.onOpenInBrowserClick(); + //Assert Captured link expires after button is clicked + assertFalse(decor.browserLinkAvailable()); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB) + public void capturedLink_openInBrowserListenerCalledOnClick() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); + final DesktopModeWindowDecoration decor = createWindowDecoration(taskInfo); + + decor.relayout(taskInfo); + decor.onOpenInBrowserClick(); + + verify(mMockOpenInBrowserClickListener).onClick(any(), any()); + } + + private void createMaximizeMenu(DesktopModeWindowDecoration decoration, MaximizeMenu menu) { + final OnTaskActionClickListener l = (taskId, tag) -> {}; + decoration.setOnMaximizeOrRestoreClickListener(l); + decoration.setOnLeftSnapClickListener(l); + decoration.setOnRightSnapClickListener(l); + decoration.createMaximizeMenu(); + verify(menu).show(any(), any(), any(), mOnMaxMenuHoverChangeListener.capture()); + } + private void fillRoundedCornersResources(int fillValue) { when(mMockRoundedCornersRadiusArray.getDimensionPixelSize(anyInt(), anyInt())) .thenReturn(fillValue); @@ -479,15 +649,22 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { private DesktopModeWindowDecoration createWindowDecoration( ActivityManager.RunningTaskInfo taskInfo) { - DesktopModeWindowDecoration windowDecor = new DesktopModeWindowDecoration(mContext, + return createWindowDecoration(taskInfo, new FakeMaximizeMenuFactory()); + } + + private DesktopModeWindowDecoration createWindowDecoration( + ActivityManager.RunningTaskInfo taskInfo, + MaximizeMenuFactory maximizeMenuFactory) { + final DesktopModeWindowDecoration windowDecor = new DesktopModeWindowDecoration(mContext, mMockDisplayController, mMockShellTaskOrganizer, taskInfo, mMockSurfaceControl, mMockHandler, mMockChoreographer, mMockSyncQueue, mMockRootTaskDisplayAreaOrganizer, SurfaceControl.Builder::new, mMockTransactionSupplier, WindowContainerTransaction::new, SurfaceControl::new, - mMockSurfaceControlViewHostFactory); + mMockSurfaceControlViewHostFactory, maximizeMenuFactory); windowDecor.setCaptionListeners(mMockTouchEventListener, mMockTouchEventListener, mMockTouchEventListener, mMockTouchEventListener); windowDecor.setExclusionRegionListener(mMockExclusionRegionListener); + windowDecor.setOpenInBrowserClickListener(mMockOpenInBrowserClickListener); return windowDecor; } @@ -499,12 +676,12 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { .setTaskDescriptionBuilder(taskDescriptionBuilder) .setVisible(visible) .build(); - taskInfo.topActivityInfo = new ActivityInfo(); - taskInfo.topActivityInfo.applicationInfo = new ApplicationInfo(); taskInfo.realActivity = new ComponentName("com.android.wm.shell.windowdecor", "DesktopModeWindowDecorationTests"); taskInfo.baseActivity = new ComponentName("com.android.wm.shell.windowdecor", "DesktopModeWindowDecorationTests"); + taskInfo.capturedLink = TEST_URI; + taskInfo.capturedLinkTimestamp = System.currentTimeMillis(); return taskInfo; } @@ -541,4 +718,27 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { return false; } } + + private static final class FakeMaximizeMenuFactory implements MaximizeMenuFactory { + private final MaximizeMenu mMaximizeMenu; + + FakeMaximizeMenuFactory() { + this(mock(MaximizeMenu.class)); + } + + FakeMaximizeMenuFactory(MaximizeMenu menu) { + mMaximizeMenu = menu; + } + + @NonNull + @Override + public MaximizeMenu create(@NonNull SyncTransactionQueue syncQueue, + @NonNull RootTaskDisplayAreaOrganizer rootTdaOrganizer, + @NonNull DisplayController displayController, + @NonNull ActivityManager.RunningTaskInfo taskInfo, + @NonNull Context decorWindowContext, @NonNull PointF menuPosition, + @NonNull Supplier<SurfaceControl.Transaction> transactionSupplier) { + return mMaximizeMenu; + } + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt index f750e6b9a6fe..e52971120478 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt @@ -21,27 +21,35 @@ import android.content.res.Resources import android.graphics.PointF import android.graphics.Rect import android.os.IBinder +import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule import android.testing.AndroidTestingRunner import android.view.Display import android.window.WindowContainerToken +import com.android.dx.mockito.inline.extended.ExtendedMockito +import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn +import com.android.dx.mockito.inline.extended.StaticMockitoSession import com.android.window.flags.Flags import com.android.wm.shell.R import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayLayout -import com.android.wm.shell.shared.DesktopModeStatus +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP import com.google.common.truth.Truth.assertThat import junit.framework.Assert.assertTrue +import org.junit.After import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.any import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations +import org.mockito.quality.Strictness /** * Tests for [DragPositioningCallbackUtility]. @@ -53,24 +61,39 @@ import org.mockito.MockitoAnnotations class DragPositioningCallbackUtilityTest { @Mock private lateinit var mockWindowDecoration: WindowDecoration<*> + @Mock private lateinit var taskToken: WindowContainerToken + @Mock private lateinit var taskBinder: IBinder + @Mock private lateinit var mockDisplayController: DisplayController + @Mock private lateinit var mockDisplayLayout: DisplayLayout + @Mock private lateinit var mockDisplay: Display + @Mock private lateinit var mockContext: Context + @Mock private lateinit var mockResources: Resources + @JvmField + @Rule + val setFlagsRule = SetFlagsRule() + + private lateinit var mockitoSession: StaticMockitoSession + @Before fun setup() { MockitoAnnotations.initMocks(this) + mockitoSession = ExtendedMockito.mockitoSession().strictness(Strictness.LENIENT) + .spyStatic(DesktopModeStatus::class.java).startMocking() whenever(taskToken.asBinder()).thenReturn(taskBinder) whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout) @@ -91,6 +114,11 @@ class DragPositioningCallbackUtilityTest { whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID } } + @After + fun tearDown() { + mockitoSession.finishMocking() + } + @Test fun testChangeBoundsDoesNotChangeHeightWhenLessThanMin() { val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.top.toFloat()) @@ -238,7 +266,7 @@ class DragPositioningCallbackUtilityTest { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS) fun taskMinWidthHeightUndefined_changeBoundsInDesktopModeLessThanMin_shouldNotChangeBounds() { - whenever(DesktopModeStatus.canEnterDesktopMode(mockContext)).thenReturn(true) + doReturn(true).`when`{ DesktopModeStatus.canEnterDesktopMode(mockContext) } initializeTaskInfo(taskMinWidth = -1, taskMinHeight = -1) val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat()) @@ -261,7 +289,7 @@ class DragPositioningCallbackUtilityTest { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS) fun taskMinWidthHeightUndefined_changeBoundsInDesktopModeAllowedSize_shouldChangeBounds() { - whenever(DesktopModeStatus.canEnterDesktopMode(mockContext)).thenReturn(true) + doReturn(true).`when`{ DesktopModeStatus.canEnterDesktopMode(mockContext) } initializeTaskInfo(taskMinWidth = -1, taskMinHeight = -1) val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat()) @@ -323,6 +351,49 @@ class DragPositioningCallbackUtilityTest { assertThat(repositionTaskBounds.bottom).isEqualTo(STARTING_BOUNDS.bottom - 50) } + @Test + @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS) + fun testChangeBounds_windowSizeExceedsStableBounds_shouldBeAllowedToChangeBounds() { + val startingPoint = + PointF(OFF_CENTER_STARTING_BOUNDS.right.toFloat(), + OFF_CENTER_STARTING_BOUNDS.bottom.toFloat()) + val repositionTaskBounds = Rect(OFF_CENTER_STARTING_BOUNDS) + // Increase height and width by STABLE_BOUNDS. Subtract by 5px so that it doesn't reach + // the disallowed drag area. + val offset = 5 + val newX = STABLE_BOUNDS.right.toFloat() - offset + val newY = STABLE_BOUNDS.bottom.toFloat() - offset + val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint) + + DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM, + repositionTaskBounds, OFF_CENTER_STARTING_BOUNDS, STABLE_BOUNDS, delta, + mockDisplayController, mockWindowDecoration) + assertThat(repositionTaskBounds.width()).isGreaterThan(STABLE_BOUNDS.right) + assertThat(repositionTaskBounds.height()).isGreaterThan(STABLE_BOUNDS.bottom) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS) + fun testChangeBoundsInDesktopMode_windowSizeExceedsStableBounds_shouldBeLimitedToDisplaySize() { + doReturn(true).`when`{ DesktopModeStatus.canEnterDesktopMode(mockContext) } + val startingPoint = + PointF(OFF_CENTER_STARTING_BOUNDS.right.toFloat(), + OFF_CENTER_STARTING_BOUNDS.bottom.toFloat()) + val repositionTaskBounds = Rect(OFF_CENTER_STARTING_BOUNDS) + // Increase height and width by STABLE_BOUNDS. Subtract by 5px so that it doesn't reach + // the disallowed drag area. + val offset = 5 + val newX = STABLE_BOUNDS.right.toFloat() - offset + val newY = STABLE_BOUNDS.bottom.toFloat() - offset + val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint) + + DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM, + repositionTaskBounds, OFF_CENTER_STARTING_BOUNDS, STABLE_BOUNDS, delta, + mockDisplayController, mockWindowDecoration) + assertThat(repositionTaskBounds.width()).isLessThan(STABLE_BOUNDS.right) + assertThat(repositionTaskBounds.height()).isLessThan(STABLE_BOUNDS.bottom) + } + private fun initializeTaskInfo(taskMinWidth: Int = MIN_WIDTH, taskMinHeight: Int = MIN_HEIGHT) { mockWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply { taskId = TASK_ID @@ -347,6 +418,7 @@ class DragPositioningCallbackUtilityTest { private const val NAVBAR_HEIGHT = 50 private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600) private val STARTING_BOUNDS = Rect(0, 0, 100, 100) + private val OFF_CENTER_STARTING_BOUNDS = Rect(-100, -100, 10, 10) private val DISALLOWED_RESIZE_AREA = Rect( DISPLAY_BOUNDS.left, DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java index 4dea5a75a0e8..6a94cd8aa283 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java @@ -55,8 +55,6 @@ public class DragResizeWindowGeometryTests { private static final Size TASK_SIZE = new Size(500, 1000); private static final int TASK_CORNER_RADIUS = 10; private static final int EDGE_RESIZE_THICKNESS = 15; - private static final int EDGE_RESIZE_DEBUG_THICKNESS = EDGE_RESIZE_THICKNESS - + (DragResizeWindowGeometry.DEBUG ? DragResizeWindowGeometry.EDGE_DEBUG_BUFFER : 0); private static final int FINE_CORNER_SIZE = EDGE_RESIZE_THICKNESS * 2 + 10; private static final int LARGE_CORNER_SIZE = FINE_CORNER_SIZE + 10; private static final DragResizeWindowGeometry GEOMETRY = new DragResizeWindowGeometry( @@ -91,14 +89,13 @@ public class DragResizeWindowGeometryTests { EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, LARGE_CORNER_SIZE), new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE, EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, LARGE_CORNER_SIZE)) - .addEqualityGroup(new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE, - EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE, LARGE_CORNER_SIZE + 5), + .addEqualityGroup( new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE, - EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE, LARGE_CORNER_SIZE + 5)) - .addEqualityGroup(new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE, - EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE + 4, LARGE_CORNER_SIZE), + EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, + LARGE_CORNER_SIZE + 5), new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE, - EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE + 4, LARGE_CORNER_SIZE)) + EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, + LARGE_CORNER_SIZE + 5)) .testEquals(); } @@ -124,21 +121,21 @@ public class DragResizeWindowGeometryTests { private static void verifyHorizontalEdge(@NonNull Region region, @NonNull Point point) { assertThat(region.contains(point.x, point.y)).isTrue(); // Horizontally along the edge is still contained. - assertThat(region.contains(point.x + EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isTrue(); - assertThat(region.contains(point.x - EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isTrue(); + assertThat(region.contains(point.x + EDGE_RESIZE_THICKNESS, point.y)).isTrue(); + assertThat(region.contains(point.x - EDGE_RESIZE_THICKNESS, point.y)).isTrue(); // Vertically along the edge is not contained. - assertThat(region.contains(point.x, point.y - EDGE_RESIZE_DEBUG_THICKNESS)).isFalse(); - assertThat(region.contains(point.x, point.y + EDGE_RESIZE_DEBUG_THICKNESS)).isFalse(); + assertThat(region.contains(point.x, point.y - EDGE_RESIZE_THICKNESS)).isFalse(); + assertThat(region.contains(point.x, point.y + EDGE_RESIZE_THICKNESS)).isFalse(); } private static void verifyVerticalEdge(@NonNull Region region, @NonNull Point point) { assertThat(region.contains(point.x, point.y)).isTrue(); // Horizontally along the edge is not contained. - assertThat(region.contains(point.x + EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isFalse(); - assertThat(region.contains(point.x - EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isFalse(); + assertThat(region.contains(point.x + EDGE_RESIZE_THICKNESS, point.y)).isFalse(); + assertThat(region.contains(point.x - EDGE_RESIZE_THICKNESS, point.y)).isFalse(); // Vertically along the edge is contained. - assertThat(region.contains(point.x, point.y - EDGE_RESIZE_DEBUG_THICKNESS)).isTrue(); - assertThat(region.contains(point.x, point.y + EDGE_RESIZE_DEBUG_THICKNESS)).isTrue(); + assertThat(region.contains(point.x, point.y - EDGE_RESIZE_THICKNESS)).isTrue(); + assertThat(region.contains(point.x, point.y + EDGE_RESIZE_THICKNESS)).isTrue(); } /** @@ -151,10 +148,7 @@ public class DragResizeWindowGeometryTests { public void testRegionUnion_edgeDragResizeEnabled_containsLargeCorners() { Region region = new Region(); GEOMETRY.union(region); - // Make sure we're choosing a point outside of any debug region buffer. - final int cornerRadius = DragResizeWindowGeometry.DEBUG - ? Math.max(LARGE_CORNER_SIZE / 2, EDGE_RESIZE_DEBUG_THICKNESS) - : LARGE_CORNER_SIZE / 2; + final int cornerRadius = LARGE_CORNER_SIZE / 2; new TestPoints(TASK_SIZE, cornerRadius).validateRegion(region); } @@ -168,9 +162,7 @@ public class DragResizeWindowGeometryTests { public void testRegionUnion_edgeDragResizeDisabled_containsFineCorners() { Region region = new Region(); GEOMETRY.union(region); - final int cornerRadius = DragResizeWindowGeometry.DEBUG - ? Math.max(LARGE_CORNER_SIZE / 2, EDGE_RESIZE_DEBUG_THICKNESS) - : LARGE_CORNER_SIZE / 2; + final int cornerRadius = FINE_CORNER_SIZE / 2; new TestPoints(TASK_SIZE, cornerRadius).validateRegion(region); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt index 5582e0f46321..0c50ab6b5008 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt @@ -196,9 +196,9 @@ class HandleMenuTest : ShellTestCase() { R.layout.desktop_mode_app_header } val handleMenu = HandleMenu(mockDesktopWindowDecoration, layoutId, - onClickListener, onTouchListener, appIcon, appName, displayController, - splitScreenController, true /* shouldShowWindowingPill */, - 50 /* captionHeight */ ) + onClickListener, onTouchListener, appIcon, appName, displayController, + splitScreenController, true /* shouldShowWindowingPill */, + true /* shouldShowBrowserPill */, 50 /* captionHeight */) handleMenu.show() return handleMenu } 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 48ac1e5717aa..943c313e5b40 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 @@ -17,6 +17,8 @@ package com.android.wm.shell.windowdecor import android.app.ActivityManager import android.app.WindowConfiguration +import android.content.Context +import android.content.res.Resources import android.graphics.Point import android.graphics.Rect import android.os.IBinder @@ -31,6 +33,7 @@ import android.view.WindowManager.TRANSIT_CHANGE import android.window.TransitionInfo import android.window.WindowContainerToken import androidx.test.filters.SmallTest +import com.android.internal.jank.InteractionJankMonitor import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.DisplayController @@ -98,6 +101,12 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { private lateinit var mockFinishCallback: TransitionFinishCallback @Mock private lateinit var mockTransitions: Transitions + @Mock + private lateinit var mockContext: Context + @Mock + private lateinit var mockResources: Resources + @Mock + private lateinit var mockInteractionJankMonitor: InteractionJankMonitor private lateinit var taskPositioner: VeiledResizeTaskPositioner @@ -105,6 +114,9 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) + mockDesktopWindowDecoration.mDisplay = mockDisplay + mockDesktopWindowDecoration.mDecorWindowContext = mockContext + whenever(mockContext.getResources()).thenReturn(mockResources) whenever(taskToken.asBinder()).thenReturn(taskBinder) whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout) whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI) @@ -141,7 +153,8 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { mockDisplayController, mockDragStartListener, mockTransactionFactory, - mockTransitions + mockTransitions, + mockInteractionJankMonitor ) } 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 f3603e1d9b46..2d1bf14ffbb3 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 @@ -18,6 +18,8 @@ package com.android.wm.shell.windowdecor; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT; +import static android.view.InsetsSource.FLAG_FORCE_CONSUMING; import static android.view.WindowInsets.Type.captionBar; import static android.view.WindowInsets.Type.mandatorySystemGestures; import static android.view.WindowInsets.Type.statusBars; @@ -54,6 +56,8 @@ import android.content.res.Resources; import android.graphics.Color; import android.graphics.Point; import android.graphics.Rect; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.testing.AndroidTestingRunner; import android.util.DisplayMetrics; import android.view.AttachedSurfaceControl; @@ -71,15 +75,17 @@ import android.window.WindowContainerTransaction; import androidx.test.filters.SmallTest; import com.android.dx.mockito.inline.extended.StaticMockitoSession; +import com.android.window.flags.Flags; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestRunningTaskInfoBuilder; import com.android.wm.shell.common.DisplayController; -import com.android.wm.shell.shared.DesktopModeStatus; +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.tests.R; import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -105,6 +111,9 @@ public class WindowDecorationTests extends ShellTestCase { private static final int CORNER_RADIUS = 20; private static final int STATUS_BAR_INSET_SOURCE_ID = 0; + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT); + private final WindowDecoration.RelayoutResult<TestView> mRelayoutResult = new WindowDecoration.RelayoutResult<>(); @@ -260,7 +269,8 @@ public class WindowDecorationTests extends ShellTestCase { eq(0 /* index */), eq(WindowInsets.Type.captionBar()), eq(new Rect(100, 300, 400, 364)), - any()); + any(), + anyInt()); verify(mMockSurfaceControlStartT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS); verify(mMockSurfaceControlFinishT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS); @@ -565,9 +575,9 @@ public class WindowDecorationTests extends ShellTestCase { windowDecor.relayout(taskInfo); verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(), - eq(0) /* index */, eq(captionBar()), any(), any()); + eq(0) /* index */, eq(captionBar()), any(), any(), anyInt()); verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(), - eq(0) /* index */, eq(mandatorySystemGestures()), any(), any()); + eq(0) /* index */, eq(mandatorySystemGestures()), any(), any(), anyInt()); } @Test @@ -654,9 +664,9 @@ public class WindowDecorationTests extends ShellTestCase { // Never added. verify(mMockWindowContainerTransaction, never()).addInsetsSource(eq(taskInfo.token), any(), - eq(0) /* index */, eq(captionBar()), any(), any()); + eq(0) /* index */, eq(captionBar()), any(), any(), anyInt()); verify(mMockWindowContainerTransaction, never()).addInsetsSource(eq(taskInfo.token), any(), - eq(0) /* index */, eq(mandatorySystemGestures()), any(), any()); + eq(0) /* index */, eq(mandatorySystemGestures()), any(), any(), anyInt()); // No need to remove them if they were never added. verify(mMockWindowContainerTransaction, never()).removeInsetsSource(eq(taskInfo.token), any(), eq(0) /* index */, eq(captionBar())); @@ -681,9 +691,9 @@ public class WindowDecorationTests extends ShellTestCase { mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, captionBar()).setVisible(true); windowDecor.relayout(taskInfo); verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(), - eq(0) /* index */, eq(captionBar()), any(), any()); + eq(0) /* index */, eq(captionBar()), any(), any(), anyInt()); verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(), - eq(0) /* index */, eq(mandatorySystemGestures()), any(), any()); + eq(0) /* index */, eq(mandatorySystemGestures()), any(), any(), anyInt()); windowDecor.close(); @@ -738,9 +748,9 @@ public class WindowDecorationTests extends ShellTestCase { // Insets should be applied twice. verify(mMockWindowContainerTransaction, times(2)).addInsetsSource(eq(token), any(), - eq(0) /* index */, eq(captionBar()), any(), any()); + eq(0) /* index */, eq(captionBar()), any(), any(), anyInt()); verify(mMockWindowContainerTransaction, times(2)).addInsetsSource(eq(token), any(), - eq(0) /* index */, eq(mandatorySystemGestures()), any(), any()); + eq(0) /* index */, eq(mandatorySystemGestures()), any(), any(), anyInt()); } @Test @@ -765,9 +775,30 @@ public class WindowDecorationTests extends ShellTestCase { // Insets should only need to be applied once. verify(mMockWindowContainerTransaction, times(1)).addInsetsSource(eq(token), any(), - eq(0) /* index */, eq(captionBar()), any(), any()); + eq(0) /* index */, eq(captionBar()), any(), any(), anyInt()); verify(mMockWindowContainerTransaction, times(1)).addInsetsSource(eq(token), any(), - eq(0) /* index */, eq(mandatorySystemGestures()), any(), any()); + eq(0) /* index */, eq(mandatorySystemGestures()), any(), any(), anyInt()); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION) + public void testRelayout_captionInsetForceConsume() { + final Display defaultDisplay = mock(Display.class); + doReturn(defaultDisplay).when(mMockDisplayController) + .getDisplay(Display.DEFAULT_DISPLAY); + final WindowContainerToken token = TestRunningTaskInfoBuilder.createMockWCToken(); + final TestRunningTaskInfoBuilder builder = new TestRunningTaskInfoBuilder() + .setDisplayId(Display.DEFAULT_DISPLAY) + .setVisible(true); + + final ActivityManager.RunningTaskInfo taskInfo = + builder.setToken(token).setBounds(new Rect(0, 0, 1000, 1000)).build(); + final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo); + windowDecor.relayout(taskInfo); + + // Caption inset source should be force-consuming. + verify(mMockWindowContainerTransaction).addInsetsSource(eq(token), any(), + eq(0) /* index */, eq(captionBar()), any(), any(), eq(FLAG_FORCE_CONSUMING)); } @Test diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp index 77800a305f02..2fff4f5e9f7c 100644 --- a/libs/androidfw/Android.bp +++ b/libs/androidfw/Android.bp @@ -212,6 +212,7 @@ cc_test { "tests/AttributeResolution_test.cpp", "tests/BigBuffer_test.cpp", "tests/ByteBucketArray_test.cpp", + "tests/CombinedIterator_test.cpp", "tests/Config_test.cpp", "tests/ConfigDescription_test.cpp", "tests/ConfigLocale_test.cpp", @@ -267,6 +268,7 @@ cc_test { cc_benchmark { name: "libandroidfw_benchmarks", defaults: ["libandroidfw_defaults"], + test_config: "tests/AndroidTest_Benchmarks.xml", srcs: [ // Helpers/infra for benchmarking. "tests/BenchMain.cpp", @@ -282,7 +284,11 @@ cc_benchmark { "tests/Theme_bench.cpp", ], shared_libs: common_test_libs, - data: ["tests/data/**/*.apk"], + data: [ + "tests/data/**/*.apk", + ":FrameworkResourcesSparseTestApp", + ":FrameworkResourcesNotSparseTestApp", + ], } cc_library { diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp index 46f636e2ae7f..822a387351e3 100644 --- a/libs/androidfw/AssetManager2.cpp +++ b/libs/androidfw/AssetManager2.cpp @@ -23,9 +23,11 @@ #include <map> #include <set> #include <span> +#include <utility> #include "android-base/logging.h" #include "android-base/stringprintf.h" +#include "androidfw/CombinedIterator.h" #include "androidfw/ResourceTypes.h" #include "androidfw/ResourceUtils.h" #include "androidfw/Util.h" @@ -1622,6 +1624,12 @@ Theme::Theme(AssetManager2* asset_manager) : asset_manager_(asset_manager) { Theme::~Theme() = default; +static bool IsUndefined(const Res_value& value) { + // DATA_NULL_EMPTY (@empty) is a valid resource value and DATA_NULL_UNDEFINED represents + // an absence of a valid value. + return value.dataType == Res_value::TYPE_NULL && value.data != Res_value::DATA_NULL_EMPTY; +} + base::expected<std::monostate, NullOrIOError> Theme::ApplyStyle(uint32_t resid, bool force) { ATRACE_NAME("Theme::ApplyStyle"); @@ -1633,39 +1641,76 @@ base::expected<std::monostate, NullOrIOError> Theme::ApplyStyle(uint32_t resid, // Merge the flags from this style. type_spec_flags_ |= (*bag)->type_spec_flags; + // + // This function is the most expensive part of applying an frro to the existing app resources, + // and needs to be as efficient as possible. + // The data structure we're working with is two parallel sorted arrays of keys (resource IDs) + // and entries (resource value + some attributes). + // The styles get applied in sequence, starting with an empty set of attributes. Each style + // contains its values for the theme attributes, and gets applied in either normal or forced way: + // - normal way never overrides the existing attribute, so only unique style attributes are added + // - forced way overrides anything for that attribute, and if it's undefined it removes the + // previous value completely + // + // Style attributes come in a Bag data type - a sorted array of attributes with their values. This + // means we don't need to re-sort the attributes ever, and instead: + // - for an already existing attribute just skip it or apply the forced value + // - if the forced value is undefined, mark it undefined as well to get rid of it later + // - for a new attribute append it to the array, forming a new sorted section of new attributes + // past the end of the original ones (ignore undefined ones here) + // - inplace merge two sorted sections to form a single sorted array again. + // - run the last pass to remove all undefined elements + // + // Using this algorithm performs better than a repeated binary search + insert in the middle, + // as that keeps shifting the tail end of the arrays and wasting CPU cycles in memcpy(). + // + const auto starting_size = keys_.size(); + if (starting_size == 0) { + keys_.reserve((*bag)->entry_count); + entries_.reserve((*bag)->entry_count); + } + bool wrote_undefined = false; for (auto it = begin(*bag); it != end(*bag); ++it) { const uint32_t attr_res_id = it->key; - // If the resource ID passed in is not a style, the key can be some other identifier that is not // a resource ID. We should fail fast instead of operating with strange resource IDs. if (!is_valid_resid(attr_res_id)) { return base::unexpected(std::nullopt); } - - // DATA_NULL_EMPTY (@empty) is a valid resource value and DATA_NULL_UNDEFINED represents - // an absence of a valid value. - bool is_undefined = it->value.dataType == Res_value::TYPE_NULL && - it->value.data != Res_value::DATA_NULL_EMPTY; + const bool is_undefined = IsUndefined(it->value); if (!force && is_undefined) { continue; } - - const auto key_it = std::lower_bound(keys_.begin(), keys_.end(), attr_res_id); - const auto entry_it = entries_.begin() + (key_it - keys_.begin()); - if (key_it != keys_.end() && *key_it == attr_res_id) { - if (is_undefined) { - // DATA_NULL_UNDEFINED clears the value of the attribute in the theme only when `force` is - // true. - keys_.erase(key_it); - entries_.erase(entry_it); - } else if (force) { + const auto key_it = std::lower_bound(keys_.begin(), keys_.begin() + starting_size, attr_res_id); + if (key_it != keys_.begin() + starting_size && *key_it == attr_res_id) { + const auto entry_it = entries_.begin() + (key_it - keys_.begin()); + if (force || IsUndefined(entry_it->value)) { *entry_it = Entry{it->cookie, (*bag)->type_spec_flags, it->value}; + wrote_undefined |= is_undefined; } - } else { - keys_.insert(key_it, attr_res_id); - entries_.insert(entry_it, Entry{it->cookie, (*bag)->type_spec_flags, it->value}); + } else if (!is_undefined) { + keys_.emplace_back(attr_res_id); + entries_.emplace_back(it->cookie, (*bag)->type_spec_flags, it->value); } } + + if (starting_size && keys_.size() != starting_size) { + std::inplace_merge( + CombinedIterator(keys_.begin(), entries_.begin()), + CombinedIterator(keys_.begin() + starting_size, entries_.begin() + starting_size), + CombinedIterator(keys_.end(), entries_.end())); + } + if (wrote_undefined) { + auto new_end = std::remove_if(CombinedIterator(keys_.begin(), entries_.begin()), + CombinedIterator(keys_.end(), entries_.end()), + [](const auto& pair) { return IsUndefined(pair.second.value); }); + keys_.erase(new_end.it1, keys_.end()); + entries_.erase(new_end.it2, entries_.end()); + } + if (android::base::kEnableDChecks && !std::is_sorted(keys_.begin(), keys_.end())) { + ALOGW("Bag %u was unsorted in the apk?", unsigned(resid)); + return base::unexpected(std::nullopt); + } return {}; } @@ -1691,6 +1736,9 @@ std::optional<AssetManager2::SelectedValue> Theme::GetAttribute(uint32_t resid) return std::nullopt; } const auto entry_it = entries_.begin() + (key_it - keys_.begin()); + if (IsUndefined(entry_it->value)) { + return std::nullopt; + } type_spec_flags |= entry_it->type_spec_flags; if (entry_it->value.dataType == Res_value::TYPE_ATTRIBUTE) { resid = entry_it->value.data; diff --git a/libs/androidfw/StringPool.cpp b/libs/androidfw/StringPool.cpp index 1cb8df311c89..629f14683b19 100644 --- a/libs/androidfw/StringPool.cpp +++ b/libs/androidfw/StringPool.cpp @@ -132,7 +132,7 @@ bool StringPool::StyleRef::operator==(const StyleRef& rhs) const { auto rhs_iter = rhs.entry_->spans.begin(); for (const Span& span : entry_->spans) { - const Span& rhs_span = *rhs_iter; + const Span& rhs_span = *rhs_iter++; if (span.first_char != rhs_span.first_char || span.last_char != rhs_span.last_char || span.name != rhs_span.name) { return false; @@ -297,24 +297,22 @@ void StringPool::Prune() { template <typename E> static void SortEntries( std::vector<std::unique_ptr<E>>& entries, - const std::function<int(const StringPool::Context&, const StringPool::Context&)>& cmp) { + base::function_ref<int(const StringPool::Context&, const StringPool::Context&)> cmp) { using UEntry = std::unique_ptr<E>; + std::sort(entries.begin(), entries.end(), [cmp](const UEntry& a, const UEntry& b) -> bool { + int r = cmp(a->context, b->context); + if (r == 0) { + r = a->value.compare(b->value); + } + return r < 0; + }); +} - if (cmp != nullptr) { - std::sort(entries.begin(), entries.end(), [&cmp](const UEntry& a, const UEntry& b) -> bool { - int r = cmp(a->context, b->context); - if (r == 0) { - r = a->value.compare(b->value); - } - return r < 0; - }); - } else { - std::sort(entries.begin(), entries.end(), - [](const UEntry& a, const UEntry& b) -> bool { return a->value < b->value; }); - } +void StringPool::Sort() { + Sort([](auto&&, auto&&) { return 0; }); } -void StringPool::Sort(const std::function<int(const Context&, const Context&)>& cmp) { +void StringPool::Sort(base::function_ref<int(const Context&, const Context&)> cmp) { SortEntries(styles_, cmp); SortEntries(strings_, cmp); ReAssignIndices(); diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h index 17a8ba6c03bd..ac46bc5c179f 100644 --- a/libs/androidfw/include/androidfw/AssetManager2.h +++ b/libs/androidfw/include/androidfw/AssetManager2.h @@ -280,9 +280,9 @@ class AssetManager2 { private: SelectedValue(uint8_t value_type, Res_value::data_type value_data, ApkAssetsCookie cookie, - uint32_t type_flags, uint32_t resid, const ResTable_config& config) : + uint32_t type_flags, uint32_t resid, ResTable_config config) : cookie(cookie), data(value_data), type(value_type), flags(type_flags), - resid(resid), config(config) {}; + resid(resid), config(std::move(config)) {} }; // Retrieves the best matching resource value with ID `resid`. diff --git a/libs/androidfw/include/androidfw/CombinedIterator.h b/libs/androidfw/include/androidfw/CombinedIterator.h new file mode 100644 index 000000000000..4ff6a7d7e6c9 --- /dev/null +++ b/libs/androidfw/include/androidfw/CombinedIterator.h @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include <compare> +#include <iterator> +#include <utility> + +namespace android { + +namespace detail { +// A few useful aliases to not repeat them everywhere +template <class It1, class It2> +using Value = std::pair<typename std::iterator_traits<It1>::value_type, + typename std::iterator_traits<It2>::value_type>; + +template <class It1, class It2> +using BaseRefPair = std::pair<typename std::iterator_traits<It1>::reference, + typename std::iterator_traits<It2>::reference>; + +template <class It1, class It2> +struct RefPair : BaseRefPair<It1, It2> { + using Base = BaseRefPair<It1, It2>; + using Value = detail::Value<It1, It2>; + + RefPair(It1 it1, It2 it2) : Base(*it1, *it2) { + } + + RefPair& operator=(const Value& v) { + this->first = v.first; + this->second = v.second; + return *this; + } + operator Value() const { + return Value(this->first, this->second); + } + bool operator==(const RefPair& other) { + return this->first == other.first; + } + bool operator==(const Value& other) { + return this->first == other.first; + } + std::strong_ordering operator<=>(const RefPair& other) const { + return this->first <=> other.first; + } + std::strong_ordering operator<=>(const Value& other) const { + return this->first <=> other.first; + } + friend void swap(RefPair& l, RefPair& r) { + using std::swap; + swap(l.first, r.first); + swap(l.second, r.second); + } +}; + +template <class It1, class It2> +struct RefPairPtr { + RefPair<It1, It2> value; + + RefPair<It1, It2>* operator->() const { + return &value; + } +}; +} // namespace detail + +// +// CombinedIterator - a class to combine two iterators to process them as a single iterator to a +// pair of values. Useful for processing a data structure of "struct of arrays", replacing +// array of structs for cache locality. +// +// The value type is a pair of copies of the values of each iterator, and the reference is a +// pair of references to the corresponding values. Comparison only compares the first element, +// making it most useful for using on data like (vector<Key>, vector<Value>) for binary searching, +// sorting both together and so on. +// +// The class is designed for handling arrays, so it requires random access iterators as an input. +// + +template <class It1, class It2> +requires std::random_access_iterator<It1> && std::random_access_iterator<It2> +struct CombinedIterator { + typedef detail::Value<It1, It2> value_type; + typedef detail::RefPair<It1, It2> reference; + typedef std::ptrdiff_t difference_type; + typedef detail::RefPairPtr<It1, It2> pointer; + typedef std::random_access_iterator_tag iterator_category; + + CombinedIterator(It1 it1 = {}, It2 it2 = {}) : it1(it1), it2(it2) { + } + + bool operator<(const CombinedIterator& other) const { + return it1 < other.it1; + } + bool operator<=(const CombinedIterator& other) const { + return it1 <= other.it1; + } + bool operator>(const CombinedIterator& other) const { + return it1 > other.it1; + } + bool operator>=(const CombinedIterator& other) const { + return it1 >= other.it1; + } + bool operator==(const CombinedIterator& other) const { + return it1 == other.it1; + } + pointer operator->() const { + return pointer{{it1, it2}}; + } + reference operator*() const { + return {it1, it2}; + } + reference operator[](difference_type n) const { + return {it1 + n, it2 + n}; + } + + CombinedIterator& operator++() { + ++it1; + ++it2; + return *this; + } + CombinedIterator operator++(int) { + const auto res = *this; + ++*this; + return res; + } + CombinedIterator& operator--() { + --it1; + --it2; + return *this; + } + CombinedIterator operator--(int) { + const auto res = *this; + --*this; + return res; + } + CombinedIterator& operator+=(difference_type n) { + it1 += n; + it2 += n; + return *this; + } + CombinedIterator operator+(difference_type n) const { + CombinedIterator res = *this; + return res += n; + } + + CombinedIterator& operator-=(difference_type n) { + it1 -= n; + it2 -= n; + return *this; + } + CombinedIterator operator-(difference_type n) const { + CombinedIterator res = *this; + return res -= n; + } + difference_type operator-(const CombinedIterator& other) { + return it1 - other.it1; + } + + It1 it1; + It2 it2; +}; + +} // namespace android diff --git a/libs/androidfw/include/androidfw/StringPool.h b/libs/androidfw/include/androidfw/StringPool.h index 0190ab57bf23..9b2c72a29f48 100644 --- a/libs/androidfw/include/androidfw/StringPool.h +++ b/libs/androidfw/include/androidfw/StringPool.h @@ -17,7 +17,6 @@ #ifndef _ANDROID_STRING_POOL_H #define _ANDROID_STRING_POOL_H -#include <functional> #include <memory> #include <string> #include <unordered_map> @@ -25,6 +24,7 @@ #include "BigBuffer.h" #include "IDiagnostics.h" +#include "android-base/function_ref.h" #include "android-base/macros.h" #include "androidfw/ConfigDescription.h" #include "androidfw/StringPiece.h" @@ -205,7 +205,8 @@ class StringPool { // Sorts the strings according to their Context using some comparison function. // Equal Contexts are further sorted by string value, lexicographically. // If no comparison function is provided, values are only sorted lexicographically. - void Sort(const std::function<int(const Context&, const Context&)>& cmp = nullptr); + void Sort(); + void Sort(base::function_ref<int(const Context&, const Context&)> cmp); // Removes any strings that have no references. void Prune(); diff --git a/libs/androidfw/tests/AndroidTest_Benchmarks.xml b/libs/androidfw/tests/AndroidTest_Benchmarks.xml new file mode 100644 index 000000000000..e61e46fb7785 --- /dev/null +++ b/libs/androidfw/tests/AndroidTest_Benchmarks.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration description="Runs libandroidfw_benchmarks and libandroidfw_tests."> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-native-metric" /> + + <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> + <option name="cleanup" value="true" /> + <option name="push" value="libandroidfw_benchmarks->/data/local/tmp/libandroidfw_benchmarks" /> + </target_preparer> + <test class="com.android.tradefed.testtype.GoogleBenchmarkTest" > + <option name="native-benchmark-device-path" value="/data/local/tmp" /> + <option name="benchmark-module-name" value="libandroidfw_benchmarks" /> + <!-- The GoogleBenchmarkTest class ordinarily expects every file in the benchmark's + directory (recursively) to be a google-benchmark binary, so we need this setting to + avoid failing on the test data files. --> + <option name="file-exclusion-filter-regex" value=".*\.(apk|config)$" /> + </test> +</configuration>
\ No newline at end of file diff --git a/libs/androidfw/tests/AssetManager2_bench.cpp b/libs/androidfw/tests/AssetManager2_bench.cpp index 2caa98c35971..136f5ea639a1 100644 --- a/libs/androidfw/tests/AssetManager2_bench.cpp +++ b/libs/androidfw/tests/AssetManager2_bench.cpp @@ -37,7 +37,7 @@ constexpr const static char* kFrameworkPath = "/system/framework/framework-res.a static void BM_AssetManagerLoadAssets(benchmark::State& state) { std::string path = GetTestDataPath() + "/basic/basic.apk"; - while (state.KeepRunning()) { + for (auto&& _ : state) { auto apk = ApkAssets::Load(path); AssetManager2 assets; assets.SetApkAssets({apk}); @@ -47,7 +47,7 @@ BENCHMARK(BM_AssetManagerLoadAssets); static void BM_AssetManagerLoadAssetsOld(benchmark::State& state) { String8 path((GetTestDataPath() + "/basic/basic.apk").data()); - while (state.KeepRunning()) { + for (auto&& _ : state) { AssetManager assets; assets.addAssetPath(path, nullptr /* cookie */, false /* appAsLib */, false /* isSystemAsset */); @@ -60,7 +60,7 @@ BENCHMARK(BM_AssetManagerLoadAssetsOld); static void BM_AssetManagerLoadFrameworkAssets(benchmark::State& state) { std::string path = kFrameworkPath; - while (state.KeepRunning()) { + for (auto&& _ : state) { auto apk = ApkAssets::Load(path); AssetManager2 assets; assets.SetApkAssets({apk}); @@ -70,7 +70,7 @@ BENCHMARK(BM_AssetManagerLoadFrameworkAssets); static void BM_AssetManagerLoadFrameworkAssetsOld(benchmark::State& state) { String8 path(kFrameworkPath); - while (state.KeepRunning()) { + for (auto&& _ : state) { AssetManager assets; assets.addAssetPath(path, nullptr /* cookie */, false /* appAsLib */, false /* isSystemAsset */); @@ -138,7 +138,7 @@ static void BM_AssetManagerGetBag(benchmark::State& state) { AssetManager2 assets; assets.SetApkAssets({apk}); - while (state.KeepRunning()) { + for (auto&& _ : state) { auto bag = assets.GetBag(app::R::style::StyleTwo); if (!bag.has_value()) { state.SkipWithError("Failed to load get bag"); @@ -165,7 +165,7 @@ static void BM_AssetManagerGetBagOld(benchmark::State& state) { const ResTable& table = assets.getResources(true); - while (state.KeepRunning()) { + for (auto&& _ : state) { const ResTable::bag_entry* bag_begin; const ssize_t N = table.lockBag(app::R::style::StyleTwo, &bag_begin); const ResTable::bag_entry* const bag_end = bag_begin + N; @@ -190,7 +190,7 @@ static void BM_AssetManagerGetResourceLocales(benchmark::State& state) { AssetManager2 assets; assets.SetApkAssets({apk}); - while (state.KeepRunning()) { + for (auto&& _ : state) { std::set<std::string> locales = assets.GetResourceLocales(false /*exclude_system*/, true /*merge_equivalent_languages*/); benchmark::DoNotOptimize(locales); @@ -208,7 +208,7 @@ static void BM_AssetManagerGetResourceLocalesOld(benchmark::State& state) { const ResTable& table = assets.getResources(true); - while (state.KeepRunning()) { + for (auto&& _ : state) { Vector<String8> locales; table.getLocales(&locales, true /*includeSystemLocales*/, true /*mergeEquivalentLangs*/); benchmark::DoNotOptimize(locales); @@ -231,7 +231,7 @@ static void BM_AssetManagerSetConfigurationFramework(benchmark::State& state) { std::vector<ResTable_config> configs; configs.push_back(config); - while (state.KeepRunning()) { + for (auto&& _ : state) { configs[0].sdkVersion = ~configs[0].sdkVersion; assets.SetConfigurations(configs); } @@ -251,7 +251,7 @@ static void BM_AssetManagerSetConfigurationFrameworkOld(benchmark::State& state) ResTable_config config; memset(&config, 0, sizeof(config)); - while (state.KeepRunning()) { + for (auto&& _ : state) { config.sdkVersion = ~config.sdkVersion; assets.setConfiguration(config); } diff --git a/libs/androidfw/tests/BenchmarkHelpers.cpp b/libs/androidfw/tests/BenchmarkHelpers.cpp index 8b883f4ed1df..e3fc0a0a4e68 100644 --- a/libs/androidfw/tests/BenchmarkHelpers.cpp +++ b/libs/androidfw/tests/BenchmarkHelpers.cpp @@ -28,7 +28,7 @@ void GetResourceBenchmarkOld(const std::vector<std::string>& paths, const ResTab for (const std::string& path : paths) { if (!assetmanager.addAssetPath(String8(path.c_str()), nullptr /* cookie */, false /* appAsLib */, false /* isSystemAssets */)) { - state.SkipWithError(base::StringPrintf("Failed to load assets %s", path.c_str()).c_str()); + state.SkipWithError(base::StringPrintf("Failed to old-load assets %s", path.c_str()).c_str()); return; } } @@ -57,7 +57,7 @@ void GetResourceBenchmark(const std::vector<std::string>& paths, const ResTable_ for (const std::string& path : paths) { auto apk = ApkAssets::Load(path); if (apk == nullptr) { - state.SkipWithError(base::StringPrintf("Failed to load assets %s", path.c_str()).c_str()); + state.SkipWithError(base::StringPrintf("Failed to new-load assets %s", path.c_str()).c_str()); return; } apk_assets.push_back(std::move(apk)); diff --git a/libs/androidfw/tests/CombinedIterator_test.cpp b/libs/androidfw/tests/CombinedIterator_test.cpp new file mode 100644 index 000000000000..c1228f34625f --- /dev/null +++ b/libs/androidfw/tests/CombinedIterator_test.cpp @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "androidfw/CombinedIterator.h" + +#include <algorithm> +#include <string> +#include <strstream> +#include <utility> +#include <vector> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace android { + +template <class Coll> +std::string toString(const Coll& coll) { + std::stringstream res; + res << "(" << std::size(coll) << ")"; + if (std::size(coll)) { + res << "{" << coll[0]; + for (int i = 1; i != std::size(coll); ++i) { + res << "," << coll[i]; + } + res << "}"; + } + return res.str(); +} + +template <class Coll> +void AssertCollectionEq(const Coll& first, const Coll& second) { + ASSERT_EQ(std::size(first), std::size(second)) + << "first: " << toString(first) << ", second: " << toString(second); + for (int i = 0; i != std::size(first); ++i) { + ASSERT_EQ(first[i], second[i]) + << "index: " << i << " first: " << toString(first) << ", second: " << toString(second); + } +} + +TEST(CombinedIteratorTest, Sorting) { + std::vector<int> v1 = {2, 1, 3, 4, 0}; + std::vector<int> v2 = {20, 10, 30, 40, 0}; + + std::sort(CombinedIterator(v1.begin(), v2.begin()), CombinedIterator(v1.end(), v2.end())); + + ASSERT_EQ(v1.size(), v2.size()); + ASSERT_TRUE(std::is_sorted(v1.begin(), v1.end())); + ASSERT_TRUE(std::is_sorted(v2.begin(), v2.end())); + AssertCollectionEq(v1, {0, 1, 2, 3, 4}); + AssertCollectionEq(v2, {0, 10, 20, 30, 40}); +} + +TEST(CombinedIteratorTest, Removing) { + std::vector<int> v1 = {1, 2, 3, 4, 5, 5, 5, 6}; + std::vector<int> v2 = {10, 20, 30, 40, 50, 50, 50, 60}; + + auto newEnd = + std::remove_if(CombinedIterator(v1.begin(), v2.begin()), CombinedIterator(v1.end(), v2.end()), + [](auto&& pair) { return pair.first >= 3 && pair.first <= 5; }); + + ASSERT_EQ(newEnd.it1, v1.begin() + 3); + ASSERT_EQ(newEnd.it2, v2.begin() + 3); + + v1.erase(newEnd.it1, v1.end()); + AssertCollectionEq(v1, {1, 2, 6}); + v2.erase(newEnd.it2, v2.end()); + AssertCollectionEq(v2, {10, 20, 60}); +} + +TEST(CombinedIteratorTest, InplaceMerge) { + std::vector<int> v1 = {1, 3, 4, 7, 2, 5, 6}; + std::vector<int> v2 = {10, 30, 40, 70, 20, 50, 60}; + + std::inplace_merge(CombinedIterator(v1.begin(), v2.begin()), + CombinedIterator(v1.begin() + 4, v2.begin() + 4), + CombinedIterator(v1.end(), v2.end())); + ASSERT_TRUE(std::is_sorted(v1.begin(), v1.end())); + ASSERT_TRUE(std::is_sorted(v2.begin(), v2.end())); + + AssertCollectionEq(v1, {1, 2, 3, 4, 5, 6, 7}); + AssertCollectionEq(v2, {10, 20, 30, 40, 50, 60, 70}); +} + +} // namespace android diff --git a/libs/androidfw/tests/Theme_bench.cpp b/libs/androidfw/tests/Theme_bench.cpp index dfbb5a76dec6..bf89617635cc 100644 --- a/libs/androidfw/tests/Theme_bench.cpp +++ b/libs/androidfw/tests/Theme_bench.cpp @@ -27,6 +27,10 @@ constexpr const static char* kFrameworkPath = "/system/framework/framework-res.a constexpr const static uint32_t kStyleId = 0x01030237u; // android:style/Theme.Material.Light constexpr const static uint32_t kAttrId = 0x01010030u; // android:attr/colorForeground +constexpr const static uint32_t kStyle2Id = 0x01030224u; // android:style/Theme.Material +constexpr const static uint32_t kStyle3Id = 0x0103024du; // android:style/Widget.Material +constexpr const static uint32_t kStyle4Id = 0x0103028eu; // android:style/Widget.Material.Light + static void BM_ThemeApplyStyleFramework(benchmark::State& state) { auto apk = ApkAssets::Load(kFrameworkPath); if (apk == nullptr) { @@ -61,6 +65,32 @@ static void BM_ThemeApplyStyleFrameworkOld(benchmark::State& state) { } BENCHMARK(BM_ThemeApplyStyleFrameworkOld); +static void BM_ThemeRebaseFramework(benchmark::State& state) { + auto apk = ApkAssets::Load(kFrameworkPath); + if (apk == nullptr) { + state.SkipWithError("Failed to load assets"); + return; + } + + AssetManager2 assets; + assets.SetApkAssets({apk}); + + // Create two arrays of styles to switch between back and forth. + const uint32_t styles1[] = {kStyle2Id, kStyleId, kStyle3Id}; + const uint8_t force1[std::size(styles1)] = {false, true, false}; + const uint32_t styles2[] = {kStyleId, kStyle2Id, kStyle4Id, kStyle3Id}; + const uint8_t force2[std::size(styles2)] = {false, true, true, false}; + const auto theme = assets.NewTheme(); + // Initialize the theme to make the first iteration the same as the rest. + theme->Rebase(&assets, styles1, force1, std::size(force1)); + + while (state.KeepRunning()) { + theme->Rebase(&assets, styles2, force2, std::size(force2)); + theme->Rebase(&assets, styles1, force1, std::size(force1)); + } +} +BENCHMARK(BM_ThemeRebaseFramework); + static void BM_ThemeGetAttribute(benchmark::State& state) { auto apk = ApkAssets::Load(kFrameworkPath); diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 341599e79662..e302fa8b1fc3 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -114,16 +114,12 @@ cc_defaults { "libbase", "libharfbuzz_ng", "libminikin", - "server_configurable_flags", - "libaconfig_storage_read_api_cc" ], static_libs: [ "libui-types", ], - whole_static_libs: ["hwui_flags_cc_lib"], - target: { android: { shared_libs: [ @@ -145,6 +141,8 @@ cc_defaults { "libsync", "libui", "aconfig_text_flags_c_lib", + "server_configurable_flags", + "libaconfig_storage_read_api_cc", ], static_libs: [ "libEGL_blobCache", @@ -155,6 +153,7 @@ cc_defaults { "libstatssocket_lazy", "libtonemap", ], + whole_static_libs: ["hwui_flags_cc_lib"], }, host: { static_libs: [ @@ -419,7 +418,6 @@ cc_defaults { ], static_libs: [ - "libnativehelper_lazy", "libziparchive_for_incfs", ], @@ -446,6 +444,7 @@ cc_defaults { ], static_libs: [ "libgif", + "libnativehelper_lazy", "libstatslog_hwui", "libstatspull_lazy", "libstatssocket_lazy", @@ -464,6 +463,7 @@ cc_defaults { ], static_libs: [ "libandroidfw", + "libnativehelper_jvm", ], }, }, diff --git a/libs/hwui/apex/LayoutlibLoader.cpp b/libs/hwui/apex/LayoutlibLoader.cpp index 70a9ef04d6f3..073bc8de3659 100644 --- a/libs/hwui/apex/LayoutlibLoader.cpp +++ b/libs/hwui/apex/LayoutlibLoader.cpp @@ -41,6 +41,7 @@ extern int register_android_graphics_Shader(JNIEnv* env); extern int register_android_graphics_RenderEffect(JNIEnv* env); extern int register_android_graphics_Typeface(JNIEnv* env); extern int register_android_graphics_YuvImage(JNIEnv* env); +extern int register_android_graphics_drawable_AnimatedImageDrawable(JNIEnv* env); namespace android { @@ -51,6 +52,8 @@ extern int register_android_graphics_ColorFilter(JNIEnv* env); extern int register_android_graphics_ColorSpace(JNIEnv* env); extern int register_android_graphics_DrawFilter(JNIEnv* env); extern int register_android_graphics_FontFamily(JNIEnv* env); +extern int register_android_graphics_Gainmap(JNIEnv* env); +extern int register_android_graphics_HardwareRendererObserver(JNIEnv* env); extern int register_android_graphics_Matrix(JNIEnv* env); extern int register_android_graphics_Paint(JNIEnv* env); extern int register_android_graphics_Path(JNIEnv* env); @@ -72,6 +75,7 @@ extern int register_android_graphics_text_GraphemeBreak(JNIEnv* env); extern int register_android_util_PathParser(JNIEnv* env); extern int register_android_view_DisplayListCanvas(JNIEnv* env); extern int register_android_view_RenderNode(JNIEnv* env); +extern int register_android_view_ThreadedRenderer(JNIEnv* env); #define REG_JNI(name) { name } struct RegJNIRec { @@ -95,7 +99,11 @@ static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = { REG_JNI(register_android_graphics_CreateJavaOutputStreamAdaptor)}, {"android.graphics.DrawFilter", REG_JNI(register_android_graphics_DrawFilter)}, {"android.graphics.FontFamily", REG_JNI(register_android_graphics_FontFamily)}, + {"android.graphics.Gainmap", REG_JNI(register_android_graphics_Gainmap)}, {"android.graphics.Graphics", REG_JNI(register_android_graphics_Graphics)}, + {"android.graphics.HardwareRenderer", REG_JNI(register_android_view_ThreadedRenderer)}, + {"android.graphics.HardwareRendererObserver", + REG_JNI(register_android_graphics_HardwareRendererObserver)}, {"android.graphics.ImageDecoder", REG_JNI(register_android_graphics_ImageDecoder)}, {"android.graphics.Interpolator", REG_JNI(register_android_graphics_Interpolator)}, {"android.graphics.MaskFilter", REG_JNI(register_android_graphics_MaskFilter)}, @@ -118,6 +126,8 @@ static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = { REG_JNI(register_android_graphics_animation_NativeInterpolatorFactory)}, {"android.graphics.animation.RenderNodeAnimator", REG_JNI(register_android_graphics_animation_RenderNodeAnimator)}, + {"android.graphics.drawable.AnimatedImageDrawable", + REG_JNI(register_android_graphics_drawable_AnimatedImageDrawable)}, {"android.graphics.drawable.AnimatedVectorDrawable", REG_JNI(register_android_graphics_drawable_AnimatedVectorDrawable)}, {"android.graphics.drawable.VectorDrawable", diff --git a/libs/hwui/hwui/DrawTextFunctor.h b/libs/hwui/hwui/DrawTextFunctor.h index cfca48084d97..0efb2c81af01 100644 --- a/libs/hwui/hwui/DrawTextFunctor.h +++ b/libs/hwui/hwui/DrawTextFunctor.h @@ -17,7 +17,6 @@ #include <SkFontMetrics.h> #include <SkRRect.h> #include <SkTextBlob.h> -#include <com_android_graphics_hwui_flags.h> #include "../utils/Color.h" #include "Canvas.h" @@ -30,7 +29,19 @@ #include "hwui/PaintFilter.h" #include "pipeline/skia/SkiaRecordingCanvas.h" +#ifdef __ANDROID__ +#include <com_android_graphics_hwui_flags.h> namespace flags = com::android::graphics::hwui::flags; +#else +namespace flags { +constexpr bool high_contrast_text_luminance() { + return false; +} +constexpr bool high_contrast_text_small_text_rect() { + return false; +} +} // namespace flags +#endif namespace android { diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp index 0d0af1110ca4..4d185c69c172 100644 --- a/libs/hwui/renderthread/VulkanManager.cpp +++ b/libs/hwui/renderthread/VulkanManager.cpp @@ -28,8 +28,8 @@ #include <include/gpu/ganesh/vk/GrVkBackendSemaphore.h> #include <include/gpu/ganesh/vk/GrVkBackendSurface.h> #include <include/gpu/ganesh/vk/GrVkDirectContext.h> +#include <include/gpu/vk/VulkanBackendContext.h> #include <ui/FatVector.h> -#include <vk/GrVkExtensions.h> #include <vk/GrVkTypes.h> #include <sstream> @@ -141,7 +141,8 @@ VulkanManager::~VulkanManager() { mPhysicalDeviceFeatures2 = {}; } -void VulkanManager::setupDevice(GrVkExtensions& grExtensions, VkPhysicalDeviceFeatures2& features) { +void VulkanManager::setupDevice(skgpu::VulkanExtensions& grExtensions, + VkPhysicalDeviceFeatures2& features) { VkResult err; constexpr VkApplicationInfo app_info = { @@ -506,7 +507,7 @@ sk_sp<GrDirectContext> VulkanManager::createContext(GrContextOptions& options, return vkGetInstanceProcAddr(instance, proc_name); }; - GrVkBackendContext backendContext; + skgpu::VulkanBackendContext backendContext; backendContext.fInstance = mInstance; backendContext.fPhysicalDevice = mPhysicalDevice; backendContext.fDevice = mDevice; diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h index b92ebb3cdf71..08f9d4253d7e 100644 --- a/libs/hwui/renderthread/VulkanManager.h +++ b/libs/hwui/renderthread/VulkanManager.h @@ -24,8 +24,7 @@ #include <SkSurface.h> #include <android-base/unique_fd.h> #include <utils/StrongPointer.h> -#include <vk/GrVkBackendContext.h> -#include <vk/GrVkExtensions.h> +#include <vk/VulkanExtensions.h> #include <vulkan/vulkan.h> // VK_ANDROID_frame_boundary is a bespoke extension defined by AGI @@ -127,7 +126,7 @@ private: // Sets up the VkInstance and VkDevice objects. Also fills out the passed in // VkPhysicalDeviceFeatures struct. - void setupDevice(GrVkExtensions&, VkPhysicalDeviceFeatures2&); + void setupDevice(skgpu::VulkanExtensions&, VkPhysicalDeviceFeatures2&); // simple wrapper class that exists only to initialize a pointer to NULL template <typename FNPTR_TYPE> @@ -206,7 +205,7 @@ private: BufferAge, }; SwapBehavior mSwapBehavior = SwapBehavior::Discard; - GrVkExtensions mExtensions; + skgpu::VulkanExtensions mExtensions; uint32_t mDriverVersion = 0; std::once_flag mInitFlag; |