diff options
Diffstat (limited to 'libs')
292 files changed, 7942 insertions, 14844 deletions
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java b/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java index 921552b6cfbb..68ff806c6765 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java @@ -199,7 +199,7 @@ public final class CommonFoldingFeature { throw new IllegalArgumentException( "Display feature rectangle cannot have zero width and height simultaneously."); } - this.mRect = rect; + this.mRect = new Rect(rect); } /** Returns the type of the feature. */ @@ -217,7 +217,7 @@ public final class CommonFoldingFeature { /** Returns the bounds of the feature. */ @NonNull public Rect getRect() { - return mRect; + return new Rect(mRect); } @Override diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java index fdcb7be597d5..cc2bb63ca8e1 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java @@ -22,7 +22,6 @@ import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_UNKNOWN; import static androidx.window.common.CommonFoldingFeature.parseListFromString; import android.annotation.NonNull; -import android.annotation.Nullable; import android.content.Context; import android.hardware.devicestate.DeviceStateManager; import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback; @@ -30,22 +29,25 @@ import android.text.TextUtils; import android.util.Log; import android.util.SparseIntArray; +import androidx.window.util.AcceptOnceConsumer; import androidx.window.util.BaseDataProducer; -import androidx.window.util.DataProducer; import com.android.internal.R; +import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.function.Consumer; /** - * An implementation of {@link androidx.window.util.DataProducer} that returns the device's posture - * by mapping the state returned from {@link DeviceStateManager} to values provided in the resources - * config at {@link R.array#config_device_state_postures}. + * An implementation of {@link androidx.window.util.BaseDataProducer} that returns + * the device's posture by mapping the state returned from {@link DeviceStateManager} to + * values provided in the resources' config at {@link R.array#config_device_state_postures}. */ -public final class DeviceStateManagerFoldingFeatureProducer extends - BaseDataProducer<List<CommonFoldingFeature>> { +public final class DeviceStateManagerFoldingFeatureProducer + extends BaseDataProducer<List<CommonFoldingFeature>> { private static final String TAG = DeviceStateManagerFoldingFeatureProducer.class.getSimpleName(); private static final boolean DEBUG = false; @@ -54,15 +56,11 @@ public final class DeviceStateManagerFoldingFeatureProducer extends private int mCurrentDeviceState = INVALID_DEVICE_STATE; - private final DeviceStateCallback mDeviceStateCallback = (state) -> { - mCurrentDeviceState = state; - notifyDataChanged(); - }; @NonNull - private final DataProducer<String> mRawFoldSupplier; + private final BaseDataProducer<String> mRawFoldSupplier; public DeviceStateManagerFoldingFeatureProducer(@NonNull Context context, - @NonNull DataProducer<String> rawFoldSupplier) { + @NonNull BaseDataProducer<String> rawFoldSupplier) { mRawFoldSupplier = rawFoldSupplier; String[] deviceStatePosturePairs = context.getResources() .getStringArray(R.array.config_device_state_postures); @@ -70,7 +68,8 @@ public final class DeviceStateManagerFoldingFeatureProducer extends String[] deviceStatePostureMapping = deviceStatePosturePair.split(":"); if (deviceStatePostureMapping.length != 2) { if (DEBUG) { - Log.e(TAG, "Malformed device state posture pair: " + deviceStatePosturePair); + Log.e(TAG, "Malformed device state posture pair: " + + deviceStatePosturePair); } continue; } @@ -82,7 +81,8 @@ public final class DeviceStateManagerFoldingFeatureProducer extends posture = Integer.parseInt(deviceStatePostureMapping[1]); } catch (NumberFormatException e) { if (DEBUG) { - Log.e(TAG, "Failed to parse device state or posture: " + deviceStatePosturePair, + Log.e(TAG, "Failed to parse device state or posture: " + + deviceStatePosturePair, e); } continue; @@ -92,32 +92,95 @@ public final class DeviceStateManagerFoldingFeatureProducer extends } if (mDeviceStateToPostureMap.size() > 0) { - context.getSystemService(DeviceStateManager.class) - .registerCallback(context.getMainExecutor(), mDeviceStateCallback); + DeviceStateCallback deviceStateCallback = (state) -> { + mCurrentDeviceState = state; + mRawFoldSupplier.getData(this::notifyFoldingFeatureChange); + }; + Objects.requireNonNull(context.getSystemService(DeviceStateManager.class)) + .registerCallback(context.getMainExecutor(), deviceStateCallback); } } - @Override - @Nullable - public Optional<List<CommonFoldingFeature>> getData() { - final int globalHingeState = globalHingeState(); - Optional<String> displayFeaturesString = mRawFoldSupplier.getData(); - if (displayFeaturesString.isEmpty() || TextUtils.isEmpty(displayFeaturesString.get())) { - return Optional.empty(); + /** + * Add a callback to mCallbacks if there is no device state. This callback will be run + * once a device state is set. Otherwise,run the callback immediately. + */ + private void runCallbackWhenValidState(@NonNull Consumer<List<CommonFoldingFeature>> callback, + String displayFeaturesString) { + if (isCurrentStateValid()) { + callback.accept(calculateFoldingFeature(displayFeaturesString)); + } else { + // This callback will be added to mCallbacks and removed once it runs once. + AcceptOnceConsumer<List<CommonFoldingFeature>> singleRunCallback = + new AcceptOnceConsumer<>(this, callback); + addDataChangedCallback(singleRunCallback); } - return Optional.of(parseListFromString(displayFeaturesString.get(), globalHingeState)); + } + + /** + * Checks to find {@link DeviceStateManagerFoldingFeatureProducer#mCurrentDeviceState} in the + * {@link DeviceStateManagerFoldingFeatureProducer#mDeviceStateToPostureMap} which was + * initialized in the constructor of {@link DeviceStateManagerFoldingFeatureProducer}. + * Returns a boolean value of whether the device state is valid. + */ + private boolean isCurrentStateValid() { + // If the device state is not found in the map, indexOfKey returns a negative number. + return mDeviceStateToPostureMap.indexOfKey(mCurrentDeviceState) >= 0; } @Override - protected void onListenersChanged(Set<Runnable> callbacks) { + protected void onListenersChanged( + @NonNull Set<Consumer<List<CommonFoldingFeature>>> callbacks) { super.onListenersChanged(callbacks); if (callbacks.isEmpty()) { - mRawFoldSupplier.removeDataChangedCallback(this::notifyDataChanged); + mCurrentDeviceState = INVALID_DEVICE_STATE; + mRawFoldSupplier.removeDataChangedCallback(this::notifyFoldingFeatureChange); + } else { + mRawFoldSupplier.addDataChangedCallback(this::notifyFoldingFeatureChange); + } + } + + @NonNull + @Override + public Optional<List<CommonFoldingFeature>> getCurrentData() { + Optional<String> displayFeaturesString = mRawFoldSupplier.getCurrentData(); + if (!isCurrentStateValid()) { + return Optional.empty(); + } else { + return displayFeaturesString.map(this::calculateFoldingFeature); + } + } + + /** + * Adds the data to the storeFeaturesConsumer when the data is ready. + * @param storeFeaturesConsumer a consumer to collect the data when it is first available. + */ + public void getData(Consumer<List<CommonFoldingFeature>> storeFeaturesConsumer) { + mRawFoldSupplier.getData((String displayFeaturesString) -> { + if (TextUtils.isEmpty(displayFeaturesString)) { + storeFeaturesConsumer.accept(new ArrayList<>()); + } else { + runCallbackWhenValidState(storeFeaturesConsumer, displayFeaturesString); + } + }); + } + + private void notifyFoldingFeatureChange(String displayFeaturesString) { + if (!isCurrentStateValid()) { + return; + } + if (TextUtils.isEmpty(displayFeaturesString)) { + notifyDataChanged(new ArrayList<>()); } else { - mRawFoldSupplier.addDataChangedCallback(this::notifyDataChanged); + notifyDataChanged(calculateFoldingFeature(displayFeaturesString)); } } + private List<CommonFoldingFeature> calculateFoldingFeature(String displayFeaturesString) { + final int globalHingeState = globalHingeState(); + return parseListFromString(displayFeaturesString, globalHingeState); + } + private int globalHingeState() { return mDeviceStateToPostureMap.get(mCurrentDeviceState, COMMON_STATE_UNKNOWN); } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java index 69ad1badce60..7906342d445d 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java @@ -32,6 +32,7 @@ import com.android.internal.R; import java.util.Optional; import java.util.Set; +import java.util.function.Consumer; /** * Implementation of {@link androidx.window.util.DataProducer} that produces a @@ -40,7 +41,7 @@ import java.util.Set; * settings where the {@link String} property is saved with the key * {@link RawFoldingFeatureProducer#DISPLAY_FEATURES}. If this value is null or empty then the * value in {@link android.content.res.Resources} is used. If both are empty then - * {@link RawFoldingFeatureProducer#getData()} returns an empty object. + * {@link RawFoldingFeatureProducer#getData} returns an empty object. * {@link RawFoldingFeatureProducer} listens to changes in the setting so that it can override * the system {@link CommonFoldingFeature} data. */ @@ -63,12 +64,13 @@ public final class RawFoldingFeatureProducer extends BaseDataProducer<String> { @Override @NonNull - public Optional<String> getData() { + public void getData(Consumer<String> dataConsumer) { String displayFeaturesString = getFeatureString(); if (displayFeaturesString == null) { - return Optional.empty(); + dataConsumer.accept(""); + } else { + dataConsumer.accept(displayFeaturesString); } - return Optional.of(displayFeaturesString); } /** @@ -84,7 +86,7 @@ public final class RawFoldingFeatureProducer extends BaseDataProducer<String> { } @Override - protected void onListenersChanged(Set<Runnable> callbacks) { + protected void onListenersChanged(Set<Consumer<String>> callbacks) { if (callbacks.isEmpty()) { unregisterObserversIfNeeded(); } else { @@ -92,6 +94,12 @@ public final class RawFoldingFeatureProducer extends BaseDataProducer<String> { } } + @NonNull + @Override + public Optional<String> getCurrentData() { + return Optional.of(getFeatureString()); + } + /** * Registers settings observers, if needed. When settings observers are registered for this * producer callbacks for changes in data will be triggered. @@ -125,8 +133,8 @@ public final class RawFoldingFeatureProducer extends BaseDataProducer<String> { @Override public void onChange(boolean selfChange, Uri uri) { if (mDisplayFeaturesUri.equals(uri)) { - notifyDataChanged(); + notifyDataChanged(getFeatureString()); } } } -} +}
\ No newline at end of file diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java index 44af1a9fd780..f09a91018bf0 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java @@ -18,6 +18,8 @@ package androidx.window.extensions.embedding; import android.annotation.NonNull; import android.app.Activity; +import android.util.Pair; +import android.util.Size; /** * Client-side descriptor of a split that holds two containers. @@ -66,6 +68,13 @@ class SplitContainer { return mSplitRule; } + /** Returns the minimum dimension pair of primary container and secondary container. */ + @NonNull + Pair<Size, Size> getMinDimensionsPair() { + return new Pair<>(mPrimaryContainer.getMinDimensions(), + mSecondaryContainer.getMinDimensions()); + } + boolean isPlaceholderContainer() { return (mSplitRule instanceof SplitPlaceholderRule); } 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 575c3f002791..242e9ab6beee 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -24,9 +24,11 @@ import static androidx.window.extensions.embedding.SplitContainer.getFinishSecon import static androidx.window.extensions.embedding.SplitContainer.isStickyPlaceholderRule; import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenAdjacent; import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenStacked; +import static androidx.window.extensions.embedding.SplitPresenter.boundsSmallerThanMinDimensions; +import static androidx.window.extensions.embedding.SplitPresenter.getActivityIntentMinDimensionsPair; +import static androidx.window.extensions.embedding.SplitPresenter.getMinDimensions; +import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSideBySide; -import android.annotation.NonNull; -import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityClient; import android.app.ActivityOptions; @@ -43,11 +45,15 @@ import android.os.IBinder; import android.os.Looper; import android.util.ArraySet; import android.util.Log; +import android.util.Pair; +import android.util.Size; import android.util.SparseArray; import android.window.TaskFragmentInfo; import android.window.WindowContainerTransaction; import androidx.annotation.GuardedBy; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.window.common.EmptyLifecycleCallbacksAdapter; import com.android.internal.annotations.VisibleForTesting; @@ -63,7 +69,7 @@ import java.util.function.Consumer; */ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmentCallback, ActivityEmbeddingComponent { - private static final String TAG = "SplitController"; + static final String TAG = "SplitController"; @VisibleForTesting @GuardedBy("mLock") @@ -350,7 +356,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (!(rule instanceof SplitRule)) { continue; } - if (mPresenter.shouldShowSideBySide(taskContainer.getTaskBounds(), (SplitRule) rule)) { + if (shouldShowSideBySide(taskContainer.getTaskBounds(), (SplitRule) rule)) { return true; } } @@ -610,11 +616,15 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen primaryActivity); final SplitContainer splitContainer = getActiveSplitForContainer(primaryContainer); if (splitContainer != null && primaryContainer == splitContainer.getPrimaryContainer() - && canReuseContainer(splitRule, splitContainer.getSplitRule())) { + && canReuseContainer(splitRule, splitContainer.getSplitRule()) + && !boundsSmallerThanMinDimensions(primaryContainer.getLastRequestedBounds(), + getMinDimensions(primaryActivity))) { // Can launch in the existing secondary container if the rules share the same // presentation. final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer(); - if (secondaryContainer == getContainerWithActivity(secondaryActivity)) { + if (secondaryContainer == getContainerWithActivity(secondaryActivity) + && !boundsSmallerThanMinDimensions(secondaryContainer.getLastRequestedBounds(), + getMinDimensions(secondaryActivity))) { // The activity is already in the target TaskFragment. return true; } @@ -791,9 +801,15 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen && (canReuseContainer(splitRule, splitContainer.getSplitRule()) // TODO(b/231845476) we should always respect clearTop. || !respectClearTop)) { - // Can launch in the existing secondary container if the rules share the same - // presentation. - return splitContainer.getSecondaryContainer(); + final Rect secondaryBounds = splitContainer.getSecondaryContainer() + .getLastRequestedBounds(); + if (secondaryBounds.isEmpty() + || !boundsSmallerThanMinDimensions(secondaryBounds, + getMinDimensions(intent))) { + // Can launch in the existing secondary container if the rules share the same + // presentation. + return splitContainer.getSecondaryContainer(); + } } // Create a new TaskFragment to split with the primary activity for the new activity. return mPresenter.createNewSplitWithEmptySideContainer(wct, primaryActivity, intent, @@ -1117,8 +1133,16 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Check if there is enough space for launch final SplitPlaceholderRule placeholderRule = getPlaceholderRule(activity); - if (placeholderRule == null || !mPresenter.shouldShowSideBySide( - mPresenter.getParentContainerBounds(activity), placeholderRule)) { + + if (placeholderRule == null) { + return false; + } + + final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair(activity, + placeholderRule.getPlaceholderIntent()); + if (!shouldShowSideBySide( + mPresenter.getParentContainerBounds(activity), placeholderRule, + minDimensionsPair)) { return false; } @@ -1161,7 +1185,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return false; } - if (mPresenter.shouldShowSideBySide(splitContainer)) { + if (shouldShowSideBySide(splitContainer)) { return false; } @@ -1233,7 +1257,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Splits that are not showing side-by-side are reported as having 0 split // ratio, since by definition in the API the primary container occupies no // width of the split when covered by the secondary. - mPresenter.shouldShowSideBySide(container) + shouldShowSideBySide(container) ? container.getSplitRule().getSplitRatio() : 0.0f); splitStates.add(splitState); @@ -1402,7 +1426,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } // Decide whether the associated container should be retained based on the current // presentation mode. - if (mPresenter.shouldShowSideBySide(splitContainer)) { + if (shouldShowSideBySide(splitContainer)) { return !shouldFinishAssociatedContainerWhenAdjacent(finishBehavior); } else { return !shouldFinishAssociatedContainerWhenStacked(finishBehavior); 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 ac3b05a0e825..1b79ad999435 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -16,15 +16,23 @@ package androidx.window.extensions.embedding; +import static android.content.pm.PackageManager.MATCH_ALL; + import android.app.Activity; +import android.app.ActivityThread; import android.app.WindowConfiguration; import android.app.WindowConfiguration.WindowingMode; import android.content.Context; import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; import android.util.LayoutDirection; +import android.util.Pair; +import android.util.Size; import android.view.View; import android.view.WindowInsets; import android.view.WindowMetrics; @@ -34,6 +42,8 @@ import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.internal.annotations.VisibleForTesting; + import java.util.concurrent.Executor; /** @@ -41,9 +51,12 @@ import java.util.concurrent.Executor; * {@link SplitController}. */ class SplitPresenter extends JetpackTaskFragmentOrganizer { - private static final int POSITION_START = 0; - private static final int POSITION_END = 1; - private static final int POSITION_FILL = 2; + @VisibleForTesting + static final int POSITION_START = 0; + @VisibleForTesting + static final int POSITION_END = 1; + @VisibleForTesting + static final int POSITION_FILL = 2; @IntDef(value = { POSITION_START, @@ -103,8 +116,10 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { @NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity, @NonNull Intent secondaryIntent, @NonNull SplitPairRule rule) { final Rect parentBounds = getParentContainerBounds(primaryActivity); + final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair( + primaryActivity, secondaryIntent); final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule, - isLtr(primaryActivity, rule)); + primaryActivity, minDimensionsPair); final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct, primaryActivity, primaryRectBounds, null); @@ -113,7 +128,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { final TaskFragmentContainer secondaryContainer = mController.newContainer( secondaryIntent, primaryActivity, taskId); final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, - rule, isLtr(primaryActivity, rule)); + rule, primaryActivity, minDimensionsPair); final int windowingMode = mController.getTaskContainer(taskId) .getWindowingModeForSplitTaskFragment(secondaryRectBounds); createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(), @@ -121,7 +136,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { windowingMode); // Set adjacent to each other so that the containers below will be invisible. - setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule); + setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule, + minDimensionsPair); mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule); @@ -144,13 +160,15 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { final WindowContainerTransaction wct = new WindowContainerTransaction(); final Rect parentBounds = getParentContainerBounds(primaryActivity); + final Pair<Size, Size> minDimensionsPair = getActivitiesMinDimensionsPair(primaryActivity, + secondaryActivity); final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule, - isLtr(primaryActivity, rule)); + primaryActivity, minDimensionsPair); final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct, primaryActivity, primaryRectBounds, null); final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule, - isLtr(primaryActivity, rule)); + primaryActivity, minDimensionsPair); final TaskFragmentContainer curSecondaryContainer = mController.getContainerWithActivity( secondaryActivity); TaskFragmentContainer containerToAvoid = primaryContainer; @@ -162,7 +180,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { secondaryActivity, secondaryRectBounds, containerToAvoid); // Set adjacent to each other so that the containers below will be invisible. - setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule); + setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule, + minDimensionsPair); mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule); @@ -211,10 +230,12 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { void startActivityToSide(@NonNull Activity launchingActivity, @NonNull Intent activityIntent, @Nullable Bundle activityOptions, @NonNull SplitRule rule, boolean isPlaceholder) { final Rect parentBounds = getParentContainerBounds(launchingActivity); + final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair( + launchingActivity, activityIntent); final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule, - isLtr(launchingActivity, rule)); + launchingActivity, minDimensionsPair); final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule, - isLtr(launchingActivity, rule)); + launchingActivity, minDimensionsPair); TaskFragmentContainer primaryContainer = mController.getContainerWithActivity( launchingActivity); @@ -258,11 +279,11 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { if (activity == null) { return; } - final boolean isLtr = isLtr(activity, rule); + final Pair<Size, Size> minDimensionsPair = splitContainer.getMinDimensionsPair(); final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule, - isLtr); + activity, minDimensionsPair); final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule, - isLtr); + activity, minDimensionsPair); final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer(); // Whether the placeholder is becoming side-by-side with the primary from fullscreen. final boolean isPlaceholderBecomingSplit = splitContainer.isPlaceholderContainer() @@ -273,7 +294,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { // are created again. resizeTaskFragmentIfRegistered(wct, primaryContainer, primaryRectBounds); resizeTaskFragmentIfRegistered(wct, secondaryContainer, secondaryRectBounds); - setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule); + setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule, + minDimensionsPair); if (isPlaceholderBecomingSplit) { // When placeholder is shown in split, we should keep the focus on the primary. wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken()); @@ -287,11 +309,12 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { private void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer primaryContainer, - @NonNull TaskFragmentContainer secondaryContainer, @NonNull SplitRule splitRule) { + @NonNull TaskFragmentContainer secondaryContainer, @NonNull SplitRule splitRule, + @NonNull Pair<Size, Size> minDimensionsPair) { final Rect parentBounds = getParentContainerBounds(primaryContainer); // Clear adjacent TaskFragments if the container is shown in fullscreen, or the // secondaryContainer could not be finished. - if (!shouldShowSideBySide(parentBounds, splitRule)) { + if (!shouldShowSideBySide(parentBounds, splitRule, minDimensionsPair)) { setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(), null /* secondary */, null /* splitRule */); } else { @@ -373,41 +396,132 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { super.updateWindowingMode(wct, fragmentToken, windowingMode); } - boolean shouldShowSideBySide(@NonNull SplitContainer splitContainer) { + static boolean shouldShowSideBySide(@NonNull Rect parentBounds, @NonNull SplitRule rule) { + return shouldShowSideBySide(parentBounds, rule, null /* minimumDimensionPair */); + } + + static boolean shouldShowSideBySide(@NonNull SplitContainer splitContainer) { final Rect parentBounds = getParentContainerBounds(splitContainer.getPrimaryContainer()); - return shouldShowSideBySide(parentBounds, splitContainer.getSplitRule()); + + return shouldShowSideBySide(parentBounds, splitContainer.getSplitRule(), + splitContainer.getMinDimensionsPair()); } - boolean shouldShowSideBySide(@Nullable Rect parentBounds, @NonNull SplitRule rule) { + static boolean shouldShowSideBySide(@NonNull Rect parentBounds, @NonNull SplitRule rule, + @Nullable Pair<Size, Size> minDimensionsPair) { // TODO(b/190433398): Supply correct insets. final WindowMetrics parentMetrics = new WindowMetrics(parentBounds, new WindowInsets(new Rect())); - return rule.checkParentMetrics(parentMetrics); + // Don't show side by side if bounds is not qualified. + if (!rule.checkParentMetrics(parentMetrics)) { + return false; + } + final float splitRatio = rule.getSplitRatio(); + // We only care the size of the bounds regardless of its position. + final Rect primaryBounds = getPrimaryBounds(parentBounds, splitRatio, true /* isLtr */); + final Rect secondaryBounds = getSecondaryBounds(parentBounds, splitRatio, true /* isLtr */); + + if (minDimensionsPair == null) { + return true; + } + return !boundsSmallerThanMinDimensions(primaryBounds, minDimensionsPair.first) + && !boundsSmallerThanMinDimensions(secondaryBounds, minDimensionsPair.second); } @NonNull - private Rect getBoundsForPosition(@Position int position, @NonNull Rect parentBounds, - @NonNull SplitRule rule, boolean isLtr) { - if (!shouldShowSideBySide(parentBounds, rule)) { - return new Rect(); + static Pair<Size, Size> getActivitiesMinDimensionsPair(Activity primaryActivity, + Activity secondaryActivity) { + return new Pair<>(getMinDimensions(primaryActivity), getMinDimensions(secondaryActivity)); + } + + @NonNull + static Pair<Size, Size> getActivityIntentMinDimensionsPair(Activity primaryActivity, + Intent secondaryIntent) { + return new Pair<>(getMinDimensions(primaryActivity), getMinDimensions(secondaryIntent)); + } + + @Nullable + static Size getMinDimensions(@Nullable Activity activity) { + if (activity == null) { + return null; + } + final ActivityInfo.WindowLayout windowLayout = activity.getActivityInfo().windowLayout; + if (windowLayout == null) { + return null; } + return new Size(windowLayout.minWidth, windowLayout.minHeight); + } + // TODO(b/232871351): find a light-weight approach for this check. + @Nullable + static Size getMinDimensions(@Nullable Intent intent) { + if (intent == null) { + return null; + } + final PackageManager packageManager = ActivityThread.currentActivityThread() + .getApplication().getPackageManager(); + final ResolveInfo resolveInfo = packageManager.resolveActivity(intent, + PackageManager.ResolveInfoFlags.of(MATCH_ALL)); + if (resolveInfo == null) { + return null; + } + final ActivityInfo activityInfo = resolveInfo.activityInfo; + if (activityInfo == null) { + return null; + } + final ActivityInfo.WindowLayout windowLayout = activityInfo.windowLayout; + if (windowLayout == null) { + return null; + } + return new Size(windowLayout.minWidth, windowLayout.minHeight); + } + + static boolean boundsSmallerThanMinDimensions(@NonNull Rect bounds, + @Nullable Size minDimensions) { + if (minDimensions == null) { + return false; + } + return bounds.width() < minDimensions.getWidth() + || bounds.height() < minDimensions.getHeight(); + } + + @VisibleForTesting + @NonNull + static Rect getBoundsForPosition(@Position int position, @NonNull Rect parentBounds, + @NonNull SplitRule rule, @NonNull Activity primaryActivity, + @Nullable Pair<Size, Size> minDimensionsPair) { + if (!shouldShowSideBySide(parentBounds, rule, minDimensionsPair)) { + return new Rect(); + } + final boolean isLtr = isLtr(primaryActivity, rule); final float splitRatio = rule.getSplitRatio(); - final float rtlSplitRatio = 1 - splitRatio; + switch (position) { case POSITION_START: - return isLtr ? getLeftContainerBounds(parentBounds, splitRatio) - : getRightContainerBounds(parentBounds, rtlSplitRatio); + return getPrimaryBounds(parentBounds, splitRatio, isLtr); case POSITION_END: - return isLtr ? getRightContainerBounds(parentBounds, splitRatio) - : getLeftContainerBounds(parentBounds, rtlSplitRatio); + return getSecondaryBounds(parentBounds, splitRatio, isLtr); case POSITION_FILL: - return parentBounds; + default: + return new Rect(); } - return parentBounds; } - private Rect getLeftContainerBounds(@NonNull Rect parentBounds, float splitRatio) { + @NonNull + private static Rect getPrimaryBounds(@NonNull Rect parentBounds, float splitRatio, + boolean isLtr) { + return isLtr ? getLeftContainerBounds(parentBounds, splitRatio) + : getRightContainerBounds(parentBounds, 1 - splitRatio); + } + + @NonNull + private static Rect getSecondaryBounds(@NonNull Rect parentBounds, float splitRatio, + boolean isLtr) { + return isLtr ? getRightContainerBounds(parentBounds, splitRatio) + : getLeftContainerBounds(parentBounds, 1 - splitRatio); + } + + private static Rect getLeftContainerBounds(@NonNull Rect parentBounds, float splitRatio) { return new Rect( parentBounds.left, parentBounds.top, @@ -415,7 +529,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { parentBounds.bottom); } - private Rect getRightContainerBounds(@NonNull Rect parentBounds, float splitRatio) { + private static Rect getRightContainerBounds(@NonNull Rect parentBounds, float splitRatio) { return new Rect( (int) (parentBounds.left + parentBounds.width() * splitRatio), parentBounds.top, @@ -427,7 +541,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { * Checks if a split with the provided rule should be displays in left-to-right layout * direction, either always or with the current configuration. */ - private boolean isLtr(@NonNull Context context, @NonNull SplitRule rule) { + private static boolean isLtr(@NonNull Context context, @NonNull SplitRule rule) { switch (rule.getLayoutDirection()) { case LayoutDirection.LOCALE: return context.getResources().getConfiguration().getLayoutDirection() @@ -441,7 +555,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { } @NonNull - Rect getParentContainerBounds(@NonNull TaskFragmentContainer container) { + static Rect getParentContainerBounds(@NonNull TaskFragmentContainer container) { return container.getTaskContainer().getTaskBounds(); } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java index 624cde50ff72..abf32a26efa2 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -26,6 +26,7 @@ import android.content.Intent; import android.graphics.Rect; import android.os.Binder; import android.os.IBinder; +import android.util.Size; import android.window.TaskFragmentInfo; import android.window.WindowContainerTransaction; @@ -414,6 +415,11 @@ class TaskFragmentContainer { } } + @NonNull + Rect getLastRequestedBounds() { + return mLastRequestedBounds; + } + /** * Checks if last requested windowing mode is equal to the provided value. */ @@ -439,6 +445,31 @@ class TaskFragmentContainer { return mTaskContainer; } + @Nullable + Size getMinDimensions() { + if (mInfo == null) { + return null; + } + int maxMinWidth = mInfo.getMinimumWidth(); + int maxMinHeight = mInfo.getMinimumHeight(); + for (Activity activity : mPendingAppearedActivities) { + final Size minDimensions = SplitPresenter.getMinDimensions(activity); + if (minDimensions == null) { + continue; + } + maxMinWidth = Math.max(maxMinWidth, minDimensions.getWidth()); + maxMinHeight = Math.max(maxMinHeight, minDimensions.getHeight()); + } + if (mPendingAppearedIntent != null) { + final Size minDimensions = SplitPresenter.getMinDimensions(mPendingAppearedIntent); + if (minDimensions != null) { + maxMinWidth = Math.max(maxMinWidth, minDimensions.getWidth()); + maxMinHeight = Math.max(maxMinHeight, minDimensions.getHeight()); + } + } + return new Size(maxMinWidth, maxMinHeight); + } + @Override public String toString() { return toString(true /* includeContainersToFinishOnExit */); 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 a6f638822d10..cfb32050e32f 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java @@ -43,7 +43,6 @@ import androidx.window.util.DataProducer; import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Set; import java.util.function.Consumer; @@ -63,7 +62,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { private final DataProducer<List<CommonFoldingFeature>> mFoldingFeatureProducer; - public WindowLayoutComponentImpl(Context context) { + public WindowLayoutComponentImpl(@NonNull Context context) { ((Application) context.getApplicationContext()) .registerActivityLifecycleCallbacks(new NotifyOnConfigurationChanged()); RawFoldingFeatureProducer foldingFeatureProducer = new RawFoldingFeatureProducer(context); @@ -80,8 +79,12 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { */ public void addWindowLayoutInfoListener(@NonNull Activity activity, @NonNull Consumer<WindowLayoutInfo> consumer) { + mFoldingFeatureProducer.getData((features) -> { + // Get the WindowLayoutInfo from the activity and pass the value to the layoutConsumer. + WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(activity, features); + consumer.accept(newWindowLayout); + }); mWindowLayoutChangeListeners.put(activity, consumer); - onDisplayFeaturesChanged(); } /** @@ -89,18 +92,8 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { * * @param consumer no longer interested in receiving updates to {@link WindowLayoutInfo} */ - public void removeWindowLayoutInfoListener( - @NonNull Consumer<WindowLayoutInfo> consumer) { + public void removeWindowLayoutInfoListener(@NonNull Consumer<WindowLayoutInfo> consumer) { mWindowLayoutChangeListeners.values().remove(consumer); - onDisplayFeaturesChanged(); - } - - void updateWindowLayout(@NonNull Activity activity, - @NonNull WindowLayoutInfo newLayout) { - Consumer<WindowLayoutInfo> consumer = mWindowLayoutChangeListeners.get(activity); - if (consumer != null) { - consumer.accept(newLayout); - } } @NonNull @@ -108,7 +101,6 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { return mWindowLayoutChangeListeners.keySet(); } - @NonNull private boolean isListeningForLayoutChanges(IBinder token) { for (Activity activity: getActivitiesListeningForLayoutChanges()) { if (token.equals(activity.getWindow().getAttributes().token)) { @@ -125,12 +117,12 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { /** * A convenience method to translate from the common feature state to the extensions feature * state. More specifically, translates from {@link CommonFoldingFeature.State} to - * {@link FoldingFeature.STATE_FLAT} or {@link FoldingFeature.STATE_HALF_OPENED}. If it is not + * {@link FoldingFeature#STATE_FLAT} or {@link FoldingFeature#STATE_HALF_OPENED}. If it is not * possible to translate, then we will return a {@code null} value. * * @param state if it matches a value in {@link CommonFoldingFeature.State}, {@code null} - * otherwise. @return a {@link FoldingFeature.STATE_FLAT} or - * {@link FoldingFeature.STATE_HALF_OPENED} if the given state matches a value in + * otherwise. @return a {@link FoldingFeature#STATE_FLAT} or + * {@link FoldingFeature#STATE_HALF_OPENED} if the given state matches a value in * {@link CommonFoldingFeature.State} and {@code null} otherwise. */ @Nullable @@ -144,17 +136,24 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { } } - private void onDisplayFeaturesChanged() { + private void onDisplayFeaturesChanged(List<CommonFoldingFeature> storedFeatures) { for (Activity activity : getActivitiesListeningForLayoutChanges()) { - WindowLayoutInfo newLayout = getWindowLayoutInfo(activity); - updateWindowLayout(activity, newLayout); + // Get the WindowLayoutInfo from the activity and pass the value to the layoutConsumer. + Consumer<WindowLayoutInfo> layoutConsumer = mWindowLayoutChangeListeners.get(activity); + WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(activity, storedFeatures); + layoutConsumer.accept(newWindowLayout); } } - @NonNull - private WindowLayoutInfo getWindowLayoutInfo(@NonNull Activity activity) { - List<DisplayFeature> displayFeatures = getDisplayFeatures(activity); - return new WindowLayoutInfo(displayFeatures); + /** + * Translates the {@link DisplayFeature} into a {@link WindowLayoutInfo} when a + * valid state is found. + * @param activity a proxy for the {@link android.view.Window} that contains the + */ + private WindowLayoutInfo getWindowLayoutInfo( + @NonNull Activity activity, List<CommonFoldingFeature> storedFeatures) { + List<DisplayFeature> displayFeatureList = getDisplayFeatures(activity, storedFeatures); + return new WindowLayoutInfo(displayFeatureList); } /** @@ -172,26 +171,21 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { * * @param activity a proxy for the {@link android.view.Window} that contains the * {@link DisplayFeature}. - * @return a {@link List} of valid {@link DisplayFeature} that * are within the {@link android.view.Window} of the {@link Activity} */ - private List<DisplayFeature> getDisplayFeatures(@NonNull Activity activity) { + private List<DisplayFeature> getDisplayFeatures( + @NonNull Activity activity, List<CommonFoldingFeature> storedFeatures) { List<DisplayFeature> features = new ArrayList<>(); int displayId = activity.getDisplay().getDisplayId(); if (displayId != DEFAULT_DISPLAY) { Log.w(TAG, "This sample doesn't support display features on secondary displays"); return features; - } - - if (activity.isInMultiWindowMode()) { + } else if (activity.isInMultiWindowMode()) { // It is recommended not to report any display features in multi-window mode, since it // won't be possible to synchronize the display feature positions with window movement. return features; - } - - Optional<List<CommonFoldingFeature>> storedFeatures = mFoldingFeatureProducer.getData(); - if (storedFeatures.isPresent()) { - for (CommonFoldingFeature baseFeature : storedFeatures.get()) { + } else { + for (CommonFoldingFeature baseFeature : storedFeatures) { Integer state = convertToExtensionState(baseFeature.getState()); if (state == null) { continue; @@ -205,8 +199,8 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { features.add(new FoldingFeature(featureRect, baseFeature.getType(), state)); } } + return features; } - return features; } /** @@ -233,7 +227,8 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { private void onDisplayFeaturesChangedIfListening(Activity activity) { IBinder token = activity.getWindow().getAttributes().token; if (token == null || isListeningForLayoutChanges(token)) { - onDisplayFeaturesChanged(); + mFoldingFeatureProducer.getData( + WindowLayoutComponentImpl.this::onDisplayFeaturesChanged); } } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java index 970f0a2af632..5bfb0ebdcaa8 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java @@ -28,41 +28,42 @@ import android.content.Context; import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; -import android.util.Log; import androidx.annotation.NonNull; import androidx.window.common.CommonFoldingFeature; import androidx.window.common.DeviceStateManagerFoldingFeatureProducer; import androidx.window.common.EmptyLifecycleCallbacksAdapter; import androidx.window.common.RawFoldingFeatureProducer; -import androidx.window.util.DataProducer; +import androidx.window.util.BaseDataProducer; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Optional; /** * Reference implementation of androidx.window.sidecar OEM interface for use with * WindowManager Jetpack. */ class SampleSidecarImpl extends StubSidecar { - private static final String TAG = "SampleSidecar"; - - private final DataProducer<List<CommonFoldingFeature>> mFoldingFeatureProducer; - + private List<CommonFoldingFeature> mStoredFeatures = new ArrayList<>(); SampleSidecarImpl(Context context) { ((Application) context.getApplicationContext()) .registerActivityLifecycleCallbacks(new NotifyOnConfigurationChanged()); - DataProducer<String> settingsFeatureProducer = new RawFoldingFeatureProducer(context); - mFoldingFeatureProducer = new DeviceStateManagerFoldingFeatureProducer(context, - settingsFeatureProducer); + BaseDataProducer<String> settingsFeatureProducer = new RawFoldingFeatureProducer(context); + BaseDataProducer<List<CommonFoldingFeature>> foldingFeatureProducer = + new DeviceStateManagerFoldingFeatureProducer(context, + settingsFeatureProducer); - mFoldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged); + foldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged); } - private void onDisplayFeaturesChanged() { + private void setStoredFeatures(List<CommonFoldingFeature> storedFeatures) { + mStoredFeatures = storedFeatures; + } + + private void onDisplayFeaturesChanged(List<CommonFoldingFeature> storedFeatures) { + setStoredFeatures(storedFeatures); updateDeviceState(getDeviceState()); for (IBinder windowToken : getWindowsListeningForLayoutChanges()) { SidecarWindowLayoutInfo newLayout = getWindowLayoutInfo(windowToken); @@ -79,16 +80,16 @@ class SampleSidecarImpl extends StubSidecar { } private int deviceStateFromFeature() { - List<CommonFoldingFeature> storedFeatures = mFoldingFeatureProducer.getData() - .orElse(Collections.emptyList()); - for (int i = 0; i < storedFeatures.size(); i++) { - CommonFoldingFeature feature = storedFeatures.get(i); + for (int i = 0; i < mStoredFeatures.size(); i++) { + CommonFoldingFeature feature = mStoredFeatures.get(i); final int state = feature.getState(); switch (state) { case CommonFoldingFeature.COMMON_STATE_FLAT: return SidecarDeviceState.POSTURE_OPENED; case CommonFoldingFeature.COMMON_STATE_HALF_OPENED: return SidecarDeviceState.POSTURE_HALF_OPENED; + case CommonFoldingFeature.COMMON_STATE_UNKNOWN: + return SidecarDeviceState.POSTURE_UNKNOWN; } } return SidecarDeviceState.POSTURE_UNKNOWN; @@ -109,7 +110,6 @@ class SampleSidecarImpl extends StubSidecar { private List<SidecarDisplayFeature> getDisplayFeatures(@NonNull Activity activity) { int displayId = activity.getDisplay().getDisplayId(); if (displayId != DEFAULT_DISPLAY) { - Log.w(TAG, "This sample doesn't support display features on secondary displays"); return Collections.emptyList(); } @@ -119,18 +119,15 @@ class SampleSidecarImpl extends StubSidecar { return Collections.emptyList(); } - Optional<List<CommonFoldingFeature>> storedFeatures = mFoldingFeatureProducer.getData(); List<SidecarDisplayFeature> features = new ArrayList<>(); - if (storedFeatures.isPresent()) { - for (CommonFoldingFeature baseFeature : storedFeatures.get()) { - SidecarDisplayFeature feature = new SidecarDisplayFeature(); - Rect featureRect = baseFeature.getRect(); - rotateRectToDisplayRotation(displayId, featureRect); - transformToWindowSpaceRect(activity, featureRect); - feature.setRect(featureRect); - feature.setType(baseFeature.getType()); - features.add(feature); - } + for (CommonFoldingFeature baseFeature : mStoredFeatures) { + SidecarDisplayFeature feature = new SidecarDisplayFeature(); + Rect featureRect = baseFeature.getRect(); + rotateRectToDisplayRotation(displayId, featureRect); + transformToWindowSpaceRect(activity, featureRect); + feature.setRect(featureRect); + feature.setType(baseFeature.getType()); + features.add(feature); } return Collections.unmodifiableList(features); } @@ -138,7 +135,7 @@ class SampleSidecarImpl extends StubSidecar { @Override protected void onListenersChanged() { if (hasListeners()) { - onDisplayFeaturesChanged(); + onDisplayFeaturesChanged(mStoredFeatures); } } @@ -158,7 +155,7 @@ class SampleSidecarImpl extends StubSidecar { private void onDisplayFeaturesChangedForActivity(@NonNull Activity activity) { IBinder token = activity.getWindow().getAttributes().token; if (token == null || mWindowLayoutChangeListenerTokens.contains(token)) { - onDisplayFeaturesChanged(); + onDisplayFeaturesChanged(mStoredFeatures); } } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java new file mode 100644 index 000000000000..7624b693ac43 --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.util; + +import android.annotation.NonNull; + +import java.util.function.Consumer; + +/** + * A base class that works with {@link BaseDataProducer} to add/remove a consumer that should + * only be used once when {@link BaseDataProducer#notifyDataChanged} is called. + * @param <T> The type of data this producer returns through {@link DataProducer#getData}. + */ +public class AcceptOnceConsumer<T> implements Consumer<T> { + private final Consumer<T> mCallback; + private final DataProducer<T> mProducer; + + public AcceptOnceConsumer(@NonNull DataProducer<T> producer, @NonNull Consumer<T> callback) { + mProducer = producer; + mCallback = callback; + } + + @Override + public void accept(@NonNull T t) { + mCallback.accept(t); + mProducer.removeDataChangedCallback(this); + } +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java index 930db3b701b7..0da44ac36a6e 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java @@ -19,38 +19,48 @@ package androidx.window.util; import androidx.annotation.NonNull; import java.util.LinkedHashSet; +import java.util.Optional; import java.util.Set; +import java.util.function.Consumer; /** * Base class that provides the implementation for the callback mechanism of the * {@link DataProducer} API. * - * @param <T> The type of data this producer returns through {@link #getData()}. + * @param <T> The type of data this producer returns through {@link DataProducer#getData}. */ public abstract class BaseDataProducer<T> implements DataProducer<T> { - private final Set<Runnable> mCallbacks = new LinkedHashSet<>(); + private final Set<Consumer<T>> mCallbacks = new LinkedHashSet<>(); @Override - public final void addDataChangedCallback(@NonNull Runnable callback) { + public final void addDataChangedCallback(@NonNull Consumer<T> callback) { mCallbacks.add(callback); + Optional<T> currentData = getCurrentData(); + currentData.ifPresent(callback); onListenersChanged(mCallbacks); } @Override - public final void removeDataChangedCallback(@NonNull Runnable callback) { + public final void removeDataChangedCallback(@NonNull Consumer<T> callback) { mCallbacks.remove(callback); onListenersChanged(mCallbacks); } - protected void onListenersChanged(Set<Runnable> callbacks) {} + protected void onListenersChanged(Set<Consumer<T>> callbacks) {} /** - * Called to notify all registered callbacks that the data provided by {@link #getData()} has - * changed. + * @return the current data if available and {@code Optional.empty()} otherwise. */ - protected void notifyDataChanged() { - for (Runnable callback : mCallbacks) { - callback.run(); + @NonNull + public abstract Optional<T> getCurrentData(); + + /** + * Called to notify all registered consumers that the data provided + * by {@link DataProducer#getData} has changed. + */ + protected void notifyDataChanged(T value) { + for (Consumer<T> callback : mCallbacks) { + callback.accept(value); } } -} +}
\ No newline at end of file diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/DataProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/DataProducer.java index d4d1a23b756b..ec301dc34aaa 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/util/DataProducer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/util/DataProducer.java @@ -18,26 +18,27 @@ package androidx.window.util; import android.annotation.NonNull; -import java.util.Optional; +import java.util.function.Consumer; /** - * Produces data through {@link #getData()} and provides a mechanism for receiving a callback when - * the data managed by the produces has changed. + * Produces data through {@link DataProducer#getData} and provides a mechanism for receiving + * a callback when the data managed by the produces has changed. * - * @param <T> The type of data this producer returns through {@link #getData()}. + * @param <T> The type of data this producer returns through {@link DataProducer#getData}. */ public interface DataProducer<T> { /** - * Returns the data currently stored in the provider, or {@link Optional#empty()} if the - * provider has no data. + * Emits the first available data at that point in time. + * @param dataConsumer a {@link Consumer} that will receive one value. */ - Optional<T> getData(); + void getData(@NonNull Consumer<T> dataConsumer); /** - * Adds a callback to be notified when the data returned from {@link #getData()} has changed. + * Adds a callback to be notified when the data returned + * from {@link DataProducer#getData} has changed. */ - void addDataChangedCallback(@NonNull Runnable callback); + void addDataChangedCallback(@NonNull Consumer<T> callback); - /** Removes a callback previously added with {@link #addDataChangedCallback(Runnable)}. */ - void removeDataChangedCallback(@NonNull Runnable callback); + /** Removes a callback previously added with {@link #addDataChangedCallback(Consumer)}. */ + void removeDataChangedCallback(@NonNull Consumer<T> callback); } diff --git a/libs/WindowManager/Jetpack/tests/unittest/AndroidManifest.xml b/libs/WindowManager/Jetpack/tests/unittest/AndroidManifest.xml index b12b6f6f0ef1..c736e9ed971e 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/AndroidManifest.xml +++ b/libs/WindowManager/Jetpack/tests/unittest/AndroidManifest.xml @@ -22,6 +22,11 @@ <application android:debuggable="true" android:largeHeap="true"> <uses-library android:name="android.test.mock" /> <uses-library android:name="android.test.runner" /> + + <activity android:name="androidx.window.extensions.embedding.MinimumDimensionActivity"> + <layout android:minWidth="600px" + android:minHeight="1200px"/> + </activity> </application> <instrumentation diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java new file mode 100644 index 000000000000..835c40365cda --- /dev/null +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java @@ -0,0 +1,108 @@ +/* + * 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 androidx.window.extensions.embedding; + +import static androidx.window.extensions.embedding.SplitRule.FINISH_ALWAYS; +import static androidx.window.extensions.embedding.SplitRule.FINISH_NEVER; + +import static org.mockito.Mockito.mock; + +import android.annotation.NonNull; +import android.app.Activity; +import android.content.Intent; +import android.content.res.Configuration; +import android.graphics.Point; +import android.graphics.Rect; +import android.util.Pair; +import android.window.TaskFragmentInfo; +import android.window.WindowContainerToken; + +import java.util.Collections; + +public class EmbeddingTestUtils { + static final Rect TASK_BOUNDS = new Rect(0, 0, 600, 1200); + static final int TASK_ID = 10; + static final float SPLIT_RATIO = 0.5f; + /** Default finish behavior in Jetpack. */ + static final int DEFAULT_FINISH_PRIMARY_WITH_SECONDARY = FINISH_NEVER; + static final int DEFAULT_FINISH_SECONDARY_WITH_PRIMARY = FINISH_ALWAYS; + + private EmbeddingTestUtils() {} + + /** Gets the bounds of a TaskFragment that is in split. */ + static Rect getSplitBounds(boolean isPrimary) { + final int width = (int) (TASK_BOUNDS.width() * SPLIT_RATIO); + return isPrimary + ? new Rect(TASK_BOUNDS.left, TASK_BOUNDS.top, TASK_BOUNDS.left + width, + TASK_BOUNDS.bottom) + : new Rect( + TASK_BOUNDS.left + width, TASK_BOUNDS.top, TASK_BOUNDS.right, + TASK_BOUNDS.bottom); + } + + /** Creates a rule to always split the given activity and the given intent. */ + static SplitRule createSplitRule(@NonNull Activity primaryActivity, + @NonNull Intent secondaryIntent) { + final Pair<Activity, Intent> targetPair = new Pair<>(primaryActivity, secondaryIntent); + return new SplitPairRule.Builder( + activityPair -> false, + targetPair::equals, + w -> true) + .setSplitRatio(SPLIT_RATIO) + .setShouldClearTop(true) + .build(); + } + + /** Creates a rule to always split the given activities. */ + static SplitRule createSplitRule(@NonNull Activity primaryActivity, + @NonNull Activity secondaryActivity) { + return createSplitRule(primaryActivity, secondaryActivity, + DEFAULT_FINISH_PRIMARY_WITH_SECONDARY, DEFAULT_FINISH_SECONDARY_WITH_PRIMARY, + true /* clearTop */); + } + + /** Creates a rule to always split the given activities with the given finish behaviors. */ + static SplitRule createSplitRule(@NonNull Activity primaryActivity, + @NonNull Activity secondaryActivity, int finishPrimaryWithSecondary, + int finishSecondaryWithPrimary, boolean clearTop) { + final Pair<Activity, Activity> targetPair = new Pair<>(primaryActivity, secondaryActivity); + return new SplitPairRule.Builder( + targetPair::equals, + activityIntentPair -> false, + w -> true) + .setSplitRatio(SPLIT_RATIO) + .setFinishPrimaryWithSecondary(finishPrimaryWithSecondary) + .setFinishSecondaryWithPrimary(finishSecondaryWithPrimary) + .setShouldClearTop(clearTop) + .build(); + } + + /** Creates a mock TaskFragmentInfo for the given TaskFragment. */ + static TaskFragmentInfo createMockTaskFragmentInfo(@NonNull TaskFragmentContainer container, + @NonNull Activity activity) { + return new TaskFragmentInfo(container.getTaskFragmentToken(), + mock(WindowContainerToken.class), + new Configuration(), + 1, + true /* isVisible */, + Collections.singletonList(activity.getActivityToken()), + new Point(), + false /* isTaskClearedForReuse */, + false /* isTaskFragmentClearedForPip */, + new Point()); + } +} diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java index a191e685f651..4d2595275f20 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java @@ -18,6 +18,8 @@ package androidx.window.extensions.embedding; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID; + import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; @@ -58,8 +60,6 @@ import java.util.ArrayList; @SmallTest @RunWith(AndroidJUnit4.class) public class JetpackTaskFragmentOrganizerTest { - private static final int TASK_ID = 10; - @Mock private WindowContainerTransaction mTransaction; @Mock @@ -131,6 +131,7 @@ public class JetpackTaskFragmentOrganizerTest { return new TaskFragmentInfo(container.getTaskFragmentToken(), mock(WindowContainerToken.class), new Configuration(), 0 /* runningActivityCount */, false /* isVisible */, new ArrayList<>(), new Point(), - false /* isTaskClearedForReuse */, false /* isTaskFragmentClearedForPip */); + false /* isTaskClearedForReuse */, false /* isTaskFragmentClearedForPip */, + new Point()); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerState.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/MinimumDimensionActivity.java index af2ab158ab46..ffcaf3e6f546 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerState.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/MinimumDimensionActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.android.wm.shell.legacysplitscreen; +package androidx.window.extensions.embedding; + +import android.app.Activity; /** - * Class to hold state of divider that needs to persist across configuration changes. + * Activity that declares minWidth and minHeight in + * {@link android.content.pm.ActivityInfo.WindowLayout} */ -final class DividerState { - public boolean animateAfterRecentsDrawn; - public float mRatioPositionBeforeMinimized; -} +public class MinimumDimensionActivity extends Activity {} 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 60390eb2b3d2..ef7728cec387 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 @@ -19,8 +19,13 @@ package androidx.window.extensions.embedding; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.SPLIT_RATIO; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.getSplitBounds; import static androidx.window.extensions.embedding.SplitRule.FINISH_ALWAYS; -import static androidx.window.extensions.embedding.SplitRule.FINISH_NEVER; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; @@ -49,20 +54,19 @@ import android.app.Activity; import android.app.ActivityOptions; import android.content.ComponentName; import android.content.Intent; +import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.content.res.Resources; -import android.graphics.Point; import android.graphics.Rect; import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.platform.test.annotations.Presubmit; -import android.util.Pair; import android.window.TaskFragmentInfo; -import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; +import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -86,16 +90,9 @@ import java.util.List; @SmallTest @RunWith(AndroidJUnit4.class) public class SplitControllerTest { - private static final int TASK_ID = 10; - private static final Rect TASK_BOUNDS = new Rect(0, 0, 600, 1200); - private static final float SPLIT_RATIO = 0.5f; private static final Intent PLACEHOLDER_INTENT = new Intent().setComponent( new ComponentName("test", "placeholder")); - /** Default finish behavior in Jetpack. */ - private static final int DEFAULT_FINISH_PRIMARY_WITH_SECONDARY = FINISH_NEVER; - private static final int DEFAULT_FINISH_SECONDARY_WITH_PRIMARY = FINISH_ALWAYS; - private Activity mActivity; @Mock private Resources mActivityResources; @@ -420,6 +417,25 @@ public class SplitControllerTest { } @Test + public void testResolveStartActivityIntent_shouldLaunchInFullscreen() { + final Intent intent = new Intent().setComponent( + new ComponentName(ApplicationProvider.getApplicationContext(), + MinimumDimensionActivity.class)); + setupSplitRule(mActivity, intent); + final Activity primaryActivity = createMockActivity(); + addSplitTaskFragments(primaryActivity, mActivity); + + final TaskFragmentContainer container = mSplitController.resolveStartActivityIntent( + mTransaction, TASK_ID, intent, null /* launchingActivity */); + final TaskFragmentContainer primaryContainer = mSplitController.getContainerWithActivity( + mActivity); + + assertNotNull(mSplitController.getActiveSplitForContainers(primaryContainer, container)); + assertTrue(primaryContainer.areLastRequestedBoundsEqual(null)); + assertTrue(container.areLastRequestedBoundsEqual(null)); + } + + @Test public void testPlaceActivityInTopContainer() { mSplitController.placeActivityInTopContainer(mActivity); @@ -767,6 +783,52 @@ public class SplitControllerTest { } @Test + public void testResolveActivityToContainer_primaryActivityMinDimensionsNotSatisfied() { + final Activity activityBelow = createMockActivity(); + setupSplitRule(mActivity, activityBelow); + + ActivityInfo aInfo = new ActivityInfo(); + final Rect primaryBounds = getSplitBounds(true /* isPrimary */); + aInfo.windowLayout = new ActivityInfo.WindowLayout(0, 0, 0, 0, 0, + primaryBounds.width() + 1, primaryBounds.height() + 1); + doReturn(aInfo).when(mActivity).getActivityInfo(); + + final TaskFragmentContainer container = mSplitController.newContainer(activityBelow, + TASK_ID); + container.addPendingAppearedActivity(mActivity); + + // Allow to split as primary. + boolean result = mSplitController.resolveActivityToContainer(mActivity, + true /* isOnReparent */); + + assertTrue(result); + assertSplitPair(mActivity, activityBelow, true /* matchParentBounds */); + } + + @Test + public void testResolveActivityToContainer_secondaryActivityMinDimensionsNotSatisfied() { + final Activity activityBelow = createMockActivity(); + setupSplitRule(activityBelow, mActivity); + + ActivityInfo aInfo = new ActivityInfo(); + final Rect secondaryBounds = getSplitBounds(false /* isPrimary */); + aInfo.windowLayout = new ActivityInfo.WindowLayout(0, 0, 0, 0, 0, + secondaryBounds.width() + 1, secondaryBounds.height() + 1); + doReturn(aInfo).when(mActivity).getActivityInfo(); + + final TaskFragmentContainer container = mSplitController.newContainer(activityBelow, + TASK_ID); + container.addPendingAppearedActivity(mActivity); + + // Allow to split as primary. + boolean result = mSplitController.resolveActivityToContainer(mActivity, + false /* isOnReparent */); + + assertTrue(result); + assertSplitPair(activityBelow, mActivity, true /* matchParentBounds */); + } + + @Test public void testResolveActivityToContainer_inUnknownTaskFragment() { doReturn(new Binder()).when(mSplitController).getInitialTaskFragmentToken(mActivity); @@ -835,23 +897,10 @@ public class SplitControllerTest { doReturn(activityToken).when(activity).getActivityToken(); doReturn(activity).when(mSplitController).getActivity(activityToken); doReturn(TASK_ID).when(activity).getTaskId(); + doReturn(new ActivityInfo()).when(activity).getActivityInfo(); return activity; } - /** Creates a mock TaskFragmentInfo for the given TaskFragment. */ - private TaskFragmentInfo createMockTaskFragmentInfo(@NonNull TaskFragmentContainer container, - @NonNull Activity activity) { - return new TaskFragmentInfo(container.getTaskFragmentToken(), - mock(WindowContainerToken.class), - new Configuration(), - 1, - true /* isVisible */, - Collections.singletonList(activity.getActivityToken()), - new Point(), - false /* isTaskClearedForReuse */, - false /* isTaskFragmentClearedForPip */); - } - /** Creates a mock TaskFragment that has been registered and appeared in the organizer. */ private TaskFragmentContainer createMockTaskFragmentContainer(@NonNull Activity activity) { final TaskFragmentContainer container = mSplitController.newContainer(activity, TASK_ID); @@ -902,49 +951,10 @@ public class SplitControllerTest { /** Setups a rule to always split the given activities. */ private void setupSplitRule(@NonNull Activity primaryActivity, @NonNull Activity secondaryActivity) { - final SplitRule splitRule = createSplitRule(primaryActivity, secondaryActivity, - DEFAULT_FINISH_PRIMARY_WITH_SECONDARY, DEFAULT_FINISH_SECONDARY_WITH_PRIMARY, - true /* clearTop */); + final SplitRule splitRule = createSplitRule(primaryActivity, secondaryActivity); mSplitController.setEmbeddingRules(Collections.singleton(splitRule)); } - /** Creates a rule to always split the given activity and the given intent. */ - private SplitRule createSplitRule(@NonNull Activity primaryActivity, - @NonNull Intent secondaryIntent) { - final Pair<Activity, Intent> targetPair = new Pair<>(primaryActivity, secondaryIntent); - return new SplitPairRule.Builder( - activityPair -> false, - targetPair::equals, - w -> true) - .setSplitRatio(SPLIT_RATIO) - .setShouldClearTop(true) - .build(); - } - - /** Creates a rule to always split the given activities. */ - private SplitRule createSplitRule(@NonNull Activity primaryActivity, - @NonNull Activity secondaryActivity) { - return createSplitRule(primaryActivity, secondaryActivity, - DEFAULT_FINISH_PRIMARY_WITH_SECONDARY, DEFAULT_FINISH_SECONDARY_WITH_PRIMARY, - true /* clearTop */); - } - - /** Creates a rule to always split the given activities with the given finish behaviors. */ - private SplitRule createSplitRule(@NonNull Activity primaryActivity, - @NonNull Activity secondaryActivity, int finishPrimaryWithSecondary, - int finishSecondaryWithPrimary, boolean clearTop) { - final Pair<Activity, Activity> targetPair = new Pair<>(primaryActivity, secondaryActivity); - return new SplitPairRule.Builder( - targetPair::equals, - activityIntentPair -> false, - w -> true) - .setSplitRatio(SPLIT_RATIO) - .setFinishPrimaryWithSecondary(finishPrimaryWithSecondary) - .setFinishSecondaryWithPrimary(finishSecondaryWithPrimary) - .setShouldClearTop(clearTop) - .build(); - } - /** Adds a pair of TaskFragments as split for the given activities. */ private void addSplitTaskFragments(@NonNull Activity primaryActivity, @NonNull Activity secondaryActivity) { @@ -973,39 +983,42 @@ public class SplitControllerTest { secondaryContainer.setLastRequestedBounds(getSplitBounds(false /* isPrimary */)); } - /** Gets the bounds of a TaskFragment that is in split. */ - private Rect getSplitBounds(boolean isPrimary) { - final int width = (int) (TASK_BOUNDS.width() * SPLIT_RATIO); - return isPrimary - ? new Rect(TASK_BOUNDS.left, TASK_BOUNDS.top, TASK_BOUNDS.left + width, - TASK_BOUNDS.bottom) - : new Rect(TASK_BOUNDS.left + width, TASK_BOUNDS.top, TASK_BOUNDS.right, - TASK_BOUNDS.bottom); + /** Asserts that the two given activities are in split. */ + private void assertSplitPair(@NonNull Activity primaryActivity, + @NonNull Activity secondaryActivity) { + assertSplitPair(primaryActivity, secondaryActivity, false /* matchParentBounds */); } /** Asserts that the two given activities are in split. */ private void assertSplitPair(@NonNull Activity primaryActivity, - @NonNull Activity secondaryActivity) { + @NonNull Activity secondaryActivity, boolean matchParentBounds) { assertSplitPair(mSplitController.getContainerWithActivity(primaryActivity), - mSplitController.getContainerWithActivity(secondaryActivity)); + mSplitController.getContainerWithActivity(secondaryActivity), matchParentBounds); } - /** Asserts that the two given TaskFragments are in split. */ private void assertSplitPair(@NonNull TaskFragmentContainer primaryContainer, @NonNull TaskFragmentContainer secondaryContainer) { + assertSplitPair(primaryContainer, secondaryContainer, false /* matchParentBounds*/); + } + + /** Asserts that the two given TaskFragments are in split. */ + private void assertSplitPair(@NonNull TaskFragmentContainer primaryContainer, + @NonNull TaskFragmentContainer secondaryContainer, boolean matchParentBounds) { assertNotNull(primaryContainer); assertNotNull(secondaryContainer); assertNotNull(mSplitController.getActiveSplitForContainers(primaryContainer, secondaryContainer)); if (primaryContainer.mInfo != null) { - assertTrue(primaryContainer.areLastRequestedBoundsEqual( - getSplitBounds(true /* isPrimary */))); + final Rect primaryBounds = matchParentBounds ? new Rect() + : getSplitBounds(true /* isPrimary */); + assertTrue(primaryContainer.areLastRequestedBoundsEqual(primaryBounds)); assertTrue(primaryContainer.isLastRequestedWindowingModeEqual( WINDOWING_MODE_MULTI_WINDOW)); } if (secondaryContainer.mInfo != null) { - assertTrue(secondaryContainer.areLastRequestedBoundsEqual( - getSplitBounds(false /* isPrimary */))); + final Rect secondaryBounds = matchParentBounds ? new Rect() + : getSplitBounds(false /* isPrimary */); + assertTrue(secondaryContainer.areLastRequestedBoundsEqual(secondaryBounds)); assertTrue(secondaryContainer.isLastRequestedWindowingModeEqual( WINDOWING_MODE_MULTI_WINDOW)); } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java index 906e9904566f..acc398a27baf 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java @@ -18,25 +18,44 @@ package androidx.window.extensions.embedding; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.getSplitBounds; +import static androidx.window.extensions.embedding.SplitPresenter.POSITION_END; +import static androidx.window.extensions.embedding.SplitPresenter.POSITION_FILL; +import static androidx.window.extensions.embedding.SplitPresenter.POSITION_START; +import static androidx.window.extensions.embedding.SplitPresenter.getBoundsForPosition; +import static androidx.window.extensions.embedding.SplitPresenter.getMinDimensions; +import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSideBySide; + import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import android.app.Activity; +import android.content.Intent; +import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; +import android.util.Pair; +import android.util.Size; import android.window.TaskFragmentInfo; import android.window.WindowContainerTransaction; +import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -56,8 +75,6 @@ import org.mockito.MockitoAnnotations; @SmallTest @RunWith(AndroidJUnit4.class) public class SplitPresenterTest { - private static final int TASK_ID = 10; - private static final Rect TASK_BOUNDS = new Rect(0, 0, 600, 1200); @Mock private Activity mActivity; @@ -77,11 +94,7 @@ public class SplitPresenterTest { mPresenter = mController.mPresenter; spyOn(mController); spyOn(mPresenter); - final Configuration activityConfig = new Configuration(); - activityConfig.windowConfiguration.setBounds(TASK_BOUNDS); - activityConfig.windowConfiguration.setMaxBounds(TASK_BOUNDS); - doReturn(mActivityResources).when(mActivity).getResources(); - doReturn(activityConfig).when(mActivityResources).getConfiguration(); + mActivity = createMockActivity(); } @Test @@ -129,4 +142,67 @@ public class SplitPresenterTest { verify(mTransaction, never()).setWindowingMode(any(), anyInt()); } + + @Test + public void testGetMinDimensionsForIntent() { + final Intent intent = new Intent(ApplicationProvider.getApplicationContext(), + MinimumDimensionActivity.class); + assertEquals(new Size(600, 1200), getMinDimensions(intent)); + } + + @Test + public void testShouldShowSideBySide() { + Activity secondaryActivity = createMockActivity(); + final SplitRule splitRule = createSplitRule(mActivity, secondaryActivity); + + assertTrue(shouldShowSideBySide(TASK_BOUNDS, splitRule)); + + // Set minDimensions of primary container to larger than primary bounds. + final Rect primaryBounds = getSplitBounds(true /* isPrimary */); + Pair<Size, Size> minDimensionsPair = new Pair<>( + new Size(primaryBounds.width() + 1, primaryBounds.height() + 1), null); + + assertFalse(shouldShowSideBySide(TASK_BOUNDS, splitRule, minDimensionsPair)); + } + + @Test + public void testGetBoundsForPosition() { + Activity secondaryActivity = createMockActivity(); + final SplitRule splitRule = createSplitRule(mActivity, secondaryActivity); + final Rect primaryBounds = getSplitBounds(true /* isPrimary */); + final Rect secondaryBounds = getSplitBounds(false /* isPrimary */); + + assertEquals("Primary bounds must be reported.", + primaryBounds, + getBoundsForPosition(POSITION_START, TASK_BOUNDS, splitRule, + mActivity, null /* miniDimensionsPair */)); + + assertEquals("Secondary bounds must be reported.", + secondaryBounds, + getBoundsForPosition(POSITION_END, TASK_BOUNDS, splitRule, + mActivity, null /* miniDimensionsPair */)); + assertEquals("Task bounds must be reported.", + new Rect(), + getBoundsForPosition(POSITION_FILL, TASK_BOUNDS, splitRule, + mActivity, null /* miniDimensionsPair */)); + + Pair<Size, Size> minDimensionsPair = new Pair<>( + new Size(primaryBounds.width() + 1, primaryBounds.height() + 1), null); + + assertEquals("Fullscreen bounds must be reported because of min dimensions.", + new Rect(), + getBoundsForPosition(POSITION_START, TASK_BOUNDS, + splitRule, mActivity, minDimensionsPair)); + } + + private Activity createMockActivity() { + final Activity activity = mock(Activity.class); + final Configuration activityConfig = new Configuration(); + activityConfig.windowConfiguration.setBounds(TASK_BOUNDS); + activityConfig.windowConfiguration.setMaxBounds(TASK_BOUNDS); + doReturn(mActivityResources).when(activity).getResources(); + doReturn(activityConfig).when(mActivityResources).getConfiguration(); + doReturn(new ActivityInfo()).when(activity).getActivityInfo(); + return activity; + } } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java index ebe202db4e54..dd67e48ef353 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java @@ -22,6 +22,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 androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; @@ -53,9 +56,6 @@ import org.mockito.MockitoAnnotations; @SmallTest @RunWith(AndroidJUnit4.class) public class TaskContainerTest { - private static final int TASK_ID = 10; - private static final Rect TASK_BOUNDS = new Rect(0, 0, 600, 1200); - @Mock private SplitController mController; diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java index af3ad70c04db..d31342bfb309 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java @@ -16,6 +16,8 @@ package androidx.window.extensions.embedding; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID; + import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static org.mockito.ArgumentMatchers.anyInt; @@ -43,8 +45,6 @@ import org.mockito.MockitoAnnotations; @SmallTest @RunWith(AndroidJUnit4.class) public class TaskFragmentAnimationControllerTest { - private static final int TASK_ID = 10; - @Mock private TaskFragmentOrganizer mOrganizer; private TaskFragmentAnimationController mAnimationController; diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java index fcbd8a3ac020..28c2773e25cb 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java @@ -16,6 +16,9 @@ package androidx.window.extensions.embedding; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo; + import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static org.junit.Assert.assertEquals; @@ -30,17 +33,13 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; -import android.annotation.NonNull; import android.app.Activity; import android.content.Intent; -import android.content.res.Configuration; -import android.graphics.Point; import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.platform.test.annotations.Presubmit; import android.window.TaskFragmentInfo; -import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -55,7 +54,6 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; -import java.util.Collections; import java.util.List; /** @@ -68,8 +66,6 @@ import java.util.List; @SmallTest @RunWith(AndroidJUnit4.class) public class TaskFragmentContainerTest { - private static final int TASK_ID = 10; - @Mock private SplitPresenter mPresenter; @Mock @@ -311,18 +307,4 @@ public class TaskFragmentContainerTest { doReturn(activity).when(mController).getActivity(activityToken); return activity; } - - /** Creates a mock TaskFragmentInfo for the given TaskFragment. */ - private TaskFragmentInfo createMockTaskFragmentInfo(@NonNull TaskFragmentContainer container, - @NonNull Activity activity) { - return new TaskFragmentInfo(container.getTaskFragmentToken(), - mock(WindowContainerToken.class), - new Configuration(), - 1, - true /* isVisible */, - Collections.singletonList(activity.getActivityToken()), - new Point(), - false /* isTaskClearedForReuse */, - false /* isTaskFragmentClearedForPip */); - } } diff --git a/libs/WindowManager/Shell/res/color/decor_button_dark_color.xml b/libs/WindowManager/Shell/res/color/decor_button_dark_color.xml new file mode 100644 index 000000000000..bf325bd84c00 --- /dev/null +++ b/libs/WindowManager/Shell/res/color/decor_button_dark_color.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<selector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + <item app:state_task_focused="true" android:color="#FF000000" /> + <item android:color="#33000000" /> +</selector> diff --git a/libs/WindowManager/Shell/res/color/decor_button_light_color.xml b/libs/WindowManager/Shell/res/color/decor_button_light_color.xml new file mode 100644 index 000000000000..2e48bca7786a --- /dev/null +++ b/libs/WindowManager/Shell/res/color/decor_button_light_color.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<selector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + <item app:state_task_focused="true" android:color="#FFFFFFFF" /> + <item android:color="#33FFFFFF" /> +</selector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/color/decor_caption_title_color.xml b/libs/WindowManager/Shell/res/color/decor_caption_title_color.xml new file mode 100644 index 000000000000..1ecc13e4da38 --- /dev/null +++ b/libs/WindowManager/Shell/res/color/decor_caption_title_color.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<selector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + <!-- Fading the to 85% blackness --> + <item app:state_task_focused="true" android:color="#D8D8D8" /> + <!-- Fading the to 95% blackness --> + <item android:color="#F2F2F2" /> +</selector> diff --git a/libs/WindowManager/Shell/res/color/taskbar_background.xml b/libs/WindowManager/Shell/res/color/taskbar_background.xml index 329e5b9b31a0..b3d260299106 100644 --- a/libs/WindowManager/Shell/res/color/taskbar_background.xml +++ b/libs/WindowManager/Shell/res/color/taskbar_background.xml @@ -14,6 +14,7 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> +<!-- Should be the same as in packages/apps/Launcher3/res/color-v31/taskbar_background.xml --> <selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:color="@android:color/system_neutral1_500" android:lStar="35" /> + <item android:color="@android:color/system_neutral1_500" android:lStar="15" /> </selector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/color/unfold_transition_background.xml b/libs/WindowManager/Shell/res/color/unfold_transition_background.xml deleted file mode 100644 index 63289a3f75d9..000000000000 --- a/libs/WindowManager/Shell/res/color/unfold_transition_background.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2021 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <!-- Matches taskbar color --> - <item android:color="@android:color/system_neutral2_500" android:lStar="35" /> -</selector> diff --git a/libs/WindowManager/Shell/res/drawable/decor_caption_title.xml b/libs/WindowManager/Shell/res/drawable/decor_caption_title.xml new file mode 100644 index 000000000000..8207365a737d --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/decor_caption_title.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<shape android:shape="rectangle" + android:tintMode="multiply" + android:tint="@color/decor_caption_title_color" + xmlns:android="http://schemas.android.com/apk/res/android"> + <solid android:color="?android:attr/colorPrimary" /> +</shape> diff --git a/libs/WindowManager/Shell/res/drawable/decor_close_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_close_button_dark.xml new file mode 100644 index 000000000000..f2f1a1d55dee --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/decor_close_button_dark.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="32.0dp" + android:height="32.0dp" + android:viewportWidth="32.0" + android:viewportHeight="32.0" + android:tint="@color/decor_button_dark_color" + > + <group android:scaleX="0.5" + android:scaleY="0.5" + android:translateX="8.0" + android:translateY="8.0" > + <path + android:fillColor="@android:color/white" + android:pathData="M6.9,4.0l-2.9,2.9 9.1,9.1 -9.1,9.200001 2.9,2.799999 9.1,-9.1 9.1,9.1 2.9,-2.799999 -9.1,-9.200001 9.1,-9.1 -2.9,-2.9 -9.1,9.2z"/> + </group> +</vector> diff --git a/libs/WindowManager/Shell/res/drawable/decor_maximize_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_maximize_button_dark.xml new file mode 100644 index 000000000000..ab4e29ac97e5 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/decor_maximize_button_dark.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="32.0dp" + android:height="32.0dp" + android:viewportWidth="32.0" + android:viewportHeight="32.0" + android:tint="@color/decor_button_dark_color"> + <group android:scaleX="0.5" + android:scaleY="0.5" + android:translateX="8.0" + android:translateY="8.0" > + <path + android:fillColor="@android:color/white" + android:pathData="M2.0,4.0l0.0,16.0l28.0,0.0L30.0,4.0L2.0,4.0zM26.0,16.0L6.0,16.0L6.0,8.0l20.0,0.0L26.0,16.0z"/> + <path + android:fillColor="@android:color/white" + android:pathData="M2.0,24.0l28.0,0.0l0.0,4.0l-28.0,0.0z"/> + </group> +</vector> + + diff --git a/libs/WindowManager/Shell/res/layout/bubble_overflow_container.xml b/libs/WindowManager/Shell/res/layout/bubble_overflow_container.xml index cb516cdbe49b..df5985c605d1 100644 --- a/libs/WindowManager/Shell/res/layout/bubble_overflow_container.xml +++ b/libs/WindowManager/Shell/res/layout/bubble_overflow_container.xml @@ -30,7 +30,8 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="center_horizontal" - android:gravity="center"/> + android:gravity="center" + android:clipChildren="false"/> <LinearLayout android:id="@+id/bubble_overflow_empty_state" diff --git a/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml b/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml new file mode 100644 index 000000000000..a112f1933dd1 --- /dev/null +++ b/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<com.android.wm.shell.windowdecor.WindowDecorLinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/caption" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="end" + android:background="@drawable/decor_caption_title"> + <Button + android:id="@+id/maximize_window" + android:layout_width="32dp" + android:layout_height="32dp" + android:layout_margin="5dp" + android:padding="4dp" + android:layout_gravity="center_vertical|end" + android:contentDescription="@string/maximize_button_text" + android:background="@drawable/decor_maximize_button_dark" + android:duplicateParentState="true"/> + <Button + android:id="@+id/close_window" + android:layout_width="32dp" + android:layout_height="32dp" + android:layout_margin="5dp" + android:padding="4dp" + android:layout_gravity="center_vertical|end" + android:contentDescription="@string/close_button_text" + android:background="@drawable/decor_close_button_dark" + android:duplicateParentState="true"/> +</com.android.wm.shell.windowdecor.WindowDecorLinearLayout> + diff --git a/libs/WindowManager/Shell/res/values-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml index 2476f65c7e5b..e7bc74a1744f 100644 --- a/libs/WindowManager/Shell/res/values-af/strings.xml +++ b/libs/WindowManager/Shell/res/values-af/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Draai jou toestel om dit volskerm te maak"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dubbeltik langs ’n program om dit te herposisioneer"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Het dit"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Vou uit vir meer inligting."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maksimeer"</string> + <string name="close_button_text" msgid="2913281996024033299">"Maak toe"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml index f0c391cd6b99..8b2a89238584 100644 --- a/libs/WindowManager/Shell/res/values-am/strings.xml +++ b/libs/WindowManager/Shell/res/values-am/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"ወደ የሙሉ ገጽ ዕይታ ለመሄድ መሣሪያዎን ያሽከርክሩት"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"ቦታውን ለመቀየር ከመተግበሪያው ቀጥሎ ላይ ሁለቴ መታ ያድርጉ"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"ገባኝ"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ለተጨማሪ መረጃ ይዘርጉ።"</string> + <string name="maximize_button_text" msgid="1650859196290301963">"አስፋ"</string> + <string name="close_button_text" msgid="2913281996024033299">"ዝጋ"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml index aa4b3b704110..98b25c8ab087 100644 --- a/libs/WindowManager/Shell/res/values-ar/strings.xml +++ b/libs/WindowManager/Shell/res/values-ar/strings.xml @@ -81,4 +81,9 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"قم بتدوير الشاشة للانتقال إلى وضع ملء الشاشة."</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"انقر مرتين بجانب التطبيق لتغيير موضعه."</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"حسنًا"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"التوسيع للحصول على مزيد من المعلومات"</string> + <!-- no translation found for maximize_button_text (1650859196290301963) --> + <skip /> + <!-- no translation found for close_button_text (2913281996024033299) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml index 985d3b9b96fd..2520267f46d2 100644 --- a/libs/WindowManager/Shell/res/values-as/strings.xml +++ b/libs/WindowManager/Shell/res/values-as/strings.xml @@ -81,4 +81,9 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"পূৰ্ণ স্ক্ৰীনলৈ যাবলৈ আপোনাৰ ডিভাইচটো ঘূৰাওক"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"এপ্টোৰ স্থান সলনি কৰিবলৈ ইয়াৰ কাষত দুবাৰ টিপক"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"বুজি পালোঁ"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"অধিক তথ্যৰ বাবে বিস্তাৰ কৰক।"</string> + <!-- no translation found for maximize_button_text (1650859196290301963) --> + <skip /> + <!-- no translation found for close_button_text (2913281996024033299) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-az/strings.xml b/libs/WindowManager/Shell/res/values-az/strings.xml index 8cd9b7a635ab..51b60774b2fd 100644 --- a/libs/WindowManager/Shell/res/values-az/strings.xml +++ b/libs/WindowManager/Shell/res/values-az/strings.xml @@ -81,4 +81,9 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Tam ekrana keçmək üçün cihazınızı fırladın"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Tətbiqin yerini dəyişmək üçün yanına iki dəfə toxunun"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Anladım"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Ətraflı məlumat üçün genişləndirin."</string> + <!-- no translation found for maximize_button_text (1650859196290301963) --> + <skip /> + <!-- no translation found for close_button_text (2913281996024033299) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml index 49524c608543..b34920e0568f 100644 --- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml +++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rotirajte uređaj za prikaz preko celog ekrana"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dvaput dodirnite pored aplikacije da biste promenili njenu poziciju"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Važi"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Proširite za još informacija."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Uvećajte"</string> + <string name="close_button_text" msgid="2913281996024033299">"Zatvorite"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml index 1767e0d66241..525e46ff371e 100644 --- a/libs/WindowManager/Shell/res/values-be/strings.xml +++ b/libs/WindowManager/Shell/res/values-be/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Каб перайсці ў поўнаэкранны рэжым, павярніце прыладу"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Двойчы націсніце побач з праграмай, каб перамясціць яе"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Зразумела"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Разгарнуць для дадатковай інфармацыі"</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Разгарнуць"</string> + <string name="close_button_text" msgid="2913281996024033299">"Закрыць"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml index c22fb86a4d4d..c44afcd74c01 100644 --- a/libs/WindowManager/Shell/res/values-bg/strings.xml +++ b/libs/WindowManager/Shell/res/values-bg/strings.xml @@ -81,4 +81,9 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Завъртете екрана си, за да преминете в режим на цял екран"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Докоснете два пъти дадено приложение, за да промените позицията му"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Разбрах"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Разгъване за още информация."</string> + <!-- no translation found for maximize_button_text (1650859196290301963) --> + <skip /> + <!-- no translation found for close_button_text (2913281996024033299) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml index c0944e0584e6..0a8b0b746231 100644 --- a/libs/WindowManager/Shell/res/values-bn/strings.xml +++ b/libs/WindowManager/Shell/res/values-bn/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"\'ফুল স্ক্রিন\' মোডে যেতে ডিভাইস ঘোরান"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"কোনও অ্যাপের পাশে ডবল ট্যাপ করে সেটির জায়গা পরিবর্তন করুন"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"বুঝেছি"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"আরও তথ্যের জন্য বড় করুন।"</string> + <string name="maximize_button_text" msgid="1650859196290301963">"বড় করুন"</string> + <string name="close_button_text" msgid="2913281996024033299">"বন্ধ করুন"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml index ae01c641cc43..fbf39255be08 100644 --- a/libs/WindowManager/Shell/res/values-bs/strings.xml +++ b/libs/WindowManager/Shell/res/values-bs/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Zarotirajte uređaj da aktivirate prikaz preko cijelog ekrana"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dvaput dodirnite pored aplikacije da promijenite njen položaj"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Razumijem"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Proširite za više informacija."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maksimiziraj"</string> + <string name="close_button_text" msgid="2913281996024033299">"Zatvori"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml index 8a522b3e6397..3ea31cebe5ab 100644 --- a/libs/WindowManager/Shell/res/values-ca/strings.xml +++ b/libs/WindowManager/Shell/res/values-ca/strings.xml @@ -81,4 +81,9 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Gira el dispositiu per passar a pantalla completa"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Fes doble toc al costat d\'una aplicació per canviar-ne la posició"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Entesos"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Desplega per obtenir més informació."</string> + <!-- no translation found for maximize_button_text (1650859196290301963) --> + <skip /> + <!-- no translation found for close_button_text (2913281996024033299) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml index d0cf80aef38c..574ed2616490 100644 --- a/libs/WindowManager/Shell/res/values-cs/strings.xml +++ b/libs/WindowManager/Shell/res/values-cs/strings.xml @@ -81,4 +81,9 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Otočením zařízení přejděte do režimu celé obrazovky"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dvojitým klepnutím vedle aplikace změňte její umístění"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Rozbalením zobrazíte další informace."</string> + <!-- no translation found for maximize_button_text (1650859196290301963) --> + <skip /> + <!-- no translation found for close_button_text (2913281996024033299) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-da/strings.xml b/libs/WindowManager/Shell/res/values-da/strings.xml index bb81c10c6e1b..2fcd37a54bc6 100644 --- a/libs/WindowManager/Shell/res/values-da/strings.xml +++ b/libs/WindowManager/Shell/res/values-da/strings.xml @@ -81,4 +81,9 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Drej din enhed for at gå til fuld skærm"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Tryk to gange ud for en app for at ændre dens placering"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Udvid for at få flere oplysninger."</string> + <!-- no translation found for maximize_button_text (1650859196290301963) --> + <skip /> + <!-- no translation found for close_button_text (2913281996024033299) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml index c5d945a982ef..07075b591f73 100644 --- a/libs/WindowManager/Shell/res/values-de/strings.xml +++ b/libs/WindowManager/Shell/res/values-de/strings.xml @@ -81,4 +81,9 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Gerät drehen, um zum Vollbildmodus zu wechseln"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Neben einer App doppeltippen, um die Position zu ändern"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Ok"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Für weitere Informationen maximieren."</string> + <!-- no translation found for maximize_button_text (1650859196290301963) --> + <skip /> + <!-- no translation found for close_button_text (2913281996024033299) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml index 70f55058925c..0d9d3eab591b 100644 --- a/libs/WindowManager/Shell/res/values-el/strings.xml +++ b/libs/WindowManager/Shell/res/values-el/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Περιστρέψτε τη συσκευή σας για μετάβαση σε πλήρη οθόνη."</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Πατήστε δύο φορές δίπλα σε μια εφαρμογή για να αλλάξετε τη θέση της."</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Το κατάλαβα"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Ανάπτυξη για περισσότερες πληροφορίες."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Μεγιστοποίηση"</string> + <string name="close_button_text" msgid="2913281996024033299">"Κλείσιμο"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml index 0b5aefa5c72e..48525cd10749 100644 --- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rotate your device to go full screen"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Double-tap next to an app to reposition it"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expand for more information."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maximise"</string> + <string name="close_button_text" msgid="2913281996024033299">"Close"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml index 0b5aefa5c72e..48525cd10749 100644 --- a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rotate your device to go full screen"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Double-tap next to an app to reposition it"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expand for more information."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maximise"</string> + <string name="close_button_text" msgid="2913281996024033299">"Close"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml index 0b5aefa5c72e..48525cd10749 100644 --- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rotate your device to go full screen"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Double-tap next to an app to reposition it"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expand for more information."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maximise"</string> + <string name="close_button_text" msgid="2913281996024033299">"Close"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml index 0b5aefa5c72e..48525cd10749 100644 --- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rotate your device to go full screen"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Double-tap next to an app to reposition it"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expand for more information."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maximise"</string> + <string name="close_button_text" msgid="2913281996024033299">"Close"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml index 5c3d0f65374a..fb6db5f91a25 100644 --- a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rotate your device to go full screen"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Double-tap next to an app to reposition it"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expand for more information."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maximize"</string> + <string name="close_button_text" msgid="2913281996024033299">"Close"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml index e523ae53b0cc..3db353a0b4ea 100644 --- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml +++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rota el dispositivo para ver la pantalla completa"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Presiona dos veces junto a una app para cambiar su posición"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendido"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expande para obtener más información."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string> + <string name="close_button_text" msgid="2913281996024033299">"Cerrar"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml index 39990dc8cb0c..8f7c741b45e2 100644 --- a/libs/WindowManager/Shell/res/values-es/strings.xml +++ b/libs/WindowManager/Shell/res/values-es/strings.xml @@ -81,4 +81,9 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Gira el dispositivo para ir al modo de pantalla completa"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Toca dos veces junto a una aplicación para cambiar su posición"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendido"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Mostrar más información"</string> + <!-- no translation found for maximize_button_text (1650859196290301963) --> + <skip /> + <!-- no translation found for close_button_text (2913281996024033299) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml index a5f82a6452c4..0d75a5552fe4 100644 --- a/libs/WindowManager/Shell/res/values-et/strings.xml +++ b/libs/WindowManager/Shell/res/values-et/strings.xml @@ -81,4 +81,9 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Pöörake seadet, et aktiveerida täisekraanirežiim"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Topeltpuudutage rakenduse kõrval, et selle asendit muuta"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Selge"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Laiendage lisateabe saamiseks."</string> + <!-- no translation found for maximize_button_text (1650859196290301963) --> + <skip /> + <!-- no translation found for close_button_text (2913281996024033299) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml index 67b9a433dc03..71ccad59e71a 100644 --- a/libs/WindowManager/Shell/res/values-eu/strings.xml +++ b/libs/WindowManager/Shell/res/values-eu/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Pantaila osoko modua erabiltzeko, biratu gailua"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Aplikazioaren posizioa aldatzeko, sakatu birritan haren ondoko edozein toki"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Ados"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Informazio gehiago lortzeko, zabaldu hau."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maximizatu"</string> + <string name="close_button_text" msgid="2913281996024033299">"Itxi"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml index 761fb9ddeb2f..3e95b484d567 100644 --- a/libs/WindowManager/Shell/res/values-fa/strings.xml +++ b/libs/WindowManager/Shell/res/values-fa/strings.xml @@ -81,4 +81,9 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"برای رفتن به حالت تمام صفحه، دستگاهتان را بچرخانید"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"در کنار برنامه دوضربه بزنید تا جابهجا شود"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"متوجهام"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"برای اطلاعات بیشتر، گسترده کنید."</string> + <!-- no translation found for maximize_button_text (1650859196290301963) --> + <skip /> + <!-- no translation found for close_button_text (2913281996024033299) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-fi/strings.xml b/libs/WindowManager/Shell/res/values-fi/strings.xml index c809b4879e71..a1e5efbad7b6 100644 --- a/libs/WindowManager/Shell/res/values-fi/strings.xml +++ b/libs/WindowManager/Shell/res/values-fi/strings.xml @@ -81,4 +81,9 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Käännä laitetta, niin se siirtyy koko näytön tilaan"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Kaksoisnapauta sovellusta, jos haluat siirtää sitä"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Katso lisätietoja laajentamalla."</string> + <!-- no translation found for maximize_button_text (1650859196290301963) --> + <skip /> + <!-- no translation found for close_button_text (2913281996024033299) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml index 62b2bb65a603..c3f36e60bddc 100644 --- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml +++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml @@ -81,4 +81,9 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Faites pivoter votre appareil pour passer en plein écran"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Touchez deux fois à côté d\'une application pour la repositionner"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Développer pour en savoir plus."</string> + <!-- no translation found for maximize_button_text (1650859196290301963) --> + <skip /> + <!-- no translation found for close_button_text (2913281996024033299) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml index 07475055f03e..1365c22017cf 100644 --- a/libs/WindowManager/Shell/res/values-fr/strings.xml +++ b/libs/WindowManager/Shell/res/values-fr/strings.xml @@ -81,4 +81,9 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Faites pivoter l\'appareil pour passer en plein écran"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Appuyez deux fois à côté d\'une appli pour la repositionner"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Développez pour obtenir plus d\'informations"</string> + <!-- no translation found for maximize_button_text (1650859196290301963) --> + <skip /> + <!-- no translation found for close_button_text (2913281996024033299) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml index b8e039602243..19430577f181 100644 --- a/libs/WindowManager/Shell/res/values-gl/strings.xml +++ b/libs/WindowManager/Shell/res/values-gl/strings.xml @@ -81,4 +81,9 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Xira o dispositivo para ver o contido en pantalla completa"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Toca dúas veces a carón dunha aplicación para cambiala de posición"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendido"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Despregar para obter máis información."</string> + <!-- no translation found for maximize_button_text (1650859196290301963) --> + <skip /> + <!-- no translation found for close_button_text (2913281996024033299) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-gu/strings.xml b/libs/WindowManager/Shell/res/values-gu/strings.xml index deda2d755e20..8e9f086daa97 100644 --- a/libs/WindowManager/Shell/res/values-gu/strings.xml +++ b/libs/WindowManager/Shell/res/values-gu/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"પૂર્ણ સ્ક્રીન મોડ લાગુ કરવા માટે, તમારા ડિવાઇસને ફેરવો"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"કોઈ ઍપની જગ્યા બદલવા માટે, તેની બાજુમાં બે વાર ટૅપ કરો"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"સમજાઈ ગયું"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"વધુ માહિતી માટે મોટું કરો."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"મોટું કરો"</string> + <string name="close_button_text" msgid="2913281996024033299">"બંધ કરો"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml index a5fcb97d1418..478bfbaed62e 100644 --- a/libs/WindowManager/Shell/res/values-hi/strings.xml +++ b/libs/WindowManager/Shell/res/values-hi/strings.xml @@ -81,4 +81,9 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"फ़ुल स्क्रीन मोड में जाने के लिए, डिवाइस को घुमाएं"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"किसी ऐप्लिकेशन की जगह बदलने के लिए, उसके बगल में दो बार टैप करें"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"ठीक है"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ज़्यादा जानकारी के लिए बड़ा करें."</string> + <!-- no translation found for maximize_button_text (1650859196290301963) --> + <skip /> + <!-- no translation found for close_button_text (2913281996024033299) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml index 5ecc5585a6e9..1c4116d6cc8a 100644 --- a/libs/WindowManager/Shell/res/values-hr/strings.xml +++ b/libs/WindowManager/Shell/res/values-hr/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Zakrenite uređaj radi prikaza na cijelom zaslonu"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dvaput dodirnite pored aplikacije da biste joj promijenili položaj"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Shvaćam"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Proširite da biste saznali više."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maksimiziraj"</string> + <string name="close_button_text" msgid="2913281996024033299">"Zatvori"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-hu/strings.xml b/libs/WindowManager/Shell/res/values-hu/strings.xml index 2295250e2853..d749775a2160 100644 --- a/libs/WindowManager/Shell/res/values-hu/strings.xml +++ b/libs/WindowManager/Shell/res/values-hu/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"A teljes képernyős mód elindításához forgassa el az eszközt"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Koppintson duplán az alkalmazás mellett az áthelyezéséhez"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Értem"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Kibontással további információkhoz juthat."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Teljes méret"</string> + <string name="close_button_text" msgid="2913281996024033299">"Bezárás"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-hy/strings.xml b/libs/WindowManager/Shell/res/values-hy/strings.xml index 208936539094..4cc183de140a 100644 --- a/libs/WindowManager/Shell/res/values-hy/strings.xml +++ b/libs/WindowManager/Shell/res/values-hy/strings.xml @@ -81,4 +81,9 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Պտտեք սարքը՝ լիաէկրան ռեժիմին անցնելու համար"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Կրկնակի հպեք հավելվածի կողքին՝ այն տեղափոխելու համար"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Եղավ"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Ծավալեք՝ ավելին իմանալու համար։"</string> + <!-- no translation found for maximize_button_text (1650859196290301963) --> + <skip /> + <!-- no translation found for close_button_text (2913281996024033299) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml index 1b46b2fe2570..af8a8ece437d 100644 --- a/libs/WindowManager/Shell/res/values-in/strings.xml +++ b/libs/WindowManager/Shell/res/values-in/strings.xml @@ -81,4 +81,9 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Putar perangkat untuk tampilan layar penuh"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Ketuk dua kali di samping aplikasi untuk mengubah posisinya"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Oke"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Luaskan untuk melihat informasi selengkapnya."</string> + <!-- no translation found for maximize_button_text (1650859196290301963) --> + <skip /> + <!-- no translation found for close_button_text (2913281996024033299) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml index a201c95137f3..c22b88138714 100644 --- a/libs/WindowManager/Shell/res/values-is/strings.xml +++ b/libs/WindowManager/Shell/res/values-is/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Snúðu tækinu til að nota allan skjáinn"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Ýttu tvisvar við hlið forritsins til að færa það"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Ég skil"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Stækka til að sjá frekari upplýsingar."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Stækka"</string> + <string name="close_button_text" msgid="2913281996024033299">"Loka"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-it/strings.xml b/libs/WindowManager/Shell/res/values-it/strings.xml index 7157ed088d30..40a0e176ca29 100644 --- a/libs/WindowManager/Shell/res/values-it/strings.xml +++ b/libs/WindowManager/Shell/res/values-it/strings.xml @@ -81,4 +81,9 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Ruota il dispositivo per passare alla modalità a schermo intero"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Tocca due volte accanto a un\'app per riposizionarla"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Espandi per avere ulteriori informazioni."</string> + <!-- no translation found for maximize_button_text (1650859196290301963) --> + <skip /> + <!-- no translation found for close_button_text (2913281996024033299) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml index 52a6b0676222..3a7381164e08 100644 --- a/libs/WindowManager/Shell/res/values-iw/strings.xml +++ b/libs/WindowManager/Shell/res/values-iw/strings.xml @@ -81,4 +81,9 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"מסובבים את המכשיר כדי לעבור לתצוגה במסך מלא"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"מקישים הקשה כפולה ליד אפליקציה כדי למקם אותה מחדש"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"הבנתי"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"מרחיבים כדי לקבל מידע נוסף."</string> + <!-- no translation found for maximize_button_text (1650859196290301963) --> + <skip /> + <!-- no translation found for close_button_text (2913281996024033299) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml index 5a25c24ba034..4d09b202ca06 100644 --- a/libs/WindowManager/Shell/res/values-ja/strings.xml +++ b/libs/WindowManager/Shell/res/values-ja/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"全画面表示にするにはデバイスを回転させてください"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"位置を変えるにはアプリの横をダブルタップしてください"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"開くと詳細が表示されます。"</string> + <string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string> + <string name="close_button_text" msgid="2913281996024033299">"閉じる"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml index bff86fa6ffe2..8cbc3d893bd8 100644 --- a/libs/WindowManager/Shell/res/values-ka/strings.xml +++ b/libs/WindowManager/Shell/res/values-ka/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"მოატრიალეთ თქვენი მოწყობილობა სრული ეკრანის გასაშლელად"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"ორმაგად შეეხეთ აპის გვერდითა სივრცეს, რათა ის სხვაგან გადაიტანოთ"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"გასაგებია"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"დამატებითი ინფორმაციისთვის გააფართოეთ."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"მაქსიმალურად გაშლა"</string> + <string name="close_button_text" msgid="2913281996024033299">"დახურვა"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml index f57f3f581c85..a1585a454322 100644 --- a/libs/WindowManager/Shell/res/values-kk/strings.xml +++ b/libs/WindowManager/Shell/res/values-kk/strings.xml @@ -81,4 +81,9 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Толық экранға ауысу үшін құрылғыңызды бұрыңыз."</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Қолданбаның орнын ауыстыру үшін жанынан екі рет түртіңіз."</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Түсінікті"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Толығырақ ақпарат алу үшін терезені жайыңыз."</string> + <!-- no translation found for maximize_button_text (1650859196290301963) --> + <skip /> + <!-- no translation found for close_button_text (2913281996024033299) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml index 5c04f881fe0e..1e72d4fe145f 100644 --- a/libs/WindowManager/Shell/res/values-km/strings.xml +++ b/libs/WindowManager/Shell/res/values-km/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"បង្វិលឧបករណ៍របស់អ្នក ដើម្បីចូលប្រើអេក្រង់ពេញ"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"ចុចពីរដងនៅជាប់កម្មវិធីណាមួយ ដើម្បីប្ដូរទីតាំងកម្មវិធីនោះ"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"យល់ហើយ"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ពង្រីកដើម្បីទទួលបានព័ត៌មានបន្ថែម។"</string> + <string name="maximize_button_text" msgid="1650859196290301963">"ពង្រីក"</string> + <string name="close_button_text" msgid="2913281996024033299">"បិទ"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml index e91383caa009..24d0c8257b7b 100644 --- a/libs/WindowManager/Shell/res/values-kn/strings.xml +++ b/libs/WindowManager/Shell/res/values-kn/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"ಪೂರ್ಣ ಸ್ಕ್ರೀನ್ಗೆ ಹೋಗಲು ನಿಮ್ಮ ಸಾಧನವನ್ನು ತಿರುಗಿಸಿ"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"ಆ್ಯಪ್ ಒಂದರ ಸ್ಥಾನವನ್ನು ಬದಲಾಯಿಸಲು ಅದರ ಪಕ್ಕದಲ್ಲಿ ಡಬಲ್-ಟ್ಯಾಪ್ ಮಾಡಿ"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"ಸರಿ"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ಇನ್ನಷ್ಟು ಮಾಹಿತಿಗಾಗಿ ವಿಸ್ತೃತಗೊಳಿಸಿ."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"ಹಿಗ್ಗಿಸಿ"</string> + <string name="close_button_text" msgid="2913281996024033299">"ಮುಚ್ಚಿರಿ"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml index 104ba3f22c96..6f516e64ca02 100644 --- a/libs/WindowManager/Shell/res/values-ko/strings.xml +++ b/libs/WindowManager/Shell/res/values-ko/strings.xml @@ -81,4 +81,9 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"전체 화면 모드로 전환하려면 기기를 회전하세요."</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"앱 위치를 조정하려면 앱 옆을 두 번 탭하세요."</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"확인"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"추가 정보는 펼쳐서 확인하세요."</string> + <!-- no translation found for maximize_button_text (1650859196290301963) --> + <skip /> + <!-- no translation found for close_button_text (2913281996024033299) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml index 8203622a33fc..94ea9fb9f950 100644 --- a/libs/WindowManager/Shell/res/values-ky/strings.xml +++ b/libs/WindowManager/Shell/res/values-ky/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Толук экран режимине өтүү үчүн түзмөктү буруңуз"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Колдонмонун ракурсун өзгөртүү үчүн анын тушуна эки жолу басыңыз"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Түшүндүм"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Толук маалымат алуу үчүн жайып көрүңүз."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Чоңойтуу"</string> + <string name="close_button_text" msgid="2913281996024033299">"Жабуу"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-lo/strings.xml b/libs/WindowManager/Shell/res/values-lo/strings.xml index 24396786f9d8..a7ad006b0347 100644 --- a/libs/WindowManager/Shell/res/values-lo/strings.xml +++ b/libs/WindowManager/Shell/res/values-lo/strings.xml @@ -81,4 +81,9 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"ໝຸນອຸປະກອນຂອງທ່ານເພື່ອໃຊ້ແບບເຕັມຈໍ"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"ແຕະສອງເທື່ອໃສ່ຖັດຈາກແອັບໃດໜຶ່ງເພື່ອຈັດຕຳແໜ່ງຂອງມັນຄືນໃໝ່"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"ເຂົ້າໃຈແລ້ວ"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ຂະຫຍາຍເພື່ອເບິ່ງຂໍ້ມູນເພີ່ມເຕີມ."</string> + <!-- no translation found for maximize_button_text (1650859196290301963) --> + <skip /> + <!-- no translation found for close_button_text (2913281996024033299) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml index e2ae643ad308..3002492989aa 100644 --- a/libs/WindowManager/Shell/res/values-lt/strings.xml +++ b/libs/WindowManager/Shell/res/values-lt/strings.xml @@ -81,4 +81,9 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Pasukite įrenginį, kad įjungtumėte viso ekrano režimą"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dukart palieskite šalia programos, kad pakeistumėte jos poziciją"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Supratau"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Išskleiskite, jei reikia daugiau informacijos."</string> + <!-- no translation found for maximize_button_text (1650859196290301963) --> + <skip /> + <!-- no translation found for close_button_text (2913281996024033299) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-lv/strings.xml b/libs/WindowManager/Shell/res/values-lv/strings.xml index a77160bc262a..cce1f5feafb7 100644 --- a/libs/WindowManager/Shell/res/values-lv/strings.xml +++ b/libs/WindowManager/Shell/res/values-lv/strings.xml @@ -81,4 +81,9 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Pagrieziet ierīci, lai aktivizētu pilnekrāna režīmu"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Veiciet dubultskārienu blakus lietotnei, lai manītu tās pozīciju"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Labi"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Izvērsiet, lai iegūtu plašāku informāciju."</string> + <!-- no translation found for maximize_button_text (1650859196290301963) --> + <skip /> + <!-- no translation found for close_button_text (2913281996024033299) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-mk/strings.xml b/libs/WindowManager/Shell/res/values-mk/strings.xml index bac0c9eee4c2..56e9b406fe8d 100644 --- a/libs/WindowManager/Shell/res/values-mk/strings.xml +++ b/libs/WindowManager/Shell/res/values-mk/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Ротирајте го уредот за да отворите на цел екран"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Допрете двапати до некоја апликација за да ја преместите"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Сфатив"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Проширете за повеќе информации."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Зголеми"</string> + <string name="close_button_text" msgid="2913281996024033299">"Затвори"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ml/strings.xml b/libs/WindowManager/Shell/res/values-ml/strings.xml index de0f837fcd3f..88e49e78d925 100644 --- a/libs/WindowManager/Shell/res/values-ml/strings.xml +++ b/libs/WindowManager/Shell/res/values-ml/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"പൂർണ്ണ സ്ക്രീനിലേക്ക് മാറാൻ ഈ ഉപകരണം റൊട്ടേറ്റ് ചെയ്യുക"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"ഒരു ആപ്പിന്റെ സ്ഥാനം മാറ്റാൻ, അതിന് തൊട്ടടുത്ത് ഡബിൾ ടാപ്പ് ചെയ്യുക"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"മനസ്സിലായി"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"കൂടുതൽ വിവരങ്ങൾക്ക് വികസിപ്പിക്കുക."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"വലുതാക്കുക"</string> + <string name="close_button_text" msgid="2913281996024033299">"അടയ്ക്കുക"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml index 1205306e0833..7a6f62577e63 100644 --- a/libs/WindowManager/Shell/res/values-mn/strings.xml +++ b/libs/WindowManager/Shell/res/values-mn/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Төхөөрөмжөө бүтэн дэлгэцээр үзэхийн тулд эргүүлнэ"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Аппыг дахин байрлуулахын тулд хажууд нь хоёр товшино"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Ойлголоо"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Нэмэлт мэдээлэл авах бол дэлгэнэ үү."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Томруулах"</string> + <string name="close_button_text" msgid="2913281996024033299">"Хаах"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml index c91d06fdf3d5..c2a947542f0a 100644 --- a/libs/WindowManager/Shell/res/values-mr/strings.xml +++ b/libs/WindowManager/Shell/res/values-mr/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"फुल स्क्रीन करण्यासाठी, तुमचे डिव्हाइस फिरवा"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"ॲपची स्थिती पुन्हा बदलण्यासाठी, त्याच्या शेजारी दोनदा टॅप करा"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"समजले"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"अधिक माहितीसाठी विस्तार करा."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"मोठे करा"</string> + <string name="close_button_text" msgid="2913281996024033299">"बंद करा"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml index 652a9919d163..35512c1c75bb 100644 --- a/libs/WindowManager/Shell/res/values-ms/strings.xml +++ b/libs/WindowManager/Shell/res/values-ms/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Putar peranti anda untuk beralih ke skrin penuh"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Ketik dua kali bersebelahan apl untuk menempatkan semula apl"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Kembangkan untuk mendapatkan maklumat lanjut."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maksimumkan"</string> + <string name="close_button_text" msgid="2913281996024033299">"Tutup"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml index 15d182c6451e..5aea49f59e2f 100644 --- a/libs/WindowManager/Shell/res/values-my/strings.xml +++ b/libs/WindowManager/Shell/res/values-my/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"ဖန်သားပြင်အပြည့်လုပ်ရန် သင့်စက်ကို လှည့်နိုင်သည်"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"အက်ပ်နေရာပြန်ချရန် ၎င်းဘေးတွင် နှစ်ချက်တို့နိုင်သည်"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"ရပြီ"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"နောက်ထပ်အချက်အလက်များအတွက် ချဲ့နိုင်သည်။"</string> + <string name="maximize_button_text" msgid="1650859196290301963">"ချဲ့ရန်"</string> + <string name="close_button_text" msgid="2913281996024033299">"ပိတ်ရန်"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml index 2f2fea6eb833..ff987ea2b7b7 100644 --- a/libs/WindowManager/Shell/res/values-nb/strings.xml +++ b/libs/WindowManager/Shell/res/values-nb/strings.xml @@ -81,4 +81,9 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Roter enheten for å starte fullskjerm"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dobbelttrykk ved siden av en app for å flytte den"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Greit"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Vis for å få mer informasjon."</string> + <!-- no translation found for maximize_button_text (1650859196290301963) --> + <skip /> + <!-- no translation found for close_button_text (2913281996024033299) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml index 8dfec88cc29d..831f6e2ef829 100644 --- a/libs/WindowManager/Shell/res/values-ne/strings.xml +++ b/libs/WindowManager/Shell/res/values-ne/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"तपाईं फुल स्क्रिन मोड हेर्न चाहनुहुन्छ भने आफ्नो डिभाइस रोटेट गर्नुहोस्"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"तपाईं जुन एपको स्थिति मिलाउन चाहनुहुन्छ सोही एपको छेउमा डबल ट्याप गर्नुहोस्"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"बुझेँ"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"थप जानकारी प्राप्त गर्न चाहनुहुन्छ भने एक्स्पान्ड गर्नुहोस्।"</string> + <string name="maximize_button_text" msgid="1650859196290301963">"ठुलो बनाउनुहोस्"</string> + <string name="close_button_text" msgid="2913281996024033299">"बन्द गर्नुहोस्"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml index 8468b04c66da..affdd0e2a223 100644 --- a/libs/WindowManager/Shell/res/values-nl/strings.xml +++ b/libs/WindowManager/Shell/res/values-nl/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Draai je apparaat om naar volledig scherm te schakelen"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dubbeltik naast een app om deze opnieuw te positioneren"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Uitvouwen voor meer informatie."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maximaliseren"</string> + <string name="close_button_text" msgid="2913281996024033299">"Sluiten"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-or/strings.xml b/libs/WindowManager/Shell/res/values-or/strings.xml index a8d8448edf99..f150cb9a9160 100644 --- a/libs/WindowManager/Shell/res/values-or/strings.xml +++ b/libs/WindowManager/Shell/res/values-or/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"ପୂର୍ଣ୍ଣ-ସ୍କ୍ରିନ ବ୍ୟବହାର କରିବାକୁ ଆପଣଙ୍କ ଡିଭାଇସକୁ ରୋଟେଟ କରନ୍ତୁ"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"ଏକ ଆପକୁ ରିପୋଜିସନ କରିବା ପାଇଁ ଏହା ପାଖରେ ଦୁଇଥର-ଟାପ କରନ୍ତୁ"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"ବୁଝିଗଲି"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ଅଧିକ ସୂଚନା ପାଇଁ ବିସ୍ତାର କରନ୍ତୁ।"</string> + <string name="maximize_button_text" msgid="1650859196290301963">"ବଡ଼ କରନ୍ତୁ"</string> + <string name="close_button_text" msgid="2913281996024033299">"ବନ୍ଦ କରନ୍ତୁ"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml index f99176cb682d..af91b36364f5 100644 --- a/libs/WindowManager/Shell/res/values-pa/strings.xml +++ b/libs/WindowManager/Shell/res/values-pa/strings.xml @@ -81,4 +81,9 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"ਪੂਰੀ-ਸਕ੍ਰੀਨ ਮੋਡ \'ਤੇ ਜਾਣ ਲਈ ਆਪਣੇ ਡੀਵਾਈਸ ਨੂੰ ਘੁਮਾਓ"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"ਕਿਸੇ ਐਪ ਦੀ ਜਗ੍ਹਾ ਬਦਲਣ ਲਈ ਉਸ ਦੇ ਅੱਗੇ ਡਬਲ ਟੈਪ ਕਰੋ"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"ਸਮਝ ਲਿਆ"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ਹੋਰ ਜਾਣਕਾਰੀ ਲਈ ਵਿਸਤਾਰ ਕਰੋ।"</string> + <!-- no translation found for maximize_button_text (1650859196290301963) --> + <skip /> + <!-- no translation found for close_button_text (2913281996024033299) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml index f2147c04d335..8c6a4d55d269 100644 --- a/libs/WindowManager/Shell/res/values-pl/strings.xml +++ b/libs/WindowManager/Shell/res/values-pl/strings.xml @@ -81,4 +81,9 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Obróć urządzenie, aby przejść do pełnego ekranu"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Kliknij dwukrotnie obok aplikacji, aby ją przenieść"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Rozwiń, aby wyświetlić więcej informacji."</string> + <!-- no translation found for maximize_button_text (1650859196290301963) --> + <skip /> + <!-- no translation found for close_button_text (2913281996024033299) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml index 2efc5543dd87..f328b6c3a781 100644 --- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Gire o dispositivo para entrar no modo de tela cheia"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Toque duas vezes ao lado de um app para reposicionar"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendi"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Abra para ver mais informações."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string> + <string name="close_button_text" msgid="2913281996024033299">"Fechar"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml index c68a6934dead..2fe448bcaf28 100644 --- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml @@ -81,4 +81,9 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rode o dispositivo para ficar em ecrã inteiro"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Toque duas vezes junto a uma app para a reposicionar"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expandir para obter mais informações"</string> + <!-- no translation found for maximize_button_text (1650859196290301963) --> + <skip /> + <!-- no translation found for close_button_text (2913281996024033299) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml index 2efc5543dd87..f328b6c3a781 100644 --- a/libs/WindowManager/Shell/res/values-pt/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Gire o dispositivo para entrar no modo de tela cheia"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Toque duas vezes ao lado de um app para reposicionar"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendi"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Abra para ver mais informações."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string> + <string name="close_button_text" msgid="2913281996024033299">"Fechar"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ro/strings.xml b/libs/WindowManager/Shell/res/values-ro/strings.xml index 804d34f980ff..8da1d67f9841 100644 --- a/libs/WindowManager/Shell/res/values-ro/strings.xml +++ b/libs/WindowManager/Shell/res/values-ro/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rotiți dispozitivul pentru a trece în modul ecran complet"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Atingeți de două ori lângă o aplicație pentru a o repoziționa"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Extindeți pentru mai multe informații"</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maximizați"</string> + <string name="close_button_text" msgid="2913281996024033299">"Închideți"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml index 95bf1cf11435..49efac67ceb0 100644 --- a/libs/WindowManager/Shell/res/values-ru/strings.xml +++ b/libs/WindowManager/Shell/res/values-ru/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Чтобы перейти в полноэкранный режим, поверните устройство."</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Чтобы переместить приложение, нажмите на него дважды."</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"ОК"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Развернуть, чтобы узнать больше."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Развернуть"</string> + <string name="close_button_text" msgid="2913281996024033299">"Закрыть"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-si/strings.xml b/libs/WindowManager/Shell/res/values-si/strings.xml index 23dd65ad7b31..b8c9b54904a2 100644 --- a/libs/WindowManager/Shell/res/values-si/strings.xml +++ b/libs/WindowManager/Shell/res/values-si/strings.xml @@ -81,4 +81,9 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"සම්පූර්ණ තිරයට යාමට ඔබගේ උපාංගය කරකවන්න"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"එය නැවත ස්ථානගත කිරීමට යෙදුමකට යාබදව දෙවරක් තට්ටු කරන්න"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"තේරුණා"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"වැඩිදුර තොරතුරු සඳහා දිග හරින්න"</string> + <!-- no translation found for maximize_button_text (1650859196290301963) --> + <skip /> + <!-- no translation found for close_button_text (2913281996024033299) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-sk/strings.xml b/libs/WindowManager/Shell/res/values-sk/strings.xml index a231cacefb20..ca14dd78aaa5 100644 --- a/libs/WindowManager/Shell/res/values-sk/strings.xml +++ b/libs/WindowManager/Shell/res/values-sk/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Otočením zariadenia prejdete do režimu celej obrazovky"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dvojitým klepnutím vedľa aplikácie zmeníte jej pozíciu"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Dobre"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Po rozbalení sa dozviete viac."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maximalizovať"</string> + <string name="close_button_text" msgid="2913281996024033299">"Zavrieť"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml index adeaae978eaa..626be8beb04d 100644 --- a/libs/WindowManager/Shell/res/values-sl/strings.xml +++ b/libs/WindowManager/Shell/res/values-sl/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Če želite preklopiti v celozaslonski način, zasukajte napravo."</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Dvakrat se dotaknite ob aplikaciji, če jo želite prestaviti."</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"V redu"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Razširitev za več informacij"</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Maksimiraj"</string> + <string name="close_button_text" msgid="2913281996024033299">"Zapri"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml index 2839b4bae7e4..a9b9a58fcae3 100644 --- a/libs/WindowManager/Shell/res/values-sq/strings.xml +++ b/libs/WindowManager/Shell/res/values-sq/strings.xml @@ -81,4 +81,9 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rrotullo ekranin për të kaluar në ekran të plotë"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Trokit dy herë pranë një aplikacioni për ta ripozicionuar"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"E kuptova"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Zgjeroje për më shumë informacion."</string> + <!-- no translation found for maximize_button_text (1650859196290301963) --> + <skip /> + <!-- no translation found for close_button_text (2913281996024033299) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-sr/strings.xml b/libs/WindowManager/Shell/res/values-sr/strings.xml index 9db6b7c63610..fdd49b5542c7 100644 --- a/libs/WindowManager/Shell/res/values-sr/strings.xml +++ b/libs/WindowManager/Shell/res/values-sr/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Ротирајте уређај за приказ преко целог екрана"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Двапут додирните поред апликације да бисте променили њену позицију"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Важи"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Проширите за још информација."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Увећајте"</string> + <string name="close_button_text" msgid="2913281996024033299">"Затворите"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml index f6bd55423cdc..d58b1e3cafef 100644 --- a/libs/WindowManager/Shell/res/values-sv/strings.xml +++ b/libs/WindowManager/Shell/res/values-sv/strings.xml @@ -81,4 +81,9 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Rotera skärmen för att gå över till helskärmsläge"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Tryck snabbt två gånger bredvid en app för att flytta den"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Utöka för mer information."</string> + <!-- no translation found for maximize_button_text (1650859196290301963) --> + <skip /> + <!-- no translation found for close_button_text (2913281996024033299) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml index f6e558527ee5..331004aaf109 100644 --- a/libs/WindowManager/Shell/res/values-sw/strings.xml +++ b/libs/WindowManager/Shell/res/values-sw/strings.xml @@ -81,4 +81,9 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Zungusha kifaa chako ili uende kwenye hali ya skrini nzima"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Gusa mara mbili karibu na programu ili uihamishe"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Nimeelewa"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Panua ili upate maelezo zaidi."</string> + <!-- no translation found for maximize_button_text (1650859196290301963) --> + <skip /> + <!-- no translation found for close_button_text (2913281996024033299) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml index d8334adfe5ef..b6269946ab32 100644 --- a/libs/WindowManager/Shell/res/values-ta/strings.xml +++ b/libs/WindowManager/Shell/res/values-ta/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"முழுத்திரைக்குச் செல்ல உங்கள் சாதனத்தைச் சுழற்றவும்"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"ஆப்ஸை இடம் மாற்ற, ஆப்ஸுக்கு அடுத்து இருமுறை தட்டவும்"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"சரி"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"கூடுதல் தகவல்களுக்கு விரிவாக்கலாம்."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"பெரிதாக்கும்"</string> + <string name="close_button_text" msgid="2913281996024033299">"மூடும்"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml index 733075550007..9f1a97347d78 100644 --- a/libs/WindowManager/Shell/res/values-te/strings.xml +++ b/libs/WindowManager/Shell/res/values-te/strings.xml @@ -81,4 +81,9 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"ఫుల్ స్క్రీన్కు వెళ్లడానికి మీ పరికరాన్ని తిప్పండి"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"యాప్ స్థానాన్ని మార్చడానికి దాని పక్కన డబుల్-ట్యాప్ చేయండి"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"అర్థమైంది"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"మరింత సమాచారం కోసం విస్తరించండి."</string> + <!-- no translation found for maximize_button_text (1650859196290301963) --> + <skip /> + <!-- no translation found for close_button_text (2913281996024033299) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml index cfee8ea3242e..0d1f23b1d350 100644 --- a/libs/WindowManager/Shell/res/values-th/strings.xml +++ b/libs/WindowManager/Shell/res/values-th/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"หมุนอุปกรณ์ให้แสดงเต็มหน้าจอ"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"แตะสองครั้งข้างแอปเพื่อเปลี่ยนตำแหน่ง"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"รับทราบ"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ขยายเพื่อดูข้อมูลเพิ่มเติม"</string> + <string name="maximize_button_text" msgid="1650859196290301963">"ขยายใหญ่สุด"</string> + <string name="close_button_text" msgid="2913281996024033299">"ปิด"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml index eed624dd5069..0a7fc9617e8f 100644 --- a/libs/WindowManager/Shell/res/values-tl/strings.xml +++ b/libs/WindowManager/Shell/res/values-tl/strings.xml @@ -81,4 +81,9 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"I-rotate ang iyong device para mag-full screen"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Mag-double tap sa tabi ng isang app para iposisyon ito ulit"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"I-expand para sa higit pang impormasyon."</string> + <!-- no translation found for maximize_button_text (1650859196290301963) --> + <skip /> + <!-- no translation found for close_button_text (2913281996024033299) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-tr/strings.xml b/libs/WindowManager/Shell/res/values-tr/strings.xml index 2b4a2d0550f0..b437392ca796 100644 --- a/libs/WindowManager/Shell/res/values-tr/strings.xml +++ b/libs/WindowManager/Shell/res/values-tr/strings.xml @@ -81,4 +81,9 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Tam ekrana geçmek için cihazınızı döndürün"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Yeniden konumlandırmak için uygulamanın yanına iki kez dokunun"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Anladım"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Daha fazla bilgi için genişletin."</string> + <!-- no translation found for maximize_button_text (1650859196290301963) --> + <skip /> + <!-- no translation found for close_button_text (2913281996024033299) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml index c3411a837c78..5a7395c237bf 100644 --- a/libs/WindowManager/Shell/res/values-uk/strings.xml +++ b/libs/WindowManager/Shell/res/values-uk/strings.xml @@ -81,4 +81,9 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Щоб перейти в повноекранний режим, поверніть пристрій"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Щоб перемістити додаток, двічі торкніться області поруч із ним"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"ОK"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Розгорніть, щоб дізнатися більше."</string> + <!-- no translation found for maximize_button_text (1650859196290301963) --> + <skip /> + <!-- no translation found for close_button_text (2913281996024033299) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-ur/strings.xml b/libs/WindowManager/Shell/res/values-ur/strings.xml index a31c2be25643..e6dc030e5fb2 100644 --- a/libs/WindowManager/Shell/res/values-ur/strings.xml +++ b/libs/WindowManager/Shell/res/values-ur/strings.xml @@ -81,4 +81,9 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"پوری اسکرین پر جانے کیلئے اپنا آلہ گھمائیں"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"کسی ایپ کی پوزیشن تبدیل کرنے کے لیے اس کے آگے دو بار تھپتھپائیں"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"سمجھ آ گئی"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"مزید معلومات کے لیے پھیلائیں۔"</string> + <!-- no translation found for maximize_button_text (1650859196290301963) --> + <skip /> + <!-- no translation found for close_button_text (2913281996024033299) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml index 2e3222560dde..0c0771f870dd 100644 --- a/libs/WindowManager/Shell/res/values-uz/strings.xml +++ b/libs/WindowManager/Shell/res/values-uz/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Butun ekranda ochish uchun qurilmani buring"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Qayta joylash uchun keyingi ilova ustiga ikki marta bosing"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Batafsil axborot olish uchun kengaytiring."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Yoyish"</string> + <string name="close_button_text" msgid="2913281996024033299">"Yopish"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml index 8f3cffecc952..8b692a71dd27 100644 --- a/libs/WindowManager/Shell/res/values-vi/strings.xml +++ b/libs/WindowManager/Shell/res/values-vi/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Xoay thiết bị để chuyển sang chế độ toàn màn hình"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Nhấn đúp vào bên cạnh ứng dụng để đặt lại vị trí"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Mở rộng để xem thêm thông tin."</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Phóng to"</string> + <string name="close_button_text" msgid="2913281996024033299">"Đóng"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml index 19a9d371e435..15bb2939406b 100644 --- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml +++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml @@ -81,4 +81,9 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"旋转设备即可进入全屏模式"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"在某个应用旁边连续点按两次,即可调整它的位置"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"知道了"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"展开即可了解详情。"</string> + <!-- no translation found for maximize_button_text (1650859196290301963) --> + <skip /> + <!-- no translation found for close_button_text (2913281996024033299) --> + <skip /> </resources> diff --git a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml index 0c40e963f2e4..add329188515 100644 --- a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml +++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"旋轉裝置方向即可進入全螢幕模式"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"在應用程式旁輕按兩下即可調整位置"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"知道了"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"展開即可查看詳情。"</string> + <string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string> + <string name="close_button_text" msgid="2913281996024033299">"關閉"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml index 8691352cf94a..f2c2a2970d8b 100644 --- a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml +++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"旋轉裝置方向即可進入全螢幕模式"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"在應用程式旁輕觸兩下即可調整位置"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"我知道了"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"展開即可查看詳細資訊。"</string> + <string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string> + <string name="close_button_text" msgid="2913281996024033299">"關閉"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values-zu/strings.xml b/libs/WindowManager/Shell/res/values-zu/strings.xml index 44ffbc6afa45..94e5b9bf4529 100644 --- a/libs/WindowManager/Shell/res/values-zu/strings.xml +++ b/libs/WindowManager/Shell/res/values-zu/strings.xml @@ -81,4 +81,7 @@ <string name="letterbox_education_screen_rotation_text" msgid="5085786687366339027">"Zungezisa idivayisi yakho ukuze uye esikrinini esigcwele"</string> <string name="letterbox_education_reposition_text" msgid="1068293354123934727">"Thepha kabili eduze kwe-app ukuze uyimise kabusha"</string> <string name="letterbox_education_got_it" msgid="4057634570866051177">"Ngiyezwa"</string> + <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Nweba ukuze uthole ulwazi olwengeziwe"</string> + <string name="maximize_button_text" msgid="1650859196290301963">"Khulisa"</string> + <string name="close_button_text" msgid="2913281996024033299">"Vala"</string> </resources> diff --git a/libs/WindowManager/Shell/res/values/attrs.xml b/libs/WindowManager/Shell/res/values/attrs.xml index 4aaeef8afcb0..2aad4c1c1805 100644 --- a/libs/WindowManager/Shell/res/values/attrs.xml +++ b/libs/WindowManager/Shell/res/values/attrs.xml @@ -19,4 +19,8 @@ <attr name="icon" format="reference" /> <attr name="text" format="string" /> </declare-styleable> + + <declare-styleable name="MessageState"> + <attr name="state_task_focused" format="boolean"/> + </declare-styleable> </resources> diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml index a24311fb1f21..b2f09895d7d8 100644 --- a/libs/WindowManager/Shell/res/values/strings.xml +++ b/libs/WindowManager/Shell/res/values/strings.xml @@ -186,4 +186,12 @@ <!-- Button text for dismissing the letterbox education dialog. [CHAR LIMIT=20] --> <string name="letterbox_education_got_it">Got it</string> + <!-- Accessibility description of the letterbox education toast expand to dialog button. [CHAR LIMIT=NONE] --> + <string name="letterbox_education_expand_button_description">Expand for more information.</string> + + <!-- Freeform window caption strings --> + <!-- Accessibility text for the maximize window button [CHAR LIMIT=NONE] --> + <string name="maximize_button_text">Maximize</string> + <!-- Accessibility text for the close window button [CHAR LIMIT=NONE] --> + <string name="close_button_text">Close</string> </resources> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java index 06f4367752fb..c5f7c19518a7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java @@ -18,11 +18,9 @@ package com.android.wm.shell; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; -import com.android.wm.shell.apppairs.AppPairsController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController; import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer; -import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController; import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.recents.RecentTasksController; @@ -39,12 +37,10 @@ import java.util.Optional; public final class ShellCommandHandlerImpl { private static final String TAG = ShellCommandHandlerImpl.class.getSimpleName(); - private final Optional<LegacySplitScreenController> mLegacySplitScreenOptional; private final Optional<SplitScreenController> mSplitScreenOptional; private final Optional<Pip> mPipOptional; private final Optional<OneHandedController> mOneHandedOptional; private final Optional<HideDisplayCutoutController> mHideDisplayCutout; - private final Optional<AppPairsController> mAppPairsOptional; private final Optional<RecentTasksController> mRecentTasks; private final ShellTaskOrganizer mShellTaskOrganizer; private final KidsModeTaskOrganizer mKidsModeTaskOrganizer; @@ -54,23 +50,19 @@ public final class ShellCommandHandlerImpl { public ShellCommandHandlerImpl( ShellTaskOrganizer shellTaskOrganizer, KidsModeTaskOrganizer kidsModeTaskOrganizer, - Optional<LegacySplitScreenController> legacySplitScreenOptional, Optional<SplitScreenController> splitScreenOptional, Optional<Pip> pipOptional, Optional<OneHandedController> oneHandedOptional, Optional<HideDisplayCutoutController> hideDisplayCutout, - Optional<AppPairsController> appPairsOptional, Optional<RecentTasksController> recentTasks, ShellExecutor mainExecutor) { mShellTaskOrganizer = shellTaskOrganizer; mKidsModeTaskOrganizer = kidsModeTaskOrganizer; mRecentTasks = recentTasks; - mLegacySplitScreenOptional = legacySplitScreenOptional; mSplitScreenOptional = splitScreenOptional; mPipOptional = pipOptional; mOneHandedOptional = oneHandedOptional; mHideDisplayCutout = hideDisplayCutout; - mAppPairsOptional = appPairsOptional; mMainExecutor = mainExecutor; } @@ -84,14 +76,10 @@ public final class ShellCommandHandlerImpl { pw.println(); pw.println(); mPipOptional.ifPresent(pip -> pip.dump(pw)); - mLegacySplitScreenOptional.ifPresent(splitScreen -> splitScreen.dump(pw)); mOneHandedOptional.ifPresent(oneHanded -> oneHanded.dump(pw)); mHideDisplayCutout.ifPresent(hideDisplayCutout -> hideDisplayCutout.dump(pw)); pw.println(); pw.println(); - mAppPairsOptional.ifPresent(appPairs -> appPairs.dump(pw, "")); - pw.println(); - pw.println(); mSplitScreenOptional.ifPresent(splitScreen -> splitScreen.dump(pw, "")); pw.println(); pw.println(); @@ -109,10 +97,6 @@ public final class ShellCommandHandlerImpl { return false; } switch (args[1]) { - case "pair": - return runPair(args, pw); - case "unpair": - return runUnpair(args, pw); case "moveToSideStage": return runMoveToSideStage(args, pw); case "removeFromSideStage": @@ -126,29 +110,6 @@ public final class ShellCommandHandlerImpl { } } - private boolean runPair(String[] args, PrintWriter pw) { - if (args.length < 4) { - // First two arguments are "WMShell" and command name. - pw.println("Error: two task ids should be provided as arguments"); - return false; - } - final int taskId1 = new Integer(args[2]); - final int taskId2 = new Integer(args[3]); - mAppPairsOptional.ifPresent(appPairs -> appPairs.pair(taskId1, taskId2)); - return true; - } - - private boolean runUnpair(String[] args, PrintWriter pw) { - if (args.length < 3) { - // First two arguments are "WMShell" and command name. - pw.println("Error: task id should be provided as an argument"); - return false; - } - final int taskId = new Integer(args[2]); - mAppPairsOptional.ifPresent(appPairs -> appPairs.unpair(taskId)); - return true; - } - private boolean runMoveToSideStage(String[] args, PrintWriter pw) { if (args.length < 3) { // First arguments are "WMShell" and command name. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java index 62fb840d29d1..992f31562145 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java @@ -18,7 +18,6 @@ package com.android.wm.shell; import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN; -import com.android.wm.shell.apppairs.AppPairsController; import com.android.wm.shell.bubbles.BubbleController; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; @@ -28,13 +27,14 @@ import com.android.wm.shell.common.annotations.ExternalThread; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.freeform.FreeformTaskListener; import com.android.wm.shell.fullscreen.FullscreenTaskListener; -import com.android.wm.shell.fullscreen.FullscreenUnfoldController; import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer; import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.startingsurface.StartingWindowController; +import com.android.wm.shell.transition.DefaultMixedHandler; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.unfold.UnfoldAnimationController; import com.android.wm.shell.unfold.UnfoldTransitionHandler; import java.util.Optional; @@ -53,12 +53,11 @@ public class ShellInitImpl { private final KidsModeTaskOrganizer mKidsModeTaskOrganizer; private final Optional<BubbleController> mBubblesOptional; private final Optional<SplitScreenController> mSplitScreenOptional; - private final Optional<AppPairsController> mAppPairsOptional; private final Optional<PipTouchHandler> mPipTouchHandlerOptional; private final FullscreenTaskListener mFullscreenTaskListener; - private final Optional<FullscreenUnfoldController> mFullscreenUnfoldController; + private final Optional<UnfoldAnimationController> mUnfoldController; private final Optional<UnfoldTransitionHandler> mUnfoldTransitionHandler; - private final Optional<FreeformTaskListener> mFreeformTaskListenerOptional; + private final Optional<FreeformTaskListener<?>> mFreeformTaskListenerOptional; private final ShellExecutor mMainExecutor; private final Transitions mTransitions; private final StartingWindowController mStartingWindow; @@ -75,12 +74,11 @@ public class ShellInitImpl { KidsModeTaskOrganizer kidsModeTaskOrganizer, Optional<BubbleController> bubblesOptional, Optional<SplitScreenController> splitScreenOptional, - Optional<AppPairsController> appPairsOptional, Optional<PipTouchHandler> pipTouchHandlerOptional, FullscreenTaskListener fullscreenTaskListener, - Optional<FullscreenUnfoldController> fullscreenUnfoldTransitionController, + Optional<UnfoldAnimationController> unfoldAnimationController, Optional<UnfoldTransitionHandler> unfoldTransitionHandler, - Optional<FreeformTaskListener> freeformTaskListenerOptional, + Optional<FreeformTaskListener<?>> freeformTaskListenerOptional, Optional<RecentTasksController> recentTasks, Transitions transitions, StartingWindowController startingWindow, @@ -93,10 +91,9 @@ public class ShellInitImpl { mKidsModeTaskOrganizer = kidsModeTaskOrganizer; mBubblesOptional = bubblesOptional; mSplitScreenOptional = splitScreenOptional; - mAppPairsOptional = appPairsOptional; mFullscreenTaskListener = fullscreenTaskListener; mPipTouchHandlerOptional = pipTouchHandlerOptional; - mFullscreenUnfoldController = fullscreenUnfoldTransitionController; + mUnfoldController = unfoldAnimationController; mUnfoldTransitionHandler = unfoldTransitionHandler; mFreeformTaskListenerOptional = freeformTaskListenerOptional; mRecentTasks = recentTasks; @@ -121,7 +118,6 @@ public class ShellInitImpl { mShellTaskOrganizer.initStartingWindow(mStartingWindow); mShellTaskOrganizer.registerOrganizer(); - mAppPairsOptional.ifPresent(AppPairsController::onOrganizerRegistered); mSplitScreenOptional.ifPresent(SplitScreenController::onOrganizerRegistered); mBubblesOptional.ifPresent(BubbleController::initialize); @@ -131,6 +127,13 @@ public class ShellInitImpl { if (Transitions.ENABLE_SHELL_TRANSITIONS) { mTransitions.register(mShellTaskOrganizer); mUnfoldTransitionHandler.ifPresent(UnfoldTransitionHandler::init); + if (mSplitScreenOptional.isPresent() && mPipTouchHandlerOptional.isPresent()) { + final DefaultMixedHandler mixedHandler = new DefaultMixedHandler(mTransitions, + mPipTouchHandlerOptional.get().getTransitionHandler(), + mSplitScreenOptional.get().getTransitionHandler()); + // Added at end so that it has highest priority. + mTransitions.addHandler(mixedHandler); + } } // TODO(b/181599115): This should really be the pip controller, but until we can provide the @@ -143,7 +146,7 @@ public class ShellInitImpl { mShellTaskOrganizer.addListenerForType( f, ShellTaskOrganizer.TASK_LISTENER_TYPE_FREEFORM)); - mFullscreenUnfoldController.ifPresent(FullscreenUnfoldController::init); + mUnfoldController.ifPresent(UnfoldAnimationController::init); mRecentTasks.ifPresent(RecentTasksController::init); // Initialize kids mode task organizer 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 31f0ef0192ae..e9d24fbf4d0a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -55,6 +55,7 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.compatui.CompatUIController; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.startingsurface.StartingWindowController; +import com.android.wm.shell.unfold.UnfoldAnimationController; import java.io.PrintWriter; import java.util.ArrayList; @@ -179,33 +180,41 @@ public class ShellTaskOrganizer extends TaskOrganizer implements private final Optional<RecentTasksController> mRecentTasks; @Nullable + private final UnfoldAnimationController mUnfoldAnimationController; + + @Nullable private RunningTaskInfo mLastFocusedTaskInfo; public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context) { this(null /* taskOrganizerController */, mainExecutor, context, null /* compatUI */, + Optional.empty() /* unfoldAnimationController */, Optional.empty() /* recentTasksController */); } public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable CompatUIController compatUI) { this(null /* taskOrganizerController */, mainExecutor, context, compatUI, + Optional.empty() /* unfoldAnimationController */, Optional.empty() /* recentTasksController */); } public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable CompatUIController compatUI, + Optional<UnfoldAnimationController> unfoldAnimationController, Optional<RecentTasksController> recentTasks) { this(null /* taskOrganizerController */, mainExecutor, context, compatUI, - recentTasks); + unfoldAnimationController, recentTasks); } @VisibleForTesting protected ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController, ShellExecutor mainExecutor, Context context, @Nullable CompatUIController compatUI, + Optional<UnfoldAnimationController> unfoldAnimationController, Optional<RecentTasksController> recentTasks) { super(taskOrganizerController, mainExecutor); mCompatUI = compatUI; mRecentTasks = recentTasks; + mUnfoldAnimationController = unfoldAnimationController.orElse(null); if (compatUI != null) { compatUI.setCompatUICallback(this); } @@ -437,6 +446,9 @@ public class ShellTaskOrganizer extends TaskOrganizer implements if (listener != null) { listener.onTaskAppeared(info.getTaskInfo(), info.getLeash()); } + if (mUnfoldAnimationController != null) { + mUnfoldAnimationController.onTaskAppeared(info.getTaskInfo(), info.getLeash()); + } notifyLocusVisibilityIfNeeded(info.getTaskInfo()); notifyCompatUI(info.getTaskInfo(), listener); } @@ -458,6 +470,11 @@ public class ShellTaskOrganizer extends TaskOrganizer implements public void onTaskInfoChanged(RunningTaskInfo taskInfo) { synchronized (mLock) { ProtoLog.v(WM_SHELL_TASK_ORG, "Task info changed taskId=%d", taskInfo.taskId); + + if (mUnfoldAnimationController != null) { + mUnfoldAnimationController.onTaskInfoChanged(taskInfo); + } + final TaskAppearedInfo data = mTasks.get(taskInfo.taskId); final TaskListener oldListener = getTaskListener(data.getTaskInfo()); final TaskListener newListener = getTaskListener(taskInfo); @@ -507,6 +524,10 @@ public class ShellTaskOrganizer extends TaskOrganizer implements public void onTaskVanished(RunningTaskInfo taskInfo) { synchronized (mLock) { ProtoLog.v(WM_SHELL_TASK_ORG, "Task vanished taskId=%d", taskInfo.taskId); + if (mUnfoldAnimationController != null) { + mUnfoldAnimationController.onTaskVanished(taskInfo); + } + final int taskId = taskInfo.taskId; final TaskListener listener = getTaskListener(mTasks.get(taskId).getTaskInfo()); mTasks.remove(taskId); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java index 2aead9392e59..a0dde6ad168d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java @@ -53,6 +53,19 @@ public class Interpolators { public static final Interpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0f, 0f, 0.2f, 1f); /** + * The accelerated emphasized interpolator. Used for hero / emphasized movement of content that + * is disappearing e.g. when moving off screen. + */ + public static final Interpolator EMPHASIZED_ACCELERATE = new PathInterpolator( + 0.3f, 0f, 0.8f, 0.15f); + + /** + * The decelerating emphasized interpolator. Used for hero / emphasized movement of content that + * is appearing e.g. when coming from off screen + */ + public static final Interpolator EMPHASIZED_DECELERATE = new PathInterpolator( + 0.05f, 0.7f, 0.1f, 1f); + /** * Interpolator to be used when animating a move based on a click. Pair with enough duration. */ public static final Interpolator TOUCH_RESPONSE = new PathInterpolator(0.3f, 0f, 0.1f, 1f); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java deleted file mode 100644 index 3f0b01bef0ce..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java +++ /dev/null @@ -1,357 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.apppairs; - -import static android.app.ActivityTaskManager.INVALID_TASK_ID; -import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; -import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; - -import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; -import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; -import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; -import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG; - -import android.app.ActivityManager; -import android.view.SurfaceControl; -import android.view.SurfaceSession; -import android.window.WindowContainerToken; -import android.window.WindowContainerTransaction; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.internal.protolog.common.ProtoLog; -import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.common.DisplayController; -import com.android.wm.shell.common.DisplayImeController; -import com.android.wm.shell.common.DisplayInsetsController; -import com.android.wm.shell.common.SurfaceUtils; -import com.android.wm.shell.common.SyncTransactionQueue; -import com.android.wm.shell.common.split.SplitLayout; -import com.android.wm.shell.common.split.SplitWindowManager; - -import java.io.PrintWriter; - -/** - * An app-pairs consisting of {@link #mRootTaskInfo} that acts as the hierarchy parent of - * {@link #mTaskInfo1} and {@link #mTaskInfo2} in the pair. - * Also includes all UI for managing the pair like the divider. - */ -class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.SplitLayoutHandler { - private static final String TAG = AppPair.class.getSimpleName(); - - private ActivityManager.RunningTaskInfo mRootTaskInfo; - private SurfaceControl mRootTaskLeash; - private ActivityManager.RunningTaskInfo mTaskInfo1; - private SurfaceControl mTaskLeash1; - private ActivityManager.RunningTaskInfo mTaskInfo2; - private SurfaceControl mTaskLeash2; - private SurfaceControl mDimLayer1; - private SurfaceControl mDimLayer2; - private final SurfaceSession mSurfaceSession = new SurfaceSession(); - - private final AppPairsController mController; - private final SyncTransactionQueue mSyncQueue; - private final DisplayController mDisplayController; - private final DisplayImeController mDisplayImeController; - private final DisplayInsetsController mDisplayInsetsController; - private SplitLayout mSplitLayout; - - private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks = - new SplitWindowManager.ParentContainerCallbacks() { - @Override - public void attachToParentSurface(SurfaceControl.Builder b) { - b.setParent(mRootTaskLeash); - } - - @Override - public void onLeashReady(SurfaceControl leash) { - mSyncQueue.runInSync(t -> t - .show(leash) - .setLayer(leash, Integer.MAX_VALUE) - .setPosition(leash, - mSplitLayout.getDividerBounds().left, - mSplitLayout.getDividerBounds().top)); - } - }; - - AppPair(AppPairsController controller) { - mController = controller; - mSyncQueue = controller.getSyncTransactionQueue(); - mDisplayController = controller.getDisplayController(); - mDisplayImeController = controller.getDisplayImeController(); - mDisplayInsetsController = controller.getDisplayInsetsController(); - } - - int getRootTaskId() { - return mRootTaskInfo != null ? mRootTaskInfo.taskId : INVALID_TASK_ID; - } - - private int getTaskId1() { - return mTaskInfo1 != null ? mTaskInfo1.taskId : INVALID_TASK_ID; - } - - private int getTaskId2() { - return mTaskInfo2 != null ? mTaskInfo2.taskId : INVALID_TASK_ID; - } - - boolean contains(int taskId) { - return taskId == getRootTaskId() || taskId == getTaskId1() || taskId == getTaskId2(); - } - - boolean pair(ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2) { - ProtoLog.v(WM_SHELL_TASK_ORG, "pair task1=%d task2=%d in AppPair=%s", - task1.taskId, task2.taskId, this); - - if (!task1.supportsMultiWindow || !task2.supportsMultiWindow) { - ProtoLog.e(WM_SHELL_TASK_ORG, - "Can't pair tasks that doesn't support multi window, " - + "task1.supportsMultiWindow=%b, task2.supportsMultiWindow=%b", - task1.supportsMultiWindow, task2.supportsMultiWindow); - return false; - } - - mTaskInfo1 = task1; - mTaskInfo2 = task2; - mSplitLayout = new SplitLayout(TAG + "SplitDivider", - mDisplayController.getDisplayContext(mRootTaskInfo.displayId), - mRootTaskInfo.configuration, this /* layoutChangeListener */, - mParentContainerCallbacks, mDisplayImeController, mController.getTaskOrganizer(), - SplitLayout.PARALLAX_DISMISSING); - mDisplayInsetsController.addInsetsChangedListener(mRootTaskInfo.displayId, mSplitLayout); - - final WindowContainerToken token1 = task1.token; - final WindowContainerToken token2 = task2.token; - final WindowContainerTransaction wct = new WindowContainerTransaction(); - - wct.setHidden(mRootTaskInfo.token, false) - .reparent(token1, mRootTaskInfo.token, true /* onTop */) - .reparent(token2, mRootTaskInfo.token, true /* onTop */) - .setWindowingMode(token1, WINDOWING_MODE_MULTI_WINDOW) - .setWindowingMode(token2, WINDOWING_MODE_MULTI_WINDOW) - .setBounds(token1, mSplitLayout.getBounds1()) - .setBounds(token2, mSplitLayout.getBounds2()) - // Moving the root task to top after the child tasks were repareted , or the root - // task cannot be visible and focused. - .reorder(mRootTaskInfo.token, true); - mController.getTaskOrganizer().applyTransaction(wct); - return true; - } - - void unpair() { - unpair(null /* toTopToken */); - } - - private void unpair(@Nullable WindowContainerToken toTopToken) { - final WindowContainerToken token1 = mTaskInfo1.token; - final WindowContainerToken token2 = mTaskInfo2.token; - final WindowContainerTransaction wct = new WindowContainerTransaction(); - - // Reparent out of this container and reset windowing mode. - wct.setHidden(mRootTaskInfo.token, true) - .reorder(mRootTaskInfo.token, false) - .reparent(token1, null, token1 == toTopToken /* onTop */) - .reparent(token2, null, token2 == toTopToken /* onTop */) - .setWindowingMode(token1, WINDOWING_MODE_UNDEFINED) - .setWindowingMode(token2, WINDOWING_MODE_UNDEFINED); - mController.getTaskOrganizer().applyTransaction(wct); - - mTaskInfo1 = null; - mTaskInfo2 = null; - mSplitLayout.release(); - mSplitLayout = null; - } - - @Override - public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { - if (mRootTaskInfo == null || taskInfo.taskId == mRootTaskInfo.taskId) { - mRootTaskInfo = taskInfo; - mRootTaskLeash = leash; - } else if (taskInfo.taskId == getTaskId1()) { - mTaskInfo1 = taskInfo; - mTaskLeash1 = leash; - mSyncQueue.runInSync(t -> mDimLayer1 = - SurfaceUtils.makeDimLayer(t, mTaskLeash1, "Dim layer", mSurfaceSession)); - } else if (taskInfo.taskId == getTaskId2()) { - mTaskInfo2 = taskInfo; - mTaskLeash2 = leash; - mSyncQueue.runInSync(t -> mDimLayer2 = - SurfaceUtils.makeDimLayer(t, mTaskLeash2, "Dim layer", mSurfaceSession)); - } else { - throw new IllegalStateException("Unknown task=" + taskInfo.taskId); - } - - if (mTaskLeash1 == null || mTaskLeash2 == null) return; - - mSplitLayout.init(); - - mSyncQueue.runInSync(t -> t - .show(mRootTaskLeash) - .show(mTaskLeash1) - .show(mTaskLeash2) - .setPosition(mTaskLeash1, - mTaskInfo1.positionInParent.x, - mTaskInfo1.positionInParent.y) - .setPosition(mTaskLeash2, - mTaskInfo2.positionInParent.x, - mTaskInfo2.positionInParent.y)); - } - - @Override - public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { - if (!taskInfo.supportsMultiWindow) { - // Dismiss AppPair if the task no longer supports multi window. - mController.unpair(mRootTaskInfo.taskId); - return; - } - if (taskInfo.taskId == getRootTaskId()) { - if (mRootTaskInfo.isVisible != taskInfo.isVisible) { - mSyncQueue.runInSync(t -> { - if (taskInfo.isVisible) { - t.show(mRootTaskLeash); - } else { - t.hide(mRootTaskLeash); - } - }); - } - mRootTaskInfo = taskInfo; - - if (mSplitLayout != null - && mSplitLayout.updateConfiguration(mRootTaskInfo.configuration)) { - onLayoutSizeChanged(mSplitLayout); - } - } else if (taskInfo.taskId == getTaskId1()) { - mTaskInfo1 = taskInfo; - } else if (taskInfo.taskId == getTaskId2()) { - mTaskInfo2 = taskInfo; - } else { - throw new IllegalStateException("Unknown task=" + taskInfo.taskId); - } - } - - @Override - public int getSplitItemPosition(WindowContainerToken token) { - if (token == null) { - return SPLIT_POSITION_UNDEFINED; - } - - if (token.equals(mTaskInfo1.getToken())) { - return SPLIT_POSITION_TOP_OR_LEFT; - } else if (token.equals(mTaskInfo2.getToken())) { - return SPLIT_POSITION_BOTTOM_OR_RIGHT; - } - - return SPLIT_POSITION_UNDEFINED; - } - - @Override - public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { - if (taskInfo.taskId == getRootTaskId()) { - // We don't want to release this object back to the pool since the root task went away. - mController.unpair(mRootTaskInfo.taskId, false /* releaseToPool */); - } else if (taskInfo.taskId == getTaskId1()) { - mController.unpair(mRootTaskInfo.taskId); - mSyncQueue.runInSync(t -> t.remove(mDimLayer1)); - } else if (taskInfo.taskId == getTaskId2()) { - mController.unpair(mRootTaskInfo.taskId); - mSyncQueue.runInSync(t -> t.remove(mDimLayer2)); - } - } - - @Override - public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) { - b.setParent(findTaskSurface(taskId)); - } - - @Override - public void reparentChildSurfaceToTask(int taskId, SurfaceControl sc, - SurfaceControl.Transaction t) { - t.reparent(sc, findTaskSurface(taskId)); - } - - private SurfaceControl findTaskSurface(int taskId) { - if (getRootTaskId() == taskId) { - return mRootTaskLeash; - } else if (getTaskId1() == taskId) { - return mTaskLeash1; - } else if (getTaskId2() == taskId) { - return mTaskLeash2; - } else { - throw new IllegalArgumentException("There is no surface for taskId=" + taskId); - } - } - - @Override - public void dump(@NonNull PrintWriter pw, String prefix) { - final String innerPrefix = prefix + " "; - final String childPrefix = innerPrefix + " "; - pw.println(prefix + this); - if (mRootTaskInfo != null) { - pw.println(innerPrefix + "Root taskId=" + mRootTaskInfo.taskId - + " winMode=" + mRootTaskInfo.getWindowingMode()); - } - if (mTaskInfo1 != null) { - pw.println(innerPrefix + "1 taskId=" + mTaskInfo1.taskId - + " winMode=" + mTaskInfo1.getWindowingMode()); - } - if (mTaskInfo2 != null) { - pw.println(innerPrefix + "2 taskId=" + mTaskInfo2.taskId - + " winMode=" + mTaskInfo2.getWindowingMode()); - } - } - - @Override - public String toString() { - return TAG + "#" + getRootTaskId(); - } - - @Override - public void onSnappedToDismiss(boolean snappedToEnd) { - unpair(snappedToEnd ? mTaskInfo1.token : mTaskInfo2.token /* toTopToken */); - } - - @Override - public void onLayoutPositionChanging(SplitLayout layout) { - mSyncQueue.runInSync(t -> - layout.applySurfaceChanges(t, mTaskLeash1, mTaskLeash2, mDimLayer1, mDimLayer2, - true /* applyResizingOffset */)); - } - - @Override - public void onLayoutSizeChanging(SplitLayout layout) { - mSyncQueue.runInSync(t -> - layout.applySurfaceChanges(t, mTaskLeash1, mTaskLeash2, mDimLayer1, mDimLayer2, - true /* applyResizingOffset */)); - } - - @Override - public void onLayoutSizeChanged(SplitLayout layout) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); - layout.applyTaskChanges(wct, mTaskInfo1, mTaskInfo2); - mSyncQueue.queue(wct); - mSyncQueue.runInSync(t -> - layout.applySurfaceChanges(t, mTaskLeash1, mTaskLeash2, mDimLayer1, mDimLayer2, - false /* applyResizingOffset */)); - } - - @Override - public void setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); - layout.applyLayoutOffsetTarget(wct, offsetX, offsetY, mTaskInfo1, mTaskInfo2); - mController.getTaskOrganizer().applyTransaction(wct); - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java deleted file mode 100644 index a9b1dbc3c23b..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.apppairs; - -import android.app.ActivityManager; - -import androidx.annotation.NonNull; - -import com.android.wm.shell.common.annotations.ExternalThread; - -import java.io.PrintWriter; - -/** - * Interface to engage app pairs feature. - */ -@ExternalThread -public interface AppPairs { - /** Pairs indicated tasks. */ - boolean pair(int task1, int task2); - /** Pairs indicated tasks. */ - boolean pair(ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2); - /** Unpairs any app-pair containing this task id. */ - void unpair(int taskId); -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java deleted file mode 100644 index 53234ab971d6..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.apppairs; - -import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG; - -import android.app.ActivityManager; -import android.util.Slog; -import android.util.SparseArray; - -import androidx.annotation.NonNull; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; -import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.common.DisplayController; -import com.android.wm.shell.common.DisplayImeController; -import com.android.wm.shell.common.DisplayInsetsController; -import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.common.SyncTransactionQueue; - -import java.io.PrintWriter; - -/** - * Class manages app-pairs multitasking mode and implements the main interface {@link AppPairs}. - */ -public class AppPairsController { - private static final String TAG = AppPairsController.class.getSimpleName(); - - private final ShellTaskOrganizer mTaskOrganizer; - private final SyncTransactionQueue mSyncQueue; - private final ShellExecutor mMainExecutor; - private final AppPairsImpl mImpl = new AppPairsImpl(); - - private AppPairsPool mPairsPool; - // Active app-pairs mapped by root task id key. - private final SparseArray<AppPair> mActiveAppPairs = new SparseArray<>(); - private final DisplayController mDisplayController; - private final DisplayImeController mDisplayImeController; - private final DisplayInsetsController mDisplayInsetsController; - - public AppPairsController(ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue, - DisplayController displayController, ShellExecutor mainExecutor, - DisplayImeController displayImeController, - DisplayInsetsController displayInsetsController) { - mTaskOrganizer = organizer; - mSyncQueue = syncQueue; - mDisplayController = displayController; - mDisplayImeController = displayImeController; - mDisplayInsetsController = displayInsetsController; - mMainExecutor = mainExecutor; - } - - public AppPairs asAppPairs() { - return mImpl; - } - - public void onOrganizerRegistered() { - if (mPairsPool == null) { - setPairsPool(new AppPairsPool(this)); - } - } - - @VisibleForTesting - public void setPairsPool(AppPairsPool pool) { - mPairsPool = pool; - } - - public boolean pair(int taskId1, int taskId2) { - final ActivityManager.RunningTaskInfo task1 = mTaskOrganizer.getRunningTaskInfo(taskId1); - final ActivityManager.RunningTaskInfo task2 = mTaskOrganizer.getRunningTaskInfo(taskId2); - if (task1 == null || task2 == null) { - return false; - } - return pair(task1, task2); - } - - public boolean pair(ActivityManager.RunningTaskInfo task1, - ActivityManager.RunningTaskInfo task2) { - return pairInner(task1, task2) != null; - } - - @VisibleForTesting - public AppPair pairInner( - @NonNull ActivityManager.RunningTaskInfo task1, - @NonNull ActivityManager.RunningTaskInfo task2) { - final AppPair pair = mPairsPool.acquire(); - if (!pair.pair(task1, task2)) { - mPairsPool.release(pair); - return null; - } - - mActiveAppPairs.put(pair.getRootTaskId(), pair); - return pair; - } - - public void unpair(int taskId) { - unpair(taskId, true /* releaseToPool */); - } - - public void unpair(int taskId, boolean releaseToPool) { - AppPair pair = mActiveAppPairs.get(taskId); - if (pair == null) { - for (int i = mActiveAppPairs.size() - 1; i >= 0; --i) { - final AppPair candidate = mActiveAppPairs.valueAt(i); - if (candidate.contains(taskId)) { - pair = candidate; - break; - } - } - } - if (pair == null) { - ProtoLog.v(WM_SHELL_TASK_ORG, "taskId %d isn't isn't in an app-pair.", taskId); - return; - } - - ProtoLog.v(WM_SHELL_TASK_ORG, "unpair taskId=%d pair=%s", taskId, pair); - mActiveAppPairs.remove(pair.getRootTaskId()); - pair.unpair(); - if (releaseToPool) { - mPairsPool.release(pair); - } - } - - ShellTaskOrganizer getTaskOrganizer() { - return mTaskOrganizer; - } - - SyncTransactionQueue getSyncTransactionQueue() { - return mSyncQueue; - } - - DisplayController getDisplayController() { - return mDisplayController; - } - - DisplayImeController getDisplayImeController() { - return mDisplayImeController; - } - - DisplayInsetsController getDisplayInsetsController() { - return mDisplayInsetsController; - } - - public void dump(@NonNull PrintWriter pw, String prefix) { - final String innerPrefix = prefix + " "; - final String childPrefix = innerPrefix + " "; - pw.println(prefix + this); - - for (int i = mActiveAppPairs.size() - 1; i >= 0; --i) { - mActiveAppPairs.valueAt(i).dump(pw, childPrefix); - } - - if (mPairsPool != null) { - mPairsPool.dump(pw, prefix); - } - } - - @Override - public String toString() { - return TAG + "#" + mActiveAppPairs.size(); - } - - private class AppPairsImpl implements AppPairs { - @Override - public boolean pair(int task1, int task2) { - boolean[] result = new boolean[1]; - try { - mMainExecutor.executeBlocking(() -> { - result[0] = AppPairsController.this.pair(task1, task2); - }); - } catch (InterruptedException e) { - Slog.e(TAG, "Failed to pair tasks: " + task1 + ", " + task2); - } - return result[0]; - } - - @Override - public boolean pair(ActivityManager.RunningTaskInfo task1, - ActivityManager.RunningTaskInfo task2) { - boolean[] result = new boolean[1]; - try { - mMainExecutor.executeBlocking(() -> { - result[0] = AppPairsController.this.pair(task1, task2); - }); - } catch (InterruptedException e) { - Slog.e(TAG, "Failed to pair tasks: " + task1 + ", " + task2); - } - return result[0]; - } - - @Override - public void unpair(int taskId) { - mMainExecutor.execute(() -> { - AppPairsController.this.unpair(taskId); - }); - } - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsPool.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsPool.java deleted file mode 100644 index 5c6037ea0702..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsPool.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.apppairs; - -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.view.Display.DEFAULT_DISPLAY; - -import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG; - -import androidx.annotation.NonNull; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; - -import java.io.PrintWriter; -import java.util.ArrayList; - -/** - * Class that manager pool of {@link AppPair} objects. Helps reduce the need to call system_server - * to create a root task for the app-pair when needed since we always have one ready to go. - */ -class AppPairsPool { - private static final String TAG = AppPairsPool.class.getSimpleName(); - - @VisibleForTesting - final AppPairsController mController; - // The pool - private final ArrayList<AppPair> mPool = new ArrayList(); - - AppPairsPool(AppPairsController controller) { - mController = controller; - incrementPool(); - } - - AppPair acquire() { - final AppPair entry = mPool.remove(mPool.size() - 1); - ProtoLog.v(WM_SHELL_TASK_ORG, "acquire entry.taskId=%s listener=%s size=%d", - entry.getRootTaskId(), entry, mPool.size()); - if (mPool.size() == 0) { - incrementPool(); - } - return entry; - } - - void release(AppPair entry) { - mPool.add(entry); - ProtoLog.v(WM_SHELL_TASK_ORG, "release entry.taskId=%s listener=%s size=%d", - entry.getRootTaskId(), entry, mPool.size()); - } - - @VisibleForTesting - void incrementPool() { - ProtoLog.v(WM_SHELL_TASK_ORG, "incrementPool size=%d", mPool.size()); - final AppPair entry = new AppPair(mController); - // TODO: multi-display... - mController.getTaskOrganizer().createRootTask( - DEFAULT_DISPLAY, WINDOWING_MODE_FULLSCREEN, entry); - mPool.add(entry); - } - - @VisibleForTesting - int poolSize() { - return mPool.size(); - } - - public void dump(@NonNull PrintWriter pw, String prefix) { - final String innerPrefix = prefix + " "; - final String childPrefix = innerPrefix + " "; - pw.println(prefix + this); - for (int i = mPool.size() - 1; i >= 0; --i) { - mPool.get(i).dump(pw, childPrefix); - } - } - - @Override - public String toString() { - return TAG + "#" + mPool.size(); - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/OWNERS deleted file mode 100644 index 4d9b520e3f0e..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/OWNERS +++ /dev/null @@ -1,2 +0,0 @@ -# WM shell sub-modules apppair owner -chenghsiuchang@google.com 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 f427a2c4bc95..ea3712ba34b4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -25,6 +25,7 @@ import static android.view.View.VISIBLE; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER; +import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_GESTURE; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.wm.shell.bubbles.BubblePositioner.TASKBAR_POSITION_BOTTOM; @@ -79,7 +80,6 @@ import android.view.View; import android.view.ViewGroup; import android.view.WindowInsets; import android.view.WindowManager; -import android.window.WindowContainerTransaction; import androidx.annotation.MainThread; import androidx.annotation.Nullable; @@ -90,7 +90,6 @@ import com.android.internal.statusbar.IStatusBarService; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.TaskViewTransitions; import com.android.wm.shell.WindowManagerShellWrapper; -import com.android.wm.shell.common.DisplayChangeController; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.ShellExecutor; @@ -435,17 +434,13 @@ public class BubbleController { }); mDisplayController.addDisplayChangingController( - new DisplayChangeController.OnDisplayChangingListener() { - @Override - public void onRotateDisplay(int displayId, int fromRotation, int toRotation, - WindowContainerTransaction t) { - // This is triggered right before the rotation is applied - if (fromRotation != toRotation) { - if (mStackView != null) { - // Layout listener set on stackView will update the positioner - // once the rotation is applied - mStackView.onOrientationChanged(); - } + (displayId, fromRotation, toRotation, newDisplayAreaInfo, t) -> { + // This is triggered right before the rotation is applied + if (fromRotation != toRotation) { + if (mStackView != null) { + // Layout listener set on stackView will update the positioner + // once the rotation is applied + mStackView.onOrientationChanged(); } } }); @@ -885,6 +880,19 @@ public class BubbleController { } } + private void onNotificationPanelExpandedChanged(boolean expanded) { + if (DEBUG_BUBBLE_GESTURE) { + Log.d(TAG, "onNotificationPanelExpandedChanged: expanded=" + expanded); + } + if (mStackView != null && mStackView.isExpanded()) { + if (expanded) { + mStackView.stopMonitoringSwipeUpGesture(); + } else { + mStackView.startMonitoringSwipeUpGesture(); + } + } + } + private void setSysuiProxy(Bubbles.SysuiProxy proxy) { mSysuiProxy = proxy; } @@ -1468,6 +1476,18 @@ public class BubbleController { } /** + * Check if notification panel is in an expanded state. + * Makes a call to System UI process and delivers the result via {@code callback} on the + * WM Shell main thread. + * + * @param callback callback that has the result of notification panel expanded state + */ + public void isNotificationPanelExpanded(Consumer<Boolean> callback) { + mSysuiProxy.isNotificationPanelExpand(expanded -> + mMainExecutor.execute(() -> callback.accept(expanded))); + } + + /** * Description of current bubble state. */ private void dump(PrintWriter pw, String[] args) { @@ -1546,7 +1566,7 @@ public class BubbleController { public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) { mBubblePositioner.setImeVisible(imeVisible, imeHeight); if (mStackView != null) { - mStackView.animateForIme(imeVisible); + mStackView.setImeVisible(imeVisible); } } } @@ -1843,6 +1863,12 @@ public class BubbleController { } @Override + public void onNotificationPanelExpandedChanged(boolean expanded) { + mMainExecutor.execute( + () -> BubbleController.this.onNotificationPanelExpandedChanged(expanded)); + } + + @Override public void dump(PrintWriter pw, String[] args) { try { mMainExecutor.executeBlocking(() -> { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java index dc2ace949f0c..dce6b56261ff 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java @@ -46,6 +46,9 @@ public class BubbleDebugConfig { static final boolean DEBUG_OVERFLOW = false; static final boolean DEBUG_USER_EDUCATION = false; static final boolean DEBUG_POSITIONER = false; + public static final boolean DEBUG_COLLAPSE_ANIMATOR = false; + static final boolean DEBUG_BUBBLE_GESTURE = false; + public static boolean DEBUG_EXPANDED_VIEW_DRAGGING = false; private static final boolean FORCE_SHOW_USER_EDUCATION = false; private static final String FORCE_SHOW_USER_EDUCATION_SETTING = 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 b8bf1a8e497e..4f225fff1451 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 @@ -43,10 +43,13 @@ import android.graphics.CornerPathEffect; import android.graphics.Outline; import android.graphics.Paint; import android.graphics.Picture; +import android.graphics.PointF; import android.graphics.Rect; import android.graphics.drawable.ShapeDrawable; import android.os.RemoteException; import android.util.AttributeSet; +import android.util.FloatProperty; +import android.util.IntProperty; import android.util.Log; import android.util.TypedValue; import android.view.LayoutInflater; @@ -75,6 +78,62 @@ import java.io.PrintWriter; public class BubbleExpandedView extends LinearLayout { private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleExpandedView" : TAG_BUBBLES; + /** {@link IntProperty} for updating bottom clip */ + public static final IntProperty<BubbleExpandedView> BOTTOM_CLIP_PROPERTY = + new IntProperty<BubbleExpandedView>("bottomClip") { + @Override + public void setValue(BubbleExpandedView expandedView, int value) { + expandedView.setBottomClip(value); + } + + @Override + public Integer get(BubbleExpandedView expandedView) { + return expandedView.mBottomClip; + } + }; + + /** {@link FloatProperty} for updating taskView or overflow alpha */ + public static final FloatProperty<BubbleExpandedView> CONTENT_ALPHA = + new FloatProperty<BubbleExpandedView>("contentAlpha") { + @Override + public void setValue(BubbleExpandedView expandedView, float value) { + expandedView.setContentAlpha(value); + } + + @Override + public Float get(BubbleExpandedView expandedView) { + return expandedView.getContentAlpha(); + } + }; + + /** {@link FloatProperty} for updating background and pointer alpha */ + public static final FloatProperty<BubbleExpandedView> BACKGROUND_ALPHA = + new FloatProperty<BubbleExpandedView>("backgroundAlpha") { + @Override + public void setValue(BubbleExpandedView expandedView, float value) { + expandedView.setBackgroundAlpha(value); + } + + @Override + public Float get(BubbleExpandedView expandedView) { + return expandedView.getAlpha(); + } + }; + + /** {@link FloatProperty} for updating manage button alpha */ + public static final FloatProperty<BubbleExpandedView> MANAGE_BUTTON_ALPHA = + new FloatProperty<BubbleExpandedView>("manageButtonAlpha") { + @Override + public void setValue(BubbleExpandedView expandedView, float value) { + expandedView.mManageButton.setAlpha(value); + } + + @Override + public Float get(BubbleExpandedView expandedView) { + return expandedView.mManageButton.getAlpha(); + } + }; + // The triangle pointing to the expanded view private View mPointerView; @Nullable private int[] mExpandedViewContainerLocation; @@ -90,7 +149,7 @@ public class BubbleExpandedView extends LinearLayout { /** * Whether we want the {@code TaskView}'s content to be visible (alpha = 1f). If - * {@link #mIsAlphaAnimating} is true, this may not reflect the {@code TaskView}'s actual alpha + * {@link #mIsAnimating} is true, this may not reflect the {@code TaskView}'s actual alpha * value until the animation ends. */ private boolean mIsContentVisible = false; @@ -99,12 +158,13 @@ public class BubbleExpandedView extends LinearLayout { * Whether we're animating the {@code TaskView}'s alpha value. If so, we will hold off on * applying alpha changes from {@link #setContentVisibility} until the animation ends. */ - private boolean mIsAlphaAnimating = false; + private boolean mIsAnimating = false; private int mPointerWidth; private int mPointerHeight; private float mPointerRadius; private float mPointerOverlap; + private final PointF mPointerPos = new PointF(); private CornerPathEffect mPointerEffect; private ShapeDrawable mCurrentPointer; private ShapeDrawable mTopPointer; @@ -113,11 +173,13 @@ public class BubbleExpandedView extends LinearLayout { private float mCornerRadius = 0f; private int mBackgroundColorFloating; private boolean mUsingMaxHeight; - + private int mTopClip = 0; + private int mBottomClip = 0; @Nullable private Bubble mBubble; private PendingIntent mPendingIntent; // TODO(b/170891664): Don't use a flag, set the BubbleOverflow object instead private boolean mIsOverflow; + private boolean mIsClipping; private BubbleController mController; private BubbleStackView mStackView; @@ -268,7 +330,8 @@ public class BubbleExpandedView extends LinearLayout { mExpandedViewContainer.setOutlineProvider(new ViewOutlineProvider() { @Override public void getOutline(View view, Outline outline) { - outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCornerRadius); + Rect clip = new Rect(0, mTopClip, view.getWidth(), view.getHeight() - mBottomClip); + outline.setRoundRect(clip, mCornerRadius); } }); mExpandedViewContainer.setClipToOutline(true); @@ -300,9 +363,9 @@ public class BubbleExpandedView extends LinearLayout { // they should not collapse the stack (which all other touches on areas around the AV // would do). if (motionEvent.getRawY() >= avBounds.top - && motionEvent.getRawY() <= avBounds.bottom - && (motionEvent.getRawX() < avBounds.left - || motionEvent.getRawX() > avBounds.right)) { + && motionEvent.getRawY() <= avBounds.bottom + && (motionEvent.getRawX() < avBounds.left + || motionEvent.getRawX() > avBounds.right)) { return true; } @@ -384,7 +447,7 @@ public class BubbleExpandedView extends LinearLayout { } void applyThemeAttrs() { - final TypedArray ta = mContext.obtainStyledAttributes(new int[] { + final TypedArray ta = mContext.obtainStyledAttributes(new int[]{ android.R.attr.dialogCornerRadius, android.R.attr.colorBackgroundFloating}); boolean supportsRoundedCorners = ScreenDecorationsUtils.supportsRoundedCornersOnWindows( @@ -429,7 +492,7 @@ public class BubbleExpandedView extends LinearLayout { * ordering surfaces during animations. When content is drawn on top of the app (e.g. bubble * being dragged out, the manage menu) this is set to false, otherwise it should be true. */ - void setSurfaceZOrderedOnTop(boolean onTop) { + public void setSurfaceZOrderedOnTop(boolean onTop) { if (mTaskView == null) { return; } @@ -510,12 +573,12 @@ public class BubbleExpandedView extends LinearLayout { } /** - * Whether we are currently animating the {@code TaskView}'s alpha value. If this is set to + * Whether we are currently animating the {@code TaskView}. If this is set to * true, calls to {@link #setContentVisibility} will not be applied until this is set to false * again. */ - void setAlphaAnimating(boolean animating) { - mIsAlphaAnimating = animating; + public void setAnimating(boolean animating) { + mIsAnimating = animating; // If we're done animating, apply the correct if (!animating) { @@ -524,18 +587,139 @@ public class BubbleExpandedView extends LinearLayout { } /** - * Sets the alpha of the underlying {@code TaskView}, since changing the expanded view's alpha - * does not affect the {@code TaskView} since it uses a Surface. + * Get alpha from underlying {@code TaskView} if this view is for a bubble. + * Or get alpha for the overflow view if this view is for overflow. + * + * @return alpha for the content being shown */ - void setTaskViewAlpha(float alpha) { + public float getContentAlpha() { + if (mIsOverflow) { + return mOverflowView.getAlpha(); + } if (mTaskView != null) { + return mTaskView.getAlpha(); + } + return 1f; + } + + /** + * Set alpha of the underlying {@code TaskView} if this view is for a bubble. + * Or set alpha for the overflow view if this view is for overflow. + * + * Changing expanded view's alpha does not affect the {@code TaskView} since it uses a Surface. + */ + public void setContentAlpha(float alpha) { + if (mIsOverflow) { + mOverflowView.setAlpha(alpha); + } else if (mTaskView != null) { mTaskView.setAlpha(alpha); } + } + + /** + * Sets the alpha of the background and the pointer view. + */ + public void setBackgroundAlpha(float alpha) { mPointerView.setAlpha(alpha); setAlpha(alpha); } /** + * Set translation Y for the expanded view content. + * Excludes manage button and pointer. + */ + public void setContentTranslationY(float translationY) { + mExpandedViewContainer.setTranslationY(translationY); + + // Left or right pointer can become detached when moving the view up + if (translationY <= 0 && (isShowingLeftPointer() || isShowingRightPointer())) { + // Y coordinate where the pointer would start to get detached from the expanded view. + // Takes into account bottom clipping and rounded corners + float detachPoint = + mExpandedViewContainer.getBottom() - mBottomClip - mCornerRadius + translationY; + float pointerBottom = mPointerPos.y + mPointerHeight; + // If pointer bottom is past detach point, move it in by that many pixels + float horizontalShift = 0; + if (pointerBottom > detachPoint) { + horizontalShift = pointerBottom - detachPoint; + } + if (isShowingLeftPointer()) { + // Move left pointer right + movePointerBy(horizontalShift, 0); + } else { + // Move right pointer left + movePointerBy(-horizontalShift, 0); + } + // Hide pointer if it is moved by entire width + mPointerView.setVisibility( + horizontalShift > mPointerWidth ? View.INVISIBLE : View.VISIBLE); + } + } + + /** + * Update alpha value for the manage button + */ + public void setManageButtonAlpha(float alpha) { + mManageButton.setAlpha(alpha); + } + + /** + * Set {@link #setTranslationY(float) translationY} for the manage button + */ + public void setManageButtonTranslationY(float translationY) { + mManageButton.setTranslationY(translationY); + } + + /** + * Set top clipping for the view + */ + public void setTopClip(int clip) { + mTopClip = clip; + onContainerClipUpdate(); + } + + /** + * Set bottom clipping for the view + */ + public void setBottomClip(int clip) { + mBottomClip = clip; + onContainerClipUpdate(); + } + + private void onContainerClipUpdate() { + if (mTopClip == 0 && mBottomClip == 0) { + if (mIsClipping) { + mIsClipping = false; + if (mTaskView != null) { + mTaskView.setClipBounds(null); + mTaskView.setEnableSurfaceClipping(false); + } + mExpandedViewContainer.invalidateOutline(); + } + } else { + if (!mIsClipping) { + mIsClipping = true; + if (mTaskView != null) { + mTaskView.setEnableSurfaceClipping(true); + } + } + mExpandedViewContainer.invalidateOutline(); + if (mTaskView != null) { + mTaskView.setClipBounds(new Rect(0, mTopClip, mTaskView.getWidth(), + mTaskView.getHeight() - mBottomClip)); + } + } + } + + /** + * Move pointer from base position + */ + public void movePointerBy(float x, float y) { + mPointerView.setTranslationX(mPointerPos.x + x); + mPointerView.setTranslationY(mPointerPos.y + y); + } + + /** * Set visibility of contents in the expanded state. * * @param visibility {@code true} if the contents should be visible on the screen. @@ -543,13 +727,13 @@ public class BubbleExpandedView extends LinearLayout { * Note that this contents visibility doesn't affect visibility at {@link android.view.View}, * and setting {@code false} actually means rendering the contents in transparent. */ - void setContentVisibility(boolean visibility) { + public void setContentVisibility(boolean visibility) { if (DEBUG_BUBBLE_EXPANDED_VIEW) { Log.d(TAG, "setContentVisibility: visibility=" + visibility + " bubble=" + getBubbleKey()); } mIsContentVisible = visibility; - if (mTaskView != null && !mIsAlphaAnimating) { + if (mTaskView != null && !mIsAnimating) { mTaskView.setAlpha(visibility ? 1f : 0f); mPointerView.setAlpha(visibility ? 1f : 0f); } @@ -560,6 +744,44 @@ public class BubbleExpandedView extends LinearLayout { return mTaskView; } + @VisibleForTesting + public BubbleOverflowContainerView getOverflow() { + return mOverflowView; + } + + + /** + * Return content height: taskView or overflow. + * Takes into account clippings set by {@link #setTopClip(int)} and {@link #setBottomClip(int)} + * + * @return if bubble is for overflow, return overflow height, otherwise return taskView height + */ + public int getContentHeight() { + if (mIsOverflow) { + return mOverflowView.getHeight() - mTopClip - mBottomClip; + } + if (mTaskView != null) { + return mTaskView.getHeight() - mTopClip - mBottomClip; + } + return 0; + } + + /** + * Return bottom position of the content on screen + * + * @return if bubble is for overflow, return value for overflow, otherwise taskView + */ + public int getContentBottomOnScreen() { + Rect out = new Rect(); + if (mIsOverflow) { + mOverflowView.getBoundsOnScreen(out); + } + if (mTaskView != null) { + mTaskView.getBoundsOnScreen(out); + } + return out.bottom; + } + int getTaskId() { return mTaskId; } @@ -687,7 +909,9 @@ public class BubbleExpandedView extends LinearLayout { mTaskView.onLocationChanged(); } if (mIsOverflow) { - mOverflowView.show(); + post(() -> { + mOverflowView.show(); + }); } } @@ -730,38 +954,59 @@ public class BubbleExpandedView extends LinearLayout { post(() -> { mCurrentPointer = showVertically ? onLeft ? mLeftPointer : mRightPointer : mTopPointer; updatePointerView(); - float pointerY; - float pointerX; if (showVertically) { - pointerY = bubbleCenter - (mPointerWidth / 2f); + mPointerPos.y = bubbleCenter - (mPointerWidth / 2f); if (!isRtl) { - pointerX = onLeft + mPointerPos.x = onLeft ? -mPointerHeight + mPointerOverlap : getWidth() - mPaddingRight - mPointerOverlap; } else { - pointerX = onLeft + mPointerPos.x = onLeft ? -(getWidth() - mPaddingLeft - mPointerOverlap) : mPointerHeight - mPointerOverlap; } } else { - pointerY = mPointerOverlap; + mPointerPos.y = mPointerOverlap; if (!isRtl) { - pointerX = bubbleCenter - (mPointerWidth / 2f); + mPointerPos.x = bubbleCenter - (mPointerWidth / 2f); } else { - pointerX = -(getWidth() - mPaddingLeft - bubbleCenter) + (mPointerWidth / 2f); + mPointerPos.x = -(getWidth() - mPaddingLeft - bubbleCenter) + + (mPointerWidth / 2f); } } if (animate) { - mPointerView.animate().translationX(pointerX).translationY(pointerY).start(); + mPointerView.animate().translationX(mPointerPos.x).translationY( + mPointerPos.y).start(); } else { - mPointerView.setTranslationY(pointerY); - mPointerView.setTranslationX(pointerX); + mPointerView.setTranslationY(mPointerPos.y); + mPointerView.setTranslationX(mPointerPos.x); mPointerView.setVisibility(VISIBLE); } }); } /** + * Return true if pointer is shown on the left + */ + public boolean isShowingLeftPointer() { + return mCurrentPointer == mLeftPointer; + } + + /** + * Return true if pointer is shown on the right + */ + public boolean isShowingRightPointer() { + return mCurrentPointer == mRightPointer; + } + + /** + * Return width of the current pointer + */ + public int getPointerWidth() { + return mPointerWidth; + } + + /** * Position of the manage button displayed in the expanded view. Used for placing user * education about the manage button. */ 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 fcd0ed7308ef..9aa285fff19c 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 @@ -167,7 +167,10 @@ public class BubbleOverflowContainerView extends LinearLayout { void updateOverflow() { Resources res = getResources(); - final int columns = res.getInteger(R.integer.bubbles_overflow_columns); + int columns = (int) Math.round(getWidth() + / (res.getDimension(R.dimen.bubble_name_width))); + columns = columns > 0 ? columns : res.getInteger(R.integer.bubbles_overflow_columns); + mRecyclerView.setLayoutManager( new OverflowGridLayoutManager(getContext(), columns)); if (mRecyclerView.getItemDecorationCount() == 0) { 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 e9729e45731b..dbad5df9cf56 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 @@ -16,6 +16,8 @@ package com.android.wm.shell.bubbles; +import static android.view.View.LAYOUT_DIRECTION_RTL; + import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.IntDef; @@ -28,7 +30,6 @@ import android.graphics.Rect; import android.graphics.RectF; import android.util.Log; import android.view.Surface; -import android.view.View; import android.view.WindowInsets; import android.view.WindowManager; import android.view.WindowMetrics; @@ -366,6 +367,14 @@ public class BubblePositioner { return mImeVisible ? mImeHeight : 0; } + /** Return top position of the IME if it's visible */ + public int getImeTop() { + if (mImeVisible) { + return getScreenRect().bottom - getImeHeight() - getInsets().bottom; + } + return 0; + } + /** Sets whether the IME is visible. **/ public void setImeVisible(boolean visible, int height) { mImeVisible = visible; @@ -557,16 +566,30 @@ public class BubblePositioner { * @return the position of the bubble on-screen when the stack is expanded. */ public PointF getExpandedBubbleXY(int index, BubbleStackView.StackViewState state) { - final float positionInRow = index * (mBubbleSize + mSpacingBetweenBubbles); + boolean showBubblesVertically = showBubblesVertically(); + boolean isRtl = mContext.getResources().getConfiguration().getLayoutDirection() + == LAYOUT_DIRECTION_RTL; + + int onScreenIndex; + if (showBubblesVertically || !isRtl) { + onScreenIndex = index; + } else { + // If bubbles are shown horizontally, check if RTL language is used. + // If RTL is active, position first bubble on the right and last on the left. + // Last bubble has screen index 0 and first bubble has max screen index value. + onScreenIndex = state.numberOfBubbles - 1 - index; + } + + final float positionInRow = onScreenIndex * (mBubbleSize + mSpacingBetweenBubbles); final float expandedStackSize = getExpandedStackSize(state.numberOfBubbles); - final float centerPosition = showBubblesVertically() + final float centerPosition = showBubblesVertically ? mPositionRect.centerY() : mPositionRect.centerX(); // alignment - centered on the edge final float rowStart = centerPosition - (expandedStackSize / 2f); float x; float y; - if (showBubblesVertically()) { + if (showBubblesVertically) { int inset = mExpandedViewLargeScreenInsetClosestEdge; y = rowStart + positionInRow; int left = mIsLargeScreen @@ -583,8 +606,8 @@ public class BubblePositioner { x = rowStart + positionInRow; } - if (showBubblesVertically() && mImeVisible) { - return new PointF(x, getExpandedBubbleYForIme(index, state)); + if (showBubblesVertically && mImeVisible) { + return new PointF(x, getExpandedBubbleYForIme(onScreenIndex, state)); } return new PointF(x, y); } @@ -693,7 +716,7 @@ public class BubblePositioner { // Start on the left if we're in LTR, right otherwise. final boolean startOnLeft = mContext.getResources().getConfiguration().getLayoutDirection() - != View.LAYOUT_DIRECTION_RTL; + != LAYOUT_DIRECTION_RTL; final float startingVerticalOffset = mContext.getResources().getDimensionPixelOffset( R.dimen.bubble_stack_starting_offset_y); // TODO: placement bug here because mPositionRect doesn't handle the overhanging edge @@ -749,4 +772,21 @@ public class BubblePositioner { public void setPinnedLocation(PointF point) { mPinLocation = point; } + + /** + * Navigation bar has an area where system gestures can be started from. + * + * @return {@link Rect} for system navigation bar gesture zone + */ + public Rect getNavBarGestureZone() { + // Gesture zone height from the bottom + int gestureZoneHeight = mContext.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.navigation_bar_gesture_height); + Rect screen = getScreenRect(); + return new Rect( + screen.left, + screen.bottom - gestureZoneHeight, + screen.right, + screen.bottom); + } } 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 0e8dc63943a6..66be2a0cebea 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -21,6 +21,7 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static com.android.wm.shell.animation.Interpolators.ALPHA_IN; import static com.android.wm.shell.animation.Interpolators.ALPHA_OUT; +import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_GESTURE; import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_STACK_VIEW; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; @@ -44,6 +45,7 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.ColorDrawable; import android.os.Bundle; +import android.os.SystemProperties; import android.provider.Settings; import android.util.Log; import android.view.Choreographer; @@ -56,6 +58,7 @@ import android.view.View; import android.view.ViewGroup; import android.view.ViewOutlineProvider; import android.view.ViewTreeObserver; +import android.view.WindowManagerPolicyConstants; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.widget.FrameLayout; @@ -75,8 +78,12 @@ import com.android.internal.util.FrameworkStatsLog; import com.android.wm.shell.R; import com.android.wm.shell.animation.Interpolators; import com.android.wm.shell.animation.PhysicsAnimator; +import com.android.wm.shell.bubbles.BubblesNavBarMotionEventHandler.MotionEventListener; import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix; import com.android.wm.shell.bubbles.animation.ExpandedAnimationController; +import com.android.wm.shell.bubbles.animation.ExpandedViewAnimationController; +import com.android.wm.shell.bubbles.animation.ExpandedViewAnimationControllerImpl; +import com.android.wm.shell.bubbles.animation.ExpandedViewAnimationControllerStub; import com.android.wm.shell.bubbles.animation.PhysicsAnimationLayout; import com.android.wm.shell.bubbles.animation.StackAnimationController; import com.android.wm.shell.common.FloatingContentCoordinator; @@ -89,6 +96,7 @@ import java.math.RoundingMode; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -97,6 +105,12 @@ import java.util.stream.Collectors; */ public class BubbleStackView extends FrameLayout implements ViewTreeObserver.OnComputeInternalInsetsListener { + /** + * Set to {@code true} to enable home gesture handling in bubbles + */ + public static final boolean HOME_GESTURE_ENABLED = + SystemProperties.getBoolean("persist.wm.debug.bubbles_home_gesture", true); + private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleStackView" : TAG_BUBBLES; /** How far the flyout needs to be dragged before it's dismissed regardless of velocity. */ @@ -123,6 +137,9 @@ public class BubbleStackView extends FrameLayout private static final float SCRIM_ALPHA = 0.6f; + /** Minimum alpha value for scrim when alpha is being changed via drag */ + private static final float MIN_SCRIM_ALPHA_FOR_DRAG = 0.2f; + /** * How long to wait to animate the stack temporarily invisible after a drag/flyout hide * animation ends, if we are in fact temporarily invisible. @@ -148,7 +165,7 @@ public class BubbleStackView extends FrameLayout * Handler to use for all delayed animations - this way, we can easily cancel them before * starting a new animation. */ - private final ShellExecutor mDelayedAnimationExecutor; + private final ShellExecutor mMainExecutor; private Runnable mDelayedAnimation; /** @@ -197,8 +214,10 @@ public class BubbleStackView extends FrameLayout private PhysicsAnimationLayout mBubbleContainer; private StackAnimationController mStackAnimationController; private ExpandedAnimationController mExpandedAnimationController; + private ExpandedViewAnimationController mExpandedViewAnimationController; private View mScrim; + private boolean mScrimAnimating; private View mManageMenuScrim; private FrameLayout mExpandedViewContainer; @@ -276,6 +295,9 @@ public class BubbleStackView extends FrameLayout */ private int mPointerIndexDown = -1; + @Nullable + private BubblesNavBarGestureTracker mBubblesNavBarGestureTracker; + /** Description of current animation controller state. */ public void dump(PrintWriter pw, String[] args) { pw.println("Stack view state:"); @@ -693,6 +715,90 @@ public class BubbleStackView extends FrameLayout } }; + /** Touch listener set on the whole view that forwards event to the swipe up listener. */ + private final RelativeTouchListener mContainerSwipeListener = new RelativeTouchListener() { + @Override + public boolean onDown(@NonNull View v, @NonNull MotionEvent ev) { + // Pass move event on to swipe listener + mSwipeUpListener.onDown(ev.getX(), ev.getY()); + return true; + } + + @Override + public void onMove(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX, + float viewInitialY, float dx, float dy) { + // Pass move event on to swipe listener + mSwipeUpListener.onMove(dx, dy); + } + + @Override + public void onUp(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX, + float viewInitialY, float dx, float dy, float velX, float velY) { + // Pass up even on to swipe listener + mSwipeUpListener.onUp(velX, velY); + } + }; + + /** MotionEventListener that listens from home gesture swipe event. */ + private final MotionEventListener mSwipeUpListener = new MotionEventListener() { + @Override + public void onDown(float x, float y) {} + + @Override + public void onMove(float dx, float dy) { + if ((mManageEduView != null && mManageEduView.getVisibility() == VISIBLE) + || isStackEduShowing()) { + return; + } + + if (mShowingManage) { + showManageMenu(false /* show */); + } + // Only allow up, normalize for up direction + float collapsed = -Math.min(dy, 0); + mExpandedViewAnimationController.updateDrag((int) collapsed); + + // Update scrim + if (!mScrimAnimating) { + mScrim.setAlpha(getScrimAlphaForDrag(collapsed)); + } + } + + @Override + public void onCancel() { + mExpandedViewAnimationController.animateBackToExpanded(); + } + + @Override + public void onUp(float velX, float velY) { + mExpandedViewAnimationController.setSwipeVelocity(velY); + if (mExpandedViewAnimationController.shouldCollapse()) { + // Update data first and start the animation when we are processing change + mBubbleData.setExpanded(false); + } else { + mExpandedViewAnimationController.animateBackToExpanded(); + + // Update scrim + if (!mScrimAnimating) { + showScrim(true); + } + } + } + + private float getScrimAlphaForDrag(float dragAmount) { + // dragAmount should be negative as we allow scroll up only + if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { + float alphaRange = SCRIM_ALPHA - MIN_SCRIM_ALPHA_FOR_DRAG; + + int dragMax = mExpandedBubble.getExpandedView().getContentHeight(); + float dragFraction = dragAmount / dragMax; + + return Math.max(SCRIM_ALPHA - alphaRange * dragFraction, MIN_SCRIM_ALPHA_FOR_DRAG); + } + return SCRIM_ALPHA; + } + }; + /** Click listener set on the flyout, which expands the stack when the flyout is tapped. */ private OnClickListener mFlyoutClickListener = new OnClickListener() { @Override @@ -766,7 +872,7 @@ public class BubbleStackView extends FrameLayout ShellExecutor mainExecutor) { super(context); - mDelayedAnimationExecutor = mainExecutor; + mMainExecutor = mainExecutor; mBubbleController = bubbleController; mBubbleData = data; @@ -796,6 +902,14 @@ public class BubbleStackView extends FrameLayout mExpandedAnimationController = new ExpandedAnimationController(mPositioner, onBubbleAnimatedOut, this); + + if (HOME_GESTURE_ENABLED) { + mExpandedViewAnimationController = + new ExpandedViewAnimationControllerImpl(context, mPositioner); + } else { + mExpandedViewAnimationController = new ExpandedViewAnimationControllerStub(); + } + mSurfaceSynchronizer = synchronizer != null ? synchronizer : DEFAULT_SURFACE_SYNCHRONIZER; // Force LTR by default since most of the Bubbles UI is positioned manually by the user, or @@ -971,7 +1085,7 @@ public class BubbleStackView extends FrameLayout if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { // We need to be Z ordered on top in order for alpha animations to work. mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(true); - mExpandedBubble.getExpandedView().setAlphaAnimating(true); + mExpandedBubble.getExpandedView().setAnimating(true); } } @@ -985,14 +1099,15 @@ public class BubbleStackView extends FrameLayout // = 0f remains in effect. && !mExpandedViewTemporarilyHidden) { mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false); - mExpandedBubble.getExpandedView().setAlphaAnimating(false); + mExpandedBubble.getExpandedView().setAnimating(false); } } }); mExpandedViewAlphaAnimator.addUpdateListener(valueAnimator -> { if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { - mExpandedBubble.getExpandedView().setTaskViewAlpha( - (float) valueAnimator.getAnimatedValue()); + float alpha = (float) valueAnimator.getAnimatedValue(); + mExpandedBubble.getExpandedView().setContentAlpha(alpha); + mExpandedBubble.getExpandedView().setBackgroundAlpha(alpha); } }); @@ -1795,6 +1910,7 @@ public class BubbleStackView extends FrameLayout private void showNewlySelectedBubble(BubbleViewProvider bubbleToSelect) { final BubbleViewProvider previouslySelected = mExpandedBubble; mExpandedBubble = bubbleToSelect; + mExpandedViewAnimationController.setExpandedView(mExpandedBubble.getExpandedView()); if (mIsExpanded) { hideCurrentInputMethod(); @@ -1843,12 +1959,19 @@ public class BubbleStackView extends FrameLayout return; } + boolean wasExpanded = mIsExpanded; + hideCurrentInputMethod(); mBubbleController.getSysuiProxy().onStackExpandChanged(shouldExpand); - if (mIsExpanded) { - animateCollapse(); + if (wasExpanded) { + stopMonitoringSwipeUpGesture(); + if (HOME_GESTURE_ENABLED) { + animateCollapse(); + } else { + animateCollapseWithScale(); + } logBubbleEvent(mExpandedBubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED); } else { animateExpansion(); @@ -1856,11 +1979,58 @@ public class BubbleStackView extends FrameLayout logBubbleEvent(mExpandedBubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED); logBubbleEvent(mExpandedBubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__STACK_EXPANDED); + if (HOME_GESTURE_ENABLED) { + mBubbleController.isNotificationPanelExpanded(notifPanelExpanded -> { + if (!notifPanelExpanded && mIsExpanded) { + startMonitoringSwipeUpGesture(); + } + }); + } } notifyExpansionChanged(mExpandedBubble, mIsExpanded); } /** + * Monitor for swipe up gesture that is used to collapse expanded view + */ + void startMonitoringSwipeUpGesture() { + if (DEBUG_BUBBLE_GESTURE) { + Log.d(TAG, "startMonitoringSwipeUpGesture"); + } + stopMonitoringSwipeUpGestureInternal(); + + if (isGestureNavEnabled()) { + mBubblesNavBarGestureTracker = new BubblesNavBarGestureTracker(mContext, mPositioner); + mBubblesNavBarGestureTracker.start(mSwipeUpListener); + setOnTouchListener(mContainerSwipeListener); + } + } + + private boolean isGestureNavEnabled() { + return mContext.getResources().getInteger( + com.android.internal.R.integer.config_navBarInteractionMode) + == WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL; + } + + /** + * Stop monitoring for swipe up gesture + */ + void stopMonitoringSwipeUpGesture() { + if (DEBUG_BUBBLE_GESTURE) { + Log.d(TAG, "stopMonitoringSwipeUpGesture"); + } + stopMonitoringSwipeUpGestureInternal(); + } + + private void stopMonitoringSwipeUpGestureInternal() { + if (mBubblesNavBarGestureTracker != null) { + mBubblesNavBarGestureTracker.stop(); + mBubblesNavBarGestureTracker = null; + setOnTouchListener(null); + } + } + + /** * Called when back press occurs while bubbles are expanded. */ public void onBackPressed() { @@ -1982,15 +2152,28 @@ public class BubbleStackView extends FrameLayout } private void showScrim(boolean show) { + AnimatorListenerAdapter listener = new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mScrimAnimating = true; + } + + @Override + public void onAnimationEnd(Animator animation) { + mScrimAnimating = false; + } + }; if (show) { mScrim.animate() .setInterpolator(ALPHA_IN) .alpha(SCRIM_ALPHA) + .setListener(listener) .start(); } else { mScrim.animate() .alpha(0f) .setInterpolator(ALPHA_OUT) + .setListener(listener) .start(); } } @@ -2072,11 +2255,12 @@ public class BubbleStackView extends FrameLayout mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix); if (mExpandedBubble.getExpandedView() != null) { - mExpandedBubble.getExpandedView().setTaskViewAlpha(0f); + mExpandedBubble.getExpandedView().setContentAlpha(0f); + mExpandedBubble.getExpandedView().setBackgroundAlpha(0f); // We'll be starting the alpha animation after a slight delay, so set this flag early // here. - mExpandedBubble.getExpandedView().setAlphaAnimating(true); + mExpandedBubble.getExpandedView().setAnimating(true); } mDelayedAnimation = () -> { @@ -2114,10 +2298,10 @@ public class BubbleStackView extends FrameLayout }) .start(); }; - mDelayedAnimationExecutor.executeDelayed(mDelayedAnimation, startDelay); + mMainExecutor.executeDelayed(mDelayedAnimation, startDelay); } - private void animateCollapse() { + private void animateCollapseWithScale() { cancelDelayedExpandCollapseSwitchAnimations(); if (mManageEduView != null && mManageEduView.getVisibility() == VISIBLE) { @@ -2217,6 +2401,68 @@ public class BubbleStackView extends FrameLayout .start(); } + private void animateCollapse() { + cancelDelayedExpandCollapseSwitchAnimations(); + + if (mManageEduView != null && mManageEduView.getVisibility() == VISIBLE) { + mManageEduView.hide(); + } + + mIsExpanded = false; + mIsExpansionAnimating = true; + + showScrim(false); + + mBubbleContainer.cancelAllAnimations(); + + // If we were in the middle of swapping, the animating-out surface would have been scaling + // to zero - finish it off. + PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer).cancel(); + mAnimatingOutSurfaceContainer.setScaleX(0f); + mAnimatingOutSurfaceContainer.setScaleY(0f); + + // Let the expanded animation controller know that it shouldn't animate child adds/reorders + // since we're about to animate collapsed. + mExpandedAnimationController.notifyPreparingToCollapse(); + + final Runnable collapseBackToStack = () -> mExpandedAnimationController.collapseBackToStack( + mStackAnimationController + .getStackPositionAlongNearestHorizontalEdge() + /* collapseTo */, + () -> mBubbleContainer.setActiveController(mStackAnimationController)); + + final Runnable after = () -> { + final BubbleViewProvider previouslySelected = mExpandedBubble; + // TODO(b/231350255): investigate why this call is needed here + beforeExpandedViewAnimation(); + if (mManageEduView != null) { + mManageEduView.hide(); + } + + if (DEBUG_BUBBLE_STACK_VIEW) { + Log.d(TAG, "animateCollapse"); + Log.d(TAG, BubbleDebugConfig.formatBubblesString(getBubblesOnScreen(), + mExpandedBubble)); + } + updateOverflowVisibility(); + updateZOrder(); + updateBadges(true /* setBadgeForCollapsedStack */); + afterExpandedViewAnimation(); + if (previouslySelected != null) { + previouslySelected.setTaskViewVisibility(false); + } + mExpandedViewAnimationController.reset(); + }; + mExpandedViewAnimationController.animateCollapse(collapseBackToStack, after); + if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { + // When the animation completes, we should no longer be showing the content. + // This won't actually update content visibility immediately, if we are currently + // animating. But updates the internal state for the content to be hidden after + // animation completes. + mExpandedBubble.getExpandedView().setContentVisibility(false); + } + } + private void animateSwitchBubbles() { // If we're no longer expanded, this is meaningless. if (!mIsExpanded) { @@ -2278,7 +2524,7 @@ public class BubbleStackView extends FrameLayout mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix); - mDelayedAnimationExecutor.executeDelayed(() -> { + mMainExecutor.executeDelayed(() -> { if (!mIsExpanded) { mIsBubbleSwitchAnimating = false; return; @@ -2309,7 +2555,7 @@ public class BubbleStackView extends FrameLayout * animating flags for those animations. */ private void cancelDelayedExpandCollapseSwitchAnimations() { - mDelayedAnimationExecutor.removeCallbacks(mDelayedAnimation); + mMainExecutor.removeCallbacks(mDelayedAnimation); mIsExpansionAnimating = false; mIsBubbleSwitchAnimating = false; @@ -2333,9 +2579,18 @@ public class BubbleStackView extends FrameLayout /** * Updates the stack based for IME changes. When collapsed it'll move the stack if it * overlaps where they IME would be. When expanded it'll shift the expanded bubbles - * if they might overlap with the IME (this only happens for large screens). + * if they might overlap with the IME (this only happens for large screens) + * and clip the expanded view. */ - public void animateForIme(boolean visible) { + public void setImeVisible(boolean visible) { + if (HOME_GESTURE_ENABLED) { + setImeVisibleInternal(visible); + } else { + setImeVisibleWithoutClipping(visible); + } + } + + private void setImeVisibleWithoutClipping(boolean visible) { if ((mIsExpansionAnimating || mIsBubbleSwitchAnimating) && mIsExpanded) { // This will update the animation so the bubbles move to position for the IME mExpandedAnimationController.expandFromStack(() -> { @@ -2386,6 +2641,62 @@ public class BubbleStackView extends FrameLayout } } + private void setImeVisibleInternal(boolean visible) { + if ((mIsExpansionAnimating || mIsBubbleSwitchAnimating) && mIsExpanded) { + // This will update the animation so the bubbles move to position for the IME + mExpandedAnimationController.expandFromStack(() -> { + updatePointerPosition(false /* forIme */); + afterExpandedViewAnimation(); + mExpandedViewAnimationController.animateForImeVisibilityChange(visible); + } /* after */); + return; + } + + if (!mIsExpanded && getBubbleCount() > 0) { + final float stackDestinationY = + mStackAnimationController.animateForImeVisibility(visible); + + // How far the stack is animating due to IME, we'll just animate the flyout by that + // much too. + final float stackDy = + stackDestinationY - mStackAnimationController.getStackPosition().y; + + // If the flyout is visible, translate it along with the bubble stack. + if (mFlyout.getVisibility() == VISIBLE) { + PhysicsAnimator.getInstance(mFlyout) + .spring(DynamicAnimation.TRANSLATION_Y, + mFlyout.getTranslationY() + stackDy, + FLYOUT_IME_ANIMATION_SPRING_CONFIG) + .start(); + } + } + + if (mIsExpanded) { + mExpandedViewAnimationController.animateForImeVisibilityChange(visible); + if (mPositioner.showBubblesVertically() + && mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { + float selectedY = mPositioner.getExpandedBubbleXY(getState().selectedIndex, + getState()).y; + float newExpandedViewTop = mPositioner.getExpandedViewY(mExpandedBubble, selectedY); + mExpandedBubble.getExpandedView().setImeVisible(visible); + if (!mExpandedBubble.getExpandedView().isUsingMaxHeight()) { + mExpandedViewContainer.animate().translationY(newExpandedViewTop); + } + List<Animator> animList = new ArrayList(); + for (int i = 0; i < mBubbleContainer.getChildCount(); i++) { + View child = mBubbleContainer.getChildAt(i); + float transY = mPositioner.getExpandedBubbleXY(i, getState()).y; + ObjectAnimator anim = ObjectAnimator.ofFloat(child, TRANSLATION_Y, transY); + animList.add(anim); + } + updatePointerPosition(true /* forIme */); + AnimatorSet set = new AnimatorSet(); + set.playTogether(animList); + set.start(); + } + } + } + @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() != MotionEvent.ACTION_DOWN && ev.getActionIndex() != mPointerIndexDown) { @@ -2474,8 +2785,10 @@ public class BubbleStackView extends FrameLayout private void dismissBubbleIfExists(@Nullable BubbleViewProvider bubble) { if (bubble != null && mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) { - if (mIsExpanded && mBubbleData.getBubbles().size() > 1) { - // If we have more than 1 bubble we will perform the switch animation + if (mIsExpanded && mBubbleData.getBubbles().size() > 1 + && Objects.equals(bubble, mExpandedBubble)) { + // If we have more than 1 bubble and it's the current bubble being dismissed, + // we will perform the switch animation mIsBubbleSwitchAnimating = true; } mBubbleData.dismissBubbleWithKey(bubble.getKey(), Bubbles.DISMISS_USER_GESTURE); @@ -2821,7 +3134,7 @@ public class BubbleStackView extends FrameLayout && mExpandedBubble.getExpandedView() != null) { BubbleExpandedView bev = mExpandedBubble.getExpandedView(); bev.setContentVisibility(false); - bev.setAlphaAnimating(!mIsExpansionAnimating); + bev.setAnimating(!mIsExpansionAnimating); mExpandedViewContainerMatrix.setScaleX(0f); mExpandedViewContainerMatrix.setScaleY(0f); mExpandedViewContainerMatrix.setTranslate(0f, 0f); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java index 8a0db0a12711..0072da19b9ef 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java @@ -214,6 +214,11 @@ public interface Bubbles { int modificationType); /** + * Called when notification panel is expanded or collapsed + */ + void onNotificationPanelExpandedChanged(boolean expanded); + + /** * Called when the status bar has become visible or invisible (either permanently or * temporarily). */ @@ -285,7 +290,7 @@ public interface Bubbles { /** Callback to tell SysUi components execute some methods. */ interface SysuiProxy { - void isNotificationShadeExpand(Consumer<Boolean> callback); + void isNotificationPanelExpand(Consumer<Boolean> callback); void getPendingOrActiveEntry(String key, Consumer<BubbleEntry> callback); 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 new file mode 100644 index 000000000000..e7beeeb06534 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java @@ -0,0 +1,104 @@ +/* + * 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.bubbles; + +import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES; +import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; + +import android.content.Context; +import android.hardware.input.InputManager; +import android.util.Log; +import android.view.Choreographer; +import android.view.InputChannel; +import android.view.InputEventReceiver; +import android.view.InputMonitor; + +import androidx.annotation.Nullable; + +import com.android.wm.shell.bubbles.BubblesNavBarMotionEventHandler.MotionEventListener; + +/** + * Set up tracking bubbles gestures that begin in navigation bar + */ +class BubblesNavBarGestureTracker { + + private static final String TAG = TAG_WITH_CLASS_NAME ? "BubblesGestureTracker" : TAG_BUBBLES; + + private static final String GESTURE_MONITOR = "bubbles-gesture"; + private final Context mContext; + private final BubblePositioner mPositioner; + + @Nullable + private InputMonitor mInputMonitor; + @Nullable + private InputEventReceiver mInputEventReceiver; + + BubblesNavBarGestureTracker(Context context, BubblePositioner positioner) { + mContext = context; + mPositioner = positioner; + } + + /** + * Start tracking gestures + * + * @param listener listener that is notified of touch events + */ + void start(MotionEventListener listener) { + if (BubbleDebugConfig.DEBUG_BUBBLE_GESTURE) { + Log.d(TAG, "start monitoring bubbles swipe up gesture"); + } + + stopInternal(); + + mInputMonitor = InputManager.getInstance().monitorGestureInput(GESTURE_MONITOR, + mContext.getDisplayId()); + InputChannel inputChannel = mInputMonitor.getInputChannel(); + + BubblesNavBarMotionEventHandler motionEventHandler = + new BubblesNavBarMotionEventHandler(mContext, mPositioner, + this::onInterceptTouch, listener); + mInputEventReceiver = new BubblesNavBarInputEventReceiver(inputChannel, + Choreographer.getInstance(), motionEventHandler); + } + + void stop() { + if (BubbleDebugConfig.DEBUG_BUBBLE_GESTURE) { + Log.d(TAG, "stop monitoring bubbles swipe up gesture"); + } + stopInternal(); + } + + private void stopInternal() { + if (mInputEventReceiver != null) { + mInputEventReceiver.dispose(); + mInputEventReceiver = null; + } + if (mInputMonitor != null) { + mInputMonitor.dispose(); + mInputMonitor = null; + } + } + + private void onInterceptTouch() { + if (BubbleDebugConfig.DEBUG_BUBBLE_GESTURE) { + Log.d(TAG, "intercept touch event"); + } + if (mInputMonitor != null) { + mInputMonitor.pilferPointers(); + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarInputEventReceiver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarInputEventReceiver.java new file mode 100644 index 000000000000..45037b87830f --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarInputEventReceiver.java @@ -0,0 +1,51 @@ +/* + * 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.bubbles; + +import android.os.Looper; +import android.view.BatchedInputEventReceiver; +import android.view.Choreographer; +import android.view.InputChannel; +import android.view.InputEvent; +import android.view.MotionEvent; + +/** + * Bubbles {@link BatchedInputEventReceiver} for monitoring touches from navbar gesture area + */ +class BubblesNavBarInputEventReceiver extends BatchedInputEventReceiver { + + private final BubblesNavBarMotionEventHandler mMotionEventHandler; + + BubblesNavBarInputEventReceiver(InputChannel inputChannel, + Choreographer choreographer, BubblesNavBarMotionEventHandler motionEventHandler) { + super(inputChannel, Looper.myLooper(), choreographer); + mMotionEventHandler = motionEventHandler; + } + + @Override + public void onInputEvent(InputEvent event) { + boolean handled = false; + try { + if (!(event instanceof MotionEvent)) { + return; + } + handled = mMotionEventHandler.onMotionEvent((MotionEvent) event); + } finally { + finishInputEvent(event, handled); + } + } +} 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 new file mode 100644 index 000000000000..844526ca0f35 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandler.java @@ -0,0 +1,175 @@ +/* + * 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.bubbles; + +import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_GESTURE; +import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES; +import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; + +import android.content.Context; +import android.graphics.PointF; +import android.util.Log; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.ViewConfiguration; + +import androidx.annotation.Nullable; + +/** + * Handles {@link MotionEvent}s for bubbles that begin in the nav bar area + */ +class BubblesNavBarMotionEventHandler { + private static final String TAG = + TAG_WITH_CLASS_NAME ? "BubblesNavBarMotionEventHandler" : TAG_BUBBLES; + private static final int VELOCITY_UNITS = 1000; + + private final Runnable mOnInterceptTouch; + private final MotionEventListener mMotionEventListener; + private final int mTouchSlop; + private final BubblePositioner mPositioner; + private final PointF mTouchDown = new PointF(); + private boolean mTrackingTouches; + private boolean mInterceptingTouches; + @Nullable + private VelocityTracker mVelocityTracker; + + BubblesNavBarMotionEventHandler(Context context, BubblePositioner positioner, + Runnable onInterceptTouch, MotionEventListener motionEventListener) { + mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + mPositioner = positioner; + mOnInterceptTouch = onInterceptTouch; + mMotionEventListener = motionEventListener; + } + + /** + * Handle {@link MotionEvent} and forward it to {@code motionEventListener} defined in + * constructor + * + * @return {@code true} if this {@link MotionEvent} is handled (it started in the gesture area) + */ + public boolean onMotionEvent(MotionEvent motionEvent) { + float dx = motionEvent.getX() - mTouchDown.x; + float dy = motionEvent.getY() - mTouchDown.y; + + switch (motionEvent.getAction()) { + case MotionEvent.ACTION_DOWN: + if (isInGestureRegion(motionEvent)) { + mTouchDown.set(motionEvent.getX(), motionEvent.getY()); + mMotionEventListener.onDown(motionEvent.getX(), motionEvent.getY()); + mTrackingTouches = true; + return true; + } + break; + case MotionEvent.ACTION_MOVE: + if (mTrackingTouches) { + if (!mInterceptingTouches && Math.hypot(dx, dy) > mTouchSlop) { + mInterceptingTouches = true; + mOnInterceptTouch.run(); + } + if (mInterceptingTouches) { + getVelocityTracker().addMovement(motionEvent); + mMotionEventListener.onMove(dx, dy); + } + return true; + } + break; + case MotionEvent.ACTION_CANCEL: + if (mTrackingTouches) { + mMotionEventListener.onCancel(); + finishTracking(); + return true; + } + break; + case MotionEvent.ACTION_UP: + if (mTrackingTouches) { + if (mInterceptingTouches) { + getVelocityTracker().computeCurrentVelocity(VELOCITY_UNITS); + mMotionEventListener.onUp(getVelocityTracker().getXVelocity(), + getVelocityTracker().getYVelocity()); + } + finishTracking(); + return true; + } + break; + } + return false; + } + + private boolean isInGestureRegion(MotionEvent ev) { + // Only handles touch events beginning in navigation bar system gesture zone + if (mPositioner.getNavBarGestureZone().contains((int) ev.getX(), (int) ev.getY())) { + if (DEBUG_BUBBLE_GESTURE) { + Log.d(TAG, "handling touch y=" + ev.getY() + + " navBarGestureZone=" + mPositioner.getNavBarGestureZone()); + } + return true; + } + return false; + } + + private VelocityTracker getVelocityTracker() { + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } + return mVelocityTracker; + } + + private void finishTracking() { + mTouchDown.set(0, 0); + mTrackingTouches = false; + mInterceptingTouches = false; + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + } + + /** + * Callback for receiving {@link MotionEvent} updates + */ + interface MotionEventListener { + /** + * Touch down action. + * + * @param x x coordinate + * @param y y coordinate + */ + void onDown(float x, float y); + + /** + * Move action. + * Reports distance from point reported in {@link #onDown(float, float)} + * + * @param dx distance moved on x-axis from starting point, in pixels + * @param dy distance moved on y-axis from starting point, in pixels + */ + void onMove(float dx, float dy); + + /** + * Touch up action. + * + * @param velX velocity of the move action on x axis + * @param velY velocity of the move actin on y axis + */ + void onUp(float velX, float velY); + + /** + * Motion action was cancelled. + */ + void onCancel(); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RelativeTouchListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RelativeTouchListener.kt index cf0cefec401a..ea9d065d5f53 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RelativeTouchListener.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RelativeTouchListener.kt @@ -17,8 +17,6 @@ package com.android.wm.shell.bubbles import android.graphics.PointF -import android.os.Handler -import android.os.Looper import android.view.MotionEvent import android.view.VelocityTracker import android.view.View @@ -146,6 +144,12 @@ abstract class RelativeTouchListener : View.OnTouchListener { velocityTracker.clear() movedEnough = false } + + MotionEvent.ACTION_CANCEL -> { + v.handler.removeCallbacksAndMessages(null) + velocityTracker.clear() + movedEnough = false + } } return true diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java index 573f42468512..b521cb6a3d38 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java @@ -16,12 +16,16 @@ package com.android.wm.shell.bubbles.animation; +import static android.view.View.LAYOUT_DIRECTION_RTL; + import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING; +import static com.android.wm.shell.bubbles.BubbleStackView.HOME_GESTURE_ENABLED; import android.content.res.Resources; import android.graphics.Path; import android.graphics.PointF; import android.view.View; +import android.view.animation.Interpolator; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -61,7 +65,10 @@ public class ExpandedAnimationController private static final float DAMPING_RATIO_MEDIUM_LOW_BOUNCY = 0.65f; /** Stiffness for the expand/collapse path-following animation. */ - private static final int EXPAND_COLLAPSE_ANIM_STIFFNESS = 1000; + private static final int EXPAND_COLLAPSE_ANIM_STIFFNESS = 400; + + /** Stiffness for the expand/collapse animation when home gesture handling is off */ + private static final int EXPAND_COLLAPSE_ANIM_STIFFNESS_WITHOUT_HOME_GESTURE = 1000; /** * Velocity required to dismiss an individual bubble without dragging it into the dismiss @@ -73,6 +80,11 @@ public class ExpandedAnimationController new PhysicsAnimator.SpringConfig( EXPAND_COLLAPSE_ANIM_STIFFNESS, SpringForce.DAMPING_RATIO_NO_BOUNCY); + private final PhysicsAnimator.SpringConfig mAnimateOutSpringConfigWithoutHomeGesture = + new PhysicsAnimator.SpringConfig( + EXPAND_COLLAPSE_ANIM_STIFFNESS_WITHOUT_HOME_GESTURE, + SpringForce.DAMPING_RATIO_NO_BOUNCY); + /** Horizontal offset between bubbles, which we need to know to re-stack them. */ private float mStackOffsetPx; /** Size of each bubble. */ @@ -233,6 +245,11 @@ public class ExpandedAnimationController }; } + boolean showBubblesVertically = mPositioner.showBubblesVertically(); + final boolean isRtl = + mLayout.getContext().getResources().getConfiguration().getLayoutDirection() + == LAYOUT_DIRECTION_RTL; + // Animate each bubble individually, since each path will end in a different spot. animationsForChildrenFromIndex(0, (index, animation) -> { final View bubble = mLayout.getChildAt(index); @@ -267,9 +284,20 @@ public class ExpandedAnimationController // right side, the first bubble is traveling to the top left, so it leads. During // collapse to the left, the first bubble has the shortest travel time back to the stack // position, so it leads (and vice versa). - final boolean firstBubbleLeads = - (expanding && !mLayout.isFirstChildXLeftOfCenter(bubble.getTranslationX())) + final boolean firstBubbleLeads; + if (showBubblesVertically || !isRtl) { + firstBubbleLeads = + (expanding && !mLayout.isFirstChildXLeftOfCenter(bubble.getTranslationX())) || (!expanding && mLayout.isFirstChildXLeftOfCenter(mCollapsePoint.x)); + } else { + // For RTL languages, when showing bubbles horizontally, it is reversed. The bubbles + // are positioned right to left. This means that when expanding from left, the top + // bubble will lead as it will be positioned on the right. And when expanding from + // right, the top bubble will have the least travel distance. + firstBubbleLeads = + (expanding && mLayout.isFirstChildXLeftOfCenter(bubble.getTranslationX())) + || (!expanding && !mLayout.isFirstChildXLeftOfCenter(mCollapsePoint.x)); + } final int startDelay = firstBubbleLeads ? (index * 10) : ((mLayout.getChildCount() - index) * 10); @@ -278,11 +306,20 @@ public class ExpandedAnimationController (firstBubbleLeads && index == 0) || (!firstBubbleLeads && index == mLayout.getChildCount() - 1); + Interpolator interpolator; + if (HOME_GESTURE_ENABLED) { + // When home gesture is enabled, we use a different animation timing for collapse + interpolator = expanding + ? Interpolators.EMPHASIZED_ACCELERATE : Interpolators.EMPHASIZED_DECELERATE; + } else { + interpolator = Interpolators.LINEAR; + } + animation .followAnimatedTargetAlongPath( path, EXPAND_COLLAPSE_TARGET_ANIM_DURATION /* targetAnimDuration */, - Interpolators.LINEAR /* targetAnimInterpolator */, + interpolator /* targetAnimInterpolator */, isLeadBubble ? mLeadBubbleEndAction : null /* endAction */, () -> mLeadBubbleEndAction = null /* endAction */) .withStartDelay(startDelay) @@ -525,10 +562,16 @@ public class ExpandedAnimationController finishRemoval.run(); mOnBubbleAnimatedOutAction.run(); } else { + PhysicsAnimator.SpringConfig springConfig; + if (HOME_GESTURE_ENABLED) { + springConfig = mAnimateOutSpringConfig; + } else { + springConfig = mAnimateOutSpringConfigWithoutHomeGesture; + } PhysicsAnimator.getInstance(child) .spring(DynamicAnimation.ALPHA, 0f) - .spring(DynamicAnimation.SCALE_X, 0f, mAnimateOutSpringConfig) - .spring(DynamicAnimation.SCALE_Y, 0f, mAnimateOutSpringConfig) + .spring(DynamicAnimation.SCALE_X, 0f, springConfig) + .spring(DynamicAnimation.SCALE_Y, 0f, springConfig) .withEndActions(finishRemoval, mOnBubbleAnimatedOutAction) .start(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationController.java new file mode 100644 index 000000000000..8a33780bc8d5 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationController.java @@ -0,0 +1,75 @@ +/* + * 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.bubbles.animation; + +import com.android.wm.shell.bubbles.BubbleExpandedView; + +/** + * Animation controller for bubble expanded view collapsing + */ +public interface ExpandedViewAnimationController { + /** + * Set expanded view that this controller is working with. + */ + void setExpandedView(BubbleExpandedView expandedView); + + /** + * Set current collapse value, in pixels. + * + * @param distance pixels that user dragged the view by + */ + void updateDrag(float distance); + + /** + * Set current swipe velocity. + * Velocity is directional: + * <ul> + * <li>velocity < 0 means swipe direction is up</li> + * <li>velocity > 0 means swipe direction is down</li> + * </ul> + */ + void setSwipeVelocity(float velocity); + + /** + * Check if view is dragged past collapse threshold or swipe up velocity exceeds min velocity + * required to collapse the view + */ + boolean shouldCollapse(); + + /** + * Animate view to collapsed state + * + * @param startStackCollapse runnable that is triggered when bubbles can start moving back to + * their collapsed location + * @param after runnable to run after animation is complete + */ + void animateCollapse(Runnable startStackCollapse, Runnable after); + + /** + * Animate the view back to fully expanded state. + */ + void animateBackToExpanded(); + + /** + * Animate view for IME visibility change + */ + void animateForImeVisibilityChange(boolean visible); + + /** + * Reset the view to fully expanded state + */ + void reset(); +} 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 new file mode 100644 index 000000000000..845dca34b41f --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java @@ -0,0 +1,431 @@ +/* + * 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.bubbles.animation; + +import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_COLLAPSE_ANIMATOR; +import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_EXPANDED_VIEW_DRAGGING; +import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES; +import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.wm.shell.bubbles.BubbleExpandedView.BACKGROUND_ALPHA; +import static com.android.wm.shell.bubbles.BubbleExpandedView.BOTTOM_CLIP_PROPERTY; +import static com.android.wm.shell.bubbles.BubbleExpandedView.CONTENT_ALPHA; +import static com.android.wm.shell.bubbles.BubbleExpandedView.MANAGE_BUTTON_ALPHA; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.annotation.SuppressLint; +import android.content.Context; +import android.util.Log; +import android.view.HapticFeedbackConstants; +import android.view.ViewConfiguration; + +import androidx.annotation.Nullable; +import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.dynamicanimation.animation.FloatPropertyCompat; +import androidx.dynamicanimation.animation.SpringAnimation; +import androidx.dynamicanimation.animation.SpringForce; + +import com.android.wm.shell.animation.FlingAnimationUtils; +import com.android.wm.shell.animation.Interpolators; +import com.android.wm.shell.bubbles.BubbleExpandedView; +import com.android.wm.shell.bubbles.BubblePositioner; + +import java.util.ArrayList; +import java.util.List; + +/** + * Implementation of {@link ExpandedViewAnimationController} that uses a collapse animation to + * hide the {@link BubbleExpandedView} + */ +public class ExpandedViewAnimationControllerImpl implements ExpandedViewAnimationController { + + private static final String TAG = TAG_WITH_CLASS_NAME ? "ExpandedViewAnimCtrl" : TAG_BUBBLES; + + private static final float COLLAPSE_THRESHOLD = 0.02f; + + private static final int COLLAPSE_DURATION_MS = 250; + + private static final int MANAGE_BUTTON_ANIM_DURATION_MS = 78; + + private static final int CONTENT_OPACITY_ANIM_DELAY_MS = 93; + private static final int CONTENT_OPACITY_ANIM_DURATION_MS = 78; + + private static final int BACKGROUND_OPACITY_ANIM_DELAY_MS = 172; + private static final int BACKGROUND_OPACITY_ANIM_DURATION_MS = 78; + + /** Animation fraction threshold for content alpha animation when stack collapse should begin */ + private static final float STACK_COLLAPSE_THRESHOLD = 0.5f; + + private static final FloatPropertyCompat<ExpandedViewAnimationControllerImpl> + COLLAPSE_HEIGHT_PROPERTY = + new FloatPropertyCompat<ExpandedViewAnimationControllerImpl>("CollapseSpring") { + @Override + public float getValue(ExpandedViewAnimationControllerImpl controller) { + return controller.getCollapsedAmount(); + } + + @Override + public void setValue(ExpandedViewAnimationControllerImpl controller, + float value) { + controller.setCollapsedAmount(value); + } + }; + + private final int mMinFlingVelocity; + private float mSwipeUpVelocity; + private float mSwipeDownVelocity; + private final BubblePositioner mPositioner; + private final FlingAnimationUtils mFlingAnimationUtils; + private int mDraggedAmount; + private float mCollapsedAmount; + @Nullable + private BubbleExpandedView mExpandedView; + @Nullable + private AnimatorSet mCollapseAnimation; + private boolean mNotifiedAboutThreshold; + private SpringAnimation mBackToExpandedAnimation; + @Nullable + private ObjectAnimator mBottomClipAnim; + + public ExpandedViewAnimationControllerImpl(Context context, BubblePositioner positioner) { + mFlingAnimationUtils = new FlingAnimationUtils(context.getResources().getDisplayMetrics(), + COLLAPSE_DURATION_MS / 1000f); + mMinFlingVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity(); + mPositioner = positioner; + } + + private static void adjustAnimatorSetDuration(AnimatorSet animatorSet, + float durationAdjustment) { + for (Animator animator : animatorSet.getChildAnimations()) { + animator.setStartDelay((long) (animator.getStartDelay() * durationAdjustment)); + animator.setDuration((long) (animator.getDuration() * durationAdjustment)); + } + } + + @Override + public void setExpandedView(BubbleExpandedView expandedView) { + if (mExpandedView != null) { + if (DEBUG_COLLAPSE_ANIMATOR) { + Log.d(TAG, "updating expandedView, resetting previous"); + } + if (mCollapseAnimation != null) { + mCollapseAnimation.cancel(); + } + if (mBackToExpandedAnimation != null) { + mBackToExpandedAnimation.cancel(); + } + reset(); + } + mExpandedView = expandedView; + } + + @Override + public void updateDrag(float distance) { + if (mExpandedView != null) { + mDraggedAmount = OverScroll.dampedScroll(distance, mExpandedView.getContentHeight()); + + if (DEBUG_COLLAPSE_ANIMATOR && DEBUG_EXPANDED_VIEW_DRAGGING) { + Log.d(TAG, "updateDrag: distance=" + distance + " dragged=" + mDraggedAmount); + } + + setCollapsedAmount(mDraggedAmount); + + if (!mNotifiedAboutThreshold && isPastCollapseThreshold()) { + mNotifiedAboutThreshold = true; + if (DEBUG_COLLAPSE_ANIMATOR) { + Log.d(TAG, "notifying over collapse threshold"); + } + vibrateIfEnabled(); + } + } + } + + @Override + public void setSwipeVelocity(float velocity) { + if (velocity < 0) { + mSwipeUpVelocity = Math.abs(velocity); + mSwipeDownVelocity = 0; + } else { + mSwipeUpVelocity = 0; + mSwipeDownVelocity = velocity; + } + } + + @Override + public boolean shouldCollapse() { + if (mSwipeDownVelocity > mMinFlingVelocity) { + // Swipe velocity is positive and over fling velocity. + // This is a swipe down, always reset to expanded state, regardless of dragged amount. + if (DEBUG_COLLAPSE_ANIMATOR) { + Log.d(TAG, + "not collapsing expanded view, swipe down velocity: " + mSwipeDownVelocity + + " minV: " + mMinFlingVelocity); + } + return false; + } + + if (mSwipeUpVelocity > mMinFlingVelocity) { + // Swiping up and over fling velocity, collapse the view. + if (DEBUG_COLLAPSE_ANIMATOR) { + Log.d(TAG, + "collapse expanded view, swipe up velocity: " + mSwipeUpVelocity + " minV: " + + mMinFlingVelocity); + } + return true; + } + + if (isPastCollapseThreshold()) { + if (DEBUG_COLLAPSE_ANIMATOR) { + Log.d(TAG, "collapse expanded view, past threshold, dragged: " + mDraggedAmount); + } + return true; + } + + if (DEBUG_COLLAPSE_ANIMATOR) { + Log.d(TAG, "not collapsing expanded view"); + } + + return false; + } + + @Override + public void animateCollapse(Runnable startStackCollapse, Runnable after) { + if (DEBUG_COLLAPSE_ANIMATOR) { + Log.d(TAG, + "expandedView animate collapse swipeVel=" + mSwipeUpVelocity + " minFlingVel=" + + mMinFlingVelocity); + } + if (mExpandedView != null) { + // Mark it as animating immediately to avoid updates to the view before animation starts + mExpandedView.setAnimating(true); + + if (mCollapseAnimation != null) { + mCollapseAnimation.cancel(); + } + mCollapseAnimation = createCollapseAnimation(mExpandedView, startStackCollapse, after); + + if (mSwipeUpVelocity >= mMinFlingVelocity) { + int contentHeight = mExpandedView.getContentHeight(); + + // Use a temp animator to get adjusted duration value for swipe. + // This new value will be used to adjust animation times proportionally in the + // animator set. If we adjust animator set duration directly, all child animations + // will get the same animation time. + ValueAnimator tempAnimator = new ValueAnimator(); + mFlingAnimationUtils.applyDismissing(tempAnimator, mCollapsedAmount, contentHeight, + mSwipeUpVelocity, (contentHeight - mCollapsedAmount)); + + float durationAdjustment = + (float) tempAnimator.getDuration() / COLLAPSE_DURATION_MS; + + adjustAnimatorSetDuration(mCollapseAnimation, durationAdjustment); + mCollapseAnimation.setInterpolator(tempAnimator.getInterpolator()); + } + mCollapseAnimation.start(); + } + } + + @Override + public void animateBackToExpanded() { + if (DEBUG_COLLAPSE_ANIMATOR) { + Log.d(TAG, "expandedView animate back to expanded"); + } + BubbleExpandedView expandedView = mExpandedView; + if (expandedView == null) { + return; + } + + expandedView.setAnimating(true); + + mBackToExpandedAnimation = new SpringAnimation(this, COLLAPSE_HEIGHT_PROPERTY); + mBackToExpandedAnimation.setSpring(new SpringForce() + .setStiffness(SpringForce.STIFFNESS_LOW) + .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY) + ); + mBackToExpandedAnimation.addEndListener(new OneTimeEndListener() { + @Override + public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value, + float velocity) { + super.onAnimationEnd(animation, canceled, value, velocity); + mNotifiedAboutThreshold = false; + mBackToExpandedAnimation = null; + expandedView.setAnimating(false); + } + }); + mBackToExpandedAnimation.setStartValue(mCollapsedAmount); + mBackToExpandedAnimation.animateToFinalPosition(0); + } + + @Override + public void animateForImeVisibilityChange(boolean visible) { + if (mExpandedView != null) { + if (mBottomClipAnim != null) { + mBottomClipAnim.cancel(); + } + int clip = 0; + if (visible) { + // Clip the expanded view at the top of the IME view + clip = mExpandedView.getContentBottomOnScreen() - mPositioner.getImeTop(); + // Don't allow negative clip value + clip = Math.max(clip, 0); + } + mBottomClipAnim = ObjectAnimator.ofInt(mExpandedView, BOTTOM_CLIP_PROPERTY, clip); + mBottomClipAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mBottomClipAnim = null; + } + }); + mBottomClipAnim.start(); + } + } + + @Override + public void reset() { + if (DEBUG_COLLAPSE_ANIMATOR) { + Log.d(TAG, "reset expandedView collapsed state"); + } + if (mExpandedView == null) { + return; + } + mExpandedView.setAnimating(false); + + if (mCollapseAnimation != null) { + mCollapseAnimation.cancel(); + } + if (mBackToExpandedAnimation != null) { + mBackToExpandedAnimation.cancel(); + } + mExpandedView.setContentAlpha(1); + mExpandedView.setBackgroundAlpha(1); + mExpandedView.setManageButtonAlpha(1); + setCollapsedAmount(0); + mExpandedView.setBottomClip(0); + mExpandedView.movePointerBy(0, 0); + mCollapsedAmount = 0; + mDraggedAmount = 0; + mSwipeUpVelocity = 0; + mSwipeDownVelocity = 0; + mNotifiedAboutThreshold = false; + } + + private float getCollapsedAmount() { + return mCollapsedAmount; + } + + private void setCollapsedAmount(float collapsed) { + if (mCollapsedAmount != collapsed) { + float previous = mCollapsedAmount; + mCollapsedAmount = collapsed; + + if (mExpandedView != null) { + if (previous == 0) { + // View was not collapsed before. Apply z order change + mExpandedView.setSurfaceZOrderedOnTop(true); + mExpandedView.setAnimating(true); + } + + mExpandedView.setTopClip((int) mCollapsedAmount); + // Move up with translationY. Use negative collapsed value + mExpandedView.setContentTranslationY(-mCollapsedAmount); + mExpandedView.setManageButtonTranslationY(-mCollapsedAmount); + + if (mCollapsedAmount == 0) { + // View is no longer collapsed. Revert z order change + mExpandedView.setSurfaceZOrderedOnTop(false); + mExpandedView.setAnimating(false); + } + } + } + } + + private boolean isPastCollapseThreshold() { + if (mExpandedView != null) { + return mDraggedAmount > mExpandedView.getContentHeight() * COLLAPSE_THRESHOLD; + } + return false; + } + + private AnimatorSet createCollapseAnimation(BubbleExpandedView expandedView, + Runnable startStackCollapse, Runnable after) { + List<Animator> animatorList = new ArrayList<>(); + animatorList.add(createHeightAnimation(expandedView)); + animatorList.add(createManageButtonAnimation()); + ObjectAnimator contentAlphaAnimation = createContentAlphaAnimation(); + final boolean[] notified = {false}; + contentAlphaAnimation.addUpdateListener(animation -> { + if (!notified[0] && animation.getAnimatedFraction() > STACK_COLLAPSE_THRESHOLD) { + notified[0] = true; + // Notify bubbles that they can start moving back to the collapsed position + startStackCollapse.run(); + } + }); + animatorList.add(contentAlphaAnimation); + animatorList.add(createBackgroundAlphaAnimation()); + + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + after.run(); + } + }); + animatorSet.playTogether(animatorList); + return animatorSet; + } + + private ValueAnimator createHeightAnimation(BubbleExpandedView expandedView) { + ValueAnimator animator = ValueAnimator.ofInt((int) mCollapsedAmount, + expandedView.getContentHeight()); + animator.setInterpolator(Interpolators.EMPHASIZED_ACCELERATE); + animator.setDuration(COLLAPSE_DURATION_MS); + animator.addUpdateListener(anim -> setCollapsedAmount((int) anim.getAnimatedValue())); + return animator; + } + + private ObjectAnimator createManageButtonAnimation() { + ObjectAnimator animator = ObjectAnimator.ofFloat(mExpandedView, MANAGE_BUTTON_ALPHA, 0f); + animator.setDuration(MANAGE_BUTTON_ANIM_DURATION_MS); + animator.setInterpolator(Interpolators.LINEAR); + return animator; + } + + private ObjectAnimator createContentAlphaAnimation() { + ObjectAnimator animator = ObjectAnimator.ofFloat(mExpandedView, CONTENT_ALPHA, 0f); + animator.setDuration(CONTENT_OPACITY_ANIM_DURATION_MS); + animator.setInterpolator(Interpolators.LINEAR); + animator.setStartDelay(CONTENT_OPACITY_ANIM_DELAY_MS); + return animator; + } + + private ObjectAnimator createBackgroundAlphaAnimation() { + ObjectAnimator animator = ObjectAnimator.ofFloat(mExpandedView, BACKGROUND_ALPHA, 0f); + animator.setDuration(BACKGROUND_OPACITY_ANIM_DURATION_MS); + animator.setInterpolator(Interpolators.LINEAR); + animator.setStartDelay(BACKGROUND_OPACITY_ANIM_DELAY_MS); + return animator; + } + + @SuppressLint("MissingPermission") + private void vibrateIfEnabled() { + if (mExpandedView != null) { + mExpandedView.performHapticFeedback(HapticFeedbackConstants.DRAG_CROSSING); + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerStub.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerStub.java new file mode 100644 index 000000000000..bb8a3aaaf551 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerStub.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.wm.shell.bubbles.animation; + +import com.android.wm.shell.bubbles.BubbleExpandedView; + +/** + * Stub implementation {@link ExpandedViewAnimationController} that does not animate the + * {@link BubbleExpandedView} + */ +public class ExpandedViewAnimationControllerStub implements ExpandedViewAnimationController { + @Override + public void setExpandedView(BubbleExpandedView expandedView) { + } + + @Override + public void updateDrag(float distance) { + } + + @Override + public void setSwipeVelocity(float velocity) { + } + + @Override + public boolean shouldCollapse() { + return false; + } + + @Override + public void animateCollapse(Runnable startStackCollapse, Runnable after) { + } + + @Override + public void animateBackToExpanded() { + } + + @Override + public void animateForImeVisibilityChange(boolean visible) { + } + + @Override + public void reset() { + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/OverScroll.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/OverScroll.java new file mode 100644 index 000000000000..d4e76ed0282e --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/OverScroll.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.wm.shell.bubbles.animation; + +/** + * Utility methods for overscroll damping and related effect. + * + * Copied from packages/apps/Launcher3/src/com/android/launcher3/touch/OverScroll.java + */ +public class OverScroll { + + private static final float OVERSCROLL_DAMP_FACTOR = 0.07f; + + /** + * This curve determines how the effect of scrolling over the limits of the page diminishes + * as the user pulls further and further from the bounds + * + * @param f The percentage of how much the user has overscrolled. + * @return A transformed percentage based on the influence curve. + */ + private static float overScrollInfluenceCurve(float f) { + f -= 1.0f; + return f * f * f + 1.0f; + } + + /** + * @param amount The original amount overscrolled. + * @param max The maximum amount that the View can overscroll. + * @return The dampened overscroll amount. + */ + public static int dampedScroll(float amount, int max) { + if (Float.compare(amount, 0) == 0) return 0; + + float f = amount / max; + f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f))); + + // Clamp this factor, f, to -1 < f < 1 + if (Math.abs(f) >= 1) { + f /= Math.abs(f); + } + + return Math.round(OVERSCROLL_DAMP_FACTOR * f * max); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java index c32733d4f73c..28c7367662a2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java @@ -16,11 +16,13 @@ package com.android.wm.shell.common; +import android.annotation.Nullable; import android.os.RemoteException; import android.util.Slog; -import android.view.IDisplayWindowRotationCallback; -import android.view.IDisplayWindowRotationController; +import android.view.IDisplayChangeWindowCallback; +import android.view.IDisplayChangeWindowController; import android.view.IWindowManager; +import android.window.DisplayAreaInfo; import android.window.WindowContainerTransaction; import androidx.annotation.BinderThread; @@ -40,17 +42,17 @@ public class DisplayChangeController { private final ShellExecutor mMainExecutor; private final IWindowManager mWmService; - private final IDisplayWindowRotationController mControllerImpl; + private final IDisplayChangeWindowController mControllerImpl; - private final CopyOnWriteArrayList<OnDisplayChangingListener> mRotationListener = + private final CopyOnWriteArrayList<OnDisplayChangingListener> mDisplayChangeListener = new CopyOnWriteArrayList<>(); public DisplayChangeController(IWindowManager wmService, ShellExecutor mainExecutor) { mMainExecutor = mainExecutor; mWmService = wmService; - mControllerImpl = new DisplayWindowRotationControllerImpl(); + mControllerImpl = new DisplayChangeWindowControllerImpl(); try { - mWmService.setDisplayWindowRotationController(mControllerImpl); + mWmService.setDisplayChangeWindowController(mControllerImpl); } catch (RemoteException e) { throw new RuntimeException("Unable to register rotation controller"); } @@ -59,63 +61,64 @@ public class DisplayChangeController { /** * Adds a display rotation controller. */ - public void addRotationListener(OnDisplayChangingListener listener) { - mRotationListener.add(listener); + public void addDisplayChangeListener(OnDisplayChangingListener listener) { + mDisplayChangeListener.add(listener); } /** * Removes a display rotation controller. */ - public void removeRotationListener(OnDisplayChangingListener listener) { - mRotationListener.remove(listener); + public void removeDisplayChangeListener(OnDisplayChangingListener listener) { + mDisplayChangeListener.remove(listener); } - /** Query all listeners for changes that should happen on rotation. */ - public void dispatchOnRotateDisplay(WindowContainerTransaction outWct, int displayId, - final int fromRotation, final int toRotation) { - for (OnDisplayChangingListener c : mRotationListener) { - c.onRotateDisplay(displayId, fromRotation, toRotation, outWct); + /** Query all listeners for changes that should happen on display change. */ + public void dispatchOnDisplayChange(WindowContainerTransaction outWct, int displayId, + int fromRotation, int toRotation, DisplayAreaInfo newDisplayAreaInfo) { + for (OnDisplayChangingListener c : mDisplayChangeListener) { + c.onDisplayChange(displayId, fromRotation, toRotation, newDisplayAreaInfo, outWct); } } - private void onRotateDisplay(int displayId, final int fromRotation, final int toRotation, - IDisplayWindowRotationCallback callback) { + private void onDisplayChange(int displayId, int fromRotation, int toRotation, + DisplayAreaInfo newDisplayAreaInfo, IDisplayChangeWindowCallback callback) { WindowContainerTransaction t = new WindowContainerTransaction(); - dispatchOnRotateDisplay(t, displayId, fromRotation, toRotation); + dispatchOnDisplayChange(t, displayId, fromRotation, toRotation, newDisplayAreaInfo); try { - callback.continueRotateDisplay(toRotation, t); + callback.continueDisplayChange(t); } catch (RemoteException e) { - Slog.e(TAG, "Failed to continue rotation", e); + Slog.e(TAG, "Failed to continue handling display change", e); } } @BinderThread - private class DisplayWindowRotationControllerImpl - extends IDisplayWindowRotationController.Stub { + private class DisplayChangeWindowControllerImpl + extends IDisplayChangeWindowController.Stub { @Override - public void onRotateDisplay(int displayId, final int fromRotation, - final int toRotation, IDisplayWindowRotationCallback callback) { - mMainExecutor.execute(() -> { - DisplayChangeController.this.onRotateDisplay(displayId, fromRotation, toRotation, - callback); - }); + public void onDisplayChange(int displayId, int fromRotation, int toRotation, + DisplayAreaInfo newDisplayAreaInfo, IDisplayChangeWindowCallback callback) { + mMainExecutor.execute(() -> DisplayChangeController.this + .onDisplayChange(displayId, fromRotation, toRotation, + newDisplayAreaInfo, callback)); } } /** * Give a listener a chance to queue up configuration changes to execute as part of a - * display rotation. The contents of {@link #onRotateDisplay} must run synchronously. + * display rotation. The contents of {@link #onDisplayChange} must run synchronously. */ @ShellMainThread public interface OnDisplayChangingListener { /** - * Called before the display is rotated. Contents of this method must run synchronously. - * @param displayId Id of display that is rotating. - * @param fromRotation starting rotation of the display. - * @param toRotation target rotation of the display (after rotating). + * Called before the display size has changed. + * Contents of this method must run synchronously. + * @param displayId display id of the display that is under the change + * @param fromRotation rotation before the change + * @param toRotation rotation after the change + * @param newDisplayAreaInfo display area info after applying the update * @param t A task transaction to populate. */ - void onRotateDisplay(int displayId, int fromRotation, int toRotation, - WindowContainerTransaction t); + void onDisplayChange(int displayId, int fromRotation, int toRotation, + @Nullable DisplayAreaInfo newDisplayAreaInfo, WindowContainerTransaction t); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java index 4ba32e93fb3d..764936cceb01 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java @@ -156,14 +156,14 @@ public class DisplayController { * Adds a display rotation controller. */ public void addDisplayChangingController(OnDisplayChangingListener controller) { - mChangeController.addRotationListener(controller); + mChangeController.addDisplayChangeListener(controller); } /** * Removes a display rotation controller. */ public void removeDisplayChangingController(OnDisplayChangingListener controller) { - mChangeController.removeRotationListener(controller); + mChangeController.removeDisplayChangeListener(controller); } private void onDisplayAdded(int displayId) { 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 index fd3aa05cfc06..ec344d345139 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java @@ -18,7 +18,9 @@ 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.InteractionJankMonitor; @@ -44,6 +46,24 @@ public class InteractionJankMonitorUtils { } /** + * Begin a trace session. + * + * @param cujType the specific {@link InteractionJankMonitor.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(@InteractionJankMonitor.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 InteractionJankMonitor.CujType}. 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 6305959bb6ac..5ebe384d84b7 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 @@ -286,6 +286,7 @@ public class DividerView extends FrameLayout implements View.OnTouchListener { setTouching(); mStartPos = touchPos; mMoving = false; + mSplitLayout.onStartDragging(); break; case MotionEvent.ACTION_MOVE: mVelocityTracker.addMovement(event); @@ -301,7 +302,10 @@ public class DividerView extends FrameLayout implements View.OnTouchListener { case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: releaseTouching(); - if (!mMoving) break; + if (!mMoving) { + mSplitLayout.onDraggingCancelled(); + break; + } mVelocityTracker.addMovement(event); mVelocityTracker.computeCurrentVelocity(1000 /* units */); 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 c94455d9151a..172418057b86 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -24,6 +24,7 @@ import static android.view.WindowManager.DOCKED_LEFT; import static android.view.WindowManager.DOCKED_RIGHT; import static android.view.WindowManager.DOCKED_TOP; +import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLIT_SCREEN_RESIZE; import static com.android.internal.policy.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_END; import static com.android.internal.policy.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START; import static com.android.wm.shell.animation.Interpolators.DIM_INTERPOLATOR; @@ -55,7 +56,6 @@ import android.window.WindowContainerTransaction; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.policy.DividerSnapAlgorithm; import com.android.internal.policy.DockedDividerUtils; import com.android.wm.shell.R; @@ -178,6 +178,11 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange return outBounds; } + /** Gets root bounds of the whole split layout */ + public Rect getRootBounds() { + return new Rect(mRootBounds); + } + /** Gets bounds of divider window with screen based coordinate. */ public Rect getDividerBounds() { return new Rect(mDividerBounds); @@ -407,6 +412,15 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange } } + void onStartDragging() { + InteractionJankMonitorUtils.beginTracing(CUJ_SPLIT_SCREEN_RESIZE, mContext, + getDividerLeash(), null /* tag */); + } + + void onDraggingCancelled() { + InteractionJankMonitorUtils.cancelTracing(CUJ_SPLIT_SCREEN_RESIZE); + } + void onDoubleTappedDivider() { mSplitLayoutHandler.onDoubleTappedDivider(); } @@ -438,10 +452,10 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange if (from == to) { // No animation run, still callback to stop resizing. mSplitLayoutHandler.onLayoutSizeChanged(this); + InteractionJankMonitorUtils.endTracing( + CUJ_SPLIT_SCREEN_RESIZE); return; } - InteractionJankMonitorUtils.beginTracing(InteractionJankMonitor.CUJ_SPLIT_SCREEN_RESIZE, - mSplitWindowManager.getDividerView(), "Divider fling"); ValueAnimator animator = ValueAnimator .ofInt(from, to) .setDuration(250); @@ -455,7 +469,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange flingFinishedCallback.run(); } InteractionJankMonitorUtils.endTracing( - InteractionJankMonitor.CUJ_SPLIT_SCREEN_RESIZE); + CUJ_SPLIT_SCREEN_RESIZE); } @Override 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 db6131a17114..2ea111b113d8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -38,8 +38,6 @@ import com.android.wm.shell.TaskViewFactory; import com.android.wm.shell.TaskViewFactoryController; import com.android.wm.shell.TaskViewTransitions; import com.android.wm.shell.WindowManagerShellWrapper; -import com.android.wm.shell.apppairs.AppPairs; -import com.android.wm.shell.apppairs.AppPairsController; import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.back.BackAnimationController; import com.android.wm.shell.bubbles.BubbleController; @@ -66,12 +64,9 @@ import com.android.wm.shell.draganddrop.DragAndDrop; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.freeform.FreeformTaskListener; import com.android.wm.shell.fullscreen.FullscreenTaskListener; -import com.android.wm.shell.fullscreen.FullscreenUnfoldController; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController; import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer; -import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; -import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.pip.Pip; @@ -92,6 +87,7 @@ import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelperController; import com.android.wm.shell.transition.ShellTransitions; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.unfold.ShellUnfoldProgressProvider; +import com.android.wm.shell.unfold.UnfoldAnimationController; import com.android.wm.shell.unfold.UnfoldTransitionHandler; import java.util.Optional; @@ -180,9 +176,11 @@ public abstract class WMShellBaseModule { static ShellTaskOrganizer provideShellTaskOrganizer(@ShellMainThread ShellExecutor mainExecutor, Context context, CompatUIController compatUI, + Optional<UnfoldAnimationController> unfoldAnimationController, Optional<RecentTasksController> recentTasksOptional ) { - return new ShellTaskOrganizer(mainExecutor, context, compatUI, recentTasksOptional); + return new ShellTaskOrganizer(mainExecutor, context, compatUI, unfoldAnimationController, + recentTasksOptional); } @WMSingleton @@ -194,10 +192,12 @@ public abstract class WMShellBaseModule { SyncTransactionQueue syncTransactionQueue, DisplayController displayController, DisplayInsetsController displayInsetsController, + Optional<UnfoldAnimationController> unfoldAnimationController, Optional<RecentTasksController> recentTasksOptional ) { return new KidsModeTaskOrganizer(mainExecutor, mainHandler, context, syncTransactionQueue, - displayController, displayInsetsController, recentTasksOptional); + displayController, displayInsetsController, unfoldAnimationController, + recentTasksOptional); } @WMSingleton @@ -294,13 +294,11 @@ public abstract class WMShellBaseModule { static FullscreenTaskListener provideFullscreenTaskListener( @DynamicOverride Optional<FullscreenTaskListener> fullscreenTaskListener, SyncTransactionQueue syncQueue, - Optional<FullscreenUnfoldController> optionalFullscreenUnfoldController, Optional<RecentTasksController> recentTasksOptional) { if (fullscreenTaskListener.isPresent()) { return fullscreenTaskListener.get(); } else { - return new FullscreenTaskListener(syncQueue, optionalFullscreenUnfoldController, - recentTasksOptional); + return new FullscreenTaskListener(syncQueue, recentTasksOptional); } } @@ -314,31 +312,33 @@ public abstract class WMShellBaseModule { // Workaround for dynamic overriding with a default implementation, see {@link DynamicOverride} @BindsOptionalOf @DynamicOverride - abstract FullscreenUnfoldController optionalFullscreenUnfoldController(); + abstract UnfoldAnimationController optionalUnfoldController(); @WMSingleton @Provides - static Optional<FullscreenUnfoldController> provideFullscreenUnfoldController( - @DynamicOverride Optional<FullscreenUnfoldController> fullscreenUnfoldController, + static Optional<UnfoldAnimationController> provideUnfoldController( + @DynamicOverride Lazy<Optional<UnfoldAnimationController>> + fullscreenUnfoldController, Optional<ShellUnfoldProgressProvider> progressProvider) { if (progressProvider.isPresent() && progressProvider.get() != ShellUnfoldProgressProvider.NO_PROVIDER) { - return fullscreenUnfoldController; + return fullscreenUnfoldController.get(); } return Optional.empty(); } + @BindsOptionalOf + @DynamicOverride + abstract UnfoldTransitionHandler optionalUnfoldTransitionHandler(); + @WMSingleton @Provides static Optional<UnfoldTransitionHandler> provideUnfoldTransitionHandler( Optional<ShellUnfoldProgressProvider> progressProvider, - TransactionPool transactionPool, - Transitions transitions, - @ShellMainThread ShellExecutor executor) { - if (progressProvider.isPresent()) { - return Optional.of( - new UnfoldTransitionHandler(progressProvider.get(), transactionPool, executor, - transitions)); + @DynamicOverride Lazy<Optional<UnfoldTransitionHandler>> handler) { + if (progressProvider.isPresent() + && progressProvider.get() != ShellUnfoldProgressProvider.NO_PROVIDER) { + return handler.get(); } return Optional.empty(); } @@ -350,12 +350,12 @@ public abstract class WMShellBaseModule { // Workaround for dynamic overriding with a default implementation, see {@link DynamicOverride} @BindsOptionalOf @DynamicOverride - abstract FreeformTaskListener optionalFreeformTaskListener(); + abstract FreeformTaskListener<?> optionalFreeformTaskListener(); @WMSingleton @Provides - static Optional<FreeformTaskListener> provideFreeformTaskListener( - @DynamicOverride Optional<FreeformTaskListener> freeformTaskListener, + static Optional<FreeformTaskListener<?>> provideFreeformTaskListener( + @DynamicOverride Optional<FreeformTaskListener<?>> freeformTaskListener, Context context) { if (FreeformTaskListener.isFreeformEnabled(context)) { return freeformTaskListener; @@ -561,29 +561,6 @@ public abstract class WMShellBaseModule { return Optional.empty(); } - // Legacy split (optional feature) - - @WMSingleton - @Provides - static Optional<LegacySplitScreen> provideLegacySplitScreen( - Optional<LegacySplitScreenController> splitScreenController) { - return splitScreenController.map((controller) -> controller.asLegacySplitScreen()); - } - - @BindsOptionalOf - abstract LegacySplitScreenController optionalLegacySplitScreenController(); - - // App Pairs (optional feature) - - @WMSingleton - @Provides - static Optional<AppPairs> provideAppPairs(Optional<AppPairsController> appPairsController) { - return appPairsController.map((controller) -> controller.asAppPairs()); - } - - @BindsOptionalOf - abstract AppPairsController optionalAppPairs(); - // // Starting window // @@ -664,12 +641,11 @@ public abstract class WMShellBaseModule { KidsModeTaskOrganizer kidsModeTaskOrganizer, Optional<BubbleController> bubblesOptional, Optional<SplitScreenController> splitScreenOptional, - Optional<AppPairsController> appPairsOptional, Optional<PipTouchHandler> pipTouchHandlerOptional, FullscreenTaskListener fullscreenTaskListener, - Optional<FullscreenUnfoldController> appUnfoldTransitionController, + Optional<UnfoldAnimationController> unfoldAnimationController, Optional<UnfoldTransitionHandler> unfoldTransitionHandler, - Optional<FreeformTaskListener> freeformTaskListener, + Optional<FreeformTaskListener<?>> freeformTaskListener, Optional<RecentTasksController> recentTasksOptional, Transitions transitions, StartingWindowController startingWindow, @@ -682,10 +658,9 @@ public abstract class WMShellBaseModule { kidsModeTaskOrganizer, bubblesOptional, splitScreenOptional, - appPairsOptional, pipTouchHandlerOptional, fullscreenTaskListener, - appUnfoldTransitionController, + unfoldAnimationController, unfoldTransitionHandler, freeformTaskListener, recentTasksOptional, @@ -709,17 +684,15 @@ public abstract class WMShellBaseModule { static ShellCommandHandlerImpl provideShellCommandHandlerImpl( ShellTaskOrganizer shellTaskOrganizer, KidsModeTaskOrganizer kidsModeTaskOrganizer, - Optional<LegacySplitScreenController> legacySplitScreenOptional, Optional<SplitScreenController> splitScreenOptional, Optional<Pip> pipOptional, Optional<OneHandedController> oneHandedOptional, Optional<HideDisplayCutoutController> hideDisplayCutout, - Optional<AppPairsController> appPairsOptional, Optional<RecentTasksController> recentTasksOptional, @ShellMainThread ShellExecutor mainExecutor) { return new ShellCommandHandlerImpl(shellTaskOrganizer, kidsModeTaskOrganizer, - legacySplitScreenOptional, splitScreenOptional, pipOptional, oneHandedOptional, - hideDisplayCutout, appPairsOptional, recentTasksOptional, mainExecutor); + splitScreenOptional, pipOptional, oneHandedOptional, hideDisplayCutout, + recentTasksOptional, mainExecutor); } @WMSingleton 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 b3799e2cf8d9..1e369899e354 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -16,7 +16,6 @@ package com.android.wm.shell.dagger; -import android.animation.AnimationHandler; import android.content.Context; import android.content.pm.LauncherApps; import android.os.Handler; @@ -31,7 +30,6 @@ import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.TaskViewTransitions; import com.android.wm.shell.WindowManagerShellWrapper; -import com.android.wm.shell.apppairs.AppPairsController; import com.android.wm.shell.bubbles.BubbleController; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; @@ -43,13 +41,10 @@ import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.TransactionPool; -import com.android.wm.shell.common.annotations.ChoreographerSfVsync; import com.android.wm.shell.common.annotations.ShellBackgroundThread; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.freeform.FreeformTaskListener; -import com.android.wm.shell.fullscreen.FullscreenUnfoldController; -import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController; import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.PipAnimationController; @@ -67,19 +62,29 @@ import com.android.wm.shell.pip.PipTransitionState; import com.android.wm.shell.pip.PipUiEventLogger; import com.android.wm.shell.pip.phone.PhonePipMenuController; import com.android.wm.shell.pip.phone.PipController; +import com.android.wm.shell.pip.phone.PipKeepClearAlgorithm; import com.android.wm.shell.pip.phone.PipMotionHelper; import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.splitscreen.SplitScreenController; -import com.android.wm.shell.splitscreen.StageTaskUnfoldController; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.unfold.UnfoldAnimationController; import com.android.wm.shell.unfold.ShellUnfoldProgressProvider; import com.android.wm.shell.unfold.UnfoldBackgroundController; - +import com.android.wm.shell.unfold.UnfoldTransitionHandler; +import com.android.wm.shell.unfold.animation.FullscreenUnfoldTaskAnimator; +import com.android.wm.shell.unfold.animation.SplitTaskUnfoldAnimator; +import com.android.wm.shell.unfold.animation.UnfoldTaskAnimator; +import com.android.wm.shell.unfold.qualifier.UnfoldTransition; +import com.android.wm.shell.unfold.qualifier.UnfoldShellTransition; +import com.android.wm.shell.windowdecor.CaptionWindowDecorViewModel; +import com.android.wm.shell.windowdecor.WindowDecorViewModel; + +import java.util.ArrayList; +import java.util.List; import java.util.Optional; -import javax.inject.Provider; - +import dagger.Binds; import dagger.Lazy; import dagger.Module; import dagger.Provides; @@ -93,7 +98,7 @@ import dagger.Provides; * dependencies should go into {@link WMShellBaseModule}. */ @Module(includes = WMShellBaseModule.class) -public class WMShellModule { +public abstract class WMShellModule { // // Bubbles @@ -129,15 +134,36 @@ public class WMShellModule { } // + // Window decoration + // + + @WMSingleton + @Provides + static WindowDecorViewModel<?> provideWindowDecorViewModel( + Context context, + @ShellMainThread Handler mainHandler, + ShellTaskOrganizer taskOrganizer, + DisplayController displayController, + SyncTransactionQueue syncQueue) { + return new CaptionWindowDecorViewModel( + context, + mainHandler, + taskOrganizer, + displayController, + syncQueue); + } + + // // Freeform // @WMSingleton @Provides @DynamicOverride - static FreeformTaskListener provideFreeformTaskListener( + static FreeformTaskListener<?> provideFreeformTaskListener( + WindowDecorViewModel<?> windowDecorViewModel, SyncTransactionQueue syncQueue) { - return new FreeformTaskListener(syncQueue); + return new FreeformTaskListener<>(windowDecorViewModel, syncQueue); } // @@ -174,37 +200,11 @@ public class WMShellModule { DisplayImeController displayImeController, DisplayInsetsController displayInsetsController, Transitions transitions, TransactionPool transactionPool, IconProvider iconProvider, - Optional<RecentTasksController> recentTasks, - Provider<Optional<StageTaskUnfoldController>> stageTaskUnfoldControllerProvider) { + Optional<RecentTasksController> recentTasks) { return new SplitScreenController(shellTaskOrganizer, syncQueue, context, rootTaskDisplayAreaOrganizer, mainExecutor, displayController, displayImeController, displayInsetsController, transitions, transactionPool, iconProvider, - recentTasks, stageTaskUnfoldControllerProvider); - } - - @WMSingleton - @Provides - static LegacySplitScreenController provideLegacySplitScreen(Context context, - DisplayController displayController, SystemWindows systemWindows, - DisplayImeController displayImeController, TransactionPool transactionPool, - ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue, - TaskStackListenerImpl taskStackListener, Transitions transitions, - @ShellMainThread ShellExecutor mainExecutor, - @ChoreographerSfVsync AnimationHandler sfVsyncAnimationHandler) { - return new LegacySplitScreenController(context, displayController, systemWindows, - displayImeController, transactionPool, shellTaskOrganizer, syncQueue, - taskStackListener, transitions, mainExecutor, sfVsyncAnimationHandler); - } - - @WMSingleton - @Provides - static AppPairsController provideAppPairs(ShellTaskOrganizer shellTaskOrganizer, - SyncTransactionQueue syncQueue, DisplayController displayController, - @ShellMainThread ShellExecutor mainExecutor, - DisplayImeController displayImeController, - DisplayInsetsController displayInsetsController) { - return new AppPairsController(shellTaskOrganizer, syncQueue, displayController, - mainExecutor, displayImeController, displayInsetsController); + recentTasks); } // @@ -215,7 +215,8 @@ public class WMShellModule { @Provides static Optional<Pip> providePip(Context context, DisplayController displayController, PipAppOpsListener pipAppOpsListener, PipBoundsAlgorithm pipBoundsAlgorithm, - PipBoundsState pipBoundsState, PipMediaController pipMediaController, + PipKeepClearAlgorithm pipKeepClearAlgorithm, PipBoundsState pipBoundsState, + PipMotionHelper pipMotionHelper, PipMediaController pipMediaController, PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer, PipTouchHandler pipTouchHandler, PipTransitionController pipTransitionController, WindowManagerShellWrapper windowManagerShellWrapper, @@ -224,7 +225,8 @@ public class WMShellModule { Optional<OneHandedController> oneHandedController, @ShellMainThread ShellExecutor mainExecutor) { return Optional.ofNullable(PipController.create(context, displayController, - pipAppOpsListener, pipBoundsAlgorithm, pipBoundsState, + pipAppOpsListener, pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState, + pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTouchHandler, pipTransitionController, windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder, oneHandedController, mainExecutor)); @@ -244,6 +246,12 @@ public class WMShellModule { @WMSingleton @Provides + static PipKeepClearAlgorithm providePipKeepClearAlgorithm() { + return new PipKeepClearAlgorithm(); + } + + @WMSingleton + @Provides static PipBoundsAlgorithm providesPipBoundsAlgorithm(Context context, PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm) { return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm); @@ -351,40 +359,77 @@ public class WMShellModule { // // Unfold transition // - @WMSingleton @Provides @DynamicOverride - static FullscreenUnfoldController provideFullscreenUnfoldController( - Context context, + static UnfoldAnimationController provideUnfoldAnimationController( Optional<ShellUnfoldProgressProvider> progressProvider, - Lazy<UnfoldBackgroundController> unfoldBackgroundController, - DisplayInsetsController displayInsetsController, + TransactionPool transactionPool, + @UnfoldTransition SplitTaskUnfoldAnimator splitAnimator, + FullscreenUnfoldTaskAnimator fullscreenAnimator, + Lazy<Optional<UnfoldTransitionHandler>> unfoldTransitionHandler, @ShellMainThread ShellExecutor mainExecutor ) { - return new FullscreenUnfoldController(context, mainExecutor, - unfoldBackgroundController.get(), progressProvider.get(), + final List<UnfoldTaskAnimator> animators = new ArrayList<>(); + animators.add(splitAnimator); + animators.add(fullscreenAnimator); + + return new UnfoldAnimationController( + transactionPool, + progressProvider.get(), + animators, + unfoldTransitionHandler, + mainExecutor + ); + } + + + @Provides + static FullscreenUnfoldTaskAnimator provideFullscreenUnfoldTaskAnimator( + Context context, + UnfoldBackgroundController unfoldBackgroundController, + DisplayInsetsController displayInsetsController + ) { + return new FullscreenUnfoldTaskAnimator(context, unfoldBackgroundController, displayInsetsController); } @Provides - static Optional<StageTaskUnfoldController> provideStageTaskUnfoldController( - Optional<ShellUnfoldProgressProvider> progressProvider, + static SplitTaskUnfoldAnimator provideSplitTaskUnfoldAnimatorBase( Context context, - TransactionPool transactionPool, - Lazy<UnfoldBackgroundController> unfoldBackgroundController, - DisplayInsetsController displayInsetsController, - @ShellMainThread ShellExecutor mainExecutor + UnfoldBackgroundController backgroundController, + @ShellMainThread ShellExecutor executor, + Lazy<Optional<SplitScreenController>> splitScreenOptional, + DisplayInsetsController displayInsetsController ) { - return progressProvider.map(shellUnfoldTransitionProgressProvider -> - new StageTaskUnfoldController( - context, - transactionPool, - shellUnfoldTransitionProgressProvider, - displayInsetsController, - unfoldBackgroundController.get(), - mainExecutor - )); + return new SplitTaskUnfoldAnimator(context, executor, splitScreenOptional, + backgroundController, displayInsetsController); + } + + @WMSingleton + @UnfoldShellTransition + @Binds + abstract SplitTaskUnfoldAnimator provideShellSplitTaskUnfoldAnimator( + SplitTaskUnfoldAnimator splitTaskUnfoldAnimator); + + @WMSingleton + @UnfoldTransition + @Binds + abstract SplitTaskUnfoldAnimator provideSplitTaskUnfoldAnimator( + SplitTaskUnfoldAnimator splitTaskUnfoldAnimator); + + @WMSingleton + @Provides + @DynamicOverride + static UnfoldTransitionHandler provideUnfoldTransitionHandler( + Optional<ShellUnfoldProgressProvider> progressProvider, + FullscreenUnfoldTaskAnimator animator, + @UnfoldShellTransition SplitTaskUnfoldAnimator unfoldAnimator, + TransactionPool transactionPool, + Transitions transitions, + @ShellMainThread ShellExecutor executor) { + return new UnfoldTransitionHandler(progressProvider.get(), animator, + unfoldAnimator, transactionPool, executor, transitions); } @WMSingleton 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 756831007c35..6373728344a4 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 @@ -67,6 +67,7 @@ import com.android.internal.logging.InstanceId; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.common.split.SplitLayout; import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.splitscreen.SplitScreenController; @@ -75,6 +76,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; +import java.util.Objects; /** * The policy for handling drag and drop operations to shell. @@ -289,18 +291,15 @@ public class DragAndDropPolicy { final ComponentName currentActivity; if (!inSplitScreen) { currentActivity = mSession.runningTaskInfo != null - ? mSession.runningTaskInfo.baseActivity + ? mSession.runningTaskInfo.baseIntent.getComponent() : null; } else { - final int nonReplacedSplitPosition = position == SPLIT_POSITION_TOP_OR_LEFT - ? SPLIT_POSITION_BOTTOM_OR_RIGHT - : SPLIT_POSITION_TOP_OR_LEFT; - ActivityManager.RunningTaskInfo nonReplacedTaskInfo = - mSplitScreen.getTaskInfo(nonReplacedSplitPosition); - currentActivity = nonReplacedTaskInfo.baseActivity; + final ActivityManager.RunningTaskInfo nonReplacedTaskInfo = + mSplitScreen.getTaskInfo(SplitLayout.reversePosition(position)); + currentActivity = nonReplacedTaskInfo.baseIntent.getComponent(); } - if (currentActivity.equals(dragIntentActivity)) { + if (Objects.equals(currentActivity, dragIntentActivity)) { // Only apply MULTIPLE_TASK if we are dragging the same activity final Intent fillInIntent = new Intent(); fillInIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); 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 fef9be36a35f..692e6acb540c 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 @@ -21,8 +21,6 @@ import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDO import android.app.ActivityManager.RunningTaskInfo; import android.content.Context; -import android.graphics.Point; -import android.graphics.Rect; import android.provider.Settings; import android.util.Slog; import android.util.SparseArray; @@ -32,26 +30,35 @@ import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.protolog.ShellProtoLogGroup; +import com.android.wm.shell.windowdecor.WindowDecorViewModel; import java.io.PrintWriter; /** * {@link ShellTaskOrganizer.TaskListener} for {@link * ShellTaskOrganizer#TASK_LISTENER_TYPE_FREEFORM}. + * + * @param <T> the type of window decoration instance */ -public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener { +public class FreeformTaskListener<T extends AutoCloseable> + implements ShellTaskOrganizer.TaskListener { private static final String TAG = "FreeformTaskListener"; + private final WindowDecorViewModel<T> mWindowDecorationViewModel; private final SyncTransactionQueue mSyncQueue; - private final SparseArray<State> mTasks = new SparseArray<>(); + private final SparseArray<State<T>> mTasks = new SparseArray<>(); - private static class State { + private static class State<T extends AutoCloseable> { RunningTaskInfo mTaskInfo; SurfaceControl mLeash; + T mWindowDecoration; } - public FreeformTaskListener(SyncTransactionQueue syncQueue) { + public FreeformTaskListener( + WindowDecorViewModel<T> windowDecorationViewModel, + SyncTransactionQueue syncQueue) { + mWindowDecorationViewModel = windowDecorationViewModel; mSyncQueue = syncQueue; } @@ -62,23 +69,17 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener { } ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Appeared: #%d", taskInfo.taskId); - final State state = new State(); + final State<T> state = new State<>(); state.mTaskInfo = taskInfo; state.mLeash = leash; + state.mWindowDecoration = + mWindowDecorationViewModel.createWindowDecoration(taskInfo, leash); mTasks.put(taskInfo.taskId, state); - - final Rect taskBounds = taskInfo.configuration.windowConfiguration.getBounds(); - mSyncQueue.runInSync(t -> { - Point taskPosition = taskInfo.positionInParent; - t.setPosition(leash, taskPosition.x, taskPosition.y) - .setWindowCrop(leash, taskBounds.width(), taskBounds.height()) - .show(leash); - }); } @Override public void onTaskVanished(RunningTaskInfo taskInfo) { - State state = mTasks.get(taskInfo.taskId); + State<T> state = mTasks.get(taskInfo.taskId); if (state == null) { Slog.e(TAG, "Task already vanished: #" + taskInfo.taskId); return; @@ -86,11 +87,17 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Vanished: #%d", taskInfo.taskId); mTasks.remove(taskInfo.taskId); + + try { + state.mWindowDecoration.close(); + } catch (Exception e) { + Slog.e(TAG, "Failed to release window decoration.", e); + } } @Override public void onTaskInfoChanged(RunningTaskInfo taskInfo) { - State state = mTasks.get(taskInfo.taskId); + State<T> state = mTasks.get(taskInfo.taskId); if (state == null) { throw new RuntimeException( "Task info changed before appearing: #" + taskInfo.taskId); @@ -98,15 +105,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Info Changed: #%d", taskInfo.taskId); state.mTaskInfo = taskInfo; - - final Rect taskBounds = taskInfo.configuration.windowConfiguration.getBounds(); - final SurfaceControl leash = state.mLeash; - mSyncQueue.runInSync(t -> { - Point taskPosition = taskInfo.positionInParent; - t.setPosition(leash, taskPosition.x, taskPosition.y) - .setWindowCrop(leash, taskBounds.width(), taskBounds.height()) - .show(leash); - }); + mWindowDecorationViewModel.onTaskInfoChanged(state.mTaskInfo, state.mWindowDecoration); } @Override 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 73e6cba43ec0..79e363bcdb41 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 @@ -16,17 +16,13 @@ package com.android.wm.shell.fullscreen; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; - import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN; import static com.android.wm.shell.ShellTaskOrganizer.taskListenerTypeToString; import android.app.ActivityManager.RunningTaskInfo; -import android.app.TaskInfo; import android.graphics.Point; import android.util.Slog; import android.util.SparseArray; -import android.util.SparseBooleanArray; import android.view.SurfaceControl; import androidx.annotation.NonNull; @@ -48,22 +44,17 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { private static final String TAG = "FullscreenTaskListener"; private final SyncTransactionQueue mSyncQueue; - private final FullscreenUnfoldController mFullscreenUnfoldController; private final Optional<RecentTasksController> mRecentTasksOptional; private final SparseArray<TaskData> mDataByTaskId = new SparseArray<>(); - private final AnimatableTasksListener mAnimatableTasksListener = new AnimatableTasksListener(); - public FullscreenTaskListener(SyncTransactionQueue syncQueue, - Optional<FullscreenUnfoldController> unfoldController) { - this(syncQueue, unfoldController, Optional.empty()); + public FullscreenTaskListener(SyncTransactionQueue syncQueue) { + this(syncQueue, Optional.empty()); } public FullscreenTaskListener(SyncTransactionQueue syncQueue, - Optional<FullscreenUnfoldController> unfoldController, Optional<RecentTasksController> recentTasks) { mSyncQueue = syncQueue; - mFullscreenUnfoldController = unfoldController.orElse(null); mRecentTasksOptional = recentTasks; } @@ -76,6 +67,7 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { taskInfo.taskId); final Point positionInParent = taskInfo.positionInParent; mDataByTaskId.put(taskInfo.taskId, new TaskData(leash, positionInParent)); + if (Transitions.ENABLE_SHELL_TRANSITIONS) return; mSyncQueue.runInSync(t -> { // Reset several properties back to fullscreen (PiP, for example, leaves all these @@ -87,7 +79,6 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { t.show(leash); }); - mAnimatableTasksListener.onTaskAppeared(taskInfo); updateRecentsForVisibleFullscreenTask(taskInfo); } @@ -95,7 +86,6 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { public void onTaskInfoChanged(RunningTaskInfo taskInfo) { if (Transitions.ENABLE_SHELL_TRANSITIONS) return; - mAnimatableTasksListener.onTaskInfoChanged(taskInfo); updateRecentsForVisibleFullscreenTask(taskInfo); final TaskData data = mDataByTaskId.get(taskInfo.taskId); @@ -115,7 +105,6 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { return; } - mAnimatableTasksListener.onTaskVanished(taskInfo); mDataByTaskId.remove(taskInfo.taskId); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Vanished: #%d", @@ -173,65 +162,4 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { this.positionInParent = positionInParent; } } - - class AnimatableTasksListener { - private final SparseBooleanArray mTaskIds = new SparseBooleanArray(); - - public void onTaskAppeared(RunningTaskInfo taskInfo) { - final boolean isApplicable = isAnimatable(taskInfo); - if (isApplicable) { - mTaskIds.put(taskInfo.taskId, true); - - if (mFullscreenUnfoldController != null) { - SurfaceControl leash = mDataByTaskId.get(taskInfo.taskId).surface; - mFullscreenUnfoldController.onTaskAppeared(taskInfo, leash); - } - } - } - - public void onTaskInfoChanged(RunningTaskInfo taskInfo) { - final boolean isCurrentlyApplicable = mTaskIds.get(taskInfo.taskId); - final boolean isApplicable = isAnimatable(taskInfo); - - if (isCurrentlyApplicable) { - if (isApplicable) { - // Still applicable, send update - if (mFullscreenUnfoldController != null) { - mFullscreenUnfoldController.onTaskInfoChanged(taskInfo); - } - } else { - // Became inapplicable - if (mFullscreenUnfoldController != null) { - mFullscreenUnfoldController.onTaskVanished(taskInfo); - } - mTaskIds.put(taskInfo.taskId, false); - } - } else { - if (isApplicable) { - // Became applicable - mTaskIds.put(taskInfo.taskId, true); - - if (mFullscreenUnfoldController != null) { - SurfaceControl leash = mDataByTaskId.get(taskInfo.taskId).surface; - mFullscreenUnfoldController.onTaskAppeared(taskInfo, leash); - } - } - } - } - - public void onTaskVanished(RunningTaskInfo taskInfo) { - final boolean isCurrentlyApplicable = mTaskIds.get(taskInfo.taskId); - if (isCurrentlyApplicable && mFullscreenUnfoldController != null) { - mFullscreenUnfoldController.onTaskVanished(taskInfo); - } - mTaskIds.put(taskInfo.taskId, false); - } - - private boolean isAnimatable(TaskInfo taskInfo) { - // Filter all visible tasks that are not launcher tasks - // We do not animate launcher as it handles the animation by itself - return taskInfo != null && taskInfo.isVisible && taskInfo.getConfiguration() - .windowConfiguration.getActivityType() != ACTIVITY_TYPE_HOME; - } - } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java index b4c87b6cbf95..2c8ba0970ccc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java @@ -51,6 +51,7 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.startingsurface.StartingWindowController; +import com.android.wm.shell.unfold.UnfoldAnimationController; import java.io.PrintWriter; import java.util.List; @@ -146,9 +147,11 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer { SyncTransactionQueue syncTransactionQueue, DisplayController displayController, DisplayInsetsController displayInsetsController, + Optional<UnfoldAnimationController> unfoldAnimationController, Optional<RecentTasksController> recentTasks, KidsModeSettingsObserver kidsModeSettingsObserver) { - super(taskOrganizerController, mainExecutor, context, /* compatUI= */ null, recentTasks); + super(taskOrganizerController, mainExecutor, context, /* compatUI= */ null, + unfoldAnimationController, recentTasks); mContext = context; mMainHandler = mainHandler; mSyncQueue = syncTransactionQueue; @@ -164,8 +167,9 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer { SyncTransactionQueue syncTransactionQueue, DisplayController displayController, DisplayInsetsController displayInsetsController, + Optional<UnfoldAnimationController> unfoldAnimationController, Optional<RecentTasksController> recentTasks) { - super(mainExecutor, context, /* compatUI= */ null, recentTasks); + super(mainExecutor, context, /* compatUI= */ null, unfoldAnimationController, recentTasks); mContext = context; mMainHandler = mainHandler; mSyncQueue = syncTransactionQueue; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerImeController.java deleted file mode 100644 index aced072c8c71..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerImeController.java +++ /dev/null @@ -1,418 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.legacysplitscreen; - -import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED; -import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; -import android.annotation.Nullable; -import android.graphics.Rect; -import android.util.Slog; -import android.view.Choreographer; -import android.view.SurfaceControl; -import android.window.TaskOrganizer; -import android.window.WindowContainerToken; -import android.window.WindowContainerTransaction; - -import com.android.wm.shell.common.DisplayImeController; -import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.common.TransactionPool; - -class DividerImeController implements DisplayImeController.ImePositionProcessor { - private static final String TAG = "DividerImeController"; - private static final boolean DEBUG = LegacySplitScreenController.DEBUG; - - private static final float ADJUSTED_NONFOCUS_DIM = 0.3f; - - private final LegacySplitScreenTaskListener mSplits; - private final TransactionPool mTransactionPool; - private final ShellExecutor mMainExecutor; - private final TaskOrganizer mTaskOrganizer; - - /** - * These are the y positions of the top of the IME surface when it is hidden and when it is - * shown respectively. These are NOT necessarily the top of the visible IME itself. - */ - private int mHiddenTop = 0; - private int mShownTop = 0; - - // The following are target states (what we are curretly animating towards). - /** - * {@code true} if, at the end of the animation, the split task positions should be - * adjusted by height of the IME. This happens when the secondary split is the IME target. - */ - private boolean mTargetAdjusted = false; - /** - * {@code true} if, at the end of the animation, the IME should be shown/visible - * regardless of what has focus. - */ - private boolean mTargetShown = false; - private float mTargetPrimaryDim = 0.f; - private float mTargetSecondaryDim = 0.f; - - // The following are the current (most recent) states set during animation - /** {@code true} if the secondary split has IME focus. */ - private boolean mSecondaryHasFocus = false; - /** The dimming currently applied to the primary/secondary splits. */ - private float mLastPrimaryDim = 0.f; - private float mLastSecondaryDim = 0.f; - /** The most recent y position of the top of the IME surface */ - private int mLastAdjustTop = -1; - - // The following are states reached last time an animation fully completed. - /** {@code true} if the IME was shown/visible by the last-completed animation. */ - private boolean mImeWasShown = false; - /** {@code true} if the split positions were adjusted by the last-completed animation. */ - private boolean mAdjusted = false; - - /** - * When some aspect of split-screen needs to animate independent from the IME, - * this will be non-null and control split animation. - */ - @Nullable - private ValueAnimator mAnimation = null; - - private boolean mPaused = true; - private boolean mPausedTargetAdjusted = false; - - DividerImeController(LegacySplitScreenTaskListener splits, TransactionPool pool, - ShellExecutor mainExecutor, TaskOrganizer taskOrganizer) { - mSplits = splits; - mTransactionPool = pool; - mMainExecutor = mainExecutor; - mTaskOrganizer = taskOrganizer; - } - - private DividerView getView() { - return mSplits.mSplitScreenController.getDividerView(); - } - - private LegacySplitDisplayLayout getLayout() { - return mSplits.mSplitScreenController.getSplitLayout(); - } - - private boolean isDividerHidden() { - final DividerView view = mSplits.mSplitScreenController.getDividerView(); - return view == null || view.isHidden(); - } - - private boolean getSecondaryHasFocus(int displayId) { - WindowContainerToken imeSplit = mTaskOrganizer.getImeTarget(displayId); - return imeSplit != null - && (imeSplit.asBinder() == mSplits.mSecondary.token.asBinder()); - } - - void reset() { - mPaused = true; - mPausedTargetAdjusted = false; - mAnimation = null; - mAdjusted = mTargetAdjusted = false; - mImeWasShown = mTargetShown = false; - mTargetPrimaryDim = mTargetSecondaryDim = mLastPrimaryDim = mLastSecondaryDim = 0.f; - mSecondaryHasFocus = false; - mLastAdjustTop = -1; - } - - private void updateDimTargets() { - final boolean splitIsVisible = !getView().isHidden(); - mTargetPrimaryDim = (mSecondaryHasFocus && mTargetShown && splitIsVisible) - ? ADJUSTED_NONFOCUS_DIM : 0.f; - mTargetSecondaryDim = (!mSecondaryHasFocus && mTargetShown && splitIsVisible) - ? ADJUSTED_NONFOCUS_DIM : 0.f; - } - - - @Override - public void onImeControlTargetChanged(int displayId, boolean controlling) { - // Restore the split layout when wm-shell is not controlling IME insets anymore. - if (!controlling && mTargetShown) { - mPaused = false; - mTargetAdjusted = mTargetShown = false; - mTargetPrimaryDim = mTargetSecondaryDim = 0.f; - updateImeAdjustState(true /* force */); - startAsyncAnimation(); - } - } - - @Override - @ImeAnimationFlags - public int onImeStartPositioning(int displayId, int hiddenTop, int shownTop, - boolean imeShouldShow, boolean imeIsFloating, SurfaceControl.Transaction t) { - if (isDividerHidden()) { - return 0; - } - mHiddenTop = hiddenTop; - mShownTop = shownTop; - mTargetShown = imeShouldShow; - mSecondaryHasFocus = getSecondaryHasFocus(displayId); - final boolean targetAdjusted = imeShouldShow && mSecondaryHasFocus - && !imeIsFloating && !getLayout().mDisplayLayout.isLandscape() - && !mSplits.mSplitScreenController.isMinimized(); - if (mLastAdjustTop < 0) { - mLastAdjustTop = imeShouldShow ? hiddenTop : shownTop; - } else if (mLastAdjustTop != (imeShouldShow ? mShownTop : mHiddenTop)) { - if (mTargetAdjusted != targetAdjusted && targetAdjusted == mAdjusted) { - // Check for an "interruption" of an existing animation. In this case, we - // need to fake-flip the last-known state direction so that the animation - // completes in the other direction. - mAdjusted = mTargetAdjusted; - } else if (targetAdjusted && mTargetAdjusted && mAdjusted) { - // Already fully adjusted for IME, but IME height has changed; so, force-start - // an async animation to the new IME height. - mAdjusted = false; - } - } - if (mPaused) { - mPausedTargetAdjusted = targetAdjusted; - if (DEBUG) Slog.d(TAG, " ime starting but paused " + dumpState()); - return (targetAdjusted || mAdjusted) ? IME_ANIMATION_NO_ALPHA : 0; - } - mTargetAdjusted = targetAdjusted; - updateDimTargets(); - if (DEBUG) Slog.d(TAG, " ime starting. " + dumpState()); - if (mAnimation != null || (mImeWasShown && imeShouldShow - && mTargetAdjusted != mAdjusted)) { - // We need to animate adjustment independently of the IME position, so - // start our own animation to drive adjustment. This happens when a - // different split's editor has gained focus while the IME is still visible. - startAsyncAnimation(); - } - updateImeAdjustState(); - - return (mTargetAdjusted || mAdjusted) ? IME_ANIMATION_NO_ALPHA : 0; - } - - private void updateImeAdjustState() { - updateImeAdjustState(false /* force */); - } - - private void updateImeAdjustState(boolean force) { - if (mAdjusted != mTargetAdjusted || force) { - // Reposition the server's secondary split position so that it evaluates - // insets properly. - WindowContainerTransaction wct = new WindowContainerTransaction(); - final LegacySplitDisplayLayout splitLayout = getLayout(); - if (mTargetAdjusted) { - splitLayout.updateAdjustedBounds(mShownTop, mHiddenTop, mShownTop); - wct.setBounds(mSplits.mSecondary.token, splitLayout.mAdjustedSecondary); - // "Freeze" the configuration size so that the app doesn't get a config - // or relaunch. This is required because normally nav-bar contributes - // to configuration bounds (via nondecorframe). - Rect adjustAppBounds = new Rect(mSplits.mSecondary.configuration - .windowConfiguration.getAppBounds()); - adjustAppBounds.offset(0, splitLayout.mAdjustedSecondary.top - - splitLayout.mSecondary.top); - wct.setAppBounds(mSplits.mSecondary.token, adjustAppBounds); - wct.setScreenSizeDp(mSplits.mSecondary.token, - mSplits.mSecondary.configuration.screenWidthDp, - mSplits.mSecondary.configuration.screenHeightDp); - - wct.setBounds(mSplits.mPrimary.token, splitLayout.mAdjustedPrimary); - adjustAppBounds = new Rect(mSplits.mPrimary.configuration - .windowConfiguration.getAppBounds()); - adjustAppBounds.offset(0, splitLayout.mAdjustedPrimary.top - - splitLayout.mPrimary.top); - wct.setAppBounds(mSplits.mPrimary.token, adjustAppBounds); - wct.setScreenSizeDp(mSplits.mPrimary.token, - mSplits.mPrimary.configuration.screenWidthDp, - mSplits.mPrimary.configuration.screenHeightDp); - } else { - wct.setBounds(mSplits.mSecondary.token, splitLayout.mSecondary); - wct.setAppBounds(mSplits.mSecondary.token, null); - wct.setScreenSizeDp(mSplits.mSecondary.token, - SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED); - wct.setBounds(mSplits.mPrimary.token, splitLayout.mPrimary); - wct.setAppBounds(mSplits.mPrimary.token, null); - wct.setScreenSizeDp(mSplits.mPrimary.token, - SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED); - } - - if (!mSplits.mSplitScreenController.getWmProxy().queueSyncTransactionIfWaiting(wct)) { - mTaskOrganizer.applyTransaction(wct); - } - } - - // Update all the adjusted-for-ime states - if (!mPaused) { - final DividerView view = getView(); - if (view != null) { - view.setAdjustedForIme(mTargetShown, mTargetShown - ? DisplayImeController.ANIMATION_DURATION_SHOW_MS - : DisplayImeController.ANIMATION_DURATION_HIDE_MS); - } - } - mSplits.mSplitScreenController.setAdjustedForIme(mTargetShown && !mPaused); - } - - @Override - public void onImePositionChanged(int displayId, int imeTop, - SurfaceControl.Transaction t) { - if (mAnimation != null || isDividerHidden() || mPaused) { - // Not synchronized with IME anymore, so return. - return; - } - final float fraction = ((float) imeTop - mHiddenTop) / (mShownTop - mHiddenTop); - final float progress = mTargetShown ? fraction : 1.f - fraction; - onProgress(progress, t); - } - - @Override - public void onImeEndPositioning(int displayId, boolean cancelled, - SurfaceControl.Transaction t) { - if (mAnimation != null || isDividerHidden() || mPaused) { - // Not synchronized with IME anymore, so return. - return; - } - onEnd(cancelled, t); - } - - private void onProgress(float progress, SurfaceControl.Transaction t) { - final DividerView view = getView(); - if (mTargetAdjusted != mAdjusted && !mPaused) { - final LegacySplitDisplayLayout splitLayout = getLayout(); - final float fraction = mTargetAdjusted ? progress : 1.f - progress; - mLastAdjustTop = (int) (fraction * mShownTop + (1.f - fraction) * mHiddenTop); - splitLayout.updateAdjustedBounds(mLastAdjustTop, mHiddenTop, mShownTop); - view.resizeSplitSurfaces(t, splitLayout.mAdjustedPrimary, - splitLayout.mAdjustedSecondary); - } - final float invProg = 1.f - progress; - view.setResizeDimLayer(t, true /* primary */, - mLastPrimaryDim * invProg + progress * mTargetPrimaryDim); - view.setResizeDimLayer(t, false /* primary */, - mLastSecondaryDim * invProg + progress * mTargetSecondaryDim); - } - - void setDimsHidden(SurfaceControl.Transaction t, boolean hidden) { - final DividerView view = getView(); - if (hidden) { - view.setResizeDimLayer(t, true /* primary */, 0.f /* alpha */); - view.setResizeDimLayer(t, false /* primary */, 0.f /* alpha */); - } else { - updateDimTargets(); - view.setResizeDimLayer(t, true /* primary */, mTargetPrimaryDim); - view.setResizeDimLayer(t, false /* primary */, mTargetSecondaryDim); - } - } - - private void onEnd(boolean cancelled, SurfaceControl.Transaction t) { - if (!cancelled) { - onProgress(1.f, t); - mAdjusted = mTargetAdjusted; - mImeWasShown = mTargetShown; - mLastAdjustTop = mAdjusted ? mShownTop : mHiddenTop; - mLastPrimaryDim = mTargetPrimaryDim; - mLastSecondaryDim = mTargetSecondaryDim; - } - } - - private void startAsyncAnimation() { - if (mAnimation != null) { - mAnimation.cancel(); - } - mAnimation = ValueAnimator.ofFloat(0.f, 1.f); - mAnimation.setDuration(DisplayImeController.ANIMATION_DURATION_SHOW_MS); - if (mTargetAdjusted != mAdjusted) { - final float fraction = - ((float) mLastAdjustTop - mHiddenTop) / (mShownTop - mHiddenTop); - final float progress = mTargetAdjusted ? fraction : 1.f - fraction; - mAnimation.setCurrentFraction(progress); - } - - mAnimation.addUpdateListener(animation -> { - SurfaceControl.Transaction t = mTransactionPool.acquire(); - float value = (float) animation.getAnimatedValue(); - onProgress(value, t); - t.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId()); - t.apply(); - mTransactionPool.release(t); - }); - mAnimation.setInterpolator(DisplayImeController.INTERPOLATOR); - mAnimation.addListener(new AnimatorListenerAdapter() { - private boolean mCancel = false; - - @Override - public void onAnimationCancel(Animator animation) { - mCancel = true; - } - - @Override - public void onAnimationEnd(Animator animation) { - SurfaceControl.Transaction t = mTransactionPool.acquire(); - onEnd(mCancel, t); - t.apply(); - mTransactionPool.release(t); - mAnimation = null; - } - }); - mAnimation.start(); - } - - private String dumpState() { - return "top:" + mHiddenTop + "->" + mShownTop - + " adj:" + mAdjusted + "->" + mTargetAdjusted + "(" + mLastAdjustTop + ")" - + " shw:" + mImeWasShown + "->" + mTargetShown - + " dims:" + mLastPrimaryDim + "," + mLastSecondaryDim - + "->" + mTargetPrimaryDim + "," + mTargetSecondaryDim - + " shf:" + mSecondaryHasFocus + " desync:" + (mAnimation != null) - + " paus:" + mPaused + "[" + mPausedTargetAdjusted + "]"; - } - - /** Completely aborts/resets adjustment state */ - public void pause(int displayId) { - if (DEBUG) Slog.d(TAG, "ime pause posting " + dumpState()); - mMainExecutor.execute(() -> { - if (DEBUG) Slog.d(TAG, "ime pause run posted " + dumpState()); - if (mPaused) { - return; - } - mPaused = true; - mPausedTargetAdjusted = mTargetAdjusted; - mTargetAdjusted = false; - mTargetPrimaryDim = mTargetSecondaryDim = 0.f; - updateImeAdjustState(); - startAsyncAnimation(); - if (mAnimation != null) { - mAnimation.end(); - } - }); - } - - public void resume(int displayId) { - if (DEBUG) Slog.d(TAG, "ime resume posting " + dumpState()); - mMainExecutor.execute(() -> { - if (DEBUG) Slog.d(TAG, "ime resume run posted " + dumpState()); - if (!mPaused) { - return; - } - mPaused = false; - mTargetAdjusted = mPausedTargetAdjusted; - updateDimTargets(); - final DividerView view = getView(); - if ((mTargetAdjusted != mAdjusted) && !mSplits.mSplitScreenController.isMinimized() - && view != null) { - // End unminimize animations since they conflict with adjustment animations. - view.finishAnimations(); - } - updateImeAdjustState(); - startAsyncAnimation(); - }); - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerView.java deleted file mode 100644 index 73be2835d2cd..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerView.java +++ /dev/null @@ -1,1314 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.legacysplitscreen; - -import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW; -import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW; -import static android.view.WindowManager.DOCKED_RIGHT; - -import static com.android.wm.shell.animation.Interpolators.DIM_INTERPOLATOR; -import static com.android.wm.shell.animation.Interpolators.SLOWDOWN_INTERPOLATOR; -import static com.android.wm.shell.common.split.DividerView.TOUCH_ANIMATION_DURATION; -import static com.android.wm.shell.common.split.DividerView.TOUCH_RELEASE_ANIMATION_DURATION; - -import android.animation.AnimationHandler; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; -import android.annotation.Nullable; -import android.content.Context; -import android.content.res.Configuration; -import android.graphics.Matrix; -import android.graphics.Rect; -import android.graphics.Region; -import android.graphics.Region.Op; -import android.hardware.display.DisplayManager; -import android.os.Bundle; -import android.util.AttributeSet; -import android.util.Slog; -import android.view.Choreographer; -import android.view.Display; -import android.view.MotionEvent; -import android.view.PointerIcon; -import android.view.SurfaceControl; -import android.view.SurfaceControl.Transaction; -import android.view.VelocityTracker; -import android.view.View; -import android.view.View.OnTouchListener; -import android.view.ViewConfiguration; -import android.view.ViewTreeObserver.InternalInsetsInfo; -import android.view.ViewTreeObserver.OnComputeInternalInsetsListener; -import android.view.WindowManager; -import android.view.accessibility.AccessibilityNodeInfo; -import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; -import android.view.animation.Interpolator; -import android.view.animation.PathInterpolator; -import android.widget.FrameLayout; - -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.internal.policy.DividerSnapAlgorithm; -import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget; -import com.android.internal.policy.DockedDividerUtils; -import com.android.wm.shell.R; -import com.android.wm.shell.animation.FlingAnimationUtils; -import com.android.wm.shell.animation.Interpolators; -import com.android.wm.shell.common.split.DividerHandleView; - -import java.util.function.Consumer; - -/** - * Docked stack divider. - */ -public class DividerView extends FrameLayout implements OnTouchListener, - OnComputeInternalInsetsListener { - private static final String TAG = "DividerView"; - private static final boolean DEBUG = LegacySplitScreenController.DEBUG; - - interface DividerCallbacks { - void onDraggingStart(); - void onDraggingEnd(); - } - - public static final int INVALID_RECENTS_GROW_TARGET = -1; - - private static final int LOG_VALUE_RESIZE_50_50 = 0; - private static final int LOG_VALUE_RESIZE_DOCKED_SMALLER = 1; - private static final int LOG_VALUE_RESIZE_DOCKED_LARGER = 2; - - private static final int LOG_VALUE_UNDOCK_MAX_DOCKED = 0; - private static final int LOG_VALUE_UNDOCK_MAX_OTHER = 1; - - private static final int TASK_POSITION_SAME = Integer.MAX_VALUE; - - /** - * How much the background gets scaled when we are in the minimized dock state. - */ - private static final float MINIMIZE_DOCK_SCALE = 0f; - private static final float ADJUSTED_FOR_IME_SCALE = 0.5f; - - private static final Interpolator IME_ADJUST_INTERPOLATOR = - new PathInterpolator(0.2f, 0f, 0.1f, 1f); - - private DividerHandleView mHandle; - private View mBackground; - private MinimizedDockShadow mMinimizedShadow; - private int mStartX; - private int mStartY; - private int mStartPosition; - private int mDockSide; - private boolean mMoving; - private int mTouchSlop; - private boolean mBackgroundLifted; - private boolean mIsInMinimizeInteraction; - SnapTarget mSnapTargetBeforeMinimized; - - private int mDividerInsets; - private final Display mDefaultDisplay; - - private int mDividerSize; - private int mTouchElevation; - private int mLongPressEntraceAnimDuration; - - private final Rect mDockedRect = new Rect(); - private final Rect mDockedTaskRect = new Rect(); - private final Rect mOtherTaskRect = new Rect(); - private final Rect mOtherRect = new Rect(); - private final Rect mDockedInsetRect = new Rect(); - private final Rect mOtherInsetRect = new Rect(); - private final Rect mLastResizeRect = new Rect(); - private final Rect mTmpRect = new Rect(); - private LegacySplitScreenController mSplitScreenController; - private WindowManagerProxy mWindowManagerProxy; - private DividerWindowManager mWindowManager; - private VelocityTracker mVelocityTracker; - private FlingAnimationUtils mFlingAnimationUtils; - private LegacySplitDisplayLayout mSplitLayout; - private DividerImeController mImeController; - private DividerCallbacks mCallback; - - private AnimationHandler mSfVsyncAnimationHandler; - private ValueAnimator mCurrentAnimator; - private boolean mEntranceAnimationRunning; - private boolean mExitAnimationRunning; - private int mExitStartPosition; - private boolean mDockedStackMinimized; - private boolean mHomeStackResizable; - private boolean mAdjustedForIme; - private DividerState mState; - - private LegacySplitScreenTaskListener mTiles; - boolean mFirstLayout = true; - int mDividerPositionX; - int mDividerPositionY; - - private final Matrix mTmpMatrix = new Matrix(); - private final float[] mTmpValues = new float[9]; - - // The view is removed or in the process of been removed from the system. - private boolean mRemoved; - - // Whether the surface for this view has been hidden regardless of actual visibility. This is - // used interact with keyguard. - private boolean mSurfaceHidden = false; - - private final AccessibilityDelegate mHandleDelegate = new AccessibilityDelegate() { - @Override - public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(host, info); - final DividerSnapAlgorithm snapAlgorithm = getSnapAlgorithm(); - if (isHorizontalDivision()) { - info.addAction(new AccessibilityAction(R.id.action_move_tl_full, - mContext.getString(R.string.accessibility_action_divider_top_full))); - if (snapAlgorithm.isFirstSplitTargetAvailable()) { - info.addAction(new AccessibilityAction(R.id.action_move_tl_70, - mContext.getString(R.string.accessibility_action_divider_top_70))); - } - if (snapAlgorithm.showMiddleSplitTargetForAccessibility()) { - // Only show the middle target if there are more than 1 split target - info.addAction(new AccessibilityAction(R.id.action_move_tl_50, - mContext.getString(R.string.accessibility_action_divider_top_50))); - } - if (snapAlgorithm.isLastSplitTargetAvailable()) { - info.addAction(new AccessibilityAction(R.id.action_move_tl_30, - mContext.getString(R.string.accessibility_action_divider_top_30))); - } - info.addAction(new AccessibilityAction(R.id.action_move_rb_full, - mContext.getString(R.string.accessibility_action_divider_bottom_full))); - } else { - info.addAction(new AccessibilityAction(R.id.action_move_tl_full, - mContext.getString(R.string.accessibility_action_divider_left_full))); - if (snapAlgorithm.isFirstSplitTargetAvailable()) { - info.addAction(new AccessibilityAction(R.id.action_move_tl_70, - mContext.getString(R.string.accessibility_action_divider_left_70))); - } - if (snapAlgorithm.showMiddleSplitTargetForAccessibility()) { - // Only show the middle target if there are more than 1 split target - info.addAction(new AccessibilityAction(R.id.action_move_tl_50, - mContext.getString(R.string.accessibility_action_divider_left_50))); - } - if (snapAlgorithm.isLastSplitTargetAvailable()) { - info.addAction(new AccessibilityAction(R.id.action_move_tl_30, - mContext.getString(R.string.accessibility_action_divider_left_30))); - } - info.addAction(new AccessibilityAction(R.id.action_move_rb_full, - mContext.getString(R.string.accessibility_action_divider_right_full))); - } - } - - @Override - public boolean performAccessibilityAction(View host, int action, Bundle args) { - int currentPosition = getCurrentPosition(); - SnapTarget nextTarget = null; - DividerSnapAlgorithm snapAlgorithm = mSplitLayout.getSnapAlgorithm(); - if (action == R.id.action_move_tl_full) { - nextTarget = snapAlgorithm.getDismissEndTarget(); - } else if (action == R.id.action_move_tl_70) { - nextTarget = snapAlgorithm.getLastSplitTarget(); - } else if (action == R.id.action_move_tl_50) { - nextTarget = snapAlgorithm.getMiddleTarget(); - } else if (action == R.id.action_move_tl_30) { - nextTarget = snapAlgorithm.getFirstSplitTarget(); - } else if (action == R.id.action_move_rb_full) { - nextTarget = snapAlgorithm.getDismissStartTarget(); - } - if (nextTarget != null) { - startDragging(true /* animate */, false /* touching */); - stopDragging(currentPosition, nextTarget, 250, Interpolators.FAST_OUT_SLOW_IN); - return true; - } - return super.performAccessibilityAction(host, action, args); - } - }; - - private final Runnable mResetBackgroundRunnable = new Runnable() { - @Override - public void run() { - resetBackground(); - } - }; - - public DividerView(Context context) { - this(context, null); - } - - public DividerView(Context context, @Nullable AttributeSet attrs) { - this(context, attrs, 0); - } - - public DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { - this(context, attrs, defStyleAttr, 0); - } - - public DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, - int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - final DisplayManager displayManager = - (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE); - mDefaultDisplay = displayManager.getDisplay(Display.DEFAULT_DISPLAY); - } - - public void setAnimationHandler(AnimationHandler sfVsyncAnimationHandler) { - mSfVsyncAnimationHandler = sfVsyncAnimationHandler; - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mHandle = findViewById(R.id.docked_divider_handle); - mBackground = findViewById(R.id.docked_divider_background); - mMinimizedShadow = findViewById(R.id.minimized_dock_shadow); - mHandle.setOnTouchListener(this); - final int dividerWindowWidth = getResources().getDimensionPixelSize( - com.android.internal.R.dimen.docked_stack_divider_thickness); - mDividerInsets = getResources().getDimensionPixelSize( - com.android.internal.R.dimen.docked_stack_divider_insets); - mDividerSize = dividerWindowWidth - 2 * mDividerInsets; - mTouchElevation = getResources().getDimensionPixelSize( - R.dimen.docked_stack_divider_lift_elevation); - mLongPressEntraceAnimDuration = getResources().getInteger( - R.integer.long_press_dock_anim_duration); - mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop(); - mFlingAnimationUtils = new FlingAnimationUtils(getResources().getDisplayMetrics(), 0.3f); - boolean landscape = getResources().getConfiguration().orientation - == Configuration.ORIENTATION_LANDSCAPE; - mHandle.setPointerIcon(PointerIcon.getSystemIcon(getContext(), - landscape ? TYPE_HORIZONTAL_DOUBLE_ARROW : TYPE_VERTICAL_DOUBLE_ARROW)); - getViewTreeObserver().addOnComputeInternalInsetsListener(this); - mHandle.setAccessibilityDelegate(mHandleDelegate); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - - // Save the current target if not minimized once attached to window - if (mDockSide != WindowManager.DOCKED_INVALID && !mIsInMinimizeInteraction) { - saveSnapTargetBeforeMinimized(mSnapTargetBeforeMinimized); - } - mFirstLayout = true; - } - - void onDividerRemoved() { - mRemoved = true; - mCallback = null; - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - if (mFirstLayout) { - // Wait for first layout so that the ViewRootImpl surface has been created. - initializeSurfaceState(); - mFirstLayout = false; - } - int minimizeLeft = 0; - int minimizeTop = 0; - if (mDockSide == WindowManager.DOCKED_TOP) { - minimizeTop = mBackground.getTop(); - } else if (mDockSide == WindowManager.DOCKED_LEFT) { - minimizeLeft = mBackground.getLeft(); - } else if (mDockSide == WindowManager.DOCKED_RIGHT) { - minimizeLeft = mBackground.getRight() - mMinimizedShadow.getWidth(); - } - mMinimizedShadow.layout(minimizeLeft, minimizeTop, - minimizeLeft + mMinimizedShadow.getMeasuredWidth(), - minimizeTop + mMinimizedShadow.getMeasuredHeight()); - if (changed) { - notifySplitScreenBoundsChanged(); - } - } - - void injectDependencies(LegacySplitScreenController splitScreenController, - DividerWindowManager windowManager, DividerState dividerState, - DividerCallbacks callback, LegacySplitScreenTaskListener tiles, - LegacySplitDisplayLayout sdl, DividerImeController imeController, - WindowManagerProxy wmProxy) { - mSplitScreenController = splitScreenController; - mWindowManager = windowManager; - mState = dividerState; - mCallback = callback; - mTiles = tiles; - mSplitLayout = sdl; - mImeController = imeController; - mWindowManagerProxy = wmProxy; - - if (mState.mRatioPositionBeforeMinimized == 0) { - // Set the middle target as the initial state - mSnapTargetBeforeMinimized = mSplitLayout.getSnapAlgorithm().getMiddleTarget(); - } else { - repositionSnapTargetBeforeMinimized(); - } - } - - /** Gets non-minimized secondary bounds of split screen. */ - public Rect getNonMinimizedSplitScreenSecondaryBounds() { - mOtherTaskRect.set(mSplitLayout.mSecondary); - return mOtherTaskRect; - } - - private boolean inSplitMode() { - return getVisibility() == VISIBLE; - } - - /** Unlike setVisible, this directly hides the surface without changing view visibility. */ - void setHidden(boolean hidden) { - if (mSurfaceHidden == hidden) { - return; - } - mSurfaceHidden = hidden; - post(() -> { - final SurfaceControl sc = getWindowSurfaceControl(); - if (sc == null) { - return; - } - Transaction t = mTiles.getTransaction(); - if (hidden) { - t.hide(sc); - } else { - t.show(sc); - } - mImeController.setDimsHidden(t, hidden); - t.apply(); - mTiles.releaseTransaction(t); - }); - } - - boolean isHidden() { - return getVisibility() != View.VISIBLE || mSurfaceHidden; - } - - /** Starts dragging the divider bar. */ - public boolean startDragging(boolean animate, boolean touching) { - cancelFlingAnimation(); - if (touching) { - mHandle.setTouching(true, animate); - } - mDockSide = mSplitLayout.getPrimarySplitSide(); - - mWindowManagerProxy.setResizing(true); - if (touching) { - mWindowManager.setSlippery(false); - liftBackground(); - } - if (mCallback != null) { - mCallback.onDraggingStart(); - } - return inSplitMode(); - } - - /** Stops dragging the divider bar. */ - public void stopDragging(int position, float velocity, boolean avoidDismissStart, - boolean logMetrics) { - mHandle.setTouching(false, true /* animate */); - fling(position, velocity, avoidDismissStart, logMetrics); - mWindowManager.setSlippery(true); - releaseBackground(); - } - - private void stopDragging(int position, SnapTarget target, long duration, - Interpolator interpolator) { - stopDragging(position, target, duration, 0 /* startDelay*/, 0 /* endDelay */, interpolator); - } - - private void stopDragging(int position, SnapTarget target, long duration, - Interpolator interpolator, long endDelay) { - stopDragging(position, target, duration, 0 /* startDelay*/, endDelay, interpolator); - } - - private void stopDragging(int position, SnapTarget target, long duration, long startDelay, - long endDelay, Interpolator interpolator) { - mHandle.setTouching(false, true /* animate */); - flingTo(position, target, duration, startDelay, endDelay, interpolator); - mWindowManager.setSlippery(true); - releaseBackground(); - } - - private void stopDragging() { - mHandle.setTouching(false, true /* animate */); - mWindowManager.setSlippery(true); - mWindowManagerProxy.setResizing(false); - releaseBackground(); - } - - private void updateDockSide() { - mDockSide = mSplitLayout.getPrimarySplitSide(); - mMinimizedShadow.setDockSide(mDockSide); - } - - public DividerSnapAlgorithm getSnapAlgorithm() { - return mDockedStackMinimized ? mSplitLayout.getMinimizedSnapAlgorithm(mHomeStackResizable) - : mSplitLayout.getSnapAlgorithm(); - } - - public int getCurrentPosition() { - return isHorizontalDivision() ? mDividerPositionY : mDividerPositionX; - } - - public boolean isMinimized() { - return mDockedStackMinimized; - } - - @Override - public boolean onTouch(View v, MotionEvent event) { - convertToScreenCoordinates(event); - final int action = event.getAction() & MotionEvent.ACTION_MASK; - switch (action) { - case MotionEvent.ACTION_DOWN: - mVelocityTracker = VelocityTracker.obtain(); - mVelocityTracker.addMovement(event); - mStartX = (int) event.getX(); - mStartY = (int) event.getY(); - boolean result = startDragging(true /* animate */, true /* touching */); - if (!result) { - - // Weren't able to start dragging successfully, so cancel it again. - stopDragging(); - } - mStartPosition = getCurrentPosition(); - mMoving = false; - return result; - case MotionEvent.ACTION_MOVE: - mVelocityTracker.addMovement(event); - int x = (int) event.getX(); - int y = (int) event.getY(); - boolean exceededTouchSlop = - isHorizontalDivision() && Math.abs(y - mStartY) > mTouchSlop - || (!isHorizontalDivision() && Math.abs(x - mStartX) > mTouchSlop); - if (!mMoving && exceededTouchSlop) { - mStartX = x; - mStartY = y; - mMoving = true; - } - if (mMoving && mDockSide != WindowManager.DOCKED_INVALID) { - SnapTarget snapTarget = getSnapAlgorithm().calculateSnapTarget( - mStartPosition, 0 /* velocity */, false /* hardDismiss */); - resizeStackSurfaces(calculatePosition(x, y), mStartPosition, snapTarget, - null /* transaction */); - } - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - if (!mMoving) { - stopDragging(); - break; - } - - x = (int) event.getRawX(); - y = (int) event.getRawY(); - mVelocityTracker.addMovement(event); - mVelocityTracker.computeCurrentVelocity(1000); - int position = calculatePosition(x, y); - stopDragging(position, isHorizontalDivision() ? mVelocityTracker.getYVelocity() - : mVelocityTracker.getXVelocity(), false /* avoidDismissStart */, - true /* log */); - mMoving = false; - break; - } - return true; - } - - private void logResizeEvent(SnapTarget snapTarget) { - if (snapTarget == mSplitLayout.getSnapAlgorithm().getDismissStartTarget()) { - MetricsLogger.action( - mContext, MetricsEvent.ACTION_WINDOW_UNDOCK_MAX, dockSideTopLeft(mDockSide) - ? LOG_VALUE_UNDOCK_MAX_OTHER - : LOG_VALUE_UNDOCK_MAX_DOCKED); - } else if (snapTarget == mSplitLayout.getSnapAlgorithm().getDismissEndTarget()) { - MetricsLogger.action( - mContext, MetricsEvent.ACTION_WINDOW_UNDOCK_MAX, dockSideBottomRight(mDockSide) - ? LOG_VALUE_UNDOCK_MAX_OTHER - : LOG_VALUE_UNDOCK_MAX_DOCKED); - } else if (snapTarget == mSplitLayout.getSnapAlgorithm().getMiddleTarget()) { - MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_RESIZE, - LOG_VALUE_RESIZE_50_50); - } else if (snapTarget == mSplitLayout.getSnapAlgorithm().getFirstSplitTarget()) { - MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_RESIZE, - dockSideTopLeft(mDockSide) - ? LOG_VALUE_RESIZE_DOCKED_SMALLER - : LOG_VALUE_RESIZE_DOCKED_LARGER); - } else if (snapTarget == mSplitLayout.getSnapAlgorithm().getLastSplitTarget()) { - MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_RESIZE, - dockSideTopLeft(mDockSide) - ? LOG_VALUE_RESIZE_DOCKED_LARGER - : LOG_VALUE_RESIZE_DOCKED_SMALLER); - } - } - - private void convertToScreenCoordinates(MotionEvent event) { - event.setLocation(event.getRawX(), event.getRawY()); - } - - private void fling(int position, float velocity, boolean avoidDismissStart, - boolean logMetrics) { - DividerSnapAlgorithm currentSnapAlgorithm = getSnapAlgorithm(); - SnapTarget snapTarget = currentSnapAlgorithm.calculateSnapTarget(position, velocity); - if (avoidDismissStart && snapTarget == currentSnapAlgorithm.getDismissStartTarget()) { - snapTarget = currentSnapAlgorithm.getFirstSplitTarget(); - } - if (logMetrics) { - logResizeEvent(snapTarget); - } - ValueAnimator anim = getFlingAnimator(position, snapTarget, 0 /* endDelay */); - mFlingAnimationUtils.apply(anim, position, snapTarget.position, velocity); - anim.start(); - } - - private void flingTo(int position, SnapTarget target, long duration, long startDelay, - long endDelay, Interpolator interpolator) { - ValueAnimator anim = getFlingAnimator(position, target, endDelay); - anim.setDuration(duration); - anim.setStartDelay(startDelay); - anim.setInterpolator(interpolator); - anim.start(); - } - - private ValueAnimator getFlingAnimator(int position, final SnapTarget snapTarget, - final long endDelay) { - if (mCurrentAnimator != null) { - cancelFlingAnimation(); - updateDockSide(); - } - if (DEBUG) Slog.d(TAG, "Getting fling " + position + "->" + snapTarget.position); - final boolean taskPositionSameAtEnd = snapTarget.flag == SnapTarget.FLAG_NONE; - ValueAnimator anim = ValueAnimator.ofInt(position, snapTarget.position); - anim.addUpdateListener(animation -> resizeStackSurfaces((int) animation.getAnimatedValue(), - taskPositionSameAtEnd && animation.getAnimatedFraction() == 1f - ? TASK_POSITION_SAME - : snapTarget.taskPosition, - snapTarget, null /* transaction */)); - Consumer<Boolean> endAction = cancelled -> { - if (DEBUG) Slog.d(TAG, "End Fling " + cancelled + " min:" + mIsInMinimizeInteraction); - final boolean wasMinimizeInteraction = mIsInMinimizeInteraction; - // Reset minimized divider position after unminimized state animation finishes. - if (!cancelled && !mDockedStackMinimized && mIsInMinimizeInteraction) { - mIsInMinimizeInteraction = false; - } - boolean dismissed = commitSnapFlags(snapTarget); - mWindowManagerProxy.setResizing(false); - updateDockSide(); - mCurrentAnimator = null; - mEntranceAnimationRunning = false; - mExitAnimationRunning = false; - if (!dismissed && !wasMinimizeInteraction) { - mWindowManagerProxy.applyResizeSplits(snapTarget.position, mSplitLayout); - } - if (mCallback != null) { - mCallback.onDraggingEnd(); - } - - // Record last snap target the divider moved to - if (!mIsInMinimizeInteraction) { - // The last snapTarget position can be negative when the last divider position was - // offscreen. In that case, save the middle (default) SnapTarget so calculating next - // position isn't negative. - final SnapTarget saveTarget; - if (snapTarget.position < 0) { - saveTarget = mSplitLayout.getSnapAlgorithm().getMiddleTarget(); - } else { - saveTarget = snapTarget; - } - final DividerSnapAlgorithm snapAlgo = mSplitLayout.getSnapAlgorithm(); - if (saveTarget.position != snapAlgo.getDismissEndTarget().position - && saveTarget.position != snapAlgo.getDismissStartTarget().position) { - saveSnapTargetBeforeMinimized(saveTarget); - } - } - notifySplitScreenBoundsChanged(); - }; - anim.addListener(new AnimatorListenerAdapter() { - - private boolean mCancelled; - - @Override - public void onAnimationCancel(Animator animation) { - mCancelled = true; - } - - @Override - public void onAnimationEnd(Animator animation) { - long delay = 0; - if (endDelay != 0) { - delay = endDelay; - } else if (mCancelled) { - delay = 0; - } - if (delay == 0) { - endAction.accept(mCancelled); - } else { - final Boolean cancelled = mCancelled; - if (DEBUG) Slog.d(TAG, "Posting endFling " + cancelled + " d:" + delay + "ms"); - getHandler().postDelayed(() -> endAction.accept(cancelled), delay); - } - } - }); - mCurrentAnimator = anim; - mCurrentAnimator.setAnimationHandler(mSfVsyncAnimationHandler); - return anim; - } - - private void notifySplitScreenBoundsChanged() { - if (mSplitLayout.mPrimary == null || mSplitLayout.mSecondary == null) { - return; - } - mOtherTaskRect.set(mSplitLayout.mSecondary); - - mTmpRect.set(mHandle.getLeft(), mHandle.getTop(), mHandle.getRight(), mHandle.getBottom()); - if (isHorizontalDivision()) { - mTmpRect.offsetTo(mHandle.getLeft(), mDividerPositionY); - } else { - mTmpRect.offsetTo(mDividerPositionX, mHandle.getTop()); - } - mWindowManagerProxy.setTouchRegion(mTmpRect); - - mTmpRect.set(mSplitLayout.mDisplayLayout.stableInsets()); - switch (mSplitLayout.getPrimarySplitSide()) { - case WindowManager.DOCKED_LEFT: - mTmpRect.left = 0; - break; - case WindowManager.DOCKED_RIGHT: - mTmpRect.right = 0; - break; - case WindowManager.DOCKED_TOP: - mTmpRect.top = 0; - break; - } - mSplitScreenController.notifyBoundsChanged(mOtherTaskRect, mTmpRect); - } - - private void cancelFlingAnimation() { - if (mCurrentAnimator != null) { - mCurrentAnimator.cancel(); - } - } - - private boolean commitSnapFlags(SnapTarget target) { - if (target.flag == SnapTarget.FLAG_NONE) { - return false; - } - final boolean dismissOrMaximize; - if (target.flag == SnapTarget.FLAG_DISMISS_START) { - dismissOrMaximize = mDockSide == WindowManager.DOCKED_LEFT - || mDockSide == WindowManager.DOCKED_TOP; - } else { - dismissOrMaximize = mDockSide == WindowManager.DOCKED_RIGHT - || mDockSide == WindowManager.DOCKED_BOTTOM; - } - mWindowManagerProxy.dismissOrMaximizeDocked(mTiles, mSplitLayout, dismissOrMaximize); - Transaction t = mTiles.getTransaction(); - setResizeDimLayer(t, true /* primary */, 0f); - setResizeDimLayer(t, false /* primary */, 0f); - t.apply(); - mTiles.releaseTransaction(t); - return true; - } - - private void liftBackground() { - if (mBackgroundLifted) { - return; - } - if (isHorizontalDivision()) { - mBackground.animate().scaleY(1.4f); - } else { - mBackground.animate().scaleX(1.4f); - } - mBackground.animate() - .setInterpolator(Interpolators.TOUCH_RESPONSE) - .setDuration(TOUCH_ANIMATION_DURATION) - .translationZ(mTouchElevation) - .start(); - - // Lift handle as well so it doesn't get behind the background, even though it doesn't - // cast shadow. - mHandle.animate() - .setInterpolator(Interpolators.TOUCH_RESPONSE) - .setDuration(TOUCH_ANIMATION_DURATION) - .translationZ(mTouchElevation) - .start(); - mBackgroundLifted = true; - } - - private void releaseBackground() { - if (!mBackgroundLifted) { - return; - } - mBackground.animate() - .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) - .setDuration(TOUCH_RELEASE_ANIMATION_DURATION) - .translationZ(0) - .scaleX(1f) - .scaleY(1f) - .start(); - mHandle.animate() - .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) - .setDuration(TOUCH_RELEASE_ANIMATION_DURATION) - .translationZ(0) - .start(); - mBackgroundLifted = false; - } - - private void initializeSurfaceState() { - int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position; - // Recalculate the split-layout's internal tile bounds - mSplitLayout.resizeSplits(midPos); - Transaction t = mTiles.getTransaction(); - if (mDockedStackMinimized) { - int position = mSplitLayout.getMinimizedSnapAlgorithm(mHomeStackResizable) - .getMiddleTarget().position; - calculateBoundsForPosition(position, mDockSide, mDockedRect); - calculateBoundsForPosition(position, DockedDividerUtils.invertDockSide(mDockSide), - mOtherRect); - mDividerPositionX = mDividerPositionY = position; - resizeSplitSurfaces(t, mDockedRect, mSplitLayout.mPrimary, - mOtherRect, mSplitLayout.mSecondary); - } else { - resizeSplitSurfaces(t, mSplitLayout.mPrimary, null, - mSplitLayout.mSecondary, null); - } - setResizeDimLayer(t, true /* primary */, 0.f /* alpha */); - setResizeDimLayer(t, false /* secondary */, 0.f /* alpha */); - t.apply(); - mTiles.releaseTransaction(t); - - // Get the actually-visible bar dimensions (relative to full window). This is a thin - // bar going through the center. - final Rect dividerBar = isHorizontalDivision() - ? new Rect(0, mDividerInsets, mSplitLayout.mDisplayLayout.width(), - mDividerInsets + mDividerSize) - : new Rect(mDividerInsets, 0, mDividerInsets + mDividerSize, - mSplitLayout.mDisplayLayout.height()); - final Region touchRegion = new Region(dividerBar); - // Add in the "draggable" portion. While not visible, this is an expanded area that the - // user can interact with. - touchRegion.union(new Rect(mHandle.getLeft(), mHandle.getTop(), - mHandle.getRight(), mHandle.getBottom())); - mWindowManager.setTouchRegion(touchRegion); - } - - void setMinimizedDockStack(boolean minimized, boolean isHomeStackResizable, - Transaction t) { - mHomeStackResizable = isHomeStackResizable; - updateDockSide(); - if (!minimized) { - resetBackground(); - } - mMinimizedShadow.setAlpha(minimized ? 1f : 0f); - if (mDockedStackMinimized != minimized) { - mDockedStackMinimized = minimized; - if (mSplitLayout.mDisplayLayout.rotation() != mDefaultDisplay.getRotation()) { - // Splitscreen to minimize is about to starts after rotating landscape to seascape, - // update display info and snap algorithm targets - repositionSnapTargetBeforeMinimized(); - } - if (mIsInMinimizeInteraction != minimized || mCurrentAnimator != null) { - cancelFlingAnimation(); - if (minimized) { - // Relayout to recalculate the divider shadow when minimizing - requestLayout(); - mIsInMinimizeInteraction = true; - resizeStackSurfaces(mSplitLayout.getMinimizedSnapAlgorithm(mHomeStackResizable) - .getMiddleTarget(), t); - } else { - resizeStackSurfaces(mSnapTargetBeforeMinimized, t); - mIsInMinimizeInteraction = false; - } - } - } - } - - void enterSplitMode(boolean isHomeStackResizable) { - setHidden(false); - - SnapTarget miniMid = - mSplitLayout.getMinimizedSnapAlgorithm(isHomeStackResizable).getMiddleTarget(); - if (mDockedStackMinimized) { - mDividerPositionY = mDividerPositionX = miniMid.position; - } - } - - /** - * Tries to grab a surface control from ViewRootImpl. If this isn't available for some reason - * (ie. the window isn't ready yet), it will get the surfacecontrol that the WindowlessWM has - * assigned to it. - */ - private SurfaceControl getWindowSurfaceControl() { - return mWindowManager.mSystemWindows.getViewSurface(this); - } - - void exitSplitMode() { - // The view is going to be removed right after this function involved, updates the surface - // in the current thread instead of posting it to the view's UI thread. - final SurfaceControl sc = getWindowSurfaceControl(); - if (sc == null) { - return; - } - Transaction t = mTiles.getTransaction(); - t.hide(sc); - mImeController.setDimsHidden(t, true); - t.apply(); - mTiles.releaseTransaction(t); - - // Reset tile bounds - int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position; - mWindowManagerProxy.applyResizeSplits(midPos, mSplitLayout); - } - - void setMinimizedDockStack(boolean minimized, long animDuration, - boolean isHomeStackResizable) { - if (DEBUG) Slog.d(TAG, "setMinDock: " + mDockedStackMinimized + "->" + minimized); - mHomeStackResizable = isHomeStackResizable; - updateDockSide(); - if (mDockedStackMinimized != minimized) { - mIsInMinimizeInteraction = true; - mDockedStackMinimized = minimized; - stopDragging(minimized - ? mSnapTargetBeforeMinimized.position - : getCurrentPosition(), - minimized - ? mSplitLayout.getMinimizedSnapAlgorithm(mHomeStackResizable) - .getMiddleTarget() - : mSnapTargetBeforeMinimized, - animDuration, Interpolators.FAST_OUT_SLOW_IN, 0); - setAdjustedForIme(false, animDuration); - } - if (!minimized) { - mBackground.animate().withEndAction(mResetBackgroundRunnable); - } - mBackground.animate() - .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) - .setDuration(animDuration) - .start(); - } - - // Needed to end any currently playing animations when they might compete with other anims - // (specifically, IME adjust animation immediately after leaving minimized). Someday maybe - // these can be unified, but not today. - void finishAnimations() { - if (mCurrentAnimator != null) { - mCurrentAnimator.end(); - } - } - - void setAdjustedForIme(boolean adjustedForIme, long animDuration) { - if (mAdjustedForIme == adjustedForIme) { - return; - } - updateDockSide(); - mHandle.animate() - .setInterpolator(IME_ADJUST_INTERPOLATOR) - .setDuration(animDuration) - .alpha(adjustedForIme ? 0f : 1f) - .start(); - if (mDockSide == WindowManager.DOCKED_TOP) { - mBackground.setPivotY(0); - mBackground.animate() - .scaleY(adjustedForIme ? ADJUSTED_FOR_IME_SCALE : 1f); - } - if (!adjustedForIme) { - mBackground.animate().withEndAction(mResetBackgroundRunnable); - } - mBackground.animate() - .setInterpolator(IME_ADJUST_INTERPOLATOR) - .setDuration(animDuration) - .start(); - mAdjustedForIme = adjustedForIme; - } - - private void saveSnapTargetBeforeMinimized(SnapTarget target) { - mSnapTargetBeforeMinimized = target; - mState.mRatioPositionBeforeMinimized = (float) target.position - / (isHorizontalDivision() ? mSplitLayout.mDisplayLayout.height() - : mSplitLayout.mDisplayLayout.width()); - } - - private void resetBackground() { - mBackground.setPivotX(mBackground.getWidth() / 2); - mBackground.setPivotY(mBackground.getHeight() / 2); - mBackground.setScaleX(1f); - mBackground.setScaleY(1f); - mMinimizedShadow.setAlpha(0f); - } - - @Override - protected void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - } - - private void repositionSnapTargetBeforeMinimized() { - int position = (int) (mState.mRatioPositionBeforeMinimized - * (isHorizontalDivision() ? mSplitLayout.mDisplayLayout.height() - : mSplitLayout.mDisplayLayout.width())); - - // Set the snap target before minimized but do not save until divider is attached and not - // minimized because it does not know its minimized state yet. - mSnapTargetBeforeMinimized = - mSplitLayout.getSnapAlgorithm().calculateNonDismissingSnapTarget(position); - } - - private int calculatePosition(int touchX, int touchY) { - return isHorizontalDivision() ? calculateYPosition(touchY) : calculateXPosition(touchX); - } - - public boolean isHorizontalDivision() { - return getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT; - } - - private int calculateXPosition(int touchX) { - return mStartPosition + touchX - mStartX; - } - - private int calculateYPosition(int touchY) { - return mStartPosition + touchY - mStartY; - } - - private void alignTopLeft(Rect containingRect, Rect rect) { - int width = rect.width(); - int height = rect.height(); - rect.set(containingRect.left, containingRect.top, - containingRect.left + width, containingRect.top + height); - } - - private void alignBottomRight(Rect containingRect, Rect rect) { - int width = rect.width(); - int height = rect.height(); - rect.set(containingRect.right - width, containingRect.bottom - height, - containingRect.right, containingRect.bottom); - } - - private void calculateBoundsForPosition(int position, int dockSide, Rect outRect) { - DockedDividerUtils.calculateBoundsForPosition(position, dockSide, outRect, - mSplitLayout.mDisplayLayout.width(), mSplitLayout.mDisplayLayout.height(), - mDividerSize); - } - - private void resizeStackSurfaces(SnapTarget taskSnapTarget, Transaction t) { - resizeStackSurfaces(taskSnapTarget.position, taskSnapTarget.position, taskSnapTarget, t); - } - - void resizeSplitSurfaces(Transaction t, Rect dockedRect, Rect otherRect) { - resizeSplitSurfaces(t, dockedRect, null, otherRect, null); - } - - private void resizeSplitSurfaces(Transaction t, Rect dockedRect, Rect dockedTaskRect, - Rect otherRect, Rect otherTaskRect) { - dockedTaskRect = dockedTaskRect == null ? dockedRect : dockedTaskRect; - otherTaskRect = otherTaskRect == null ? otherRect : otherTaskRect; - - mDividerPositionX = mSplitLayout.getPrimarySplitSide() == DOCKED_RIGHT - ? otherRect.right : dockedRect.right; - mDividerPositionY = dockedRect.bottom; - - if (DEBUG) { - Slog.d(TAG, "Resizing split surfaces: " + dockedRect + " " + dockedTaskRect - + " " + otherRect + " " + otherTaskRect); - } - - t.setPosition(mTiles.mPrimarySurface, dockedTaskRect.left, dockedTaskRect.top); - Rect crop = new Rect(dockedRect); - crop.offsetTo(-Math.min(dockedTaskRect.left - dockedRect.left, 0), - -Math.min(dockedTaskRect.top - dockedRect.top, 0)); - t.setWindowCrop(mTiles.mPrimarySurface, crop); - t.setPosition(mTiles.mSecondarySurface, otherTaskRect.left, otherTaskRect.top); - crop.set(otherRect); - crop.offsetTo(-(otherTaskRect.left - otherRect.left), - -(otherTaskRect.top - otherRect.top)); - t.setWindowCrop(mTiles.mSecondarySurface, crop); - final SurfaceControl dividerCtrl = getWindowSurfaceControl(); - if (dividerCtrl != null) { - if (isHorizontalDivision()) { - t.setPosition(dividerCtrl, 0, mDividerPositionY - mDividerInsets); - } else { - t.setPosition(dividerCtrl, mDividerPositionX - mDividerInsets, 0); - } - } - } - - void setResizeDimLayer(Transaction t, boolean primary, float alpha) { - SurfaceControl dim = primary ? mTiles.mPrimaryDim : mTiles.mSecondaryDim; - if (alpha <= 0.001f) { - t.hide(dim); - } else { - t.setAlpha(dim, alpha); - t.show(dim); - } - } - - void resizeStackSurfaces(int position, int taskPosition, SnapTarget taskSnapTarget, - Transaction transaction) { - if (mRemoved) { - // This divider view has been removed so shouldn't have any additional influence. - return; - } - calculateBoundsForPosition(position, mDockSide, mDockedRect); - calculateBoundsForPosition(position, DockedDividerUtils.invertDockSide(mDockSide), - mOtherRect); - - if (mDockedRect.equals(mLastResizeRect) && !mEntranceAnimationRunning) { - return; - } - - // Make sure shadows are updated - if (mBackground.getZ() > 0f) { - mBackground.invalidate(); - } - - final boolean ownTransaction = transaction == null; - final Transaction t = ownTransaction ? mTiles.getTransaction() : transaction; - mLastResizeRect.set(mDockedRect); - if (mIsInMinimizeInteraction) { - calculateBoundsForPosition(mSnapTargetBeforeMinimized.position, mDockSide, - mDockedTaskRect); - calculateBoundsForPosition(mSnapTargetBeforeMinimized.position, - DockedDividerUtils.invertDockSide(mDockSide), mOtherTaskRect); - - // Move a right-docked-app to line up with the divider while dragging it - if (mDockSide == DOCKED_RIGHT) { - mDockedTaskRect.offset(Math.max(position, -mDividerSize) - - mDockedTaskRect.left + mDividerSize, 0); - } - resizeSplitSurfaces(t, mDockedRect, mDockedTaskRect, mOtherRect, mOtherTaskRect); - if (ownTransaction) { - t.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId()); - t.apply(); - mTiles.releaseTransaction(t); - } - return; - } - - if (mEntranceAnimationRunning && taskPosition != TASK_POSITION_SAME) { - calculateBoundsForPosition(taskPosition, mDockSide, mDockedTaskRect); - - // Move a docked app if from the right in position with the divider up to insets - if (mDockSide == DOCKED_RIGHT) { - mDockedTaskRect.offset(Math.max(position, -mDividerSize) - - mDockedTaskRect.left + mDividerSize, 0); - } - calculateBoundsForPosition(taskPosition, DockedDividerUtils.invertDockSide(mDockSide), - mOtherTaskRect); - resizeSplitSurfaces(t, mDockedRect, mDockedTaskRect, mOtherRect, mOtherTaskRect); - } else if (mExitAnimationRunning && taskPosition != TASK_POSITION_SAME) { - calculateBoundsForPosition(taskPosition, mDockSide, mDockedTaskRect); - mDockedInsetRect.set(mDockedTaskRect); - calculateBoundsForPosition(mExitStartPosition, - DockedDividerUtils.invertDockSide(mDockSide), mOtherTaskRect); - mOtherInsetRect.set(mOtherTaskRect); - applyExitAnimationParallax(mOtherTaskRect, position); - - // Move a right-docked-app to line up with the divider while dragging it - if (mDockSide == DOCKED_RIGHT) { - mDockedTaskRect.offset(position + mDividerSize, 0); - } - resizeSplitSurfaces(t, mDockedRect, mDockedTaskRect, mOtherRect, mOtherTaskRect); - } else if (taskPosition != TASK_POSITION_SAME) { - calculateBoundsForPosition(position, DockedDividerUtils.invertDockSide(mDockSide), - mOtherRect); - int dockSideInverted = DockedDividerUtils.invertDockSide(mDockSide); - int taskPositionDocked = - restrictDismissingTaskPosition(taskPosition, mDockSide, taskSnapTarget); - int taskPositionOther = - restrictDismissingTaskPosition(taskPosition, dockSideInverted, taskSnapTarget); - calculateBoundsForPosition(taskPositionDocked, mDockSide, mDockedTaskRect); - calculateBoundsForPosition(taskPositionOther, dockSideInverted, mOtherTaskRect); - mTmpRect.set(0, 0, mSplitLayout.mDisplayLayout.width(), - mSplitLayout.mDisplayLayout.height()); - alignTopLeft(mDockedRect, mDockedTaskRect); - alignTopLeft(mOtherRect, mOtherTaskRect); - mDockedInsetRect.set(mDockedTaskRect); - mOtherInsetRect.set(mOtherTaskRect); - if (dockSideTopLeft(mDockSide)) { - alignTopLeft(mTmpRect, mDockedInsetRect); - alignBottomRight(mTmpRect, mOtherInsetRect); - } else { - alignBottomRight(mTmpRect, mDockedInsetRect); - alignTopLeft(mTmpRect, mOtherInsetRect); - } - applyDismissingParallax(mDockedTaskRect, mDockSide, taskSnapTarget, position, - taskPositionDocked); - applyDismissingParallax(mOtherTaskRect, dockSideInverted, taskSnapTarget, position, - taskPositionOther); - resizeSplitSurfaces(t, mDockedRect, mDockedTaskRect, mOtherRect, mOtherTaskRect); - } else { - resizeSplitSurfaces(t, mDockedRect, null, mOtherRect, null); - } - SnapTarget closestDismissTarget = getSnapAlgorithm().getClosestDismissTarget(position); - float dimFraction = getDimFraction(position, closestDismissTarget); - setResizeDimLayer(t, isDismissTargetPrimary(closestDismissTarget), dimFraction); - if (ownTransaction) { - t.apply(); - mTiles.releaseTransaction(t); - } - } - - private void applyExitAnimationParallax(Rect taskRect, int position) { - if (mDockSide == WindowManager.DOCKED_TOP) { - taskRect.offset(0, (int) ((position - mExitStartPosition) * 0.25f)); - } else if (mDockSide == WindowManager.DOCKED_LEFT) { - taskRect.offset((int) ((position - mExitStartPosition) * 0.25f), 0); - } else if (mDockSide == WindowManager.DOCKED_RIGHT) { - taskRect.offset((int) ((mExitStartPosition - position) * 0.25f), 0); - } - } - - private float getDimFraction(int position, SnapTarget dismissTarget) { - if (mEntranceAnimationRunning) { - return 0f; - } - float fraction = getSnapAlgorithm().calculateDismissingFraction(position); - fraction = Math.max(0, Math.min(fraction, 1f)); - fraction = DIM_INTERPOLATOR.getInterpolation(fraction); - return fraction; - } - - /** - * When the snap target is dismissing one side, make sure that the dismissing side doesn't get - * 0 size. - */ - private int restrictDismissingTaskPosition(int taskPosition, int dockSide, - SnapTarget snapTarget) { - if (snapTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(dockSide)) { - return Math.max(mSplitLayout.getSnapAlgorithm().getFirstSplitTarget().position, - mStartPosition); - } else if (snapTarget.flag == SnapTarget.FLAG_DISMISS_END - && dockSideBottomRight(dockSide)) { - return Math.min(mSplitLayout.getSnapAlgorithm().getLastSplitTarget().position, - mStartPosition); - } else { - return taskPosition; - } - } - - /** - * Applies a parallax to the task when dismissing. - */ - private void applyDismissingParallax(Rect taskRect, int dockSide, SnapTarget snapTarget, - int position, int taskPosition) { - float fraction = Math.min(1, Math.max(0, - mSplitLayout.getSnapAlgorithm().calculateDismissingFraction(position))); - SnapTarget dismissTarget = null; - SnapTarget splitTarget = null; - int start = 0; - if (position <= mSplitLayout.getSnapAlgorithm().getLastSplitTarget().position - && dockSideTopLeft(dockSide)) { - dismissTarget = mSplitLayout.getSnapAlgorithm().getDismissStartTarget(); - splitTarget = mSplitLayout.getSnapAlgorithm().getFirstSplitTarget(); - start = taskPosition; - } else if (position >= mSplitLayout.getSnapAlgorithm().getLastSplitTarget().position - && dockSideBottomRight(dockSide)) { - dismissTarget = mSplitLayout.getSnapAlgorithm().getDismissEndTarget(); - splitTarget = mSplitLayout.getSnapAlgorithm().getLastSplitTarget(); - start = splitTarget.position; - } - if (dismissTarget != null && fraction > 0f - && isDismissing(splitTarget, position, dockSide)) { - fraction = calculateParallaxDismissingFraction(fraction, dockSide); - int offsetPosition = (int) (start + fraction - * (dismissTarget.position - splitTarget.position)); - int width = taskRect.width(); - int height = taskRect.height(); - switch (dockSide) { - case WindowManager.DOCKED_LEFT: - taskRect.left = offsetPosition - width; - taskRect.right = offsetPosition; - break; - case WindowManager.DOCKED_RIGHT: - taskRect.left = offsetPosition + mDividerSize; - taskRect.right = offsetPosition + width + mDividerSize; - break; - case WindowManager.DOCKED_TOP: - taskRect.top = offsetPosition - height; - taskRect.bottom = offsetPosition; - break; - case WindowManager.DOCKED_BOTTOM: - taskRect.top = offsetPosition + mDividerSize; - taskRect.bottom = offsetPosition + height + mDividerSize; - break; - } - } - } - - /** - * @return for a specified {@code fraction}, this returns an adjusted value that simulates a - * slowing down parallax effect - */ - private static float calculateParallaxDismissingFraction(float fraction, int dockSide) { - float result = SLOWDOWN_INTERPOLATOR.getInterpolation(fraction) / 3.5f; - - // Less parallax at the top, just because. - if (dockSide == WindowManager.DOCKED_TOP) { - result /= 2f; - } - return result; - } - - private static boolean isDismissing(SnapTarget snapTarget, int position, int dockSide) { - if (dockSide == WindowManager.DOCKED_TOP || dockSide == WindowManager.DOCKED_LEFT) { - return position < snapTarget.position; - } else { - return position > snapTarget.position; - } - } - - private boolean isDismissTargetPrimary(SnapTarget dismissTarget) { - return (dismissTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(mDockSide)) - || (dismissTarget.flag == SnapTarget.FLAG_DISMISS_END - && dockSideBottomRight(mDockSide)); - } - - /** - * @return true if and only if {@code dockSide} is top or left - */ - private static boolean dockSideTopLeft(int dockSide) { - return dockSide == WindowManager.DOCKED_TOP || dockSide == WindowManager.DOCKED_LEFT; - } - - /** - * @return true if and only if {@code dockSide} is bottom or right - */ - private static boolean dockSideBottomRight(int dockSide) { - return dockSide == WindowManager.DOCKED_BOTTOM || dockSide == WindowManager.DOCKED_RIGHT; - } - - @Override - public void onComputeInternalInsets(InternalInsetsInfo inoutInfo) { - inoutInfo.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION); - inoutInfo.touchableRegion.set(mHandle.getLeft(), mHandle.getTop(), mHandle.getRight(), - mHandle.getBottom()); - inoutInfo.touchableRegion.op(mBackground.getLeft(), mBackground.getTop(), - mBackground.getRight(), mBackground.getBottom(), Op.UNION); - } - - void onUndockingTask() { - int dockSide = mSplitLayout.getPrimarySplitSide(); - if (inSplitMode()) { - startDragging(false /* animate */, false /* touching */); - SnapTarget target = dockSideTopLeft(dockSide) - ? mSplitLayout.getSnapAlgorithm().getDismissEndTarget() - : mSplitLayout.getSnapAlgorithm().getDismissStartTarget(); - - // Don't start immediately - give a little bit time to settle the drag resize change. - mExitAnimationRunning = true; - mExitStartPosition = getCurrentPosition(); - stopDragging(mExitStartPosition, target, 336 /* duration */, 100 /* startDelay */, - 0 /* endDelay */, Interpolators.FAST_OUT_SLOW_IN); - } - } - - private int calculatePositionForInsetBounds() { - mSplitLayout.mDisplayLayout.getStableBounds(mTmpRect); - return DockedDividerUtils.calculatePositionForBounds(mTmpRect, mDockSide, mDividerSize); - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerWindowManager.java deleted file mode 100644 index 2c3ae68e4749..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerWindowManager.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.legacysplitscreen; - -import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; -import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; -import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; -import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY; -import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH; -import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; -import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; -import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; -import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; -import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; -import static android.view.WindowManager.SHELL_ROOT_LAYER_DIVIDER; - -import android.graphics.PixelFormat; -import android.graphics.Region; -import android.os.Binder; -import android.view.View; -import android.view.WindowManager; - -import com.android.wm.shell.common.SystemWindows; - -/** - * Manages the window parameters of the docked stack divider. - */ -final class DividerWindowManager { - - private static final String WINDOW_TITLE = "DockedStackDivider"; - - final SystemWindows mSystemWindows; - private WindowManager.LayoutParams mLp; - private View mView; - - DividerWindowManager(SystemWindows systemWindows) { - mSystemWindows = systemWindows; - } - - /** Add a divider view */ - void add(View view, int width, int height, int displayId) { - mLp = new WindowManager.LayoutParams( - width, height, TYPE_DOCK_DIVIDER, - FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL - | FLAG_WATCH_OUTSIDE_TOUCH | FLAG_SPLIT_TOUCH | FLAG_SLIPPERY, - PixelFormat.TRANSLUCENT); - mLp.token = new Binder(); - mLp.setTitle(WINDOW_TITLE); - mLp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY; - mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; - view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); - mSystemWindows.addView(view, mLp, displayId, SHELL_ROOT_LAYER_DIVIDER); - mView = view; - } - - void remove() { - if (mView != null) { - mSystemWindows.removeView(mView); - } - mView = null; - } - - void setSlippery(boolean slippery) { - boolean changed = false; - if (slippery && (mLp.flags & FLAG_SLIPPERY) == 0) { - mLp.flags |= FLAG_SLIPPERY; - changed = true; - } else if (!slippery && (mLp.flags & FLAG_SLIPPERY) != 0) { - mLp.flags &= ~FLAG_SLIPPERY; - changed = true; - } - if (changed) { - mSystemWindows.updateViewLayout(mView, mLp); - } - } - - void setTouchable(boolean touchable) { - if (mView == null) { - return; - } - boolean changed = false; - if (!touchable && (mLp.flags & FLAG_NOT_TOUCHABLE) == 0) { - mLp.flags |= FLAG_NOT_TOUCHABLE; - changed = true; - } else if (touchable && (mLp.flags & FLAG_NOT_TOUCHABLE) != 0) { - mLp.flags &= ~FLAG_NOT_TOUCHABLE; - changed = true; - } - if (changed) { - mSystemWindows.updateViewLayout(mView, mLp); - } - } - - /** Sets the touch region to `touchRegion`. Use null to unset.*/ - void setTouchRegion(Region touchRegion) { - if (mView == null) { - return; - } - mSystemWindows.setTouchableRegion(mView, touchRegion); - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/ForcedResizableInfoActivity.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/ForcedResizableInfoActivity.java deleted file mode 100644 index 4fe28e630114..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/ForcedResizableInfoActivity.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.legacysplitscreen; - -import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY; -import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN; - -import android.annotation.Nullable; -import android.app.Activity; -import android.app.ActivityManager; -import android.os.Bundle; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.View; -import android.view.View.OnTouchListener; -import android.widget.TextView; - -import com.android.wm.shell.R; - -/** - * Translucent activity that gets started on top of a task in multi-window to inform the user that - * we forced the activity below to be resizable. - * - * Note: This activity runs on the main thread of the process hosting the Shell lib. - */ -public class ForcedResizableInfoActivity extends Activity implements OnTouchListener { - - public static final String EXTRA_FORCED_RESIZEABLE_REASON = "extra_forced_resizeable_reason"; - - private static final long DISMISS_DELAY = 2500; - - private final Runnable mFinishRunnable = new Runnable() { - @Override - public void run() { - finish(); - } - }; - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.forced_resizable_activity); - TextView tv = findViewById(com.android.internal.R.id.message); - int reason = getIntent().getIntExtra(EXTRA_FORCED_RESIZEABLE_REASON, -1); - String text; - switch (reason) { - case FORCED_RESIZEABLE_REASON_SPLIT_SCREEN: - text = getString(R.string.dock_forced_resizable); - break; - case FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY: - text = getString(R.string.forced_resizable_secondary_display); - break; - default: - throw new IllegalArgumentException("Unexpected forced resizeable reason: " - + reason); - } - tv.setText(text); - getWindow().setTitle(text); - getWindow().getDecorView().setOnTouchListener(this); - } - - @Override - protected void onStart() { - super.onStart(); - getWindow().getDecorView().postDelayed(mFinishRunnable, DISMISS_DELAY); - } - - @Override - protected void onStop() { - super.onStop(); - finish(); - } - - @Override - public boolean onTouch(View v, MotionEvent event) { - finish(); - return true; - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - finish(); - return true; - } - - @Override - public void finish() { - super.finish(); - overridePendingTransition(0, R.anim.forced_resizable_exit); - } - - @Override - public void setTaskDescription(ActivityManager.TaskDescription taskDescription) { - // Do nothing - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/ForcedResizableInfoActivityController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/ForcedResizableInfoActivityController.java deleted file mode 100644 index 139544f951ce..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/ForcedResizableInfoActivityController.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.legacysplitscreen; - - -import static com.android.wm.shell.legacysplitscreen.ForcedResizableInfoActivity.EXTRA_FORCED_RESIZEABLE_REASON; - -import android.app.ActivityOptions; -import android.content.Context; -import android.content.Intent; -import android.os.UserHandle; -import android.util.ArraySet; -import android.widget.Toast; - -import com.android.wm.shell.R; -import com.android.wm.shell.common.ShellExecutor; - -import java.util.function.Consumer; - -/** - * Controller that decides when to show the {@link ForcedResizableInfoActivity}. - */ -final class ForcedResizableInfoActivityController implements DividerView.DividerCallbacks { - - private static final String SELF_PACKAGE_NAME = "com.android.systemui"; - - private static final int TIMEOUT = 1000; - private final Context mContext; - private final ShellExecutor mMainExecutor; - private final ArraySet<PendingTaskRecord> mPendingTasks = new ArraySet<>(); - private final ArraySet<String> mPackagesShownInSession = new ArraySet<>(); - private boolean mDividerDragging; - - private final Runnable mTimeoutRunnable = this::showPending; - - private final Consumer<Boolean> mDockedStackExistsListener = exists -> { - if (!exists) { - mPackagesShownInSession.clear(); - } - }; - - /** Record of force resized task that's pending to be handled. */ - private class PendingTaskRecord { - int mTaskId; - /** - * {@link android.app.ITaskStackListener#FORCED_RESIZEABLE_REASON_SPLIT_SCREEN} or - * {@link android.app.ITaskStackListener#FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY} - */ - int mReason; - - PendingTaskRecord(int taskId, int reason) { - this.mTaskId = taskId; - this.mReason = reason; - } - } - - ForcedResizableInfoActivityController(Context context, - LegacySplitScreenController splitScreenController, - ShellExecutor mainExecutor) { - mContext = context; - mMainExecutor = mainExecutor; - splitScreenController.registerInSplitScreenListener(mDockedStackExistsListener); - } - - @Override - public void onDraggingStart() { - mDividerDragging = true; - mMainExecutor.removeCallbacks(mTimeoutRunnable); - } - - @Override - public void onDraggingEnd() { - mDividerDragging = false; - showPending(); - } - - void onAppTransitionFinished() { - if (!mDividerDragging) { - showPending(); - } - } - - void activityForcedResizable(String packageName, int taskId, int reason) { - if (debounce(packageName)) { - return; - } - mPendingTasks.add(new PendingTaskRecord(taskId, reason)); - postTimeout(); - } - - void activityDismissingSplitScreen() { - Toast.makeText(mContext, R.string.dock_non_resizeble_failed_to_dock_text, - Toast.LENGTH_SHORT).show(); - } - - void activityLaunchOnSecondaryDisplayFailed() { - Toast.makeText(mContext, R.string.activity_launch_on_secondary_display_failed_text, - Toast.LENGTH_SHORT).show(); - } - - private void showPending() { - mMainExecutor.removeCallbacks(mTimeoutRunnable); - for (int i = mPendingTasks.size() - 1; i >= 0; i--) { - PendingTaskRecord pendingRecord = mPendingTasks.valueAt(i); - Intent intent = new Intent(mContext, ForcedResizableInfoActivity.class); - ActivityOptions options = ActivityOptions.makeBasic(); - options.setLaunchTaskId(pendingRecord.mTaskId); - // Set as task overlay and allow to resume, so that when an app enters split-screen and - // becomes paused, the overlay will still be shown. - options.setTaskOverlay(true, true /* canResume */); - intent.putExtra(EXTRA_FORCED_RESIZEABLE_REASON, pendingRecord.mReason); - mContext.startActivityAsUser(intent, options.toBundle(), UserHandle.CURRENT); - } - mPendingTasks.clear(); - } - - private void postTimeout() { - mMainExecutor.removeCallbacks(mTimeoutRunnable); - mMainExecutor.executeDelayed(mTimeoutRunnable, TIMEOUT); - } - - private boolean debounce(String packageName) { - if (packageName == null) { - return false; - } - - // We launch ForcedResizableInfoActivity into a task that was forced resizable, so that - // triggers another notification. So ignore our own activity. - if (SELF_PACKAGE_NAME.equals(packageName)) { - return true; - } - boolean debounce = mPackagesShownInSession.contains(packageName); - mPackagesShownInSession.add(packageName); - return debounce; - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitDisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitDisplayLayout.java deleted file mode 100644 index f201634d3d4a..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitDisplayLayout.java +++ /dev/null @@ -1,326 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.legacysplitscreen; - -import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; -import static android.content.res.Configuration.ORIENTATION_PORTRAIT; -import static android.util.RotationUtils.rotateBounds; -import static android.view.WindowManager.DOCKED_BOTTOM; -import static android.view.WindowManager.DOCKED_INVALID; -import static android.view.WindowManager.DOCKED_LEFT; -import static android.view.WindowManager.DOCKED_RIGHT; -import static android.view.WindowManager.DOCKED_TOP; - -import android.annotation.NonNull; -import android.content.Context; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.graphics.Rect; -import android.util.TypedValue; -import android.window.WindowContainerTransaction; - -import com.android.internal.policy.DividerSnapAlgorithm; -import com.android.internal.policy.DockedDividerUtils; -import com.android.wm.shell.common.DisplayLayout; - -/** - * Handles split-screen related internal display layout. In general, this represents the - * WM-facing understanding of the splits. - */ -public class LegacySplitDisplayLayout { - /** Minimum size of an adjusted stack bounds relative to original stack bounds. Used to - * restrict IME adjustment so that a min portion of top stack remains visible.*/ - private static final float ADJUSTED_STACK_FRACTION_MIN = 0.3f; - - private static final int DIVIDER_WIDTH_INACTIVE_DP = 4; - - LegacySplitScreenTaskListener mTiles; - DisplayLayout mDisplayLayout; - Context mContext; - - // Lazy stuff - boolean mResourcesValid = false; - int mDividerSize; - int mDividerSizeInactive; - private DividerSnapAlgorithm mSnapAlgorithm = null; - private DividerSnapAlgorithm mMinimizedSnapAlgorithm = null; - Rect mPrimary = null; - Rect mSecondary = null; - Rect mAdjustedPrimary = null; - Rect mAdjustedSecondary = null; - final Rect mTmpBounds = new Rect(); - - public LegacySplitDisplayLayout(Context ctx, DisplayLayout dl, - LegacySplitScreenTaskListener taskTiles) { - mTiles = taskTiles; - mDisplayLayout = dl; - mContext = ctx; - } - - void rotateTo(int newRotation) { - mDisplayLayout.rotateTo(mContext.getResources(), newRotation); - final Configuration config = new Configuration(); - config.unset(); - config.orientation = mDisplayLayout.getOrientation(); - Rect tmpRect = new Rect(0, 0, mDisplayLayout.width(), mDisplayLayout.height()); - tmpRect.inset(mDisplayLayout.nonDecorInsets()); - config.windowConfiguration.setAppBounds(tmpRect); - tmpRect.set(0, 0, mDisplayLayout.width(), mDisplayLayout.height()); - tmpRect.inset(mDisplayLayout.stableInsets()); - config.screenWidthDp = (int) (tmpRect.width() / mDisplayLayout.density()); - config.screenHeightDp = (int) (tmpRect.height() / mDisplayLayout.density()); - mContext = mContext.createConfigurationContext(config); - mSnapAlgorithm = null; - mMinimizedSnapAlgorithm = null; - mResourcesValid = false; - } - - private void updateResources() { - if (mResourcesValid) { - return; - } - mResourcesValid = true; - Resources res = mContext.getResources(); - mDividerSize = DockedDividerUtils.getDividerSize(res, - DockedDividerUtils.getDividerInsets(res)); - mDividerSizeInactive = (int) TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, DIVIDER_WIDTH_INACTIVE_DP, res.getDisplayMetrics()); - } - - int getPrimarySplitSide() { - switch (mDisplayLayout.getNavigationBarPosition(mContext.getResources())) { - case DisplayLayout.NAV_BAR_BOTTOM: - return mDisplayLayout.isLandscape() ? DOCKED_LEFT : DOCKED_TOP; - case DisplayLayout.NAV_BAR_LEFT: - return DOCKED_RIGHT; - case DisplayLayout.NAV_BAR_RIGHT: - return DOCKED_LEFT; - default: - return DOCKED_INVALID; - } - } - - DividerSnapAlgorithm getSnapAlgorithm() { - if (mSnapAlgorithm == null) { - updateResources(); - boolean isHorizontalDivision = !mDisplayLayout.isLandscape(); - mSnapAlgorithm = new DividerSnapAlgorithm(mContext.getResources(), - mDisplayLayout.width(), mDisplayLayout.height(), mDividerSize, - isHorizontalDivision, mDisplayLayout.stableInsets(), getPrimarySplitSide()); - } - return mSnapAlgorithm; - } - - DividerSnapAlgorithm getMinimizedSnapAlgorithm(boolean homeStackResizable) { - if (mMinimizedSnapAlgorithm == null) { - updateResources(); - boolean isHorizontalDivision = !mDisplayLayout.isLandscape(); - mMinimizedSnapAlgorithm = new DividerSnapAlgorithm(mContext.getResources(), - mDisplayLayout.width(), mDisplayLayout.height(), mDividerSize, - isHorizontalDivision, mDisplayLayout.stableInsets(), getPrimarySplitSide(), - true /* isMinimized */, homeStackResizable); - } - return mMinimizedSnapAlgorithm; - } - - /** - * Resize primary bounds and secondary bounds by divider position. - * - * @param position divider position. - * @return true if calculated bounds changed. - */ - boolean resizeSplits(int position) { - mPrimary = mPrimary == null ? new Rect() : mPrimary; - mSecondary = mSecondary == null ? new Rect() : mSecondary; - int dockSide = getPrimarySplitSide(); - boolean boundsChanged; - - mTmpBounds.set(mPrimary); - DockedDividerUtils.calculateBoundsForPosition(position, dockSide, mPrimary, - mDisplayLayout.width(), mDisplayLayout.height(), mDividerSize); - boundsChanged = !mPrimary.equals(mTmpBounds); - - mTmpBounds.set(mSecondary); - DockedDividerUtils.calculateBoundsForPosition(position, - DockedDividerUtils.invertDockSide(dockSide), mSecondary, mDisplayLayout.width(), - mDisplayLayout.height(), mDividerSize); - boundsChanged |= !mSecondary.equals(mTmpBounds); - return boundsChanged; - } - - void resizeSplits(int position, WindowContainerTransaction t) { - if (resizeSplits(position)) { - t.setBounds(mTiles.mPrimary.token, mPrimary); - t.setBounds(mTiles.mSecondary.token, mSecondary); - - t.setSmallestScreenWidthDp(mTiles.mPrimary.token, - getSmallestWidthDpForBounds(mContext, mDisplayLayout, mPrimary)); - t.setSmallestScreenWidthDp(mTiles.mSecondary.token, - getSmallestWidthDpForBounds(mContext, mDisplayLayout, mSecondary)); - } - } - - Rect calcResizableMinimizedHomeStackBounds() { - DividerSnapAlgorithm.SnapTarget miniMid = - getMinimizedSnapAlgorithm(true /* resizable */).getMiddleTarget(); - Rect homeBounds = new Rect(); - DockedDividerUtils.calculateBoundsForPosition(miniMid.position, - DockedDividerUtils.invertDockSide(getPrimarySplitSide()), homeBounds, - mDisplayLayout.width(), mDisplayLayout.height(), mDividerSize); - return homeBounds; - } - - /** - * Updates the adjustment depending on it's current state. - */ - void updateAdjustedBounds(int currImeTop, int hiddenTop, int shownTop) { - adjustForIME(mDisplayLayout, currImeTop, hiddenTop, shownTop, mDividerSize, - mDividerSizeInactive, mPrimary, mSecondary); - } - - /** Assumes top/bottom split. Splits are not adjusted for left/right splits. */ - private void adjustForIME(DisplayLayout dl, int currImeTop, int hiddenTop, int shownTop, - int dividerWidth, int dividerWidthInactive, Rect primaryBounds, Rect secondaryBounds) { - if (mAdjustedPrimary == null) { - mAdjustedPrimary = new Rect(); - mAdjustedSecondary = new Rect(); - } - - final Rect displayStableRect = new Rect(); - dl.getStableBounds(displayStableRect); - - final float shownFraction = ((float) (currImeTop - hiddenTop)) / (shownTop - hiddenTop); - final int currDividerWidth = - (int) (dividerWidthInactive * shownFraction + dividerWidth * (1.f - shownFraction)); - - // Calculate the highest we can move the bottom of the top stack to keep 30% visible. - final int minTopStackBottom = displayStableRect.top - + (int) ((mPrimary.bottom - displayStableRect.top) * ADJUSTED_STACK_FRACTION_MIN); - // Based on that, calculate the maximum amount we'll allow the ime to shift things. - final int maxOffset = mPrimary.bottom - minTopStackBottom; - // Calculate how much we would shift things without limits (basically the height of ime). - final int desiredOffset = hiddenTop - shownTop; - // Calculate an "adjustedTop" which is the currImeTop but restricted by our constraints. - // We want an effect where the adjustment only occurs during the "highest" portion of the - // ime animation. This is done by shifting the adjustment values by the difference in - // offsets (effectively playing the whole adjustment animation some fixed amount of pixels - // below the ime top). - final int topCorrection = Math.max(0, desiredOffset - maxOffset); - final int adjustedTop = currImeTop + topCorrection; - // The actual yOffset is the distance between adjustedTop and the bottom of the display. - // Since our adjustedTop values are playing "below" the ime, we clamp at 0 so we only - // see adjustment upward. - final int yOffset = Math.max(0, dl.height() - adjustedTop); - - // TOP - // Reduce the offset by an additional small amount to squish the divider bar. - mAdjustedPrimary.set(primaryBounds); - mAdjustedPrimary.offset(0, -yOffset + (dividerWidth - currDividerWidth)); - - // BOTTOM - mAdjustedSecondary.set(secondaryBounds); - mAdjustedSecondary.offset(0, -yOffset); - } - - static int getSmallestWidthDpForBounds(@NonNull Context context, DisplayLayout dl, - Rect bounds) { - int dividerSize = DockedDividerUtils.getDividerSize(context.getResources(), - DockedDividerUtils.getDividerInsets(context.getResources())); - - int minWidth = Integer.MAX_VALUE; - - // Go through all screen orientations and find the orientation in which the task has the - // smallest width. - Rect tmpRect = new Rect(); - Rect rotatedDisplayRect = new Rect(); - Rect displayRect = new Rect(0, 0, dl.width(), dl.height()); - - DisplayLayout tmpDL = new DisplayLayout(); - for (int rotation = 0; rotation < 4; rotation++) { - tmpDL.set(dl); - tmpDL.rotateTo(context.getResources(), rotation); - DividerSnapAlgorithm snap = initSnapAlgorithmForRotation(context, tmpDL, dividerSize); - - tmpRect.set(bounds); - rotateBounds(tmpRect, displayRect, dl.rotation(), rotation); - rotatedDisplayRect.set(0, 0, tmpDL.width(), tmpDL.height()); - final int dockSide = getPrimarySplitSide(tmpRect, rotatedDisplayRect, - tmpDL.getOrientation()); - final int position = DockedDividerUtils.calculatePositionForBounds(tmpRect, dockSide, - dividerSize); - - final int snappedPosition = - snap.calculateNonDismissingSnapTarget(position).position; - DockedDividerUtils.calculateBoundsForPosition(snappedPosition, dockSide, tmpRect, - tmpDL.width(), tmpDL.height(), dividerSize); - Rect insettedDisplay = new Rect(rotatedDisplayRect); - insettedDisplay.inset(tmpDL.stableInsets()); - tmpRect.intersect(insettedDisplay); - minWidth = Math.min(tmpRect.width(), minWidth); - } - return (int) (minWidth / dl.density()); - } - - static DividerSnapAlgorithm initSnapAlgorithmForRotation(Context context, DisplayLayout dl, - int dividerSize) { - final Configuration config = new Configuration(); - config.unset(); - config.orientation = dl.getOrientation(); - Rect tmpRect = new Rect(0, 0, dl.width(), dl.height()); - tmpRect.inset(dl.nonDecorInsets()); - config.windowConfiguration.setAppBounds(tmpRect); - tmpRect.set(0, 0, dl.width(), dl.height()); - tmpRect.inset(dl.stableInsets()); - config.screenWidthDp = (int) (tmpRect.width() / dl.density()); - config.screenHeightDp = (int) (tmpRect.height() / dl.density()); - final Context rotationContext = context.createConfigurationContext(config); - return new DividerSnapAlgorithm( - rotationContext.getResources(), dl.width(), dl.height(), dividerSize, - config.orientation == ORIENTATION_PORTRAIT, dl.stableInsets()); - } - - /** - * Get the current primary-split side. Determined by its location of {@param bounds} within - * {@param displayRect} but if both are the same, it will try to dock to each side and determine - * if allowed in its respected {@param orientation}. - * - * @param bounds bounds of the primary split task to get which side is docked - * @param displayRect bounds of the display that contains the primary split task - * @param orientation the origination of device - * @return current primary-split side - */ - static int getPrimarySplitSide(Rect bounds, Rect displayRect, int orientation) { - if (orientation == ORIENTATION_PORTRAIT) { - // Portrait mode, docked either at the top or the bottom. - final int diff = (displayRect.bottom - bounds.bottom) - (bounds.top - displayRect.top); - if (diff < 0) { - return DOCKED_BOTTOM; - } else { - // Top is default - return DOCKED_TOP; - } - } else if (orientation == ORIENTATION_LANDSCAPE) { - // Landscape mode, docked either on the left or on the right. - final int diff = (displayRect.right - bounds.right) - (bounds.left - displayRect.left); - if (diff < 0) { - return DOCKED_RIGHT; - } - return DOCKED_LEFT; - } - return DOCKED_INVALID; - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreen.java deleted file mode 100644 index 499a9c5fa631..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreen.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.legacysplitscreen; - -import android.graphics.Rect; -import android.window.WindowContainerToken; - -import com.android.wm.shell.common.annotations.ExternalThread; - -import java.io.PrintWriter; -import java.util.function.BiConsumer; -import java.util.function.Consumer; - -/** - * Interface to engage split screen feature. - */ -@ExternalThread -public interface LegacySplitScreen { - /** Called when keyguard showing state changed. */ - void onKeyguardVisibilityChanged(boolean isShowing); - - /** Returns {@link DividerView}. */ - DividerView getDividerView(); - - /** Returns {@code true} if one of the split screen is in minimized mode. */ - boolean isMinimized(); - - /** Returns {@code true} if the home stack is resizable. */ - boolean isHomeStackResizable(); - - /** Returns {@code true} if the divider is visible. */ - boolean isDividerVisible(); - - /** Switch to minimized state if appropriate. */ - void setMinimized(boolean minimized); - - /** Called when there's a task undocking. */ - void onUndockingTask(); - - /** Called when app transition finished. */ - void onAppTransitionFinished(); - - /** Dumps current status of Split Screen. */ - void dump(PrintWriter pw); - - /** Registers listener that gets called whenever the existence of the divider changes. */ - void registerInSplitScreenListener(Consumer<Boolean> listener); - - /** Unregisters listener that gets called whenever the existence of the divider changes. */ - void unregisterInSplitScreenListener(Consumer<Boolean> listener); - - /** Registers listener that gets called whenever the split screen bounds changes. */ - void registerBoundsChangeListener(BiConsumer<Rect, Rect> listener); - - /** @return the container token for the secondary split root task. */ - WindowContainerToken getSecondaryRoot(); - - /** - * Splits the primary task if feasible, this is to preserve legacy way to toggle split screen. - * Like triggering split screen through long pressing recents app button or through - * {@link android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN}. - * - * @return {@code true} if it successes to split the primary task. - */ - boolean splitPrimaryTask(); - - /** - * Exits the split to make the primary task fullscreen. - */ - void dismissSplitToPrimaryTask(); -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java deleted file mode 100644 index 67e487de0993..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java +++ /dev/null @@ -1,762 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.legacysplitscreen; - -import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; -import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; -import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; -import static android.view.Display.DEFAULT_DISPLAY; - -import android.animation.AnimationHandler; -import android.app.ActivityManager; -import android.app.ActivityManager.RunningTaskInfo; -import android.app.ActivityTaskManager; -import android.content.Context; -import android.content.res.Configuration; -import android.graphics.Rect; -import android.os.RemoteException; -import android.provider.Settings; -import android.util.Slog; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.Toast; -import android.window.TaskOrganizer; -import android.window.WindowContainerToken; -import android.window.WindowContainerTransaction; - -import com.android.internal.policy.DividerSnapAlgorithm; -import com.android.wm.shell.R; -import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.common.DisplayChangeController; -import com.android.wm.shell.common.DisplayController; -import com.android.wm.shell.common.DisplayImeController; -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.common.SystemWindows; -import com.android.wm.shell.common.TaskStackListenerCallback; -import com.android.wm.shell.common.TaskStackListenerImpl; -import com.android.wm.shell.common.TransactionPool; -import com.android.wm.shell.transition.Transitions; - -import java.io.PrintWriter; -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.function.BiConsumer; -import java.util.function.Consumer; - -/** - * Controls split screen feature. - */ -public class LegacySplitScreenController implements DisplayController.OnDisplaysChangedListener { - static final boolean DEBUG = false; - - private static final String TAG = "SplitScreenCtrl"; - private static final int DEFAULT_APP_TRANSITION_DURATION = 336; - - private final Context mContext; - private final DisplayChangeController.OnDisplayChangingListener mRotationController; - private final DisplayController mDisplayController; - private final DisplayImeController mImeController; - private final DividerImeController mImePositionProcessor; - private final DividerState mDividerState = new DividerState(); - private final ForcedResizableInfoActivityController mForcedResizableController; - private final ShellExecutor mMainExecutor; - private final AnimationHandler mSfVsyncAnimationHandler; - private final LegacySplitScreenTaskListener mSplits; - private final SystemWindows mSystemWindows; - final TransactionPool mTransactionPool; - private final WindowManagerProxy mWindowManagerProxy; - private final TaskOrganizer mTaskOrganizer; - private final SplitScreenImpl mImpl = new SplitScreenImpl(); - - private final CopyOnWriteArrayList<WeakReference<Consumer<Boolean>>> mDockedStackExistsListeners - = new CopyOnWriteArrayList<>(); - private final ArrayList<WeakReference<BiConsumer<Rect, Rect>>> mBoundsChangedListeners = - new ArrayList<>(); - - - private DividerWindowManager mWindowManager; - private DividerView mView; - - // Keeps track of real-time split geometry including snap positions and ime adjustments - private LegacySplitDisplayLayout mSplitLayout; - - // Transient: this contains the layout calculated for a new rotation requested by WM. This is - // kept around so that we can wait for a matching configuration change and then use the exact - // layout that we sent back to WM. - private LegacySplitDisplayLayout mRotateSplitLayout; - - private boolean mIsKeyguardShowing; - private boolean mVisible = false; - private volatile boolean mMinimized = false; - private volatile boolean mAdjustedForIme = false; - private boolean mHomeStackResizable = false; - - public LegacySplitScreenController(Context context, - DisplayController displayController, SystemWindows systemWindows, - DisplayImeController imeController, TransactionPool transactionPool, - ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue, - TaskStackListenerImpl taskStackListener, Transitions transitions, - ShellExecutor mainExecutor, AnimationHandler sfVsyncAnimationHandler) { - mContext = context; - mDisplayController = displayController; - mSystemWindows = systemWindows; - mImeController = imeController; - mMainExecutor = mainExecutor; - mSfVsyncAnimationHandler = sfVsyncAnimationHandler; - mForcedResizableController = new ForcedResizableInfoActivityController(context, this, - mainExecutor); - mTransactionPool = transactionPool; - mWindowManagerProxy = new WindowManagerProxy(syncQueue, shellTaskOrganizer); - mTaskOrganizer = shellTaskOrganizer; - mSplits = new LegacySplitScreenTaskListener(this, shellTaskOrganizer, transitions, - syncQueue); - mImePositionProcessor = new DividerImeController(mSplits, mTransactionPool, mMainExecutor, - shellTaskOrganizer); - mRotationController = - (display, fromRotation, toRotation, wct) -> { - if (!mSplits.isSplitScreenSupported() || mWindowManagerProxy == null) { - return; - } - WindowContainerTransaction t = new WindowContainerTransaction(); - DisplayLayout displayLayout = - new DisplayLayout(mDisplayController.getDisplayLayout(display)); - LegacySplitDisplayLayout sdl = - new LegacySplitDisplayLayout(mContext, displayLayout, mSplits); - sdl.rotateTo(toRotation); - mRotateSplitLayout = sdl; - // snap resets to middle target when not minimized and rotation changed. - final int position = mMinimized ? mView.mSnapTargetBeforeMinimized.position - : sdl.getSnapAlgorithm().getMiddleTarget().position; - DividerSnapAlgorithm snap = sdl.getSnapAlgorithm(); - final DividerSnapAlgorithm.SnapTarget target = - snap.calculateNonDismissingSnapTarget(position); - sdl.resizeSplits(target.position, t); - - if (isSplitActive() && mHomeStackResizable) { - mWindowManagerProxy - .applyHomeTasksMinimized(sdl, mSplits.mSecondary.token, t); - } - if (mWindowManagerProxy.queueSyncTransactionIfWaiting(t)) { - // Because sync transactions are serialized, its possible for an "older" - // bounds-change to get applied after a screen rotation. In that case, we - // want to actually defer on that rather than apply immediately. Of course, - // this means that the bounds may not change until after the rotation so - // the user might see some artifacts. This should be rare. - Slog.w(TAG, "Screen rotated while other operations were pending, this may" - + " result in some graphical artifacts."); - } else { - wct.merge(t, true /* transfer */); - } - }; - - mWindowManager = new DividerWindowManager(mSystemWindows); - - // No need to listen to display window container or create root tasks if the device is not - // using legacy split screen. - if (!context.getResources().getBoolean(com.android.internal.R.bool.config_useLegacySplit)) { - return; - } - - - mDisplayController.addDisplayWindowListener(this); - // Don't initialize the divider or anything until we get the default display. - - taskStackListener.addListener( - new TaskStackListenerCallback() { - @Override - public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task, - boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) { - if (!wasVisible || task.getWindowingMode() - != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY - || !mSplits.isSplitScreenSupported()) { - return; - } - - if (isMinimized()) { - onUndockingTask(); - } - } - - @Override - public void onActivityForcedResizable(String packageName, int taskId, - int reason) { - mForcedResizableController.activityForcedResizable(packageName, taskId, - reason); - } - - @Override - public void onActivityDismissingDockedStack() { - mForcedResizableController.activityDismissingSplitScreen(); - } - - @Override - public void onActivityLaunchOnSecondaryDisplayFailed() { - mForcedResizableController.activityLaunchOnSecondaryDisplayFailed(); - } - }); - } - - public LegacySplitScreen asLegacySplitScreen() { - return mImpl; - } - - public void onSplitScreenSupported() { - // Set starting tile bounds based on middle target - final WindowContainerTransaction tct = new WindowContainerTransaction(); - int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position; - mSplitLayout.resizeSplits(midPos, tct); - mTaskOrganizer.applyTransaction(tct); - } - - public void onKeyguardVisibilityChanged(boolean showing) { - if (!isSplitActive() || mView == null) { - return; - } - mView.setHidden(showing); - mIsKeyguardShowing = showing; - } - - @Override - public void onDisplayAdded(int displayId) { - if (displayId != DEFAULT_DISPLAY) { - return; - } - mSplitLayout = new LegacySplitDisplayLayout(mDisplayController.getDisplayContext(displayId), - mDisplayController.getDisplayLayout(displayId), mSplits); - mImeController.addPositionProcessor(mImePositionProcessor); - mDisplayController.addDisplayChangingController(mRotationController); - if (!ActivityTaskManager.supportsSplitScreenMultiWindow(mContext)) { - removeDivider(); - return; - } - try { - mSplits.init(); - } catch (Exception e) { - Slog.e(TAG, "Failed to register docked stack listener", e); - removeDivider(); - return; - } - } - - @Override - public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) { - if (displayId != DEFAULT_DISPLAY || !mSplits.isSplitScreenSupported()) { - return; - } - mSplitLayout = new LegacySplitDisplayLayout(mDisplayController.getDisplayContext(displayId), - mDisplayController.getDisplayLayout(displayId), mSplits); - if (mRotateSplitLayout == null) { - int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position; - final WindowContainerTransaction tct = new WindowContainerTransaction(); - mSplitLayout.resizeSplits(midPos, tct); - mTaskOrganizer.applyTransaction(tct); - } else if (mSplitLayout.mDisplayLayout.rotation() - == mRotateSplitLayout.mDisplayLayout.rotation()) { - mSplitLayout.mPrimary = new Rect(mRotateSplitLayout.mPrimary); - mSplitLayout.mSecondary = new Rect(mRotateSplitLayout.mSecondary); - mRotateSplitLayout = null; - } - if (isSplitActive()) { - update(newConfig); - } - } - - public boolean isMinimized() { - return mMinimized; - } - - public boolean isHomeStackResizable() { - return mHomeStackResizable; - } - - public DividerView getDividerView() { - return mView; - } - - public boolean isDividerVisible() { - return mView != null && mView.getVisibility() == View.VISIBLE; - } - - /** - * This indicates that at-least one of the splits has content. This differs from - * isDividerVisible because the divider is only visible once *everything* is in split mode - * while this only cares if some things are (eg. while entering/exiting as well). - */ - public boolean isSplitActive() { - return mSplits.mPrimary != null && mSplits.mSecondary != null - && (mSplits.mPrimary.topActivityType != ACTIVITY_TYPE_UNDEFINED - || mSplits.mSecondary.topActivityType != ACTIVITY_TYPE_UNDEFINED); - } - - public void addDivider(Configuration configuration) { - Context dctx = mDisplayController.getDisplayContext(mContext.getDisplayId()); - mView = (DividerView) - LayoutInflater.from(dctx).inflate(R.layout.docked_stack_divider, null); - mView.setAnimationHandler(mSfVsyncAnimationHandler); - DisplayLayout displayLayout = mDisplayController.getDisplayLayout(mContext.getDisplayId()); - mView.injectDependencies(this, mWindowManager, mDividerState, mForcedResizableController, - mSplits, mSplitLayout, mImePositionProcessor, mWindowManagerProxy); - mView.setVisibility(mVisible ? View.VISIBLE : View.INVISIBLE); - mView.setMinimizedDockStack(mMinimized, mHomeStackResizable, null /* transaction */); - final int size = dctx.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.docked_stack_divider_thickness); - final boolean landscape = configuration.orientation == ORIENTATION_LANDSCAPE; - final int width = landscape ? size : displayLayout.width(); - final int height = landscape ? displayLayout.height() : size; - mWindowManager.add(mView, width, height, mContext.getDisplayId()); - } - - public void removeDivider() { - if (mView != null) { - mView.onDividerRemoved(); - } - mWindowManager.remove(); - } - - public void update(Configuration configuration) { - final boolean isDividerHidden = mView != null && mIsKeyguardShowing; - - removeDivider(); - addDivider(configuration); - - if (mMinimized) { - mView.setMinimizedDockStack(true, mHomeStackResizable, null /* transaction */); - updateTouchable(); - } - mView.setHidden(isDividerHidden); - } - - public void onTaskVanished() { - removeDivider(); - } - - public void updateVisibility(final boolean visible) { - if (DEBUG) Slog.d(TAG, "Updating visibility " + mVisible + "->" + visible); - if (mVisible != visible) { - mVisible = visible; - mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); - - if (visible) { - mView.enterSplitMode(mHomeStackResizable); - // Update state because animations won't finish. - mWindowManagerProxy.runInSync( - t -> mView.setMinimizedDockStack(mMinimized, mHomeStackResizable, t)); - - } else { - mView.exitSplitMode(); - mWindowManagerProxy.runInSync( - t -> mView.setMinimizedDockStack(false, mHomeStackResizable, t)); - } - // Notify existence listeners - synchronized (mDockedStackExistsListeners) { - mDockedStackExistsListeners.removeIf(wf -> { - Consumer<Boolean> l = wf.get(); - if (l != null) l.accept(visible); - return l == null; - }); - } - } - } - - public void setMinimized(final boolean minimized) { - if (DEBUG) Slog.d(TAG, "posting ext setMinimized " + minimized + " vis:" + mVisible); - mMainExecutor.execute(() -> { - if (DEBUG) Slog.d(TAG, "run posted ext setMinimized " + minimized + " vis:" + mVisible); - if (!mVisible) { - return; - } - setHomeMinimized(minimized); - }); - } - - public void setHomeMinimized(final boolean minimized) { - if (DEBUG) { - Slog.d(TAG, "setHomeMinimized min:" + mMinimized + "->" + minimized + " hrsz:" - + mHomeStackResizable + " split:" + isDividerVisible()); - } - WindowContainerTransaction wct = new WindowContainerTransaction(); - final boolean minimizedChanged = mMinimized != minimized; - // Update minimized state - if (minimizedChanged) { - mMinimized = minimized; - } - // Always set this because we could be entering split when mMinimized is already true - wct.setFocusable(mSplits.mPrimary.token, !mMinimized); - - // Sync state to DividerView if it exists. - if (mView != null) { - final int displayId = mView.getDisplay() != null - ? mView.getDisplay().getDisplayId() : DEFAULT_DISPLAY; - // pause ime here (before updateMinimizedDockedStack) - if (mMinimized) { - mImePositionProcessor.pause(displayId); - } - if (minimizedChanged) { - // This conflicts with IME adjustment, so only call it when things change. - mView.setMinimizedDockStack(minimized, getAnimDuration(), mHomeStackResizable); - } - if (!mMinimized) { - // afterwards so it can end any animations started in view - mImePositionProcessor.resume(displayId); - } - } - updateTouchable(); - - // If we are only setting focusability, a sync transaction isn't necessary (in fact it - // can interrupt other animations), so see if it can be submitted on pending instead. - if (!mWindowManagerProxy.queueSyncTransactionIfWaiting(wct)) { - mTaskOrganizer.applyTransaction(wct); - } - } - - public void setAdjustedForIme(boolean adjustedForIme) { - if (mAdjustedForIme == adjustedForIme) { - return; - } - mAdjustedForIme = adjustedForIme; - updateTouchable(); - } - - public void updateTouchable() { - mWindowManager.setTouchable(!mAdjustedForIme); - } - - public void onUndockingTask() { - if (mView != null) { - mView.onUndockingTask(); - } - } - - public void onAppTransitionFinished() { - if (mView == null) { - return; - } - mForcedResizableController.onAppTransitionFinished(); - } - - public void dump(PrintWriter pw) { - pw.print(" mVisible="); pw.println(mVisible); - pw.print(" mMinimized="); pw.println(mMinimized); - pw.print(" mAdjustedForIme="); pw.println(mAdjustedForIme); - } - - public long getAnimDuration() { - float transitionScale = Settings.Global.getFloat(mContext.getContentResolver(), - Settings.Global.TRANSITION_ANIMATION_SCALE, - mContext.getResources().getFloat( - com.android.internal.R.dimen - .config_appTransitionAnimationDurationScaleDefault)); - final long transitionDuration = DEFAULT_APP_TRANSITION_DURATION; - return (long) (transitionDuration * transitionScale); - } - - public void registerInSplitScreenListener(Consumer<Boolean> listener) { - listener.accept(isDividerVisible()); - synchronized (mDockedStackExistsListeners) { - mDockedStackExistsListeners.add(new WeakReference<>(listener)); - } - } - - public void unregisterInSplitScreenListener(Consumer<Boolean> listener) { - synchronized (mDockedStackExistsListeners) { - for (int i = mDockedStackExistsListeners.size() - 1; i >= 0; i--) { - if (mDockedStackExistsListeners.get(i) == listener) { - mDockedStackExistsListeners.remove(i); - } - } - } - } - - public void registerBoundsChangeListener(BiConsumer<Rect, Rect> listener) { - synchronized (mBoundsChangedListeners) { - mBoundsChangedListeners.add(new WeakReference<>(listener)); - } - } - - public boolean splitPrimaryTask() { - try { - if (ActivityTaskManager.getService().getLockTaskModeState() == LOCK_TASK_MODE_PINNED) { - return false; - } - } catch (RemoteException e) { - return false; - } - if (isSplitActive() || mSplits.mPrimary == null) { - return false; - } - - // Try fetching the top running task. - final List<RunningTaskInfo> runningTasks = - ActivityTaskManager.getInstance().getTasks(1 /* maxNum */); - if (runningTasks == null || runningTasks.isEmpty()) { - return false; - } - // Note: The set of running tasks from the system is ordered by recency. - final RunningTaskInfo topRunningTask = runningTasks.get(0); - final int activityType = topRunningTask.getActivityType(); - if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) { - return false; - } - - if (!topRunningTask.supportsSplitScreenMultiWindow) { - Toast.makeText(mContext, R.string.dock_non_resizeble_failed_to_dock_text, - Toast.LENGTH_SHORT).show(); - return false; - } - - final WindowContainerTransaction wct = new WindowContainerTransaction(); - // Clear out current windowing mode before reparenting to split task. - wct.setWindowingMode(topRunningTask.token, WINDOWING_MODE_UNDEFINED); - wct.reparent(topRunningTask.token, mSplits.mPrimary.token, true /* onTop */); - mWindowManagerProxy.applySyncTransaction(wct); - return true; - } - - public void dismissSplitToPrimaryTask() { - startDismissSplit(true /* toPrimaryTask */); - } - - /** Notifies the bounds of split screen changed. */ - public void notifyBoundsChanged(Rect secondaryWindowBounds, Rect secondaryWindowInsets) { - synchronized (mBoundsChangedListeners) { - mBoundsChangedListeners.removeIf(wf -> { - BiConsumer<Rect, Rect> l = wf.get(); - if (l != null) l.accept(secondaryWindowBounds, secondaryWindowInsets); - return l == null; - }); - } - } - - public void startEnterSplit() { - update(mDisplayController.getDisplayContext( - mContext.getDisplayId()).getResources().getConfiguration()); - // Set resizable directly here because applyEnterSplit already resizes home stack. - mHomeStackResizable = mWindowManagerProxy.applyEnterSplit(mSplits, - mRotateSplitLayout != null ? mRotateSplitLayout : mSplitLayout); - } - - public void prepareEnterSplitTransition(WindowContainerTransaction outWct) { - // Set resizable directly here because buildEnterSplit already resizes home stack. - mHomeStackResizable = mWindowManagerProxy.buildEnterSplit(outWct, mSplits, - mRotateSplitLayout != null ? mRotateSplitLayout : mSplitLayout); - } - - public void finishEnterSplitTransition(boolean minimized) { - update(mDisplayController.getDisplayContext( - mContext.getDisplayId()).getResources().getConfiguration()); - if (minimized) { - ensureMinimizedSplit(); - } else { - ensureNormalSplit(); - } - } - - public void startDismissSplit(boolean toPrimaryTask) { - startDismissSplit(toPrimaryTask, false /* snapped */); - } - - public void startDismissSplit(boolean toPrimaryTask, boolean snapped) { - if (Transitions.ENABLE_SHELL_TRANSITIONS) { - mSplits.getSplitTransitions().dismissSplit( - mSplits, mSplitLayout, !toPrimaryTask, snapped); - } else { - mWindowManagerProxy.applyDismissSplit(mSplits, mSplitLayout, !toPrimaryTask); - onDismissSplit(); - } - } - - public void onDismissSplit() { - updateVisibility(false /* visible */); - mMinimized = false; - // Resets divider bar position to undefined, so new divider bar will apply default position - // next time entering split mode. - mDividerState.mRatioPositionBeforeMinimized = 0; - removeDivider(); - mImePositionProcessor.reset(); - } - - public void ensureMinimizedSplit() { - setHomeMinimized(true /* minimized */); - if (mView != null && !isDividerVisible()) { - // Wasn't in split-mode yet, so enter now. - if (DEBUG) { - Slog.d(TAG, " entering split mode with minimized=true"); - } - updateVisibility(true /* visible */); - } - } - - public void ensureNormalSplit() { - setHomeMinimized(false /* minimized */); - if (mView != null && !isDividerVisible()) { - // Wasn't in split-mode, so enter now. - if (DEBUG) { - Slog.d(TAG, " enter split mode unminimized "); - } - updateVisibility(true /* visible */); - } - } - - public LegacySplitDisplayLayout getSplitLayout() { - return mSplitLayout; - } - - public WindowManagerProxy getWmProxy() { - return mWindowManagerProxy; - } - - public WindowContainerToken getSecondaryRoot() { - if (mSplits == null || mSplits.mSecondary == null) { - return null; - } - return mSplits.mSecondary.token; - } - - private class SplitScreenImpl implements LegacySplitScreen { - @Override - public boolean isMinimized() { - return mMinimized; - } - - @Override - public boolean isHomeStackResizable() { - return mHomeStackResizable; - } - - /** - * TODO: Remove usage from outside the shell. - */ - @Override - public DividerView getDividerView() { - return LegacySplitScreenController.this.getDividerView(); - } - - @Override - public boolean isDividerVisible() { - boolean[] result = new boolean[1]; - try { - mMainExecutor.executeBlocking(() -> { - result[0] = LegacySplitScreenController.this.isDividerVisible(); - }); - } catch (InterruptedException e) { - Slog.e(TAG, "Failed to get divider visible"); - } - return result[0]; - } - - @Override - public void onKeyguardVisibilityChanged(boolean isShowing) { - mMainExecutor.execute(() -> { - LegacySplitScreenController.this.onKeyguardVisibilityChanged(isShowing); - }); - } - - @Override - public void setMinimized(boolean minimized) { - mMainExecutor.execute(() -> { - LegacySplitScreenController.this.setMinimized(minimized); - }); - } - - @Override - public void onUndockingTask() { - mMainExecutor.execute(() -> { - LegacySplitScreenController.this.onUndockingTask(); - }); - } - - @Override - public void onAppTransitionFinished() { - mMainExecutor.execute(() -> { - LegacySplitScreenController.this.onAppTransitionFinished(); - }); - } - - @Override - public void registerInSplitScreenListener(Consumer<Boolean> listener) { - mMainExecutor.execute(() -> { - LegacySplitScreenController.this.registerInSplitScreenListener(listener); - }); - } - - @Override - public void unregisterInSplitScreenListener(Consumer<Boolean> listener) { - mMainExecutor.execute(() -> { - LegacySplitScreenController.this.unregisterInSplitScreenListener(listener); - }); - } - - @Override - public void registerBoundsChangeListener(BiConsumer<Rect, Rect> listener) { - mMainExecutor.execute(() -> { - LegacySplitScreenController.this.registerBoundsChangeListener(listener); - }); - } - - @Override - public WindowContainerToken getSecondaryRoot() { - WindowContainerToken[] result = new WindowContainerToken[1]; - try { - mMainExecutor.executeBlocking(() -> { - result[0] = LegacySplitScreenController.this.getSecondaryRoot(); - }); - } catch (InterruptedException e) { - Slog.e(TAG, "Failed to get secondary root"); - } - return result[0]; - } - - @Override - public boolean splitPrimaryTask() { - boolean[] result = new boolean[1]; - try { - mMainExecutor.executeBlocking(() -> { - result[0] = LegacySplitScreenController.this.splitPrimaryTask(); - }); - } catch (InterruptedException e) { - Slog.e(TAG, "Failed to split primary task"); - } - return result[0]; - } - - @Override - public void dismissSplitToPrimaryTask() { - mMainExecutor.execute(() -> { - LegacySplitScreenController.this.dismissSplitToPrimaryTask(); - }); - } - - @Override - public void dump(PrintWriter pw) { - try { - mMainExecutor.executeBlocking(() -> { - LegacySplitScreenController.this.dump(pw); - }); - } catch (InterruptedException e) { - Slog.e(TAG, "Failed to dump LegacySplitScreenController in 2s"); - } - } - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java deleted file mode 100644 index d2f42c39acd5..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java +++ /dev/null @@ -1,376 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.legacysplitscreen; - -import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; -import static android.view.Display.DEFAULT_DISPLAY; - -import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG; - -import android.app.ActivityManager.RunningTaskInfo; -import android.graphics.Point; -import android.graphics.Rect; -import android.util.Log; -import android.util.SparseArray; -import android.view.SurfaceControl; -import android.view.SurfaceSession; -import android.window.TaskOrganizer; - -import androidx.annotation.NonNull; - -import com.android.internal.protolog.common.ProtoLog; -import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.common.SurfaceUtils; -import com.android.wm.shell.common.SyncTransactionQueue; -import com.android.wm.shell.transition.Transitions; - -import java.io.PrintWriter; -import java.util.ArrayList; - -class LegacySplitScreenTaskListener implements ShellTaskOrganizer.TaskListener { - private static final String TAG = LegacySplitScreenTaskListener.class.getSimpleName(); - private static final boolean DEBUG = LegacySplitScreenController.DEBUG; - - private final ShellTaskOrganizer mTaskOrganizer; - private final SyncTransactionQueue mSyncQueue; - private final SparseArray<SurfaceControl> mLeashByTaskId = new SparseArray<>(); - - // TODO(shell-transitions): Remove when switched to shell-transitions. - private final SparseArray<Point> mPositionByTaskId = new SparseArray<>(); - - RunningTaskInfo mPrimary; - RunningTaskInfo mSecondary; - SurfaceControl mPrimarySurface; - SurfaceControl mSecondarySurface; - SurfaceControl mPrimaryDim; - SurfaceControl mSecondaryDim; - Rect mHomeBounds = new Rect(); - final LegacySplitScreenController mSplitScreenController; - private boolean mSplitScreenSupported = false; - - final SurfaceSession mSurfaceSession = new SurfaceSession(); - - private final LegacySplitScreenTransitions mSplitTransitions; - - LegacySplitScreenTaskListener(LegacySplitScreenController splitScreenController, - ShellTaskOrganizer shellTaskOrganizer, - Transitions transitions, - SyncTransactionQueue syncQueue) { - mSplitScreenController = splitScreenController; - mTaskOrganizer = shellTaskOrganizer; - mSplitTransitions = new LegacySplitScreenTransitions(splitScreenController.mTransactionPool, - transitions, mSplitScreenController, this); - transitions.addHandler(mSplitTransitions); - mSyncQueue = syncQueue; - } - - void init() { - synchronized (this) { - try { - mTaskOrganizer.createRootTask( - DEFAULT_DISPLAY, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, this); - mTaskOrganizer.createRootTask( - DEFAULT_DISPLAY, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, this); - } catch (Exception e) { - // teardown to prevent callbacks - mTaskOrganizer.removeListener(this); - throw e; - } - } - } - - boolean isSplitScreenSupported() { - return mSplitScreenSupported; - } - - SurfaceControl.Transaction getTransaction() { - return mSplitScreenController.mTransactionPool.acquire(); - } - - void releaseTransaction(SurfaceControl.Transaction t) { - mSplitScreenController.mTransactionPool.release(t); - } - - TaskOrganizer getTaskOrganizer() { - return mTaskOrganizer; - } - - LegacySplitScreenTransitions getSplitTransitions() { - return mSplitTransitions; - } - - @Override - public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { - synchronized (this) { - if (taskInfo.hasParentTask()) { - handleChildTaskAppeared(taskInfo, leash); - return; - } - - final int winMode = taskInfo.getWindowingMode(); - if (winMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { - ProtoLog.v(WM_SHELL_TASK_ORG, - "%s onTaskAppeared Primary taskId=%d", TAG, taskInfo.taskId); - mPrimary = taskInfo; - mPrimarySurface = leash; - } else if (winMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) { - ProtoLog.v(WM_SHELL_TASK_ORG, - "%s onTaskAppeared Secondary taskId=%d", TAG, taskInfo.taskId); - mSecondary = taskInfo; - mSecondarySurface = leash; - } else { - ProtoLog.v(WM_SHELL_TASK_ORG, "%s onTaskAppeared unknown taskId=%d winMode=%d", - TAG, taskInfo.taskId, winMode); - } - - if (!mSplitScreenSupported && mPrimarySurface != null && mSecondarySurface != null) { - mSplitScreenSupported = true; - mSplitScreenController.onSplitScreenSupported(); - ProtoLog.v(WM_SHELL_TASK_ORG, "%s onTaskAppeared Supported", TAG); - - // Initialize dim surfaces: - SurfaceControl.Transaction t = getTransaction(); - mPrimaryDim = SurfaceUtils.makeDimLayer( - t, mPrimarySurface, "Primary Divider Dim", mSurfaceSession); - mSecondaryDim = SurfaceUtils.makeDimLayer( - t, mSecondarySurface, "Secondary Divider Dim", mSurfaceSession); - t.apply(); - releaseTransaction(t); - } - } - } - - @Override - public void onTaskVanished(RunningTaskInfo taskInfo) { - synchronized (this) { - mPositionByTaskId.remove(taskInfo.taskId); - if (taskInfo.hasParentTask()) { - mLeashByTaskId.remove(taskInfo.taskId); - return; - } - - final boolean isPrimaryTask = mPrimary != null - && taskInfo.token.equals(mPrimary.token); - final boolean isSecondaryTask = mSecondary != null - && taskInfo.token.equals(mSecondary.token); - - if (mSplitScreenSupported && (isPrimaryTask || isSecondaryTask)) { - mSplitScreenSupported = false; - - SurfaceControl.Transaction t = getTransaction(); - t.remove(mPrimaryDim); - t.remove(mSecondaryDim); - t.remove(mPrimarySurface); - t.remove(mSecondarySurface); - t.apply(); - releaseTransaction(t); - - mSplitScreenController.onTaskVanished(); - } - } - } - - @Override - public void onTaskInfoChanged(RunningTaskInfo taskInfo) { - if (taskInfo.displayId != DEFAULT_DISPLAY) { - return; - } - synchronized (this) { - if (!taskInfo.supportsMultiWindow) { - if (mSplitScreenController.isDividerVisible()) { - // Dismiss the split screen if the task no longer supports multi window. - if (taskInfo.taskId == mPrimary.taskId - || taskInfo.parentTaskId == mPrimary.taskId) { - // If the primary is focused, dismiss to primary. - mSplitScreenController - .startDismissSplit(taskInfo.isFocused /* toPrimaryTask */); - } else { - // If the secondary is not focused, dismiss to primary. - mSplitScreenController - .startDismissSplit(!taskInfo.isFocused /* toPrimaryTask */); - } - } - return; - } - if (taskInfo.hasParentTask()) { - // changed messages are noisy since it reports on every ensureVisibility. This - // conflicts with legacy app-transitions which "swaps" the position to a - // leash. For now, only update when position actually changes to avoid - // poorly-timed duplicate calls. - if (taskInfo.positionInParent.equals(mPositionByTaskId.get(taskInfo.taskId))) { - return; - } - handleChildTaskChanged(taskInfo); - } else { - handleTaskInfoChanged(taskInfo); - } - mPositionByTaskId.put(taskInfo.taskId, new Point(taskInfo.positionInParent)); - } - } - - private void handleChildTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { - mLeashByTaskId.put(taskInfo.taskId, leash); - mPositionByTaskId.put(taskInfo.taskId, new Point(taskInfo.positionInParent)); - if (Transitions.ENABLE_SHELL_TRANSITIONS) return; - updateChildTaskSurface(taskInfo, leash, true /* firstAppeared */); - } - - private void handleChildTaskChanged(RunningTaskInfo taskInfo) { - if (Transitions.ENABLE_SHELL_TRANSITIONS) return; - final SurfaceControl leash = mLeashByTaskId.get(taskInfo.taskId); - updateChildTaskSurface(taskInfo, leash, false /* firstAppeared */); - } - - private void updateChildTaskSurface( - RunningTaskInfo taskInfo, SurfaceControl leash, boolean firstAppeared) { - final Point taskPositionInParent = taskInfo.positionInParent; - mSyncQueue.runInSync(t -> { - t.setWindowCrop(leash, null); - t.setPosition(leash, taskPositionInParent.x, taskPositionInParent.y); - if (firstAppeared && !Transitions.ENABLE_SHELL_TRANSITIONS) { - t.setAlpha(leash, 1f); - t.setMatrix(leash, 1, 0, 0, 1); - t.show(leash); - } - }); - } - - /** - * This is effectively a finite state machine which moves between the various split-screen - * presentations based on the contents of the split regions. - */ - private void handleTaskInfoChanged(RunningTaskInfo info) { - if (!mSplitScreenSupported) { - // This shouldn't happen; but apparently there is a chance that SysUI crashes without - // system server receiving binder-death (or maybe it receives binder-death too late?). - // In this situation, when sys-ui restarts, the split root-tasks will still exist so - // there is a small window of time during init() where WM might send messages here - // before init() fails. So, avoid a cycle of crashes by returning early. - Log.e(TAG, "Got handleTaskInfoChanged when not initialized: " + info); - return; - } - final boolean secondaryImpliedMinimize = mSecondary.topActivityType == ACTIVITY_TYPE_HOME - || (mSecondary.topActivityType == ACTIVITY_TYPE_RECENTS - && mSplitScreenController.isHomeStackResizable()); - final boolean primaryWasEmpty = mPrimary.topActivityType == ACTIVITY_TYPE_UNDEFINED; - final boolean secondaryWasEmpty = mSecondary.topActivityType == ACTIVITY_TYPE_UNDEFINED; - if (info.token.asBinder() == mPrimary.token.asBinder()) { - mPrimary = info; - } else if (info.token.asBinder() == mSecondary.token.asBinder()) { - mSecondary = info; - } - if (DEBUG) { - Log.d(TAG, "onTaskInfoChanged " + mPrimary + " " + mSecondary); - } - if (Transitions.ENABLE_SHELL_TRANSITIONS) return; - final boolean primaryIsEmpty = mPrimary.topActivityType == ACTIVITY_TYPE_UNDEFINED; - final boolean secondaryIsEmpty = mSecondary.topActivityType == ACTIVITY_TYPE_UNDEFINED; - final boolean secondaryImpliesMinimize = mSecondary.topActivityType == ACTIVITY_TYPE_HOME - || (mSecondary.topActivityType == ACTIVITY_TYPE_RECENTS - && mSplitScreenController.isHomeStackResizable()); - if (primaryIsEmpty == primaryWasEmpty && secondaryWasEmpty == secondaryIsEmpty - && secondaryImpliedMinimize == secondaryImpliesMinimize) { - // No relevant changes - return; - } - if (primaryIsEmpty || secondaryIsEmpty) { - // At-least one of the splits is empty which means we are currently transitioning - // into or out-of split-screen mode. - if (DEBUG) { - Log.d(TAG, " at-least one split empty " + mPrimary.topActivityType - + " " + mSecondary.topActivityType); - } - if (mSplitScreenController.isDividerVisible()) { - // Was in split-mode, which means we are leaving split, so continue that. - // This happens when the stack in the primary-split is dismissed. - if (DEBUG) { - Log.d(TAG, " was in split, so this means leave it " - + mPrimary.topActivityType + " " + mSecondary.topActivityType); - } - mSplitScreenController.startDismissSplit(false /* toPrimaryTask */); - } else if (!primaryIsEmpty && primaryWasEmpty && secondaryWasEmpty) { - // Wasn't in split-mode (both were empty), but now that the primary split is - // populated, we should fully enter split by moving everything else into secondary. - // This just tells window-manager to reparent things, the UI will respond - // when it gets new task info for the secondary split. - if (DEBUG) { - Log.d(TAG, " was not in split, but primary is populated, so enter it"); - } - mSplitScreenController.startEnterSplit(); - } - } else if (secondaryImpliesMinimize) { - // Workaround for b/172686383, we can't rely on the sync bounds change transaction for - // the home task to finish before the last updateChildTaskSurface() call even if it's - // queued on the sync transaction queue, so ensure that the home task surface is updated - // again before we minimize - final ArrayList<RunningTaskInfo> tasks = new ArrayList<>(); - mSplitScreenController.getWmProxy().getHomeAndRecentsTasks(tasks, - mSplitScreenController.getSecondaryRoot()); - for (int i = 0; i < tasks.size(); i++) { - final RunningTaskInfo taskInfo = tasks.get(i); - final SurfaceControl leash = mLeashByTaskId.get(taskInfo.taskId); - if (leash != null) { - updateChildTaskSurface(taskInfo, leash, false /* firstAppeared */); - } - } - - // Both splits are populated but the secondary split has a home/recents stack on top, - // so enter minimized mode. - mSplitScreenController.ensureMinimizedSplit(); - } else { - // Both splits are populated by normal activities, so make sure we aren't minimized. - mSplitScreenController.ensureNormalSplit(); - } - } - - @Override - public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) { - b.setParent(findTaskSurface(taskId)); - } - - @Override - public void reparentChildSurfaceToTask(int taskId, SurfaceControl sc, - SurfaceControl.Transaction t) { - t.reparent(sc, findTaskSurface(taskId)); - } - - private SurfaceControl findTaskSurface(int taskId) { - if (!mLeashByTaskId.contains(taskId)) { - throw new IllegalArgumentException("There is no surface for taskId=" + taskId); - } - return mLeashByTaskId.get(taskId); - } - - @Override - public void dump(@NonNull PrintWriter pw, String prefix) { - final String innerPrefix = prefix + " "; - final String childPrefix = innerPrefix + " "; - pw.println(prefix + this); - pw.println(innerPrefix + "mSplitScreenSupported=" + mSplitScreenSupported); - if (mPrimary != null) pw.println(innerPrefix + "mPrimary.taskId=" + mPrimary.taskId); - if (mSecondary != null) pw.println(innerPrefix + "mSecondary.taskId=" + mSecondary.taskId); - } - - @Override - public String toString() { - return TAG; - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTransitions.java deleted file mode 100644 index b1fa2ac25fe7..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTransitions.java +++ /dev/null @@ -1,348 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.legacysplitscreen; - -import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; -import static android.view.WindowManager.TRANSIT_CHANGE; -import static android.view.WindowManager.TRANSIT_CLOSE; -import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM; -import static android.view.WindowManager.TRANSIT_OPEN; -import static android.view.WindowManager.TRANSIT_TO_BACK; -import static android.view.WindowManager.TRANSIT_TO_FRONT; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.app.ActivityManager; -import android.app.WindowConfiguration; -import android.graphics.Rect; -import android.os.IBinder; -import android.view.SurfaceControl; -import android.view.WindowManager; -import android.window.TransitionInfo; -import android.window.TransitionRequestInfo; -import android.window.WindowContainerTransaction; - -import com.android.wm.shell.common.TransactionPool; -import com.android.wm.shell.common.annotations.ExternalThread; -import com.android.wm.shell.transition.Transitions; - -import java.util.ArrayList; - -/** Plays transition animations for split-screen */ -public class LegacySplitScreenTransitions implements Transitions.TransitionHandler { - private static final String TAG = "SplitScreenTransitions"; - - public static final int TRANSIT_SPLIT_DISMISS_SNAP = TRANSIT_FIRST_CUSTOM + 10; - - private final TransactionPool mTransactionPool; - private final Transitions mTransitions; - private final LegacySplitScreenController mSplitScreen; - private final LegacySplitScreenTaskListener mListener; - - private IBinder mPendingDismiss = null; - private boolean mDismissFromSnap = false; - private IBinder mPendingEnter = null; - private IBinder mAnimatingTransition = null; - - /** Keeps track of currently running animations */ - private final ArrayList<Animator> mAnimations = new ArrayList<>(); - - private Transitions.TransitionFinishCallback mFinishCallback = null; - private SurfaceControl.Transaction mFinishTransaction; - - LegacySplitScreenTransitions(@NonNull TransactionPool pool, @NonNull Transitions transitions, - @NonNull LegacySplitScreenController splitScreen, - @NonNull LegacySplitScreenTaskListener listener) { - mTransactionPool = pool; - mTransitions = transitions; - mSplitScreen = splitScreen; - mListener = listener; - } - - @Override - public WindowContainerTransaction handleRequest(@NonNull IBinder transition, - @Nullable TransitionRequestInfo request) { - WindowContainerTransaction out = null; - final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask(); - final @WindowManager.TransitionType int type = request.getType(); - if (mSplitScreen.isDividerVisible()) { - // try to handle everything while in split-screen - out = new WindowContainerTransaction(); - if (triggerTask != null) { - final boolean shouldDismiss = - // if we close the primary-docked task, then leave split-screen since there - // is nothing behind it. - ((type == TRANSIT_CLOSE || type == TRANSIT_TO_BACK) - && triggerTask.parentTaskId == mListener.mPrimary.taskId) - // if an activity that is not supported in multi window mode is launched, - // we also need to leave split-screen. - || ((type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT) - && !triggerTask.supportsMultiWindow); - // In both cases, dismiss the primary - if (shouldDismiss) { - WindowManagerProxy.buildDismissSplit(out, mListener, - mSplitScreen.getSplitLayout(), true /* dismiss */); - if (type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT) { - out.reorder(triggerTask.token, true /* onTop */); - } - mPendingDismiss = transition; - } - } - } else if (triggerTask != null) { - // Not in split mode, so look for an open with a trigger task. - if ((type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT) - && triggerTask.configuration.windowConfiguration.getWindowingMode() - == WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { - out = new WindowContainerTransaction(); - mSplitScreen.prepareEnterSplitTransition(out); - mPendingEnter = transition; - } - } - return out; - } - - // TODO(shell-transitions): real animations - private void startExampleAnimation(@NonNull SurfaceControl leash, boolean show) { - final float end = show ? 1.f : 0.f; - final float start = 1.f - end; - final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); - final ValueAnimator va = ValueAnimator.ofFloat(start, end); - va.setDuration(500); - va.addUpdateListener(animation -> { - float fraction = animation.getAnimatedFraction(); - transaction.setAlpha(leash, start * (1.f - fraction) + end * fraction); - transaction.apply(); - }); - final Runnable finisher = () -> { - transaction.setAlpha(leash, end); - transaction.apply(); - mTransactionPool.release(transaction); - mTransitions.getMainExecutor().execute(() -> { - mAnimations.remove(va); - onFinish(); - }); - }; - va.addListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { } - - @Override - public void onAnimationEnd(Animator animation) { - finisher.run(); - } - - @Override - public void onAnimationCancel(Animator animation) { - finisher.run(); - } - - @Override - public void onAnimationRepeat(Animator animation) { } - }); - mAnimations.add(va); - mTransitions.getAnimExecutor().execute(va::start); - } - - // TODO(shell-transitions): real animations - private void startExampleResizeAnimation(@NonNull SurfaceControl leash, - @NonNull Rect startBounds, @NonNull Rect endBounds) { - final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); - final ValueAnimator va = ValueAnimator.ofFloat(0.f, 1.f); - va.setDuration(500); - va.addUpdateListener(animation -> { - float fraction = animation.getAnimatedFraction(); - transaction.setWindowCrop(leash, - (int) (startBounds.width() * (1.f - fraction) + endBounds.width() * fraction), - (int) (startBounds.height() * (1.f - fraction) - + endBounds.height() * fraction)); - transaction.setPosition(leash, - startBounds.left * (1.f - fraction) + endBounds.left * fraction, - startBounds.top * (1.f - fraction) + endBounds.top * fraction); - transaction.apply(); - }); - final Runnable finisher = () -> { - transaction.setWindowCrop(leash, 0, 0); - transaction.setPosition(leash, endBounds.left, endBounds.top); - transaction.apply(); - mTransactionPool.release(transaction); - mTransitions.getMainExecutor().execute(() -> { - mAnimations.remove(va); - onFinish(); - }); - }; - va.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - finisher.run(); - } - - @Override - public void onAnimationCancel(Animator animation) { - finisher.run(); - } - }); - mAnimations.add(va); - mTransitions.getAnimExecutor().execute(va::start); - } - - @Override - public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, - @NonNull SurfaceControl.Transaction startTransaction, - @NonNull SurfaceControl.Transaction finishTransaction, - @NonNull Transitions.TransitionFinishCallback finishCallback) { - if (transition != mPendingDismiss && transition != mPendingEnter) { - // If we're not in split-mode, just abort - if (!mSplitScreen.isDividerVisible()) return false; - // Check to see if HOME is involved - for (int i = info.getChanges().size() - 1; i >= 0; --i) { - final TransitionInfo.Change change = info.getChanges().get(i); - if (change.getTaskInfo() == null - || change.getTaskInfo().getActivityType() != ACTIVITY_TYPE_HOME) continue; - if (change.getMode() == TRANSIT_OPEN || change.getMode() == TRANSIT_TO_FRONT) { - mSplitScreen.ensureMinimizedSplit(); - } else if (change.getMode() == TRANSIT_CLOSE - || change.getMode() == TRANSIT_TO_BACK) { - mSplitScreen.ensureNormalSplit(); - } - } - // Use normal animations. - return false; - } - - mFinishCallback = finishCallback; - mFinishTransaction = mTransactionPool.acquire(); - mAnimatingTransition = transition; - - // Play fade animations - for (int i = info.getChanges().size() - 1; i >= 0; --i) { - final TransitionInfo.Change change = info.getChanges().get(i); - final SurfaceControl leash = change.getLeash(); - final int mode = info.getChanges().get(i).getMode(); - - if (mode == TRANSIT_CHANGE) { - if (change.getParent() != null) { - // This is probably reparented, so we want the parent to be immediately visible - final TransitionInfo.Change parentChange = info.getChange(change.getParent()); - startTransaction.show(parentChange.getLeash()); - startTransaction.setAlpha(parentChange.getLeash(), 1.f); - // and then animate this layer outside the parent (since, for example, this is - // the home task animating from fullscreen to part-screen). - startTransaction.reparent(leash, info.getRootLeash()); - startTransaction.setLayer(leash, info.getChanges().size() - i); - // build the finish reparent/reposition - mFinishTransaction.reparent(leash, parentChange.getLeash()); - mFinishTransaction.setPosition(leash, - change.getEndRelOffset().x, change.getEndRelOffset().y); - } - // TODO(shell-transitions): screenshot here - final Rect startBounds = new Rect(change.getStartAbsBounds()); - final boolean isHome = change.getTaskInfo() != null - && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME; - if (mPendingDismiss == transition && mDismissFromSnap && !isHome) { - // Home is special since it doesn't move during fling. Everything else, though, - // when dismissing from snap, the top/left is at 0,0. - startBounds.offsetTo(0, 0); - } - final Rect endBounds = new Rect(change.getEndAbsBounds()); - startBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y); - endBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y); - startExampleResizeAnimation(leash, startBounds, endBounds); - } - if (change.getParent() != null) { - continue; - } - - if (transition == mPendingEnter - && mListener.mPrimary.token.equals(change.getContainer()) - || mListener.mSecondary.token.equals(change.getContainer())) { - startTransaction.setWindowCrop(leash, change.getStartAbsBounds().width(), - change.getStartAbsBounds().height()); - if (mListener.mPrimary.token.equals(change.getContainer())) { - // Move layer to top since we want it above the oversized home task during - // animation even though home task is on top in hierarchy. - startTransaction.setLayer(leash, info.getChanges().size() + 1); - } - } - boolean isOpening = Transitions.isOpeningType(info.getType()); - if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) { - // fade in - startExampleAnimation(leash, true /* show */); - } else if (!isOpening && (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK)) { - // fade out - if (transition == mPendingDismiss && mDismissFromSnap) { - // Dismissing via snap-to-top/bottom means that the dismissed task is already - // not-visible (usually cropped to oblivion) so immediately set its alpha to 0 - // and don't animate it so it doesn't pop-in when reparented. - startTransaction.setAlpha(leash, 0.f); - } else { - startExampleAnimation(leash, false /* show */); - } - } - } - if (transition == mPendingEnter) { - // If entering, check if we should enter into minimized or normal split - boolean homeIsVisible = false; - for (int i = info.getChanges().size() - 1; i >= 0; --i) { - final TransitionInfo.Change change = info.getChanges().get(i); - if (change.getTaskInfo() == null - || change.getTaskInfo().getActivityType() != ACTIVITY_TYPE_HOME) { - continue; - } - homeIsVisible = change.getMode() == TRANSIT_OPEN - || change.getMode() == TRANSIT_TO_FRONT - || change.getMode() == TRANSIT_CHANGE; - break; - } - mSplitScreen.finishEnterSplitTransition(homeIsVisible); - } - startTransaction.apply(); - onFinish(); - return true; - } - - @ExternalThread - void dismissSplit(LegacySplitScreenTaskListener tiles, LegacySplitDisplayLayout layout, - boolean dismissOrMaximize, boolean snapped) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); - WindowManagerProxy.buildDismissSplit(wct, tiles, layout, dismissOrMaximize); - mTransitions.getMainExecutor().execute(() -> { - mDismissFromSnap = snapped; - mPendingDismiss = mTransitions.startTransition(TRANSIT_SPLIT_DISMISS_SNAP, wct, this); - }); - } - - private void onFinish() { - if (!mAnimations.isEmpty()) return; - mFinishTransaction.apply(); - mTransactionPool.release(mFinishTransaction); - mFinishTransaction = null; - mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); - mFinishCallback = null; - if (mAnimatingTransition == mPendingEnter) { - mPendingEnter = null; - } - if (mAnimatingTransition == mPendingDismiss) { - mSplitScreen.onDismissSplit(); - mPendingDismiss = null; - } - mDismissFromSnap = false; - mAnimatingTransition = null; - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/MinimizedDockShadow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/MinimizedDockShadow.java deleted file mode 100644 index 1e9223cbe3e2..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/MinimizedDockShadow.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.legacysplitscreen; - -import android.annotation.Nullable; -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.LinearGradient; -import android.graphics.Paint; -import android.graphics.Shader; -import android.util.AttributeSet; -import android.view.View; -import android.view.WindowManager; - -import com.android.wm.shell.R; - -/** - * Shadow for the minimized dock state on homescreen. - */ -public class MinimizedDockShadow extends View { - - private final Paint mShadowPaint = new Paint(); - - private int mDockSide = WindowManager.DOCKED_INVALID; - - public MinimizedDockShadow(Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - } - - void setDockSide(int dockSide) { - if (dockSide != mDockSide) { - mDockSide = dockSide; - updatePaint(getLeft(), getTop(), getRight(), getBottom()); - invalidate(); - } - } - - private void updatePaint(int left, int top, int right, int bottom) { - int startColor = mContext.getResources().getColor( - R.color.minimize_dock_shadow_start, null); - int endColor = mContext.getResources().getColor( - R.color.minimize_dock_shadow_end, null); - final int middleColor = Color.argb( - (Color.alpha(startColor) + Color.alpha(endColor)) / 2, 0, 0, 0); - final int quarter = Color.argb( - (int) (Color.alpha(startColor) * 0.25f + Color.alpha(endColor) * 0.75f), - 0, 0, 0); - if (mDockSide == WindowManager.DOCKED_TOP) { - mShadowPaint.setShader(new LinearGradient( - 0, 0, 0, bottom - top, - new int[] { startColor, middleColor, quarter, endColor }, - new float[] { 0f, 0.35f, 0.6f, 1f }, Shader.TileMode.CLAMP)); - } else if (mDockSide == WindowManager.DOCKED_LEFT) { - mShadowPaint.setShader(new LinearGradient( - 0, 0, right - left, 0, - new int[] { startColor, middleColor, quarter, endColor }, - new float[] { 0f, 0.35f, 0.6f, 1f }, Shader.TileMode.CLAMP)); - } else if (mDockSide == WindowManager.DOCKED_RIGHT) { - mShadowPaint.setShader(new LinearGradient( - right - left, 0, 0, 0, - new int[] { startColor, middleColor, quarter, endColor }, - new float[] { 0f, 0.35f, 0.6f, 1f }, Shader.TileMode.CLAMP)); - } - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - if (changed) { - updatePaint(left, top, right, bottom); - invalidate(); - } - } - - @Override - protected void onDraw(Canvas canvas) { - canvas.drawRect(0, 0, getWidth(), getHeight(), mShadowPaint); - } - - @Override - public boolean hasOverlappingRendering() { - return false; - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java deleted file mode 100644 index e42e43bbc2be..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java +++ /dev/null @@ -1,386 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.legacysplitscreen; - -import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; -import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; -import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; -import static android.content.res.Configuration.ORIENTATION_UNDEFINED; -import static android.view.Display.DEFAULT_DISPLAY; - -import android.annotation.NonNull; -import android.app.ActivityManager; -import android.app.ActivityTaskManager; -import android.graphics.Rect; -import android.os.RemoteException; -import android.util.Log; -import android.view.Display; -import android.view.SurfaceControl; -import android.view.WindowManagerGlobal; -import android.window.TaskOrganizer; -import android.window.WindowContainerToken; -import android.window.WindowContainerTransaction; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.util.ArrayUtils; -import com.android.wm.shell.common.SyncTransactionQueue; -import com.android.wm.shell.transition.Transitions; - -import java.util.ArrayList; -import java.util.List; - -/** - * Proxy to simplify calls into window manager/activity manager - */ -class WindowManagerProxy { - - private static final String TAG = "WindowManagerProxy"; - private static final int[] HOME_AND_RECENTS = {ACTIVITY_TYPE_HOME, ACTIVITY_TYPE_RECENTS}; - private static final int[] CONTROLLED_ACTIVITY_TYPES = { - ACTIVITY_TYPE_STANDARD, - ACTIVITY_TYPE_HOME, - ACTIVITY_TYPE_RECENTS, - ACTIVITY_TYPE_UNDEFINED - }; - private static final int[] CONTROLLED_WINDOWING_MODES = { - WINDOWING_MODE_FULLSCREEN, - WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, - WINDOWING_MODE_UNDEFINED - }; - - @GuardedBy("mDockedRect") - private final Rect mDockedRect = new Rect(); - - private final Rect mTmpRect1 = new Rect(); - - @GuardedBy("mDockedRect") - private final Rect mTouchableRegion = new Rect(); - - private final SyncTransactionQueue mSyncTransactionQueue; - private final TaskOrganizer mTaskOrganizer; - - WindowManagerProxy(SyncTransactionQueue syncQueue, TaskOrganizer taskOrganizer) { - mSyncTransactionQueue = syncQueue; - mTaskOrganizer = taskOrganizer; - } - - void dismissOrMaximizeDocked(final LegacySplitScreenTaskListener tiles, - LegacySplitDisplayLayout layout, final boolean dismissOrMaximize) { - if (Transitions.ENABLE_SHELL_TRANSITIONS) { - tiles.mSplitScreenController.startDismissSplit(!dismissOrMaximize, true /* snapped */); - } else { - applyDismissSplit(tiles, layout, dismissOrMaximize); - } - } - - public void setResizing(final boolean resizing) { - try { - ActivityTaskManager.getService().setSplitScreenResizing(resizing); - } catch (RemoteException e) { - Log.w(TAG, "Error calling setDockedStackResizing: " + e); - } - } - - /** Sets a touch region */ - public void setTouchRegion(Rect region) { - try { - synchronized (mDockedRect) { - mTouchableRegion.set(region); - } - WindowManagerGlobal.getWindowManagerService().setDockedTaskDividerTouchRegion( - mTouchableRegion); - } catch (RemoteException e) { - Log.w(TAG, "Failed to set touchable region: " + e); - } - } - - void applyResizeSplits(int position, LegacySplitDisplayLayout splitLayout) { - WindowContainerTransaction t = new WindowContainerTransaction(); - splitLayout.resizeSplits(position, t); - applySyncTransaction(t); - } - - boolean getHomeAndRecentsTasks(List<ActivityManager.RunningTaskInfo> out, - WindowContainerToken parent) { - boolean resizable = false; - List<ActivityManager.RunningTaskInfo> rootTasks = parent == null - ? mTaskOrganizer.getRootTasks(Display.DEFAULT_DISPLAY, HOME_AND_RECENTS) - : mTaskOrganizer.getChildTasks(parent, HOME_AND_RECENTS); - for (int i = 0, n = rootTasks.size(); i < n; ++i) { - final ActivityManager.RunningTaskInfo ti = rootTasks.get(i); - out.add(ti); - if (ti.topActivityType == ACTIVITY_TYPE_HOME) { - resizable = ti.isResizeable; - } - } - return resizable; - } - - /** - * Assign a fixed override-bounds to home tasks that reflect their geometry while the primary - * split is minimized. This actually "sticks out" of the secondary split area, but when in - * minimized mode, the secondary split gets a 'negative' crop to expose it. - */ - boolean applyHomeTasksMinimized(LegacySplitDisplayLayout layout, WindowContainerToken parent, - @NonNull WindowContainerTransaction wct) { - // Resize the home/recents stacks to the larger minimized-state size - final Rect homeBounds; - final ArrayList<ActivityManager.RunningTaskInfo> homeStacks = new ArrayList<>(); - boolean isHomeResizable = getHomeAndRecentsTasks(homeStacks, parent); - if (isHomeResizable) { - homeBounds = layout.calcResizableMinimizedHomeStackBounds(); - } else { - // home is not resizable, so lock it to its inherent orientation size. - homeBounds = new Rect(0, 0, 0, 0); - for (int i = homeStacks.size() - 1; i >= 0; --i) { - if (homeStacks.get(i).topActivityType == ACTIVITY_TYPE_HOME) { - final int orient = homeStacks.get(i).configuration.orientation; - final boolean displayLandscape = layout.mDisplayLayout.isLandscape(); - final boolean isLandscape = orient == ORIENTATION_LANDSCAPE - || (orient == ORIENTATION_UNDEFINED && displayLandscape); - homeBounds.right = isLandscape == displayLandscape - ? layout.mDisplayLayout.width() : layout.mDisplayLayout.height(); - homeBounds.bottom = isLandscape == displayLandscape - ? layout.mDisplayLayout.height() : layout.mDisplayLayout.width(); - break; - } - } - } - for (int i = homeStacks.size() - 1; i >= 0; --i) { - // For non-resizable homes, the minimized size is actually the fullscreen-size. As a - // result, we don't minimize for recents since it only shows half-size screenshots. - if (!isHomeResizable) { - if (homeStacks.get(i).topActivityType == ACTIVITY_TYPE_RECENTS) { - continue; - } - wct.setWindowingMode(homeStacks.get(i).token, WINDOWING_MODE_FULLSCREEN); - } - wct.setBounds(homeStacks.get(i).token, homeBounds); - } - layout.mTiles.mHomeBounds.set(homeBounds); - return isHomeResizable; - } - - /** @see #buildEnterSplit */ - boolean applyEnterSplit(LegacySplitScreenTaskListener tiles, LegacySplitDisplayLayout layout) { - // Set launchtile first so that any stack created after - // getAllRootTaskInfos and before reparent (even if unlikely) are placed - // correctly. - WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.setLaunchRoot(tiles.mSecondary.token, CONTROLLED_WINDOWING_MODES, - CONTROLLED_ACTIVITY_TYPES); - final boolean isHomeResizable = buildEnterSplit(wct, tiles, layout); - applySyncTransaction(wct); - return isHomeResizable; - } - - /** - * Finishes entering split-screen by reparenting all FULLSCREEN tasks into the secondary split. - * This assumes there is already something in the primary split since that is usually what - * triggers a call to this. In the same transaction, this overrides the home task bounds via - * {@link #applyHomeTasksMinimized}. - * - * @return whether the home stack is resizable - */ - boolean buildEnterSplit(WindowContainerTransaction outWct, LegacySplitScreenTaskListener tiles, - LegacySplitDisplayLayout layout) { - List<ActivityManager.RunningTaskInfo> rootTasks = - mTaskOrganizer.getRootTasks(DEFAULT_DISPLAY, null /* activityTypes */); - if (rootTasks.isEmpty()) { - return false; - } - ActivityManager.RunningTaskInfo topHomeTask = null; - for (int i = rootTasks.size() - 1; i >= 0; --i) { - final ActivityManager.RunningTaskInfo rootTask = rootTasks.get(i); - // Check whether the task can be moved to split secondary. - if (!rootTask.supportsMultiWindow && rootTask.topActivityType != ACTIVITY_TYPE_HOME) { - continue; - } - // Only move split controlling tasks to split secondary. - final int windowingMode = rootTask.getWindowingMode(); - if (!ArrayUtils.contains(CONTROLLED_WINDOWING_MODES, windowingMode) - || !ArrayUtils.contains(CONTROLLED_ACTIVITY_TYPES, rootTask.getActivityType()) - // Excludes split screen secondary due to it's the root we're reparenting to. - || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) { - continue; - } - // Since this iterates from bottom to top, update topHomeTask for every fullscreen task - // so it will be left with the status of the top one. - topHomeTask = isHomeOrRecentTask(rootTask) ? rootTask : null; - outWct.reparent(rootTask.token, tiles.mSecondary.token, true /* onTop */); - } - // Move the secondary split-forward. - outWct.reorder(tiles.mSecondary.token, true /* onTop */); - boolean isHomeResizable = applyHomeTasksMinimized(layout, null /* parent */, - outWct); - if (topHomeTask != null && !Transitions.ENABLE_SHELL_TRANSITIONS) { - // Translate/update-crop of secondary out-of-band with sync transaction -- Until BALST - // is enabled, this temporarily syncs the home surface position with offset until - // sync transaction finishes. - outWct.setBoundsChangeTransaction(topHomeTask.token, tiles.mHomeBounds); - } - return isHomeResizable; - } - - static boolean isHomeOrRecentTask(ActivityManager.RunningTaskInfo ti) { - final int atype = ti.getActivityType(); - return atype == ACTIVITY_TYPE_HOME || atype == ACTIVITY_TYPE_RECENTS; - } - - /** @see #buildDismissSplit */ - void applyDismissSplit(LegacySplitScreenTaskListener tiles, LegacySplitDisplayLayout layout, - boolean dismissOrMaximize) { - // TODO(task-org): Once task-org is more complete, consider using Appeared/Vanished - // plus specific APIs to clean this up. - final WindowContainerTransaction wct = new WindowContainerTransaction(); - // Set launch root first so that any task created after getChildContainers and - // before reparent (pretty unlikely) are put into fullscreen. - wct.setLaunchRoot(tiles.mSecondary.token, null, null); - buildDismissSplit(wct, tiles, layout, dismissOrMaximize); - applySyncTransaction(wct); - } - - /** - * Reparents all tile members back to their display and resets home task override bounds. - * @param dismissOrMaximize When {@code true} this resolves the split by closing the primary - * split (thus resulting in the top of the secondary split becoming - * fullscreen. {@code false} resolves the other way. - */ - static void buildDismissSplit(WindowContainerTransaction outWct, - LegacySplitScreenTaskListener tiles, LegacySplitDisplayLayout layout, - boolean dismissOrMaximize) { - // TODO(task-org): Once task-org is more complete, consider using Appeared/Vanished - // plus specific APIs to clean this up. - final TaskOrganizer taskOrg = tiles.getTaskOrganizer(); - List<ActivityManager.RunningTaskInfo> primaryChildren = - taskOrg.getChildTasks(tiles.mPrimary.token, null /* activityTypes */); - List<ActivityManager.RunningTaskInfo> secondaryChildren = - taskOrg.getChildTasks(tiles.mSecondary.token, null /* activityTypes */); - // In some cases (eg. non-resizable is launched), system-server will leave split-screen. - // as a result, the above will not capture any tasks; yet, we need to clean-up the - // home task bounds. - List<ActivityManager.RunningTaskInfo> freeHomeAndRecents = - taskOrg.getRootTasks(DEFAULT_DISPLAY, HOME_AND_RECENTS); - // Filter out the root split tasks - freeHomeAndRecents.removeIf(p -> p.token.equals(tiles.mSecondary.token) - || p.token.equals(tiles.mPrimary.token)); - - if (primaryChildren.isEmpty() && secondaryChildren.isEmpty() - && freeHomeAndRecents.isEmpty()) { - return; - } - if (dismissOrMaximize) { - // Dismissing, so move all primary split tasks first - for (int i = primaryChildren.size() - 1; i >= 0; --i) { - outWct.reparent(primaryChildren.get(i).token, null /* parent */, - true /* onTop */); - } - boolean homeOnTop = false; - // Don't need to worry about home tasks because they are already in the "proper" - // order within the secondary split. - for (int i = secondaryChildren.size() - 1; i >= 0; --i) { - final ActivityManager.RunningTaskInfo ti = secondaryChildren.get(i); - outWct.reparent(ti.token, null /* parent */, true /* onTop */); - if (isHomeOrRecentTask(ti)) { - outWct.setBounds(ti.token, null); - outWct.setWindowingMode(ti.token, WINDOWING_MODE_UNDEFINED); - if (i == 0) { - homeOnTop = true; - } - } - } - if (homeOnTop && !Transitions.ENABLE_SHELL_TRANSITIONS) { - // Translate/update-crop of secondary out-of-band with sync transaction -- instead - // play this in sync with new home-app frame because until BALST is enabled this - // shows up on screen before the syncTransaction returns. - // We only have access to the secondary root surface, though, so in order to - // position things properly, we have to take into account the existing negative - // offset/crop of the minimized-home task. - final boolean landscape = layout.mDisplayLayout.isLandscape(); - final int posX = landscape ? layout.mSecondary.left - tiles.mHomeBounds.left - : layout.mSecondary.left; - final int posY = landscape ? layout.mSecondary.top - : layout.mSecondary.top - tiles.mHomeBounds.top; - final SurfaceControl.Transaction sft = new SurfaceControl.Transaction(); - sft.setPosition(tiles.mSecondarySurface, posX, posY); - final Rect crop = new Rect(0, 0, layout.mDisplayLayout.width(), - layout.mDisplayLayout.height()); - crop.offset(-posX, -posY); - sft.setWindowCrop(tiles.mSecondarySurface, crop); - outWct.setBoundsChangeTransaction(tiles.mSecondary.token, sft); - } - } else { - // Maximize, so move non-home secondary split first - for (int i = secondaryChildren.size() - 1; i >= 0; --i) { - if (isHomeOrRecentTask(secondaryChildren.get(i))) { - continue; - } - outWct.reparent(secondaryChildren.get(i).token, null /* parent */, - true /* onTop */); - } - // Find and place home tasks in-between. This simulates the fact that there was - // nothing behind the primary split's tasks. - for (int i = secondaryChildren.size() - 1; i >= 0; --i) { - final ActivityManager.RunningTaskInfo ti = secondaryChildren.get(i); - if (isHomeOrRecentTask(ti)) { - outWct.reparent(ti.token, null /* parent */, true /* onTop */); - // reset bounds and mode too - outWct.setBounds(ti.token, null); - outWct.setWindowingMode(ti.token, WINDOWING_MODE_UNDEFINED); - } - } - for (int i = primaryChildren.size() - 1; i >= 0; --i) { - outWct.reparent(primaryChildren.get(i).token, null /* parent */, - true /* onTop */); - } - } - for (int i = freeHomeAndRecents.size() - 1; i >= 0; --i) { - outWct.setBounds(freeHomeAndRecents.get(i).token, null); - outWct.setWindowingMode(freeHomeAndRecents.get(i).token, WINDOWING_MODE_UNDEFINED); - } - // Reset focusable to true - outWct.setFocusable(tiles.mPrimary.token, true /* focusable */); - } - - /** - * Utility to apply a sync transaction serially with other sync transactions. - * - * @see SyncTransactionQueue#queue - */ - void applySyncTransaction(WindowContainerTransaction wct) { - mSyncTransactionQueue.queue(wct); - } - - /** - * @see SyncTransactionQueue#queueIfWaiting - */ - boolean queueSyncTransactionIfWaiting(WindowContainerTransaction wct) { - return mSyncTransactionQueue.queueIfWaiting(wct); - } - - /** - * @see SyncTransactionQueue#runInSync - */ - void runInSync(SyncTransactionQueue.TransactionRunnable runnable) { - mSyncTransactionQueue.runInSync(runnable); - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java index 179b725ab210..1d8ac2b576e9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java @@ -39,6 +39,7 @@ import android.provider.Settings; import android.util.Slog; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; +import android.window.DisplayAreaInfo; import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; @@ -659,11 +660,11 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, } /** - * Handles rotation based on OnDisplayChangingListener callback + * Handles display change based on OnDisplayChangingListener callback */ @Override - public void onRotateDisplay(int displayId, int fromRotation, int toRotation, - WindowContainerTransaction wct) { + public void onDisplayChange(int displayId, int fromRotation, int toRotation, + DisplayAreaInfo newDisplayAreaInfo, WindowContainerTransaction wct) { if (!isInitialized()) { return; } 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 e624de661737..bd386b5681d8 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 @@ -297,6 +297,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, displayController.addDisplayWindowListener(this); } + public PipTransitionController getTransitionController() { + return mPipTransitionController; + } + public Rect getCurrentOrAnimatingBounds() { PipAnimationController.PipTransitionAnimator animator = mPipAnimationController.getCurrentAnimator(); @@ -458,7 +462,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } // Cancel the existing animator if there is any. - cancelCurrentAnimator(); + // TODO(b/232439933): this is disabled temporarily to unblock b/234502692. + // cancelCurrentAnimator(); // Set the exiting state first so if there is fixed rotation later, the running animation // won't be interrupted by alpha animation for existing PiP. 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 36e712459863..28427a808d90 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 @@ -28,7 +28,6 @@ import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_PIP; import static android.view.WindowManager.transitTypeToString; import static android.window.TransitionInfo.FLAG_IS_DISPLAY; -import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA; import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_BOUNDS; @@ -52,6 +51,7 @@ import android.graphics.Rect; import android.os.IBinder; import android.view.Surface; import android.view.SurfaceControl; +import android.view.WindowManager; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; import android.window.WindowContainerToken; @@ -145,6 +145,11 @@ public class PipTransition extends PipTransitionController { if (destinationBounds != null) { mExitDestinationBounds.set(destinationBounds); } + final PipAnimationController.PipTransitionAnimator animator = + mPipAnimationController.getCurrentAnimator(); + if (animator != null && animator.isRunning()) { + animator.cancel(); + } mExitTransition = mTransitions.startTransition(type, out, this); } @@ -217,13 +222,20 @@ public class PipTransition extends PipTransitionController { } // Entering PIP. - if (isEnteringPip(info, mCurrentPipTaskToken)) { - return startEnterAnimation(info, startTransaction, finishTransaction, finishCallback); + if (isEnteringPip(info)) { + startEnterAnimation(info, startTransaction, finishTransaction, finishCallback); + return true; } // For transition that we don't animate, but contains the PIP leash, we need to update the // PIP surface, otherwise it will be reset after the transition. if (currentPipTaskChange != null) { + // Set the "end" bounds of pip. The default setup uses the start bounds. Since this is + // changing the *finish*Transaction, we need to use the end bounds. This will also + // make sure that the fade-in animation (below) uses the end bounds as well. + if (!currentPipTaskChange.getEndAbsBounds().isEmpty()) { + mPipBoundsState.setBounds(currentPipTaskChange.getEndAbsBounds()); + } updatePipForUnhandledTransition(currentPipTaskChange, startTransaction, finishTransaction); } @@ -245,16 +257,9 @@ public class PipTransition extends PipTransitionController { @Override public WindowContainerTransaction handleRequest(@NonNull IBinder transition, @NonNull TransitionRequestInfo request) { - if (request.getType() == TRANSIT_PIP) { + if (requestHasPipEnter(request)) { WindowContainerTransaction wct = new WindowContainerTransaction(); - if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { - mRequestedEnterTransition = transition; - mRequestedEnterTask = request.getTriggerTask().token; - wct.setActivityWindowingMode(request.getTriggerTask().token, - WINDOWING_MODE_UNDEFINED); - final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds(); - wct.setBounds(request.getTriggerTask().token, destinationBounds); - } + augmentRequest(transition, request, wct); return wct; } else { return null; @@ -262,6 +267,22 @@ public class PipTransition extends PipTransitionController { } @Override + public void augmentRequest(@NonNull IBinder transition, + @NonNull TransitionRequestInfo request, @NonNull WindowContainerTransaction outWCT) { + if (!requestHasPipEnter(request)) { + throw new IllegalStateException("Called PiP augmentRequest when request has no PiP"); + } + if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { + mRequestedEnterTransition = transition; + mRequestedEnterTask = request.getTriggerTask().token; + outWCT.setActivityWindowingMode(request.getTriggerTask().token, + WINDOWING_MODE_UNDEFINED); + final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds(); + outWCT.setBounds(request.getTriggerTask().token, destinationBounds); + } + } + + @Override public boolean handleRotateDisplay(int startRotation, int endRotation, WindowContainerTransaction wct) { if (mRequestedEnterTransition != null && mOneShotAnimationType == ANIM_TYPE_ALPHA) { @@ -315,11 +336,27 @@ public class PipTransition extends PipTransitionController { // (likely a remote like launcher), so don't fire the finish-callback here -- wait until // the exit transition is merged. if ((mExitTransition == null || isAnimatingLocally()) && mFinishCallback != null) { - WindowContainerTransaction wct = new WindowContainerTransaction(); - prepareFinishResizeTransaction(taskInfo, destinationBounds, - direction, wct); - if (tx != null) { - wct.setBoundsChangeTransaction(taskInfo.token, tx); + WindowContainerTransaction wct = null; + if (isOutPipDirection(direction)) { + // Only need to reset surface properties. The server-side operations were already + // done at the start. + if (tx != null) { + mFinishTransaction.merge(tx); + } + } else { + wct = new WindowContainerTransaction(); + if (isInPipDirection(direction)) { + // If we are animating from fullscreen using a bounds animation, then reset the + // activity windowing mode, and set the task bounds to the final bounds + wct.setActivityWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED); + wct.scheduleFinishEnterPip(taskInfo.token, destinationBounds); + wct.setBounds(taskInfo.token, destinationBounds); + } else { + wct.setBounds(taskInfo.token, null /* bounds */); + } + if (tx != null) { + wct.setBoundsChangeTransaction(taskInfo.token, tx); + } } final SurfaceControl leash = mPipOrganizer.getSurfaceControl(); final int displayRotation = taskInfo.getConfiguration().windowConfiguration @@ -559,92 +596,94 @@ public class PipTransition extends PipTransitionController { } /** Whether we should handle the given {@link TransitionInfo} animation as entering PIP. */ - private static boolean isEnteringPip(@NonNull TransitionInfo info, - @Nullable WindowContainerToken currentPipTaskToken) { + private boolean isEnteringPip(@NonNull TransitionInfo info) { for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); - if (change.getTaskInfo() != null - && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED - && !change.getContainer().equals(currentPipTaskToken)) { - // We support TRANSIT_PIP type (from RootWindowContainer) or TRANSIT_OPEN (from apps - // that enter PiP instantly on opening, mostly from CTS/Flicker tests) - if (info.getType() == TRANSIT_PIP || info.getType() == TRANSIT_OPEN) { - return true; - } - // This can happen if the request to enter PIP happens when we are collecting for - // another transition, such as TRANSIT_CHANGE (display rotation). - if (info.getType() == TRANSIT_CHANGE) { - return true; - } + if (isEnteringPip(change, info.getType())) return true; + } + return false; + } - // Please file a bug to handle the unexpected transition type. - throw new IllegalStateException("Entering PIP with unexpected transition type=" - + transitTypeToString(info.getType())); + /** Whether a particular change is a window that is entering pip. */ + @Override + public boolean isEnteringPip(@NonNull TransitionInfo.Change change, + @WindowManager.TransitionType int transitType) { + if (change.getTaskInfo() != null + && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED + && !change.getContainer().equals(mCurrentPipTaskToken)) { + // We support TRANSIT_PIP type (from RootWindowContainer) or TRANSIT_OPEN (from apps + // that enter PiP instantly on opening, mostly from CTS/Flicker tests) + if (transitType == TRANSIT_PIP || transitType == TRANSIT_OPEN) { + return true; + } + // This can happen if the request to enter PIP happens when we are collecting for + // another transition, such as TRANSIT_CHANGE (display rotation). + if (transitType == TRANSIT_CHANGE) { + return true; } + + // Please file a bug to handle the unexpected transition type. + throw new IllegalStateException("Entering PIP with unexpected transition type=" + + transitTypeToString(transitType)); } return false; } - private boolean startEnterAnimation(@NonNull TransitionInfo info, + private void startEnterAnimation(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { - // Search for an Enter PiP transition (along with a show wallpaper one) + // Search for an Enter PiP transition TransitionInfo.Change enterPip = null; - TransitionInfo.Change wallpaper = null; for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); if (change.getTaskInfo() != null && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED) { enterPip = change; - } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) { - wallpaper = change; } } if (enterPip == null) { - return false; - } - // Keep track of the PIP task. - mCurrentPipTaskToken = enterPip.getContainer(); - mHasFadeOut = false; - - if (mFinishCallback != null) { - callFinishCallback(null /* wct */); - mFinishTransaction = null; - throw new RuntimeException("Previous callback not called, aborting entering PIP."); + throw new IllegalStateException("Trying to start PiP animation without a pip" + + "participant"); } - // Show the wallpaper if there is a wallpaper change. - if (wallpaper != null) { - startTransaction.show(wallpaper.getLeash()); - startTransaction.setAlpha(wallpaper.getLeash(), 1.f); - } // Make sure other open changes are visible as entering PIP. Some may be hidden in // Transitions#setupStartState because the transition type is OPEN (such as auto-enter). for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); - if (change == enterPip || change == wallpaper) { - continue; - } + if (change == enterPip) continue; if (isOpeningType(change.getMode())) { final SurfaceControl leash = change.getLeash(); startTransaction.show(leash).setAlpha(leash, 1.f); } } + startEnterAnimation(enterPip, startTransaction, finishTransaction, finishCallback); + } + + @Override + public void startEnterAnimation(@NonNull final TransitionInfo.Change pipChange, + @NonNull final SurfaceControl.Transaction startTransaction, + @NonNull final SurfaceControl.Transaction finishTransaction, + @NonNull final Transitions.TransitionFinishCallback finishCallback) { + if (mFinishCallback != null) { + callFinishCallback(null /* wct */); + mFinishTransaction = null; + throw new RuntimeException("Previous callback not called, aborting entering PIP."); + } + + // Keep track of the PIP task and animation. + mCurrentPipTaskToken = pipChange.getContainer(); + mHasFadeOut = false; mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP); mFinishCallback = finishCallback; mFinishTransaction = finishTransaction; - final int endRotation = mInFixedRotation ? mEndFixedRotation : enterPip.getEndRotation(); - return startEnterAnimation(enterPip.getTaskInfo(), enterPip.getLeash(), - startTransaction, finishTransaction, enterPip.getStartRotation(), - endRotation); - } - private boolean startEnterAnimation(final TaskInfo taskInfo, final SurfaceControl leash, - final SurfaceControl.Transaction startTransaction, - final SurfaceControl.Transaction finishTransaction, - final int startRotation, final int endRotation) { + final ActivityManager.RunningTaskInfo taskInfo = pipChange.getTaskInfo(); + final SurfaceControl leash = pipChange.getLeash(); + final int startRotation = pipChange.getStartRotation(); + final int endRotation = mInFixedRotation ? mEndFixedRotation : pipChange.getEndRotation(); + setBoundsStateForEntry(taskInfo.topActivity, taskInfo.pictureInPictureParams, taskInfo.topActivityInfo); final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds(); @@ -657,7 +696,6 @@ public class PipTransition extends PipTransitionController { computeEnterPipRotatedBounds(rotationDelta, startRotation, endRotation, taskInfo, destinationBounds, sourceHintRect); } - PipAnimationController.PipTransitionAnimator animator; // Set corner radius for entering pip. mSurfaceTransactionHelper .crop(finishTransaction, leash, destinationBounds) @@ -694,7 +732,7 @@ public class PipTransition extends PipTransitionController { null /* callback */, false /* withStartDelay */); } mPipTransitionState.setInSwipePipToHomeTransition(false); - return true; + return; } if (rotationDelta != Surface.ROTATION_0) { @@ -702,6 +740,12 @@ public class PipTransition extends PipTransitionController { tmpTransform.postRotate(rotationDelta); startTransaction.setMatrix(leash, tmpTransform, new float[9]); } + if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { + startTransaction.setAlpha(leash, 0f); + } + startTransaction.apply(); + + PipAnimationController.PipTransitionAnimator animator; if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) { animator = mPipAnimationController.getAnimator(taskInfo, leash, currentBounds, currentBounds, destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP, @@ -712,7 +756,6 @@ public class PipTransition extends PipTransitionController { animator.setColorContentOverlay(mContext); } } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { - startTransaction.setAlpha(leash, 0f); animator = mPipAnimationController.getAnimator(taskInfo, leash, destinationBounds, 0f, 1f); mOneShotAnimationType = ANIM_TYPE_BOUNDS; @@ -720,7 +763,6 @@ public class PipTransition extends PipTransitionController { throw new RuntimeException("Unrecognized animation type: " + mOneShotAnimationType); } - startTransaction.apply(); animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP) .setPipAnimationCallback(mPipAnimationCallback) .setDuration(mEnterExitAnimationDuration); @@ -731,8 +773,6 @@ public class PipTransition extends PipTransitionController { animator.setDestinationBounds(mPipBoundsAlgorithm.getEntryDestinationBounds()); } animator.start(); - - return true; } /** Computes destination bounds in old rotation and updates source hint rect if available. */ @@ -852,27 +892,4 @@ public class PipTransition extends PipTransitionController { mPipMenuController.movePipMenu(null, null, destinationBounds); mPipMenuController.updateMenuBounds(destinationBounds); } - - private void prepareFinishResizeTransaction(TaskInfo taskInfo, Rect destinationBounds, - @PipAnimationController.TransitionDirection int direction, - WindowContainerTransaction wct) { - Rect taskBounds = null; - if (isInPipDirection(direction)) { - // If we are animating from fullscreen using a bounds animation, then reset the - // activity windowing mode set by WM, and set the task bounds to the final bounds - taskBounds = destinationBounds; - wct.setActivityWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED); - wct.scheduleFinishEnterPip(taskInfo.token, destinationBounds); - } else if (isOutPipDirection(direction)) { - // If we are animating to fullscreen, then we need to reset the override bounds - // on the task to ensure that the task "matches" the parent's bounds. - taskBounds = (direction == TRANSITION_DIRECTION_LEAVE_PIP) - ? null : destinationBounds; - wct.setWindowingMode(taskInfo.token, getOutPipWindowingMode()); - // Simply reset the activity mode set prior to the animation running. - wct.setActivityWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED); - } - - wct.setBounds(taskInfo.token, taskBounds); - } } 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 54f46e0c9938..d3f69f6762f9 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 @@ -17,6 +17,7 @@ package com.android.wm.shell.pip; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.view.WindowManager.TRANSIT_PIP; import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_REMOVE_STACK; import static com.android.wm.shell.pip.PipAnimationController.isInPipDirection; @@ -28,10 +29,16 @@ import android.content.ComponentName; import android.content.pm.ActivityInfo; import android.graphics.Rect; import android.os.Handler; +import android.os.IBinder; import android.os.Looper; import android.view.SurfaceControl; +import android.view.WindowManager; +import android.window.TransitionInfo; +import android.window.TransitionRequestInfo; import android.window.WindowContainerTransaction; +import androidx.annotation.NonNull; + import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.transition.Transitions; @@ -206,6 +213,30 @@ public abstract class PipTransitionController implements Transitions.TransitionH return false; } + /** @return whether the transition-request represents a pip-entry. */ + public boolean requestHasPipEnter(@NonNull TransitionRequestInfo request) { + return request.getType() == TRANSIT_PIP; + } + + /** Whether a particular change is a window that is entering pip. */ + public boolean isEnteringPip(@NonNull TransitionInfo.Change change, + @WindowManager.TransitionType int transitType) { + return false; + } + + /** Add PiP-related changes to `outWCT` for the given request. */ + public void augmentRequest(@NonNull IBinder transition, + @NonNull TransitionRequestInfo request, @NonNull WindowContainerTransaction outWCT) { + throw new IllegalStateException("Request isn't entering PiP"); + } + + /** Play a transition animation for entering PiP on a specific PiP change. */ + public void startEnterAnimation(@NonNull final TransitionInfo.Change pipChange, + @NonNull final SurfaceControl.Transaction startTransaction, + @NonNull final SurfaceControl.Transaction finishTransaction, + @NonNull final Transitions.TransitionFinishCallback finishCallback) { + } + /** * Callback interface for PiP transitions (both from and to PiP mode) */ 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 4b1e5f8c0d7c..c3e6d82df781 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 @@ -110,7 +110,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb private PipAppOpsListener mAppOpsListener; private PipMediaController mMediaController; private PipBoundsAlgorithm mPipBoundsAlgorithm; + private PipKeepClearAlgorithm mPipKeepClearAlgorithm; private PipBoundsState mPipBoundsState; + private PipMotionHelper mPipMotionHelper; private PipTouchHandler mTouchHandler; private PipTransitionController mPipTransitionController; private TaskStackListenerImpl mTaskStackListener; @@ -156,7 +158,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb * Handler for display rotation changes. */ private final DisplayChangeController.OnDisplayChangingListener mRotationController = ( - int displayId, int fromRotation, int toRotation, WindowContainerTransaction t) -> { + displayId, fromRotation, toRotation, newDisplayAreaInfo, t) -> { if (mPipTransitionController.handleRotateDisplay(fromRotation, toRotation, t)) { return; } @@ -286,7 +288,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb @Nullable public static Pip create(Context context, DisplayController displayController, PipAppOpsListener pipAppOpsListener, PipBoundsAlgorithm pipBoundsAlgorithm, - PipBoundsState pipBoundsState, PipMediaController pipMediaController, + PipKeepClearAlgorithm pipKeepClearAlgorithm, PipBoundsState pipBoundsState, + PipMotionHelper pipMotionHelper, PipMediaController pipMediaController, PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer, PipTouchHandler pipTouchHandler, PipTransitionController pipTransitionController, WindowManagerShellWrapper windowManagerShellWrapper, @@ -301,7 +304,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb } return new PipController(context, displayController, pipAppOpsListener, pipBoundsAlgorithm, - pipBoundsState, pipMediaController, + pipKeepClearAlgorithm, pipBoundsState, pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTouchHandler, pipTransitionController, windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder, oneHandedController, mainExecutor) @@ -312,7 +315,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb DisplayController displayController, PipAppOpsListener pipAppOpsListener, PipBoundsAlgorithm pipBoundsAlgorithm, + PipKeepClearAlgorithm pipKeepClearAlgorithm, @NonNull PipBoundsState pipBoundsState, + PipMotionHelper pipMotionHelper, PipMediaController pipMediaController, PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer, @@ -335,7 +340,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb mWindowManagerShellWrapper = windowManagerShellWrapper; mDisplayController = displayController; mPipBoundsAlgorithm = pipBoundsAlgorithm; + mPipKeepClearAlgorithm = pipKeepClearAlgorithm; mPipBoundsState = pipBoundsState; + mPipMotionHelper = pipMotionHelper; mPipTaskOrganizer = pipTaskOrganizer; mMainExecutor = mainExecutor; mMediaController = pipMediaController; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithm.java new file mode 100644 index 000000000000..78084fafe197 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithm.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.pip.phone; + +import android.graphics.Rect; +import android.util.ArraySet; + +import com.android.wm.shell.pip.PipBoundsAlgorithm; +import com.android.wm.shell.pip.PipBoundsState; + +import java.util.Set; + +/** + * Calculates the adjusted position that does not occlude keep clear areas. + */ +public class PipKeepClearAlgorithm { + + /** + * Adjusts the current position of PiP to avoid occluding keep clear areas. If the user has + * moved PiP manually, the unmodified current position will be returned instead. + */ + public Rect adjust(PipBoundsState boundsState, PipBoundsAlgorithm boundsAlgorithm) { + if (boundsState.hasUserResizedPip()) { + return boundsState.getBounds(); + } + return adjust(boundsAlgorithm.getEntryDestinationBounds(), + boundsState.getRestrictedKeepClearAreas(), + boundsState.getUnrestrictedKeepClearAreas(), boundsState.getDisplayBounds()); + } + + /** Returns a new {@code Rect} that does not occlude the provided keep clear areas. */ + public Rect adjust(Rect defaultBounds, Set<Rect> restrictedKeepClearAreas, + Set<Rect> unrestrictedKeepClearAreas, Rect displayBounds) { + if (restrictedKeepClearAreas.isEmpty()) { + return defaultBounds; + } + Set<Rect> keepClearAreas = new ArraySet<>(); + if (!restrictedKeepClearAreas.isEmpty()) { + keepClearAreas.addAll(restrictedKeepClearAreas); + } + Rect outBounds = new Rect(defaultBounds); + for (Rect r : keepClearAreas) { + if (Rect.intersects(r, outBounds)) { + if (tryOffsetUp(outBounds, r, displayBounds)) continue; + if (tryOffsetLeft(outBounds, r, displayBounds)) continue; + if (tryOffsetDown(outBounds, r, displayBounds)) continue; + if (tryOffsetRight(outBounds, r, displayBounds)) continue; + } + } + return outBounds; + } + + private boolean tryOffsetLeft(Rect rectToMove, Rect rectToAvoid, Rect displayBounds) { + return tryOffset(rectToMove, rectToAvoid, displayBounds, + rectToAvoid.left - rectToMove.right, 0); + } + + private boolean tryOffsetRight(Rect rectToMove, Rect rectToAvoid, Rect displayBounds) { + return tryOffset(rectToMove, rectToAvoid, displayBounds, + rectToAvoid.right - rectToMove.left, 0); + } + + private boolean tryOffsetUp(Rect rectToMove, Rect rectToAvoid, Rect displayBounds) { + return tryOffset(rectToMove, rectToAvoid, displayBounds, + 0, rectToAvoid.top - rectToMove.bottom); + } + + private boolean tryOffsetDown(Rect rectToMove, Rect rectToAvoid, Rect displayBounds) { + return tryOffset(rectToMove, rectToAvoid, displayBounds, + 0, rectToAvoid.bottom - rectToMove.top); + } + + private boolean tryOffset(Rect rectToMove, Rect rectToAvoid, Rect displayBounds, + int dx, int dy) { + Rect tmp = new Rect(rectToMove); + tmp.offset(dx, dy); + if (!Rect.intersects(rectToAvoid, tmp) && displayBounds.contains(tmp)) { + rectToMove.offsetTo(tmp.left, tmp.top); + return true; + } + return false; + } +} 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 6390c8984dac..1958157fc319 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 @@ -282,7 +282,7 @@ public class PipMenuView extends FrameLayout { && mSplitScreenControllerOptional.get().isTaskInSplitScreen(taskInfo.taskId); mFocusedTaskAllowSplitScreen = isSplitScreen || (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN - && taskInfo.supportsSplitScreenMultiWindow + && taskInfo.supportsMultiWindow && taskInfo.topActivityType != WindowConfiguration.ACTIVITY_TYPE_HOME); } 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 ac7b9033b2b9..a2ff97247189 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 @@ -54,6 +54,7 @@ import com.android.wm.shell.pip.PipAnimationController; import com.android.wm.shell.pip.PipBoundsAlgorithm; import com.android.wm.shell.pip.PipBoundsState; import com.android.wm.shell.pip.PipTaskOrganizer; +import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipUiEventLogger; import com.android.wm.shell.protolog.ShellProtoLogGroup; @@ -250,6 +251,10 @@ public class PipTouchHandler { }); } + public PipTransitionController getTransitionHandler() { + return mPipTaskOrganizer.getTransitionController(); + } + private void reloadResources() { final Resources res = mContext.getResources(); mBottomOffsetBufferPx = res.getDimensionPixelSize(R.dimen.pip_bottom_offset_buffer); 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 c166178e9bbd..0f7a4daf6d08 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 @@ -44,7 +44,7 @@ import com.android.wm.shell.common.annotations.ExternalThread; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.util.GroupedRecentTaskInfo; -import com.android.wm.shell.util.StagedSplitBounds; +import com.android.wm.shell.util.SplitBounds; import java.io.PrintWriter; import java.util.ArrayList; @@ -69,12 +69,12 @@ public class RecentTasksController implements TaskStackListenerCallback, // pair, then mSplitTasks[t1] = t2, and mSplitTasks[t2] = t1) private final SparseIntArray mSplitTasks = new SparseIntArray(); /** - * Maps taskId to {@link StagedSplitBounds} for both taskIDs. + * Maps taskId to {@link SplitBounds} for both taskIDs. * Meaning there will be two taskId integers mapping to the same object. * If there's any ordering to the pairing than we can probably just get away with only one * taskID mapping to it, leaving both for consistency with {@link #mSplitTasks} for now. */ - private final Map<Integer, StagedSplitBounds> mTaskSplitBoundsMap = new HashMap<>(); + private final Map<Integer, SplitBounds> mTaskSplitBoundsMap = new HashMap<>(); /** * Creates {@link RecentTasksController}, returns {@code null} if the feature is not @@ -110,7 +110,7 @@ public class RecentTasksController implements TaskStackListenerCallback, /** * Adds a split pair. This call does not validate the taskIds, only that they are not the same. */ - public void addSplitPair(int taskId1, int taskId2, StagedSplitBounds splitBounds) { + public void addSplitPair(int taskId1, int taskId2, SplitBounds splitBounds) { if (taskId1 == taskId2) { return; } 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 ae5e075c4d3f..2bfa5db502ce 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 @@ -16,7 +16,6 @@ package com.android.wm.shell.splitscreen; -import android.annotation.Nullable; import android.content.Context; import android.view.SurfaceSession; import android.window.WindowContainerToken; @@ -38,10 +37,9 @@ class MainStage extends StageTaskListener { MainStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId, StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue, - SurfaceSession surfaceSession, IconProvider iconProvider, - @Nullable StageTaskUnfoldController stageTaskUnfoldController) { - super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession, iconProvider, - stageTaskUnfoldController); + SurfaceSession surfaceSession, IconProvider iconProvider) { + super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession, + iconProvider); } boolean isActive() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java index d55619f5e5ed..f92a0d3901b9 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 @@ -16,7 +16,6 @@ package com.android.wm.shell.splitscreen; -import android.annotation.Nullable; import android.app.ActivityManager; import android.content.Context; import android.view.SurfaceSession; @@ -38,10 +37,9 @@ class SideStage extends StageTaskListener { SideStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId, StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue, - SurfaceSession surfaceSession, IconProvider iconProvider, - @Nullable StageTaskUnfoldController stageTaskUnfoldController) { - super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession, iconProvider, - stageTaskUnfoldController); + SurfaceSession surfaceSession, IconProvider iconProvider) { + super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession, + iconProvider); } boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java index 448773ae9ea2..29b6311e5041 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java @@ -18,6 +18,7 @@ package com.android.wm.shell.splitscreen; import android.annotation.IntDef; import android.annotation.NonNull; +import android.graphics.Rect; import com.android.wm.shell.common.annotations.ExternalThread; import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; @@ -58,6 +59,7 @@ public interface SplitScreen { interface SplitScreenListener { default void onStagePositionChanged(@StageType int stage, @SplitPosition int position) {} default void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {} + default void onSplitBoundsChanged(Rect rootBounds, Rect mainBounds, Rect sideBounds) {} default void onSplitVisibilityChanged(boolean visible) {} } 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 31b510c38457..ee493668994d 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 @@ -83,11 +83,10 @@ import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; +import java.util.Objects; import java.util.Optional; import java.util.concurrent.Executor; -import javax.inject.Provider; - /** * Class manages split-screen multitasking mode and implements the main interface * {@link SplitScreen}. @@ -108,7 +107,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, static final int EXIT_REASON_ROOT_TASK_VANISHED = 6; static final int EXIT_REASON_SCREEN_LOCKED = 7; static final int EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP = 8; - static final int EXIT_REASON_CHILD_TASK_ENTER_PIP = 9; + public static final int EXIT_REASON_CHILD_TASK_ENTER_PIP = 9; @IntDef(value = { EXIT_REASON_UNKNOWN, EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW, @@ -138,7 +137,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, private final SplitscreenEventLogger mLogger; private final IconProvider mIconProvider; private final Optional<RecentTasksController> mRecentTasksOptional; - private final Provider<Optional<StageTaskUnfoldController>> mUnfoldControllerProvider; private StageCoordinator mStageCoordinator; // Only used for the legacy recents animation from splitscreen to allow the tasks to be animated @@ -152,8 +150,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, DisplayImeController displayImeController, DisplayInsetsController displayInsetsController, Transitions transitions, TransactionPool transactionPool, IconProvider iconProvider, - Optional<RecentTasksController> recentTasks, - Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) { + Optional<RecentTasksController> recentTasks) { mTaskOrganizer = shellTaskOrganizer; mSyncQueue = syncQueue; mContext = context; @@ -164,7 +161,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, mDisplayInsetsController = displayInsetsController; mTransitions = transitions; mTransactionPool = transactionPool; - mUnfoldControllerProvider = unfoldControllerProvider; mLogger = new SplitscreenEventLogger(); mIconProvider = iconProvider; mRecentTasksOptional = recentTasks; @@ -190,7 +186,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue, mTaskOrganizer, mDisplayController, mDisplayImeController, mDisplayInsetsController, mTransitions, mTransactionPool, mLogger, - mIconProvider, mMainExecutor, mRecentTasksOptional, mUnfoldControllerProvider); + mIconProvider, mMainExecutor, mRecentTasksOptional); } } @@ -198,6 +194,10 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, return mStageCoordinator.isSplitScreenVisible(); } + public StageCoordinator getTransitionHandler() { + return mStageCoordinator; + } + @Nullable public ActivityManager.RunningTaskInfo getTaskInfo(@SplitPosition int splitPosition) { if (!isSplitScreenVisible() || splitPosition == SPLIT_POSITION_UNDEFINED) { @@ -222,6 +222,14 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, new WindowContainerTransaction()); } + /** + * Update surfaces of the split screen layout based on the current state + * @param transaction to write the updates to + */ + public void updateSplitScreenSurfaces(SurfaceControl.Transaction transaction) { + mStageCoordinator.updateSurfaces(transaction); + } + private boolean moveToStage(int taskId, @StageType int stageType, @SplitPosition int stagePosition, WindowContainerTransaction wct) { final ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId); @@ -362,11 +370,12 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, if (apps == null || apps.length == 0) { final ActivityManager.RunningTaskInfo pairedTaskInfo = getTaskInfo(SplitLayout.reversePosition(position)); - final ComponentName pairedActivity = - pairedTaskInfo != null ? pairedTaskInfo.baseActivity : null; - final ComponentName intentActivity = - intent.getIntent() != null ? intent.getIntent().getComponent() : null; - if (pairedActivity != null && pairedActivity.equals(intentActivity)) { + final ComponentName pairedActivity = pairedTaskInfo != null + ? pairedTaskInfo.baseIntent.getComponent() : null; + final ComponentName intentActivity = intent.getIntent() != null + ? intent.getIntent().getComponent() : null; + + if (Objects.equals(pairedActivity, intentActivity)) { // Switch split position if dragging the same activity to another side. setSideStagePosition(SplitLayout.reversePosition( mStageCoordinator.getSideStagePosition())); @@ -535,6 +544,17 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } @Override + public void onSplitBoundsChanged(Rect rootBounds, Rect mainBounds, Rect sideBounds) { + for (int i = 0; i < mExecutors.size(); i++) { + final int index = i; + mExecutors.valueAt(index).execute(() -> { + mExecutors.keyAt(index).onSplitBoundsChanged(rootBounds, mainBounds, + sideBounds); + }); + } + } + + @Override public void onSplitVisibilityChanged(boolean visible) { for (int i = 0; i < mExecutors.size(); i++) { final int index = i; 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 cd121ed41fdd..f7057d454df9 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 @@ -21,7 +21,6 @@ import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; -import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM; import static com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER; @@ -58,9 +57,6 @@ import java.util.ArrayList; class SplitScreenTransitions { private static final String TAG = "SplitScreenTransitions"; - /** Flag applied to a transition change to identify it as a divider bar for animation. */ - public static final int FLAG_IS_DIVIDER_BAR = FLAG_FIRST_CUSTOM; - private final TransactionPool mTransactionPool; private final Transitions mTransitions; private final Runnable mOnFinish; @@ -70,8 +66,9 @@ class SplitScreenTransitions { IBinder mPendingRecent = null; private IBinder mAnimatingTransition = null; - private OneShotRemoteHandler mPendingRemoteHandler = null; + OneShotRemoteHandler mPendingRemoteHandler = null; private OneShotRemoteHandler mActiveRemoteHandler = null; + private boolean mEnterTransitionMerged; private final Transitions.TransitionFinishCallback mRemoteFinishCB = this::onFinish; @@ -94,7 +91,8 @@ class SplitScreenTransitions { @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback, - @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot) { + @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot, + @NonNull WindowContainerToken topRoot) { mFinishCallback = finishCallback; mAnimatingTransition = transition; if (mPendingRemoteHandler != null) { @@ -104,12 +102,12 @@ class SplitScreenTransitions { mPendingRemoteHandler = null; return; } - playInternalAnimation(transition, info, startTransaction, mainRoot, sideRoot); + playInternalAnimation(transition, info, startTransaction, mainRoot, sideRoot, topRoot); } private void playInternalAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull WindowContainerToken mainRoot, - @NonNull WindowContainerToken sideRoot) { + @NonNull WindowContainerToken sideRoot, @NonNull WindowContainerToken topRoot) { mFinishTransaction = mTransactionPool.acquire(); // Play some place-holder fade animations @@ -140,7 +138,10 @@ class SplitScreenTransitions { endBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y); startExampleResizeAnimation(leash, startBounds, endBounds); } - if (change.getParent() != null) { + boolean isRootOrSplitSideRoot = change.getParent() == null + || topRoot.equals(change.getParent()); + // For enter or exit, we only want to animate the side roots but not the top-root. + if (!isRootOrSplitSideRoot || topRoot.equals(change.getContainer())) { continue; } @@ -187,27 +188,28 @@ class SplitScreenTransitions { } /** Starts a transition to dismiss split. */ - IBinder startDismissTransition(@Nullable IBinder transition, WindowContainerTransaction wct, + IBinder startDismissTransition(WindowContainerTransaction wct, Transitions.TransitionHandler handler, @SplitScreen.StageType int dismissTop, @SplitScreenController.ExitReason int reason) { final int type = reason == EXIT_REASON_DRAG_DIVIDER ? TRANSIT_SPLIT_DISMISS_SNAP : TRANSIT_SPLIT_DISMISS; - if (transition == null) { - transition = mTransitions.startTransition(type, wct, handler); - } + IBinder transition = mTransitions.startTransition(type, wct, handler); + setDismissTransition(transition, dismissTop, reason); + return transition; + } + + /** Sets a transition to dismiss split. */ + void setDismissTransition(@NonNull IBinder transition, @SplitScreen.StageType int dismissTop, + @SplitScreenController.ExitReason int reason) { mPendingDismiss = new DismissTransition(transition, reason, dismissTop); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition " + " deduced Dismiss due to %s. toTop=%s", exitReasonToString(reason), stageTypeToString(dismissTop)); - return transition; } - IBinder startRecentTransition(@Nullable IBinder transition, WindowContainerTransaction wct, - Transitions.TransitionHandler handler, @Nullable RemoteTransition remoteTransition) { - if (transition == null) { - transition = mTransitions.startTransition(TRANSIT_OPEN, wct, handler); - } + void setRecentTransition(@NonNull IBinder transition, + @Nullable RemoteTransition remoteTransition) { mPendingRecent = transition; if (remoteTransition != null) { @@ -219,7 +221,6 @@ class SplitScreenTransitions { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition " + " deduced Enter recent panel"); - return transition; } void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t, @@ -229,6 +230,18 @@ class SplitScreenTransitions { } } + void onTransitionMerged(@NonNull IBinder transition) { + // Once a pending enter transition got merged, make sure to append the reset of finishing + // operations to the finish transition. + if (transition == mPendingEnter) { + mFinishTransaction = mTransactionPool.acquire(); + mStageCoordinator.finishEnterSplitScreen(mFinishTransaction); + mPendingEnter = null; + mPendingRemoteHandler = null; + mEnterTransitionMerged = true; + } + } + void onFinish(WindowContainerTransaction wct, WindowContainerTransactionCallback wctCB) { if (!mAnimations.isEmpty()) return; if (mAnimatingTransition == mPendingEnter) { @@ -238,18 +251,16 @@ class SplitScreenTransitions { mPendingDismiss = null; } if (mAnimatingTransition == mPendingRecent) { - // If the clean-up wct is null when finishing recent transition, it indicates it's - // returning to home and thus no need to reorder tasks. - final boolean returnToHome = wct == null; - if (returnToHome) { - wct = new WindowContainerTransaction(); + if (!mEnterTransitionMerged) { + if (wct == null) wct = new WindowContainerTransaction(); + mStageCoordinator.onRecentTransitionFinished(wct, mFinishTransaction); } - mStageCoordinator.onRecentTransitionFinished(returnToHome, wct, mFinishTransaction); mPendingRecent = null; } mPendingRemoteHandler = null; mActiveRemoteHandler = null; mAnimatingTransition = null; + mEnterTransitionMerged = false; mOnFinish.run(); if (mFinishTransaction != null) { 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 7ea32a6d8f86..774d6aecf16b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -28,7 +28,9 @@ import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.view.WindowManager.transitTypeToString; +import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM; import static android.window.TransitionInfo.FLAG_IS_DISPLAY; +import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER; import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_ALIGN_CENTER; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; @@ -47,7 +49,6 @@ import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_UNKNOWN; import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonToString; -import static com.android.wm.shell.splitscreen.SplitScreenTransitions.FLAG_IS_DIVIDER_BAR; import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS; import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE; import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN; @@ -83,6 +84,7 @@ import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; import android.view.SurfaceSession; import android.view.WindowManager; +import android.window.DisplayAreaInfo; import android.window.RemoteTransition; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; @@ -109,15 +111,13 @@ import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.splitscreen.SplitScreen.StageType; import com.android.wm.shell.splitscreen.SplitScreenController.ExitReason; import com.android.wm.shell.transition.Transitions; -import com.android.wm.shell.util.StagedSplitBounds; +import com.android.wm.shell.util.SplitBounds; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.Optional; -import javax.inject.Provider; - /** * Coordinates the staging (visibility, sizing, ...) of the split-screen {@link MainStage} and * {@link SideStage} stages. @@ -131,20 +131,21 @@ import javax.inject.Provider; * This rules are mostly implemented in {@link #onStageVisibilityChanged(StageListenerImpl)} and * {@link #onStageHasChildrenChanged(StageListenerImpl).} */ -class StageCoordinator implements SplitLayout.SplitLayoutHandler, +public class StageCoordinator implements SplitLayout.SplitLayoutHandler, DisplayController.OnDisplaysChangedListener, Transitions.TransitionHandler, ShellTaskOrganizer.TaskListener { private static final String TAG = StageCoordinator.class.getSimpleName(); + /** Flag applied to a transition change to identify it as a divider bar for animation. */ + public static final int FLAG_IS_DIVIDER_BAR = FLAG_FIRST_CUSTOM; + private final SurfaceSession mSurfaceSession = new SurfaceSession(); private final MainStage mMainStage; private final StageListenerImpl mMainStageListener = new StageListenerImpl(); - private final StageTaskUnfoldController mMainUnfoldController; private final SideStage mSideStage; private final StageListenerImpl mSideStageListener = new StageListenerImpl(); - private final StageTaskUnfoldController mSideUnfoldController; private final DisplayLayout mDisplayLayout; @SplitPosition private int mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT; @@ -205,8 +206,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, DisplayInsetsController displayInsetsController, Transitions transitions, TransactionPool transactionPool, SplitscreenEventLogger logger, IconProvider iconProvider, ShellExecutor mainExecutor, - Optional<RecentTasksController> recentTasks, - Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) { + Optional<RecentTasksController> recentTasks) { mContext = context; mDisplayId = displayId; mSyncQueue = syncQueue; @@ -214,8 +214,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, mLogger = logger; mMainExecutor = mainExecutor; mRecentTasks = recentTasks; - mMainUnfoldController = unfoldControllerProvider.get().orElse(null); - mSideUnfoldController = unfoldControllerProvider.get().orElse(null); + taskOrganizer.createRootTask(displayId, WINDOWING_MODE_FULLSCREEN, this /* listener */); mMainStage = new MainStage( @@ -225,8 +224,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, mMainStageListener, mSyncQueue, mSurfaceSession, - iconProvider, - mMainUnfoldController); + iconProvider); mSideStage = new SideStage( mContext, mTaskOrganizer, @@ -234,8 +232,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSideStageListener, mSyncQueue, mSurfaceSession, - iconProvider, - mSideUnfoldController); + iconProvider); mDisplayController = displayController; mDisplayImeController = displayImeController; mDisplayInsetsController = displayInsetsController; @@ -258,8 +255,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, DisplayInsetsController displayInsetsController, SplitLayout splitLayout, Transitions transitions, TransactionPool transactionPool, SplitscreenEventLogger logger, ShellExecutor mainExecutor, - Optional<RecentTasksController> recentTasks, - Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) { + Optional<RecentTasksController> recentTasks) { mContext = context; mDisplayId = displayId; mSyncQueue = syncQueue; @@ -273,8 +269,6 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitLayout = splitLayout; mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions, this::onTransitionAnimationComplete, this); - mMainUnfoldController = unfoldControllerProvider.get().orElse(null); - mSideUnfoldController = unfoldControllerProvider.get().orElse(null); mLogger = logger; mMainExecutor = mainExecutor; mRecentTasks = recentTasks; @@ -365,10 +359,15 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, sideOptions = sideOptions != null ? sideOptions : new Bundle(); setSideStagePosition(sidePosition, wct); + if (mMainStage.isActive()) { + mMainStage.evictAllChildren(wct); + mSideStage.evictAllChildren(wct); + } else { + // Build a request WCT that will launch both apps such that task 0 is on the main stage + // while task 1 is on the side stage. + mMainStage.activate(wct, false /* reparent */); + } mSplitLayout.setDivideRatio(splitRatio); - // Build a request WCT that will launch both apps such that task 0 is on the main stage - // while task 1 is on the side stage. - mMainStage.activate(wct, false /* reparent */); updateWindowBounds(mSplitLayout, wct); wct.reorder(mRootTaskInfo.token, true); @@ -626,7 +625,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, onLayoutSizeChanged(mSplitLayout); } else { updateWindowBounds(mSplitLayout, wct); - updateUnfoldBounds(); + sendOnBoundsChanged(); } } } @@ -641,7 +640,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (ENABLE_SHELL_TRANSITIONS) { final WindowContainerTransaction wct = new WindowContainerTransaction(); prepareExitSplitScreen(mTopStageAfterFoldDismiss, wct); - mSplitTransitions.startDismissTransition(null /* transition */, wct, this, + mSplitTransitions.startDismissTransition(wct, this, mTopStageAfterFoldDismiss, EXIT_REASON_DEVICE_FOLDED); } else { exitSplitScreen( @@ -674,8 +673,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, final int dismissTop = mainStageVisible ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE; final WindowContainerTransaction wct = new WindowContainerTransaction(); prepareExitSplitScreen(dismissTop, wct); - mSplitTransitions.startDismissTransition(null /* transition */, wct, this, - dismissTop, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP); + mSplitTransitions.startDismissTransition(wct, this, dismissTop, + EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP); } } } @@ -738,10 +737,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, setDividerVisibility(false, t); }); - // Hide divider and reset its position. - mSplitLayout.resetDividerPosition(); - mSplitLayout.release(); - mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED; + onTransitionAnimationComplete(); Slog.i(TAG, "applyExitSplitScreen, reason = " + exitReasonToString(exitReason)); // Log the exit if (childrenToTop != null) { @@ -859,6 +855,10 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, listener.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition()); listener.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition()); listener.onSplitVisibilityChanged(isSplitScreenVisible()); + if (mSplitLayout != null) { + listener.onSplitBoundsChanged(mSplitLayout.getRootBounds(), getMainStageBounds(), + getSideStageBounds()); + } mSideStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_SIDE); mMainStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_MAIN); } @@ -871,6 +871,14 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } + private void sendOnBoundsChanged() { + if (mSplitLayout == null) return; + for (int i = mListeners.size() - 1; i >= 0; --i) { + mListeners.get(i).onSplitBoundsChanged(mSplitLayout.getRootBounds(), + getMainStageBounds(), getSideStageBounds()); + } + } + private void onStageChildTaskStatusChanged(StageListenerImpl stageListener, int taskId, boolean present, boolean visible) { int stage; @@ -920,7 +928,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, leftTopTaskId = mainStageTopTaskId; rightBottomTaskId = sideStageTopTaskId; } - StagedSplitBounds splitBounds = new StagedSplitBounds(topLeftBounds, bottomRightBounds, + SplitBounds splitBounds = new SplitBounds(topLeftBounds, bottomRightBounds, leftTopTaskId, rightBottomTaskId); if (mainStageTopTaskId != INVALID_TASK_ID && sideStageTopTaskId != INVALID_TASK_ID) { // Update the pair for the top tasks @@ -934,12 +942,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, final SplitScreen.SplitScreenListener l = mListeners.get(i); l.onSplitVisibilityChanged(mDividerVisible); } - - if (mMainUnfoldController != null && mSideUnfoldController != null) { - mMainUnfoldController.onSplitVisibilityChanged(mDividerVisible); - mSideUnfoldController.onSplitVisibilityChanged(mDividerVisible); - updateUnfoldBounds(); - } + sendOnBoundsChanged(); } @Override @@ -960,11 +963,6 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout); } - if (mMainUnfoldController != null && mSideUnfoldController != null) { - mMainUnfoldController.init(); - mSideUnfoldController.init(); - } - onRootTaskAppeared(); } @@ -978,13 +976,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, mRootTaskInfo = taskInfo; if (mSplitLayout != null && mSplitLayout.updateConfiguration(mRootTaskInfo.configuration) - && mMainStage.isActive()) { - // TODO(b/204925795): With Shell transition, We are handling split bounds rotation at - // onRotateDisplay. But still need to handle unfold case. - if (ENABLE_SHELL_TRANSITIONS) { - updateUnfoldBounds(); - return; - } + && mMainStage.isActive() + && !ENABLE_SHELL_TRANSITIONS) { // Clear the divider remote animating flag as the divider will be re-rendered to apply // the new rotation config. mIsDividerRemoteAnimating = false; @@ -1024,8 +1017,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, wct.reparent(mMainStage.mRootTaskInfo.token, mRootTaskInfo.token, true); wct.reparent(mSideStage.mRootTaskInfo.token, mRootTaskInfo.token, true); // Make the stages adjacent to each other so they occlude what's behind them. - wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token, - true /* moveTogether */); + wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token); wct.setLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token); mTaskOrganizer.applyTransaction(wct); } @@ -1202,8 +1194,10 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, final int dismissTop = mainStageToTop ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE; final WindowContainerTransaction wct = new WindowContainerTransaction(); prepareExitSplitScreen(dismissTop, wct); - mSplitTransitions.startDismissTransition( - null /* transition */, wct, this, dismissTop, EXIT_REASON_DRAG_DIVIDER); + if (mRootTaskInfo != null) { + wct.setDoNotPip(mRootTaskInfo.token); + } + mSplitTransitions.startDismissTransition(wct, this, dismissTop, EXIT_REASON_DRAG_DIVIDER); } @Override @@ -1233,7 +1227,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, public void onLayoutSizeChanged(SplitLayout layout) { final WindowContainerTransaction wct = new WindowContainerTransaction(); updateWindowBounds(layout, wct); - updateUnfoldBounds(); + sendOnBoundsChanged(); mSyncQueue.queue(wct); mSyncQueue.runInSync(t -> { setResizingSplits(false /* resizing */); @@ -1244,15 +1238,6 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, mLogger.logResize(mSplitLayout.getDividerPositionAsFraction()); } - private void updateUnfoldBounds() { - if (mMainUnfoldController != null && mSideUnfoldController != null) { - mMainUnfoldController.onLayoutChanged(getMainStageBounds(), getMainStagePosition(), - isLandscape()); - mSideUnfoldController.onLayoutChanged(getSideStageBounds(), getSideStagePosition(), - isLandscape()); - } - } - private boolean isLandscape() { return mSplitLayout.isLandscape(); } @@ -1321,7 +1306,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (displayId != DEFAULT_DISPLAY) { return; } - mDisplayController.addDisplayChangingController(this::onRotateDisplay); + mDisplayController.addDisplayChangingController(this::onDisplayChange); } @Override @@ -1332,16 +1317,24 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, mDisplayLayout.set(mDisplayController.getDisplayLayout(displayId)); } - private void onRotateDisplay(int displayId, int fromRotation, int toRotation, - WindowContainerTransaction wct) { + void updateSurfaces(SurfaceControl.Transaction transaction) { + updateSurfaceBounds(mSplitLayout, transaction, /* applyResizingOffset */ false); + mSplitLayout.update(transaction); + } + + private void onDisplayChange(int displayId, int fromRotation, int toRotation, + @Nullable DisplayAreaInfo newDisplayAreaInfo, WindowContainerTransaction wct) { if (!mMainStage.isActive()) return; // Only do this when shell transition if (!ENABLE_SHELL_TRANSITIONS) return; mDisplayLayout.rotateTo(mContext.getResources(), toRotation); mSplitLayout.rotateTo(toRotation, mDisplayLayout.stableInsets()); + if (newDisplayAreaInfo != null) { + mSplitLayout.updateConfiguration(newDisplayAreaInfo.configuration); + } updateWindowBounds(mSplitLayout, wct); - updateUnfoldBounds(); + sendOnBoundsChanged(); } private void onFoldedStateChanged(boolean folded) { @@ -1392,7 +1385,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, @Nullable TransitionRequestInfo request) { final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask(); if (triggerTask == null) { - if (mMainStage.isActive()) { + if (isSplitActive()) { + // Check if the display is rotating. final TransitionRequestInfo.DisplayChange displayChange = request.getDisplayChange(); if (request.getType() == TRANSIT_CHANGE && displayChange != null @@ -1419,7 +1413,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, mRecentTasks.ifPresent(recentTasks -> recentTasks.removeSplitPair(triggerTask.taskId)); } - if (mMainStage.isActive()) { + if (isSplitActive()) { // Try to handle everything while in split-screen, so return a WCT even if it's empty. ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " split is active so using split" + "Transition to handle request. triggerTask=%d type=%s mainChildren=%d" @@ -1434,7 +1428,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, int dismissTop = getStageType(stage) == STAGE_TYPE_MAIN ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN; prepareExitSplitScreen(dismissTop, out); - mSplitTransitions.startDismissTransition(transition, out, this, dismissTop, + mSplitTransitions.setDismissTransition(transition, dismissTop, EXIT_REASON_APP_FINISHED); } } else if (isOpening && inFullscreen) { @@ -1444,12 +1438,13 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, } else if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) { // Enter overview panel, so start recent transition. - mSplitTransitions.startRecentTransition(transition, out, this, + mSplitTransitions.setRecentTransition(transition, request.getRemoteTransition()); - } else { - // Occluded by the other fullscreen task, so dismiss both. + } else if (mSplitTransitions.mPendingRecent == null) { + // If split-task is not controlled by recents animation + // and occluded by the other fullscreen task, dismiss both. prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, out); - mSplitTransitions.startDismissTransition(transition, out, this, + mSplitTransitions.setDismissTransition(transition, STAGE_TYPE_UNDEFINED, EXIT_REASON_UNKNOWN); } } @@ -1464,6 +1459,33 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, return out; } + /** + * This is used for mixed scenarios. For such scenarios, just make sure to include exiting + * split or entering split when appropriate. + */ + public void addEnterOrExitIfNeeded(@Nullable TransitionRequestInfo request, + @NonNull WindowContainerTransaction outWCT) { + final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask(); + if (triggerTask != null && triggerTask.displayId != mDisplayId) { + // Skip handling task on the other display. + return; + } + final @WindowManager.TransitionType int type = request.getType(); + if (isSplitActive() && !isOpeningType(type) + && (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0)) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " One of the splits became " + + "empty during a mixed transition (one not handled by split)," + + " so make sure split-screen state is cleaned-up. " + + "mainStageCount=%d sideStageCount=%d", mMainStage.getChildCount(), + mSideStage.getChildCount()); + prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, outWCT); + } + } + + public boolean isSplitActive() { + return mMainStage.isActive(); + } + @Override public void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t, IBinder mergeTarget, @@ -1473,15 +1495,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, @Override public void onTransitionMerged(@NonNull IBinder transition) { - // Once the pending enter transition got merged, make sure to bring divider bar visible and - // clear the pending transition from cache to prevent mess-up the following state. - if (transition == mSplitTransitions.mPendingEnter) { - final SurfaceControl.Transaction t = mTransactionPool.acquire(); - finishEnterSplitScreen(t); - mSplitTransitions.mPendingEnter = null; - t.apply(); - mTransactionPool.release(t); - } + mSplitTransitions.onTransitionMerged(transition); } @Override @@ -1552,11 +1566,13 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (!shouldAnimate) return false; mSplitTransitions.playAnimation(transition, info, startTransaction, finishTransaction, - finishCallback, mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token); + finishCallback, mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token, + mRootTaskInfo.token); return true; } - void onTransitionAnimationComplete() { + /** Called to clean-up state and do house-keeping after the animation is done. */ + public void onTransitionAnimationComplete() { // If still playing, let it finish. if (!mMainStage.isActive()) { // Update divider state after animation so that it is still around and positioned @@ -1620,8 +1636,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, return true; } - private boolean startPendingDismissAnimation( - @NonNull SplitScreenTransitions.DismissTransition dismissTransition, + /** Synchronize split-screen state with transition and make appropriate preparations. */ + public void prepareDismissAnimation(@StageType int toStage, @ExitReason int dismissReason, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) { // Make some noise if things aren't totally expected. These states shouldn't effect @@ -1654,7 +1670,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, mRecentTasks.ifPresent(recentTasks -> { // Notify recents if we are exiting in a way that breaks the pair, and disable further // updates to splits in the recents until we enter split again - if (shouldBreakPairedTaskInRecents(dismissTransition.mReason) && mShouldUpdateRecents) { + if (shouldBreakPairedTaskInRecents(dismissReason) && mShouldUpdateRecents) { for (TransitionInfo.Change change : info.getChanges()) { final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); if (taskInfo != null @@ -1671,30 +1687,37 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, // Wait until after animation to update divider // Reset crops so they don't interfere with subsequent launches - t.setWindowCrop(mMainStage.mRootLeash, null); - t.setWindowCrop(mSideStage.mRootLeash, null); + t.setCrop(mMainStage.mRootLeash, null); + t.setCrop(mSideStage.mRootLeash, null); + + if (toStage == STAGE_TYPE_UNDEFINED) { + logExit(dismissReason); + } else { + logExitToStage(dismissReason, toStage == STAGE_TYPE_MAIN); + } + // Hide divider and dim layer on transition finished. + setDividerVisibility(false, finishT); + finishT.hide(mMainStage.mDimLayer); + finishT.hide(mSideStage.mDimLayer); + } + + private boolean startPendingDismissAnimation( + @NonNull SplitScreenTransitions.DismissTransition dismissTransition, + @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, + @NonNull SurfaceControl.Transaction finishT) { + prepareDismissAnimation(dismissTransition.mDismissTop, dismissTransition.mReason, info, + t, finishT); if (dismissTransition.mDismissTop == STAGE_TYPE_UNDEFINED) { - logExit(dismissTransition.mReason); // TODO: Have a proper remote for this. Until then, though, reset state and use the // normal animation stuff (which falls back to the normal launcher remote). + t.hide(mSplitLayout.getDividerLeash()); mSplitLayout.release(t); mSplitTransitions.mPendingDismiss = null; return false; - } else { - logExitToStage(dismissTransition.mReason, - dismissTransition.mDismissTop == STAGE_TYPE_MAIN); } addDividerBarToTransition(info, t, false /* show */); - // We're dismissing split by moving the other one to fullscreen. - // Since we don't have any animations for this yet, just use the internal example - // animations. - - // Hide divider and dim layer on transition finished. - setDividerVisibility(false, finishT); - finishT.hide(mMainStage.mDimLayer); - finishT.hide(mSideStage.mDimLayer); return true; } @@ -1704,26 +1727,26 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, return true; } - void onRecentTransitionFinished(boolean returnToHome, WindowContainerTransaction wct, + void onRecentTransitionFinished(WindowContainerTransaction wct, SurfaceControl.Transaction finishT) { - // Exclude the case that the split screen has been dismissed already. - if (!mMainStage.isActive()) { - // The latest split dismissing transition might be a no-op transition and thus won't - // callback startAnimation, update split visibility here to cover this kind of no-op - // transition case. - setSplitsVisible(false); - return; + // Check if the recent transition is finished by returning to the current split so we can + // restore the divider bar. + for (int i = 0; i < wct.getHierarchyOps().size(); ++i) { + final WindowContainerTransaction.HierarchyOp op = wct.getHierarchyOps().get(i); + final IBinder container = op.getContainer(); + if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop() + && (mMainStage.containsContainer(container) + || mSideStage.containsContainer(container))) { + setDividerVisibility(true, finishT); + return; + } } - if (returnToHome) { - // When returning to home from recent apps, the splitting tasks are already hidden, so - // append the reset of dismissing operations into the clean-up wct. - prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct); - setSplitsVisible(false); - logExit(EXIT_REASON_RETURN_HOME); - } else { - setDividerVisibility(true, finishT); - } + // Dismiss the split screen is it's not returning to split. + prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct); + setSplitsVisible(false); + setDividerVisibility(false, finishT); + logExit(EXIT_REASON_UNKNOWN); } private void addDividerBarToTransition(@NonNull TransitionInfo info, @@ -1870,8 +1893,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, final int stageType = isMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE; final WindowContainerTransaction wct = new WindowContainerTransaction(); prepareExitSplitScreen(stageType, wct); - mSplitTransitions.startDismissTransition(null /* transition */, wct, - StageCoordinator.this, stageType, + mSplitTransitions.startDismissTransition(wct,StageCoordinator.this, stageType, EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW); } } 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 949bf5f55808..23eec96a5d8f 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 @@ -31,6 +31,7 @@ import android.app.ActivityManager; import android.content.Context; import android.graphics.Point; import android.graphics.Rect; +import android.os.IBinder; import android.util.SparseArray; import android.view.SurfaceControl; import android.view.SurfaceSession; @@ -47,6 +48,7 @@ import com.android.wm.shell.common.split.SplitDecorManager; import com.android.wm.shell.splitscreen.SplitScreen.StageType; import java.io.PrintWriter; +import java.util.function.Predicate; /** * Base class that handle common task org. related for split-screen stages. @@ -95,18 +97,14 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { // TODO(b/204308910): Extracts SplitDecorManager related code to common package. private SplitDecorManager mSplitDecorManager; - private final StageTaskUnfoldController mStageTaskUnfoldController; - StageTaskListener(Context context, ShellTaskOrganizer taskOrganizer, int displayId, StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue, - SurfaceSession surfaceSession, IconProvider iconProvider, - @Nullable StageTaskUnfoldController stageTaskUnfoldController) { + SurfaceSession surfaceSession, IconProvider iconProvider) { mContext = context; mCallbacks = callbacks; mSyncQueue = syncQueue; mSurfaceSession = surfaceSession; mIconProvider = iconProvider; - mStageTaskUnfoldController = stageTaskUnfoldController; taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this); } @@ -119,63 +117,53 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { } boolean containsToken(WindowContainerToken token) { - if (token.equals(mRootTaskInfo.token)) { - return true; - } - - for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) { - if (token.equals(mChildrenTaskInfo.valueAt(i).token)) { - return true; - } - } + return contains(t -> t.token.equals(token)); + } - return false; + boolean containsContainer(IBinder binder) { + return contains(t -> t.token.asBinder() == binder); } /** * Returns the top visible child task's id. */ int getTopVisibleChildTaskId() { - for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) { - final ActivityManager.RunningTaskInfo info = mChildrenTaskInfo.valueAt(i); - if (info.isVisible) { - return info.taskId; - } - } - return INVALID_TASK_ID; + final ActivityManager.RunningTaskInfo taskInfo = getChildTaskInfo(t -> t.isVisible); + return taskInfo != null ? taskInfo.taskId : INVALID_TASK_ID; } /** * Returns the top activity uid for the top child task. */ int getTopChildTaskUid() { - for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) { - final ActivityManager.RunningTaskInfo info = mChildrenTaskInfo.valueAt(i); - if (info.topActivityInfo == null) { - continue; - } - return info.topActivityInfo.applicationInfo.uid; - } - return 0; + final ActivityManager.RunningTaskInfo taskInfo = + getChildTaskInfo(t -> t.topActivityInfo != null); + return taskInfo != null ? taskInfo.topActivityInfo.applicationInfo.uid : 0; } /** @return {@code true} if this listener contains the currently focused task. */ boolean isFocused() { - if (mRootTaskInfo == null) { - return false; - } + return contains(t -> t.isFocused); + } - if (mRootTaskInfo.isFocused) { + private boolean contains(Predicate<ActivityManager.RunningTaskInfo> predicate) { + if (mRootTaskInfo != null && predicate.test(mRootTaskInfo)) { return true; } + return getChildTaskInfo(predicate) != null; + } + + @Nullable + private ActivityManager.RunningTaskInfo getChildTaskInfo( + Predicate<ActivityManager.RunningTaskInfo> predicate) { for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) { - if (mChildrenTaskInfo.valueAt(i).isFocused) { - return true; + final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i); + if (predicate.test(taskInfo)) { + return taskInfo; } } - - return false; + return null; } @Override @@ -207,10 +195,6 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo + "\n mRootTaskInfo: " + mRootTaskInfo); } - - if (mStageTaskUnfoldController != null) { - mStageTaskUnfoldController.onTaskAppeared(taskInfo, leash); - } } @Override @@ -266,22 +250,18 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { mChildrenTaskInfo.remove(taskId); mChildrenLeashes.remove(taskId); mCallbacks.onChildTaskStatusChanged(taskId, false /* present */, taskInfo.isVisible); - if (taskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) { - mCallbacks.onChildTaskEnterPip(taskId); - } if (ENABLE_SHELL_TRANSITIONS) { // Status is managed/synchronized by the transition lifecycle. return; } + if (taskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) { + mCallbacks.onChildTaskEnterPip(taskId); + } sendStatusChanged(); } else { throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo + "\n mRootTaskInfo: " + mRootTaskInfo); } - - if (mStageTaskUnfoldController != null) { - mStageTaskUnfoldController.onTaskVanished(taskInfo); - } } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreen.aidl deleted file mode 100644 index 45f6d3c8b154..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreen.aidl +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.stagesplit; - -import android.app.PendingIntent; -import android.content.Intent; -import android.os.Bundle; -import android.os.UserHandle; -import android.view.RemoteAnimationAdapter; -import android.view.RemoteAnimationTarget; -import android.window.RemoteTransition; - -import com.android.wm.shell.stagesplit.ISplitScreenListener; - -/** - * Interface that is exposed to remote callers to manipulate the splitscreen feature. - */ -interface ISplitScreen { - - /** - * Registers a split screen listener. - */ - oneway void registerSplitScreenListener(in ISplitScreenListener listener) = 1; - - /** - * Unregisters a split screen listener. - */ - oneway void unregisterSplitScreenListener(in ISplitScreenListener listener) = 2; - - /** - * Hides the side-stage if it is currently visible. - */ - oneway void setSideStageVisibility(boolean visible) = 3; - - /** - * Removes a task from the side stage. - */ - oneway void removeFromSideStage(int taskId) = 4; - - /** - * Removes the split-screen stages and leaving indicated task to top. Passing INVALID_TASK_ID - * to indicate leaving no top task after leaving split-screen. - */ - oneway void exitSplitScreen(int toTopTaskId) = 5; - - /** - * @param exitSplitScreenOnHide if to exit split-screen if both stages are not visible. - */ - oneway void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) = 6; - - /** - * Starts a task in a stage. - */ - oneway void startTask(int taskId, int stage, int position, in Bundle options) = 7; - - /** - * Starts a shortcut in a stage. - */ - oneway void startShortcut(String packageName, String shortcutId, int stage, int position, - in Bundle options, in UserHandle user) = 8; - - /** - * Starts an activity in a stage. - */ - oneway void startIntent(in PendingIntent intent, in Intent fillInIntent, int stage, - int position, in Bundle options) = 9; - - /** - * Starts tasks simultaneously in one transition. - */ - oneway void startTasks(int mainTaskId, in Bundle mainOptions, int sideTaskId, - in Bundle sideOptions, int sidePosition, in RemoteTransition remoteTransition) = 10; - - /** - * Version of startTasks using legacy transition system. - */ - oneway void startTasksWithLegacyTransition(int mainTaskId, in Bundle mainOptions, - int sideTaskId, in Bundle sideOptions, int sidePosition, - in RemoteAnimationAdapter adapter) = 11; - - /** - * Blocking call that notifies and gets additional split-screen targets when entering - * recents (for example: the dividerBar). - * @param cancel is true if leaving recents back to split (eg. the gesture was cancelled). - * @param appTargets apps that will be re-parented to display area - */ - RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel, - in RemoteAnimationTarget[] appTargets) = 12; -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/MainStage.java deleted file mode 100644 index 83855be91e04..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/MainStage.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.stagesplit; - -import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; - -import android.annotation.Nullable; -import android.graphics.Rect; -import android.view.SurfaceSession; -import android.window.WindowContainerToken; -import android.window.WindowContainerTransaction; - -import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.common.SyncTransactionQueue; - -/** - * Main stage for split-screen mode. When split-screen is active all standard activity types launch - * on the main stage, except for task that are explicitly pinned to the {@link SideStage}. - * @see StageCoordinator - */ -class MainStage extends StageTaskListener { - private static final String TAG = MainStage.class.getSimpleName(); - - private boolean mIsActive = false; - - MainStage(ShellTaskOrganizer taskOrganizer, int displayId, - StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue, - SurfaceSession surfaceSession, - @Nullable StageTaskUnfoldController stageTaskUnfoldController) { - super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession, - stageTaskUnfoldController); - } - - boolean isActive() { - return mIsActive; - } - - void activate(Rect rootBounds, WindowContainerTransaction wct) { - if (mIsActive) return; - - final WindowContainerToken rootToken = mRootTaskInfo.token; - wct.setBounds(rootToken, rootBounds) - .setWindowingMode(rootToken, WINDOWING_MODE_MULTI_WINDOW) - .setLaunchRoot( - rootToken, - CONTROLLED_WINDOWING_MODES, - CONTROLLED_ACTIVITY_TYPES) - .reparentTasks( - null /* currentParent */, - rootToken, - CONTROLLED_WINDOWING_MODES, - CONTROLLED_ACTIVITY_TYPES, - true /* onTop */) - // Moving the root task to top after the child tasks were re-parented , or the root - // task cannot be visible and focused. - .reorder(rootToken, true /* onTop */); - - mIsActive = true; - } - - void deactivate(WindowContainerTransaction wct) { - deactivate(wct, false /* toTop */); - } - - void deactivate(WindowContainerTransaction wct, boolean toTop) { - if (!mIsActive) return; - mIsActive = false; - - if (mRootTaskInfo == null) return; - final WindowContainerToken rootToken = mRootTaskInfo.token; - wct.setLaunchRoot( - rootToken, - null, - null) - .reparentTasks( - rootToken, - null /* newParent */, - CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE, - CONTROLLED_ACTIVITY_TYPES, - toTop) - // We want this re-order to the bottom regardless since we are re-parenting - // all its tasks. - .reorder(rootToken, false /* onTop */); - } - - void updateConfiguration(int windowingMode, Rect bounds, WindowContainerTransaction wct) { - wct.setBounds(mRootTaskInfo.token, bounds) - .setWindowingMode(mRootTaskInfo.token, windowingMode); - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OWNERS deleted file mode 100644 index 264e88f32bff..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OWNERS +++ /dev/null @@ -1,2 +0,0 @@ -# WM shell sub-modules stagesplit owner -chenghsiuchang@google.com diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineManager.java deleted file mode 100644 index 8fbad52c630f..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineManager.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.stagesplit; - -import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; -import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; -import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; -import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; - -import android.annotation.Nullable; -import android.content.Context; -import android.content.res.Configuration; -import android.graphics.PixelFormat; -import android.graphics.Rect; -import android.os.Binder; -import android.view.IWindow; -import android.view.InsetsSource; -import android.view.InsetsState; -import android.view.LayoutInflater; -import android.view.SurfaceControl; -import android.view.SurfaceControlViewHost; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.view.WindowlessWindowManager; -import android.widget.FrameLayout; - -import com.android.wm.shell.R; - -/** - * Handles drawing outline of the bounds of provided root surface. The outline will be drown with - * the consideration of display insets like status bar, navigation bar and display cutout. - */ -class OutlineManager extends WindowlessWindowManager { - private static final String WINDOW_NAME = "SplitOutlineLayer"; - private final Context mContext; - private final Rect mRootBounds = new Rect(); - private final Rect mTempRect = new Rect(); - private final Rect mLastOutlineBounds = new Rect(); - private final InsetsState mInsetsState = new InsetsState(); - private final int mExpandedTaskBarHeight; - private OutlineView mOutlineView; - private SurfaceControlViewHost mViewHost; - private SurfaceControl mHostLeash; - private SurfaceControl mLeash; - - OutlineManager(Context context, Configuration configuration) { - super(configuration, null /* rootSurface */, null /* hostInputToken */); - mContext = context.createWindowContext(context.getDisplay(), TYPE_APPLICATION_OVERLAY, - null /* options */); - mExpandedTaskBarHeight = mContext.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.taskbar_frame_height); - } - - @Override - protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) { - b.setParent(mHostLeash); - } - - void inflate(SurfaceControl rootLeash, Rect rootBounds) { - if (mLeash != null || mViewHost != null) return; - - mHostLeash = rootLeash; - mRootBounds.set(rootBounds); - mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this); - - final FrameLayout rootLayout = (FrameLayout) LayoutInflater.from(mContext) - .inflate(R.layout.split_outline, null); - mOutlineView = rootLayout.findViewById(R.id.split_outline); - - final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( - 0 /* width */, 0 /* height */, TYPE_APPLICATION_OVERLAY, - FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCHABLE, PixelFormat.TRANSLUCENT); - lp.width = mRootBounds.width(); - lp.height = mRootBounds.height(); - lp.token = new Binder(); - lp.setTitle(WINDOW_NAME); - lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY; - // TODO(b/189839391): Set INPUT_FEATURE_NO_INPUT_CHANNEL after WM supports - // TRUSTED_OVERLAY for windowless window without input channel. - mViewHost.setView(rootLayout, lp); - mLeash = getSurfaceControl(mViewHost.getWindowToken()); - - drawOutline(); - } - - void release() { - if (mViewHost != null) { - mViewHost.release(); - mViewHost = null; - } - mRootBounds.setEmpty(); - mLastOutlineBounds.setEmpty(); - mOutlineView = null; - mHostLeash = null; - mLeash = null; - } - - @Nullable - SurfaceControl getOutlineLeash() { - return mLeash; - } - - void setVisibility(boolean visible) { - if (mOutlineView != null) { - mOutlineView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); - } - } - - void setRootBounds(Rect rootBounds) { - if (mViewHost == null || mViewHost.getView() == null) { - return; - } - - if (!mRootBounds.equals(rootBounds)) { - WindowManager.LayoutParams lp = - (WindowManager.LayoutParams) mViewHost.getView().getLayoutParams(); - lp.width = rootBounds.width(); - lp.height = rootBounds.height(); - mViewHost.relayout(lp); - mRootBounds.set(rootBounds); - drawOutline(); - } - } - - void onInsetsChanged(InsetsState insetsState) { - if (!mInsetsState.equals(insetsState)) { - mInsetsState.set(insetsState); - drawOutline(); - } - } - - private void computeOutlineBounds(Rect rootBounds, InsetsState insetsState, Rect outBounds) { - outBounds.set(rootBounds); - final InsetsSource taskBarInsetsSource = - insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR); - // Only insets the divider bar with task bar when it's expanded so that the rounded corners - // will be drawn against task bar. - if (taskBarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) { - outBounds.inset(taskBarInsetsSource.calculateVisibleInsets(outBounds)); - } - - // Offset the coordinate from screen based to surface based. - outBounds.offset(-rootBounds.left, -rootBounds.top); - } - - void drawOutline() { - if (mOutlineView == null) { - return; - } - - computeOutlineBounds(mRootBounds, mInsetsState, mTempRect); - if (mTempRect.equals(mLastOutlineBounds)) { - return; - } - - ViewGroup.MarginLayoutParams lp = - (ViewGroup.MarginLayoutParams) mOutlineView.getLayoutParams(); - lp.leftMargin = mTempRect.left; - lp.topMargin = mTempRect.top; - lp.width = mTempRect.width(); - lp.height = mTempRect.height(); - mOutlineView.setLayoutParams(lp); - mLastOutlineBounds.set(mTempRect); - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineView.java deleted file mode 100644 index 92b1381fc808..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineView.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.stagesplit; - -import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT; -import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT; -import static android.view.RoundedCorner.POSITION_TOP_LEFT; -import static android.view.RoundedCorner.POSITION_TOP_RIGHT; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Path; -import android.util.AttributeSet; -import android.view.RoundedCorner; -import android.view.View; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.internal.R; - -/** View for drawing split outline. */ -public class OutlineView extends View { - private final Paint mPaint = new Paint(); - private final Path mPath = new Path(); - private final float[] mRadii = new float[8]; - - public OutlineView(@NonNull Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - mPaint.setStyle(Paint.Style.STROKE); - mPaint.setStrokeWidth( - getResources().getDimension(R.dimen.accessibility_focus_highlight_stroke_width)); - mPaint.setColor(getResources().getColor(R.color.system_accent1_100, null)); - } - - @Override - protected void onAttachedToWindow() { - // TODO(b/200850654): match the screen corners with the actual display decor. - mRadii[0] = mRadii[1] = getCornerRadius(POSITION_TOP_LEFT); - mRadii[2] = mRadii[3] = getCornerRadius(POSITION_TOP_RIGHT); - mRadii[4] = mRadii[5] = getCornerRadius(POSITION_BOTTOM_RIGHT); - mRadii[6] = mRadii[7] = getCornerRadius(POSITION_BOTTOM_LEFT); - } - - private int getCornerRadius(@RoundedCorner.Position int position) { - final RoundedCorner roundedCorner = getDisplay().getRoundedCorner(position); - return roundedCorner == null ? 0 : roundedCorner.getRadius(); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - if (changed) { - mPath.reset(); - mPath.addRoundRect(0, 0, getWidth(), getHeight(), mRadii, Path.Direction.CW); - } - } - - @Override - protected void onDraw(Canvas canvas) { - canvas.drawPath(mPath, mPaint); - } - - @Override - public boolean hasOverlappingRendering() { - return false; - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SideStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SideStage.java deleted file mode 100644 index 55c4f3aea19a..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SideStage.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.stagesplit; - -import android.annotation.CallSuper; -import android.annotation.Nullable; -import android.app.ActivityManager; -import android.content.Context; -import android.graphics.Rect; -import android.view.InsetsSourceControl; -import android.view.InsetsState; -import android.view.SurfaceControl; -import android.view.SurfaceSession; -import android.window.WindowContainerToken; -import android.window.WindowContainerTransaction; - -import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.common.DisplayInsetsController; -import com.android.wm.shell.common.SyncTransactionQueue; - -/** - * Side stage for split-screen mode. Only tasks that are explicitly pinned to this stage show up - * here. All other task are launch in the {@link MainStage}. - * - * @see StageCoordinator - */ -class SideStage extends StageTaskListener implements - DisplayInsetsController.OnInsetsChangedListener { - private static final String TAG = SideStage.class.getSimpleName(); - private final Context mContext; - private OutlineManager mOutlineManager; - - SideStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId, - StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue, - SurfaceSession surfaceSession, - @Nullable StageTaskUnfoldController stageTaskUnfoldController) { - super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession, - stageTaskUnfoldController); - mContext = context; - } - - void addTask(ActivityManager.RunningTaskInfo task, Rect rootBounds, - WindowContainerTransaction wct) { - final WindowContainerToken rootToken = mRootTaskInfo.token; - wct.setBounds(rootToken, rootBounds) - .reparent(task.token, rootToken, true /* onTop*/) - // Moving the root task to top after the child tasks were reparented , or the root - // task cannot be visible and focused. - .reorder(rootToken, true /* onTop */); - } - - boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) { - // No matter if the root task is empty or not, moving the root to bottom because it no - // longer preserves visible child task. - wct.reorder(mRootTaskInfo.token, false /* onTop */); - if (mChildrenTaskInfo.size() == 0) return false; - wct.reparentTasks( - mRootTaskInfo.token, - null /* newParent */, - CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE, - CONTROLLED_ACTIVITY_TYPES, - toTop); - return true; - } - - boolean removeTask(int taskId, WindowContainerToken newParent, WindowContainerTransaction wct) { - final ActivityManager.RunningTaskInfo task = mChildrenTaskInfo.get(taskId); - if (task == null) return false; - wct.reparent(task.token, newParent, false /* onTop */); - return true; - } - - @Nullable - public SurfaceControl getOutlineLeash() { - return mOutlineManager.getOutlineLeash(); - } - - @Override - @CallSuper - public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { - super.onTaskAppeared(taskInfo, leash); - if (isRootTask(taskInfo)) { - mOutlineManager = new OutlineManager(mContext, taskInfo.configuration); - enableOutline(true); - } - } - - @Override - @CallSuper - public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { - super.onTaskInfoChanged(taskInfo); - if (isRootTask(taskInfo)) { - mOutlineManager.setRootBounds(taskInfo.configuration.windowConfiguration.getBounds()); - } - } - - private boolean isRootTask(ActivityManager.RunningTaskInfo taskInfo) { - return mRootTaskInfo != null && mRootTaskInfo.taskId == taskInfo.taskId; - } - - void enableOutline(boolean enable) { - if (mOutlineManager == null) { - return; - } - - if (enable) { - if (mRootTaskInfo != null) { - mOutlineManager.inflate(mRootLeash, - mRootTaskInfo.configuration.windowConfiguration.getBounds()); - } - } else { - mOutlineManager.release(); - } - } - - void setOutlineVisibility(boolean visible) { - mOutlineManager.setVisibility(visible); - } - - @Override - public void insetsChanged(InsetsState insetsState) { - mOutlineManager.onInsetsChanged(insetsState); - } - - @Override - public void insetsControlChanged(InsetsState insetsState, - InsetsSourceControl[] activeControls) { - insetsChanged(insetsState); - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreen.java deleted file mode 100644 index c5d231262cd2..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreen.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.stagesplit; - -import android.annotation.IntDef; -import android.annotation.NonNull; - -import com.android.wm.shell.common.annotations.ExternalThread; -import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; - -import java.util.concurrent.Executor; - -/** - * Interface to engage split-screen feature. - * TODO: Figure out which of these are actually needed outside of the Shell - */ -@ExternalThread -public interface SplitScreen { - /** - * Stage type isn't specified normally meaning to use what ever the default is. - * E.g. exit split-screen and launch the app in fullscreen. - */ - int STAGE_TYPE_UNDEFINED = -1; - /** - * The main stage type. - * @see MainStage - */ - int STAGE_TYPE_MAIN = 0; - - /** - * The side stage type. - * @see SideStage - */ - int STAGE_TYPE_SIDE = 1; - - @IntDef(prefix = { "STAGE_TYPE_" }, value = { - STAGE_TYPE_UNDEFINED, - STAGE_TYPE_MAIN, - STAGE_TYPE_SIDE - }) - @interface StageType {} - - /** Callback interface for listening to changes in a split-screen stage. */ - interface SplitScreenListener { - default void onStagePositionChanged(@StageType int stage, @SplitPosition int position) {} - default void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {} - default void onSplitVisibilityChanged(boolean visible) {} - } - - /** Registers listener that gets split screen callback. */ - void registerSplitScreenListener(@NonNull SplitScreenListener listener, - @NonNull Executor executor); - - /** Unregisters listener that gets split screen callback. */ - void unregisterSplitScreenListener(@NonNull SplitScreenListener listener); - - /** - * Returns a binder that can be passed to an external process to manipulate SplitScreen. - */ - default ISplitScreen createExternalInterface() { - return null; - } - - /** - * Called when the keyguard occluded state changes. - * @param occluded Indicates if the keyguard is now occluded. - */ - void onKeyguardOccludedChanged(boolean occluded); - - /** - * Called when the visibility of the keyguard changes. - * @param showing Indicates if the keyguard is now visible. - */ - void onKeyguardVisibilityChanged(boolean showing); - - /** Get a string representation of a stage type */ - static String stageTypeToString(@StageType int stage) { - switch (stage) { - case STAGE_TYPE_UNDEFINED: return "UNDEFINED"; - case STAGE_TYPE_MAIN: return "MAIN"; - case STAGE_TYPE_SIDE: return "SIDE"; - default: return "UNKNOWN(" + stage + ")"; - } - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenController.java deleted file mode 100644 index 07174051a344..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenController.java +++ /dev/null @@ -1,595 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.stagesplit; - -import static android.view.Display.DEFAULT_DISPLAY; -import static android.view.RemoteAnimationTarget.MODE_OPENING; - -import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; -import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; -import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; -import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; - -import android.app.ActivityManager; -import android.app.ActivityTaskManager; -import android.app.PendingIntent; -import android.content.ActivityNotFoundException; -import android.content.Context; -import android.content.Intent; -import android.content.pm.LauncherApps; -import android.graphics.Rect; -import android.os.Bundle; -import android.os.IBinder; -import android.os.RemoteException; -import android.os.UserHandle; -import android.util.ArrayMap; -import android.util.Slog; -import android.view.IRemoteAnimationFinishedCallback; -import android.view.RemoteAnimationAdapter; -import android.view.RemoteAnimationTarget; -import android.view.SurfaceControl; -import android.view.SurfaceSession; -import android.view.WindowManager; -import android.window.RemoteTransition; -import android.window.WindowContainerTransaction; - -import androidx.annotation.BinderThread; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.internal.logging.InstanceId; -import com.android.internal.util.FrameworkStatsLog; -import com.android.wm.shell.RootTaskDisplayAreaOrganizer; -import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.common.DisplayImeController; -import com.android.wm.shell.common.DisplayInsetsController; -import com.android.wm.shell.common.RemoteCallable; -import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.common.SyncTransactionQueue; -import com.android.wm.shell.common.TransactionPool; -import com.android.wm.shell.common.annotations.ExternalThread; -import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; -import com.android.wm.shell.draganddrop.DragAndDropPolicy; -import com.android.wm.shell.transition.LegacyTransitions; -import com.android.wm.shell.transition.Transitions; - -import java.io.PrintWriter; -import java.util.Arrays; -import java.util.Optional; -import java.util.concurrent.Executor; - -import javax.inject.Provider; - -/** - * Class manages split-screen multitasking mode and implements the main interface - * {@link SplitScreen}. - * @see StageCoordinator - */ -// TODO(b/198577848): Implement split screen flicker test to consolidate CUJ of split screen. -public class SplitScreenController implements DragAndDropPolicy.Starter, - RemoteCallable<SplitScreenController> { - private static final String TAG = SplitScreenController.class.getSimpleName(); - - private final ShellTaskOrganizer mTaskOrganizer; - private final SyncTransactionQueue mSyncQueue; - private final Context mContext; - private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer; - private final ShellExecutor mMainExecutor; - private final SplitScreenImpl mImpl = new SplitScreenImpl(); - private final DisplayImeController mDisplayImeController; - private final DisplayInsetsController mDisplayInsetsController; - private final Transitions mTransitions; - private final TransactionPool mTransactionPool; - private final SplitscreenEventLogger mLogger; - private final Provider<Optional<StageTaskUnfoldController>> mUnfoldControllerProvider; - - private StageCoordinator mStageCoordinator; - - public SplitScreenController(ShellTaskOrganizer shellTaskOrganizer, - SyncTransactionQueue syncQueue, Context context, - RootTaskDisplayAreaOrganizer rootTDAOrganizer, - ShellExecutor mainExecutor, DisplayImeController displayImeController, - DisplayInsetsController displayInsetsController, - Transitions transitions, TransactionPool transactionPool, - Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) { - mTaskOrganizer = shellTaskOrganizer; - mSyncQueue = syncQueue; - mContext = context; - mRootTDAOrganizer = rootTDAOrganizer; - mMainExecutor = mainExecutor; - mDisplayImeController = displayImeController; - mDisplayInsetsController = displayInsetsController; - mTransitions = transitions; - mTransactionPool = transactionPool; - mUnfoldControllerProvider = unfoldControllerProvider; - mLogger = new SplitscreenEventLogger(); - } - - public SplitScreen asSplitScreen() { - return mImpl; - } - - @Override - public Context getContext() { - return mContext; - } - - @Override - public ShellExecutor getRemoteCallExecutor() { - return mMainExecutor; - } - - public void onOrganizerRegistered() { - if (mStageCoordinator == null) { - // TODO: Multi-display - mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue, - mRootTDAOrganizer, mTaskOrganizer, mDisplayImeController, - mDisplayInsetsController, mTransitions, mTransactionPool, mLogger, - mUnfoldControllerProvider); - } - } - - public boolean isSplitScreenVisible() { - return mStageCoordinator.isSplitScreenVisible(); - } - - public boolean moveToSideStage(int taskId, @SplitPosition int sideStagePosition) { - final ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId); - if (task == null) { - throw new IllegalArgumentException("Unknown taskId" + taskId); - } - return moveToSideStage(task, sideStagePosition); - } - - public boolean moveToSideStage(ActivityManager.RunningTaskInfo task, - @SplitPosition int sideStagePosition) { - return mStageCoordinator.moveToSideStage(task, sideStagePosition); - } - - public boolean removeFromSideStage(int taskId) { - return mStageCoordinator.removeFromSideStage(taskId); - } - - public void setSideStageOutline(boolean enable) { - mStageCoordinator.setSideStageOutline(enable); - } - - public void setSideStagePosition(@SplitPosition int sideStagePosition) { - mStageCoordinator.setSideStagePosition(sideStagePosition, null /* wct */); - } - - public void setSideStageVisibility(boolean visible) { - mStageCoordinator.setSideStageVisibility(visible); - } - - public void enterSplitScreen(int taskId, boolean leftOrTop) { - moveToSideStage(taskId, - leftOrTop ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT); - } - - public void exitSplitScreen(int toTopTaskId, int exitReason) { - mStageCoordinator.exitSplitScreen(toTopTaskId, exitReason); - } - - public void onKeyguardOccludedChanged(boolean occluded) { - mStageCoordinator.onKeyguardOccludedChanged(occluded); - } - - public void onKeyguardVisibilityChanged(boolean showing) { - mStageCoordinator.onKeyguardVisibilityChanged(showing); - } - - public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) { - mStageCoordinator.exitSplitScreenOnHide(exitSplitScreenOnHide); - } - - public void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) { - mStageCoordinator.getStageBounds(outTopOrLeftBounds, outBottomOrRightBounds); - } - - public void registerSplitScreenListener(SplitScreen.SplitScreenListener listener) { - mStageCoordinator.registerSplitScreenListener(listener); - } - - public void unregisterSplitScreenListener(SplitScreen.SplitScreenListener listener) { - mStageCoordinator.unregisterSplitScreenListener(listener); - } - - public void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) { - options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, - null /* wct */); - - try { - ActivityTaskManager.getService().startActivityFromRecents(taskId, options); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to launch task", e); - } - } - - public void startShortcut(String packageName, String shortcutId, @SplitPosition int position, - @Nullable Bundle options, UserHandle user) { - options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, - null /* wct */); - - try { - LauncherApps launcherApps = - mContext.getSystemService(LauncherApps.class); - launcherApps.startShortcut(packageName, shortcutId, null /* sourceBounds */, - options, user); - } catch (ActivityNotFoundException e) { - Slog.e(TAG, "Failed to launch shortcut", e); - } - } - - public void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position, - @Nullable Bundle options) { - if (!Transitions.ENABLE_SHELL_TRANSITIONS) { - startIntentLegacy(intent, fillInIntent, position, options); - return; - } - mStageCoordinator.startIntent(intent, fillInIntent, STAGE_TYPE_UNDEFINED, position, options, - null /* remote */); - } - - private void startIntentLegacy(PendingIntent intent, Intent fillInIntent, - @SplitPosition int position, @Nullable Bundle options) { - LegacyTransitions.ILegacyTransition transition = new LegacyTransitions.ILegacyTransition() { - @Override - public void onAnimationStart(int transit, RemoteAnimationTarget[] apps, - RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, - IRemoteAnimationFinishedCallback finishedCallback, - SurfaceControl.Transaction t) { - mStageCoordinator.updateSurfaceBounds(null /* layout */, t, - false /* applyResizingOffset */); - - if (apps != null) { - for (int i = 0; i < apps.length; ++i) { - if (apps[i].mode == MODE_OPENING) { - t.show(apps[i].leash); - } - } - } - - t.apply(); - if (finishedCallback != null) { - try { - finishedCallback.onAnimationFinished(); - } catch (RemoteException e) { - Slog.e(TAG, "Error finishing legacy transition: ", e); - } - } - } - }; - WindowContainerTransaction wct = new WindowContainerTransaction(); - options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, wct); - wct.sendPendingIntent(intent, fillInIntent, options); - mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct); - } - - RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel, RemoteAnimationTarget[] apps) { - if (!isSplitScreenVisible()) return null; - final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession()) - .setContainerLayer() - .setName("RecentsAnimationSplitTasks") - .setHidden(false) - .setCallsite("SplitScreenController#onGoingtoRecentsLegacy"); - mRootTDAOrganizer.attachToDisplayArea(DEFAULT_DISPLAY, builder); - SurfaceControl sc = builder.build(); - SurfaceControl.Transaction transaction = new SurfaceControl.Transaction(); - - // Ensure that we order these in the parent in the right z-order as their previous order - Arrays.sort(apps, (a1, a2) -> a1.prefixOrderIndex - a2.prefixOrderIndex); - int layer = 1; - for (RemoteAnimationTarget appTarget : apps) { - transaction.reparent(appTarget.leash, sc); - transaction.setPosition(appTarget.leash, appTarget.screenSpaceBounds.left, - appTarget.screenSpaceBounds.top); - transaction.setLayer(appTarget.leash, layer++); - } - transaction.apply(); - transaction.close(); - return new RemoteAnimationTarget[]{ - mStageCoordinator.getDividerBarLegacyTarget(), - mStageCoordinator.getOutlineLegacyTarget()}; - } - - /** - * Sets drag info to be logged when splitscreen is entered. - */ - public void logOnDroppedToSplit(@SplitPosition int position, InstanceId dragSessionId) { - mStageCoordinator.logOnDroppedToSplit(position, dragSessionId); - } - - public void dump(@NonNull PrintWriter pw, String prefix) { - pw.println(prefix + TAG); - if (mStageCoordinator != null) { - mStageCoordinator.dump(pw, prefix); - } - } - - /** - * The interface for calls from outside the Shell, within the host process. - */ - @ExternalThread - private class SplitScreenImpl implements SplitScreen { - private ISplitScreenImpl mISplitScreen; - private final ArrayMap<SplitScreenListener, Executor> mExecutors = new ArrayMap<>(); - private final SplitScreenListener mListener = new SplitScreenListener() { - @Override - public void onStagePositionChanged(int stage, int position) { - for (int i = 0; i < mExecutors.size(); i++) { - final int index = i; - mExecutors.valueAt(index).execute(() -> { - mExecutors.keyAt(index).onStagePositionChanged(stage, position); - }); - } - } - - @Override - public void onTaskStageChanged(int taskId, int stage, boolean visible) { - for (int i = 0; i < mExecutors.size(); i++) { - final int index = i; - mExecutors.valueAt(index).execute(() -> { - mExecutors.keyAt(index).onTaskStageChanged(taskId, stage, visible); - }); - } - } - - @Override - public void onSplitVisibilityChanged(boolean visible) { - for (int i = 0; i < mExecutors.size(); i++) { - final int index = i; - mExecutors.valueAt(index).execute(() -> { - mExecutors.keyAt(index).onSplitVisibilityChanged(visible); - }); - } - } - }; - - @Override - public ISplitScreen createExternalInterface() { - if (mISplitScreen != null) { - mISplitScreen.invalidate(); - } - mISplitScreen = new ISplitScreenImpl(SplitScreenController.this); - return mISplitScreen; - } - - @Override - public void onKeyguardOccludedChanged(boolean occluded) { - mMainExecutor.execute(() -> { - SplitScreenController.this.onKeyguardOccludedChanged(occluded); - }); - } - - @Override - public void registerSplitScreenListener(SplitScreenListener listener, Executor executor) { - if (mExecutors.containsKey(listener)) return; - - mMainExecutor.execute(() -> { - if (mExecutors.size() == 0) { - SplitScreenController.this.registerSplitScreenListener(mListener); - } - - mExecutors.put(listener, executor); - }); - - executor.execute(() -> { - mStageCoordinator.sendStatusToListener(listener); - }); - } - - @Override - public void unregisterSplitScreenListener(SplitScreenListener listener) { - mMainExecutor.execute(() -> { - mExecutors.remove(listener); - - if (mExecutors.size() == 0) { - SplitScreenController.this.unregisterSplitScreenListener(mListener); - } - }); - } - - @Override - public void onKeyguardVisibilityChanged(boolean showing) { - mMainExecutor.execute(() -> { - SplitScreenController.this.onKeyguardVisibilityChanged(showing); - }); - } - } - - /** - * The interface for calls from outside the host process. - */ - @BinderThread - private static class ISplitScreenImpl extends ISplitScreen.Stub { - private SplitScreenController mController; - private ISplitScreenListener mListener; - private final SplitScreen.SplitScreenListener mSplitScreenListener = - new SplitScreen.SplitScreenListener() { - @Override - public void onStagePositionChanged(int stage, int position) { - try { - if (mListener != null) { - mListener.onStagePositionChanged(stage, position); - } - } catch (RemoteException e) { - Slog.e(TAG, "onStagePositionChanged", e); - } - } - - @Override - public void onTaskStageChanged(int taskId, int stage, boolean visible) { - try { - if (mListener != null) { - mListener.onTaskStageChanged(taskId, stage, visible); - } - } catch (RemoteException e) { - Slog.e(TAG, "onTaskStageChanged", e); - } - } - }; - private final IBinder.DeathRecipient mListenerDeathRecipient = - new IBinder.DeathRecipient() { - @Override - @BinderThread - public void binderDied() { - final SplitScreenController controller = mController; - controller.getRemoteCallExecutor().execute(() -> { - mListener = null; - controller.unregisterSplitScreenListener(mSplitScreenListener); - }); - } - }; - - public ISplitScreenImpl(SplitScreenController controller) { - mController = controller; - } - - /** - * Invalidates this instance, preventing future calls from updating the controller. - */ - void invalidate() { - mController = null; - } - - @Override - public void registerSplitScreenListener(ISplitScreenListener listener) { - executeRemoteCallWithTaskPermission(mController, "registerSplitScreenListener", - (controller) -> { - if (mListener != null) { - mListener.asBinder().unlinkToDeath(mListenerDeathRecipient, - 0 /* flags */); - } - if (listener != null) { - try { - listener.asBinder().linkToDeath(mListenerDeathRecipient, - 0 /* flags */); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to link to death"); - return; - } - } - mListener = listener; - controller.registerSplitScreenListener(mSplitScreenListener); - }); - } - - @Override - public void unregisterSplitScreenListener(ISplitScreenListener listener) { - executeRemoteCallWithTaskPermission(mController, "unregisterSplitScreenListener", - (controller) -> { - if (mListener != null) { - mListener.asBinder().unlinkToDeath(mListenerDeathRecipient, - 0 /* flags */); - } - mListener = null; - controller.unregisterSplitScreenListener(mSplitScreenListener); - }); - } - - @Override - public void exitSplitScreen(int toTopTaskId) { - executeRemoteCallWithTaskPermission(mController, "exitSplitScreen", - (controller) -> { - controller.exitSplitScreen(toTopTaskId, - FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__UNKNOWN_EXIT); - }); - } - - @Override - public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) { - executeRemoteCallWithTaskPermission(mController, "exitSplitScreenOnHide", - (controller) -> { - controller.exitSplitScreenOnHide(exitSplitScreenOnHide); - }); - } - - @Override - public void setSideStageVisibility(boolean visible) { - executeRemoteCallWithTaskPermission(mController, "setSideStageVisibility", - (controller) -> { - controller.setSideStageVisibility(visible); - }); - } - - @Override - public void removeFromSideStage(int taskId) { - executeRemoteCallWithTaskPermission(mController, "removeFromSideStage", - (controller) -> { - controller.removeFromSideStage(taskId); - }); - } - - @Override - public void startTask(int taskId, int stage, int position, @Nullable Bundle options) { - executeRemoteCallWithTaskPermission(mController, "startTask", - (controller) -> { - controller.startTask(taskId, position, options); - }); - } - - @Override - public void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions, - int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition, - RemoteAnimationAdapter adapter) { - executeRemoteCallWithTaskPermission(mController, "startTasks", - (controller) -> controller.mStageCoordinator.startTasksWithLegacyTransition( - mainTaskId, mainOptions, sideTaskId, sideOptions, sidePosition, - adapter)); - } - - @Override - public void startTasks(int mainTaskId, @Nullable Bundle mainOptions, - int sideTaskId, @Nullable Bundle sideOptions, - @SplitPosition int sidePosition, - @Nullable RemoteTransition remoteTransition) { - executeRemoteCallWithTaskPermission(mController, "startTasks", - (controller) -> controller.mStageCoordinator.startTasks(mainTaskId, mainOptions, - sideTaskId, sideOptions, sidePosition, remoteTransition)); - } - - @Override - public void startShortcut(String packageName, String shortcutId, int stage, int position, - @Nullable Bundle options, UserHandle user) { - executeRemoteCallWithTaskPermission(mController, "startShortcut", - (controller) -> { - controller.startShortcut(packageName, shortcutId, position, - options, user); - }); - } - - @Override - public void startIntent(PendingIntent intent, Intent fillInIntent, int stage, int position, - @Nullable Bundle options) { - executeRemoteCallWithTaskPermission(mController, "startIntent", - (controller) -> { - controller.startIntent(intent, fillInIntent, position, options); - }); - } - - @Override - public RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel, - RemoteAnimationTarget[] apps) { - final RemoteAnimationTarget[][] out = new RemoteAnimationTarget[][]{null}; - executeRemoteCallWithTaskPermission(mController, "onGoingToRecentsLegacy", - (controller) -> out[0] = controller.onGoingToRecentsLegacy(cancel, apps), - true /* blocking */); - return out[0]; - } - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java deleted file mode 100644 index 018365420177..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java +++ /dev/null @@ -1,292 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.stagesplit; - -import static android.view.WindowManager.TRANSIT_CHANGE; -import static android.view.WindowManager.TRANSIT_CLOSE; -import static android.view.WindowManager.TRANSIT_OPEN; -import static android.view.WindowManager.TRANSIT_TO_BACK; -import static android.view.WindowManager.TRANSIT_TO_FRONT; -import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM; - -import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS_SNAP; -import static com.android.wm.shell.transition.Transitions.isOpeningType; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.graphics.Rect; -import android.os.IBinder; -import android.view.SurfaceControl; -import android.view.WindowManager; -import android.window.RemoteTransition; -import android.window.TransitionInfo; -import android.window.WindowContainerToken; -import android.window.WindowContainerTransaction; - -import com.android.wm.shell.common.TransactionPool; -import com.android.wm.shell.transition.OneShotRemoteHandler; -import com.android.wm.shell.transition.Transitions; - -import java.util.ArrayList; - -/** Manages transition animations for split-screen. */ -class SplitScreenTransitions { - private static final String TAG = "SplitScreenTransitions"; - - /** Flag applied to a transition change to identify it as a divider bar for animation. */ - public static final int FLAG_IS_DIVIDER_BAR = FLAG_FIRST_CUSTOM; - - private final TransactionPool mTransactionPool; - private final Transitions mTransitions; - private final Runnable mOnFinish; - - IBinder mPendingDismiss = null; - IBinder mPendingEnter = null; - - private IBinder mAnimatingTransition = null; - private OneShotRemoteHandler mRemoteHandler = null; - - private Transitions.TransitionFinishCallback mRemoteFinishCB = (wct, wctCB) -> { - if (wct != null || wctCB != null) { - throw new UnsupportedOperationException("finish transactions not supported yet."); - } - onFinish(); - }; - - /** Keeps track of currently running animations */ - private final ArrayList<Animator> mAnimations = new ArrayList<>(); - - private Transitions.TransitionFinishCallback mFinishCallback = null; - private SurfaceControl.Transaction mFinishTransaction; - - SplitScreenTransitions(@NonNull TransactionPool pool, @NonNull Transitions transitions, - @NonNull Runnable onFinishCallback) { - mTransactionPool = pool; - mTransitions = transitions; - mOnFinish = onFinishCallback; - } - - void playAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, - @NonNull SurfaceControl.Transaction startTransaction, - @NonNull SurfaceControl.Transaction finishTransaction, - @NonNull Transitions.TransitionFinishCallback finishCallback, - @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot) { - mFinishCallback = finishCallback; - mAnimatingTransition = transition; - if (mRemoteHandler != null) { - mRemoteHandler.startAnimation(transition, info, startTransaction, finishTransaction, - mRemoteFinishCB); - mRemoteHandler = null; - return; - } - playInternalAnimation(transition, info, startTransaction, mainRoot, sideRoot); - } - - private void playInternalAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, - @NonNull SurfaceControl.Transaction t, @NonNull WindowContainerToken mainRoot, - @NonNull WindowContainerToken sideRoot) { - mFinishTransaction = mTransactionPool.acquire(); - - // Play some place-holder fade animations - for (int i = info.getChanges().size() - 1; i >= 0; --i) { - final TransitionInfo.Change change = info.getChanges().get(i); - final SurfaceControl leash = change.getLeash(); - final int mode = info.getChanges().get(i).getMode(); - - if (mode == TRANSIT_CHANGE) { - if (change.getParent() != null) { - // This is probably reparented, so we want the parent to be immediately visible - final TransitionInfo.Change parentChange = info.getChange(change.getParent()); - t.show(parentChange.getLeash()); - t.setAlpha(parentChange.getLeash(), 1.f); - // and then animate this layer outside the parent (since, for example, this is - // the home task animating from fullscreen to part-screen). - t.reparent(leash, info.getRootLeash()); - t.setLayer(leash, info.getChanges().size() - i); - // build the finish reparent/reposition - mFinishTransaction.reparent(leash, parentChange.getLeash()); - mFinishTransaction.setPosition(leash, - change.getEndRelOffset().x, change.getEndRelOffset().y); - } - // TODO(shell-transitions): screenshot here - final Rect startBounds = new Rect(change.getStartAbsBounds()); - final Rect endBounds = new Rect(change.getEndAbsBounds()); - startBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y); - endBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y); - startExampleResizeAnimation(leash, startBounds, endBounds); - } - if (change.getParent() != null) { - continue; - } - - if (transition == mPendingEnter && (mainRoot.equals(change.getContainer()) - || sideRoot.equals(change.getContainer()))) { - t.setWindowCrop(leash, change.getStartAbsBounds().width(), - change.getStartAbsBounds().height()); - } - boolean isOpening = isOpeningType(info.getType()); - if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) { - // fade in - startExampleAnimation(leash, true /* show */); - } else if (!isOpening && (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK)) { - // fade out - if (info.getType() == TRANSIT_SPLIT_DISMISS_SNAP) { - // Dismissing via snap-to-top/bottom means that the dismissed task is already - // not-visible (usually cropped to oblivion) so immediately set its alpha to 0 - // and don't animate it so it doesn't pop-in when reparented. - t.setAlpha(leash, 0.f); - } else { - startExampleAnimation(leash, false /* show */); - } - } - } - t.apply(); - onFinish(); - } - - /** Starts a transition to enter split with a remote transition animator. */ - IBinder startEnterTransition(@WindowManager.TransitionType int transitType, - @NonNull WindowContainerTransaction wct, @Nullable RemoteTransition remoteTransition, - @NonNull Transitions.TransitionHandler handler) { - if (remoteTransition != null) { - // Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff) - mRemoteHandler = new OneShotRemoteHandler( - mTransitions.getMainExecutor(), remoteTransition); - } - final IBinder transition = mTransitions.startTransition(transitType, wct, handler); - mPendingEnter = transition; - if (mRemoteHandler != null) { - mRemoteHandler.setTransition(transition); - } - return transition; - } - - /** Starts a transition for dismissing split after dragging the divider to a screen edge */ - IBinder startSnapToDismiss(@NonNull WindowContainerTransaction wct, - @NonNull Transitions.TransitionHandler handler) { - final IBinder transition = mTransitions.startTransition( - TRANSIT_SPLIT_DISMISS_SNAP, wct, handler); - mPendingDismiss = transition; - return transition; - } - - void onFinish() { - if (!mAnimations.isEmpty()) return; - mOnFinish.run(); - if (mFinishTransaction != null) { - mFinishTransaction.apply(); - mTransactionPool.release(mFinishTransaction); - mFinishTransaction = null; - } - mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); - mFinishCallback = null; - if (mAnimatingTransition == mPendingEnter) { - mPendingEnter = null; - } - if (mAnimatingTransition == mPendingDismiss) { - mPendingDismiss = null; - } - mAnimatingTransition = null; - } - - // TODO(shell-transitions): real animations - private void startExampleAnimation(@NonNull SurfaceControl leash, boolean show) { - final float end = show ? 1.f : 0.f; - final float start = 1.f - end; - final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); - final ValueAnimator va = ValueAnimator.ofFloat(start, end); - va.setDuration(500); - va.addUpdateListener(animation -> { - float fraction = animation.getAnimatedFraction(); - transaction.setAlpha(leash, start * (1.f - fraction) + end * fraction); - transaction.apply(); - }); - final Runnable finisher = () -> { - transaction.setAlpha(leash, end); - transaction.apply(); - mTransactionPool.release(transaction); - mTransitions.getMainExecutor().execute(() -> { - mAnimations.remove(va); - onFinish(); - }); - }; - va.addListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { } - - @Override - public void onAnimationEnd(Animator animation) { - finisher.run(); - } - - @Override - public void onAnimationCancel(Animator animation) { - finisher.run(); - } - - @Override - public void onAnimationRepeat(Animator animation) { } - }); - mAnimations.add(va); - mTransitions.getAnimExecutor().execute(va::start); - } - - // TODO(shell-transitions): real animations - private void startExampleResizeAnimation(@NonNull SurfaceControl leash, - @NonNull Rect startBounds, @NonNull Rect endBounds) { - final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); - final ValueAnimator va = ValueAnimator.ofFloat(0.f, 1.f); - va.setDuration(500); - va.addUpdateListener(animation -> { - float fraction = animation.getAnimatedFraction(); - transaction.setWindowCrop(leash, - (int) (startBounds.width() * (1.f - fraction) + endBounds.width() * fraction), - (int) (startBounds.height() * (1.f - fraction) - + endBounds.height() * fraction)); - transaction.setPosition(leash, - startBounds.left * (1.f - fraction) + endBounds.left * fraction, - startBounds.top * (1.f - fraction) + endBounds.top * fraction); - transaction.apply(); - }); - final Runnable finisher = () -> { - transaction.setWindowCrop(leash, 0, 0); - transaction.setPosition(leash, endBounds.left, endBounds.top); - transaction.apply(); - mTransactionPool.release(transaction); - mTransitions.getMainExecutor().execute(() -> { - mAnimations.remove(va); - onFinish(); - }); - }; - va.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - finisher.run(); - } - - @Override - public void onAnimationCancel(Animator animation) { - finisher.run(); - } - }); - mAnimations.add(va); - mTransitions.getAnimExecutor().execute(va::start); - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitscreenEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitscreenEventLogger.java deleted file mode 100644 index e1850396a5c0..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitscreenEventLogger.java +++ /dev/null @@ -1,324 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.stagesplit; - -import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__OVERVIEW; -import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; -import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; - -import com.android.internal.logging.InstanceId; -import com.android.internal.logging.InstanceIdSequence; -import com.android.internal.util.FrameworkStatsLog; -import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; - -/** - * Helper class that to log Drag & Drop UIEvents for a single session, see also go/uievent - */ -public class SplitscreenEventLogger { - - // Used to generate instance ids for this drag if one is not provided - private final InstanceIdSequence mIdSequence; - - // The instance id for the current splitscreen session (from start to end) - private InstanceId mLoggerSessionId; - - // Drag info - private @SplitPosition int mDragEnterPosition; - private InstanceId mDragEnterSessionId; - - // For deduping async events - private int mLastMainStagePosition = -1; - private int mLastMainStageUid = -1; - private int mLastSideStagePosition = -1; - private int mLastSideStageUid = -1; - private float mLastSplitRatio = -1f; - - public SplitscreenEventLogger() { - mIdSequence = new InstanceIdSequence(Integer.MAX_VALUE); - } - - /** - * Return whether a splitscreen session has started. - */ - public boolean hasStartedSession() { - return mLoggerSessionId != null; - } - - /** - * May be called before logEnter() to indicate that the session was started from a drag. - */ - public void enterRequestedByDrag(@SplitPosition int position, InstanceId dragSessionId) { - mDragEnterPosition = position; - mDragEnterSessionId = dragSessionId; - } - - /** - * Logs when the user enters splitscreen. - */ - public void logEnter(float splitRatio, - @SplitPosition int mainStagePosition, int mainStageUid, - @SplitPosition int sideStagePosition, int sideStageUid, - boolean isLandscape) { - mLoggerSessionId = mIdSequence.newInstanceId(); - int enterReason = mDragEnterPosition != SPLIT_POSITION_UNDEFINED - ? getDragEnterReasonFromSplitPosition(mDragEnterPosition, isLandscape) - : SPLITSCREEN_UICHANGED__ENTER_REASON__OVERVIEW; - updateMainStageState(getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape), - mainStageUid); - updateSideStageState(getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape), - sideStageUid); - updateSplitRatioState(splitRatio); - FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED, - FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__ENTER, - enterReason, - 0 /* exitReason */, - splitRatio, - mLastMainStagePosition, - mLastMainStageUid, - mLastSideStagePosition, - mLastSideStageUid, - mDragEnterSessionId != null ? mDragEnterSessionId.getId() : 0, - mLoggerSessionId.getId()); - } - - /** - * Logs when the user exits splitscreen. Only one of the main or side stages should be - * specified to indicate which position was focused as a part of exiting (both can be unset). - */ - public void logExit(int exitReason, @SplitPosition int mainStagePosition, int mainStageUid, - @SplitPosition int sideStagePosition, int sideStageUid, boolean isLandscape) { - if (mLoggerSessionId == null) { - // Ignore changes until we've started logging the session - return; - } - if ((mainStagePosition != SPLIT_POSITION_UNDEFINED - && sideStagePosition != SPLIT_POSITION_UNDEFINED) - || (mainStageUid != 0 && sideStageUid != 0)) { - throw new IllegalArgumentException("Only main or side stage should be set"); - } - FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED, - FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__EXIT, - 0 /* enterReason */, - exitReason, - 0f /* splitRatio */, - getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape), - mainStageUid, - getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape), - sideStageUid, - 0 /* dragInstanceId */, - mLoggerSessionId.getId()); - - // Reset states - mLoggerSessionId = null; - mDragEnterPosition = SPLIT_POSITION_UNDEFINED; - mDragEnterSessionId = null; - mLastMainStagePosition = -1; - mLastMainStageUid = -1; - mLastSideStagePosition = -1; - mLastSideStageUid = -1; - } - - /** - * Logs when an app in the main stage changes. - */ - public void logMainStageAppChange(@SplitPosition int mainStagePosition, int mainStageUid, - boolean isLandscape) { - if (mLoggerSessionId == null) { - // Ignore changes until we've started logging the session - return; - } - if (!updateMainStageState(getMainStagePositionFromSplitPosition(mainStagePosition, - isLandscape), mainStageUid)) { - // Ignore if there are no user perceived changes - return; - } - - FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED, - FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__APP_CHANGE, - 0 /* enterReason */, - 0 /* exitReason */, - 0f /* splitRatio */, - mLastMainStagePosition, - mLastMainStageUid, - 0 /* sideStagePosition */, - 0 /* sideStageUid */, - 0 /* dragInstanceId */, - mLoggerSessionId.getId()); - } - - /** - * Logs when an app in the side stage changes. - */ - public void logSideStageAppChange(@SplitPosition int sideStagePosition, int sideStageUid, - boolean isLandscape) { - if (mLoggerSessionId == null) { - // Ignore changes until we've started logging the session - return; - } - if (!updateSideStageState(getSideStagePositionFromSplitPosition(sideStagePosition, - isLandscape), sideStageUid)) { - // Ignore if there are no user perceived changes - return; - } - - FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED, - FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__APP_CHANGE, - 0 /* enterReason */, - 0 /* exitReason */, - 0f /* splitRatio */, - 0 /* mainStagePosition */, - 0 /* mainStageUid */, - mLastSideStagePosition, - mLastSideStageUid, - 0 /* dragInstanceId */, - mLoggerSessionId.getId()); - } - - /** - * Logs when the splitscreen ratio changes. - */ - public void logResize(float splitRatio) { - if (mLoggerSessionId == null) { - // Ignore changes until we've started logging the session - return; - } - if (splitRatio <= 0f || splitRatio >= 1f) { - // Don't bother reporting resizes that end up dismissing the split, that will be logged - // via the exit event - return; - } - if (!updateSplitRatioState(splitRatio)) { - // Ignore if there are no user perceived changes - return; - } - FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED, - FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__RESIZE, - 0 /* enterReason */, - 0 /* exitReason */, - mLastSplitRatio, - 0 /* mainStagePosition */, 0 /* mainStageUid */, - 0 /* sideStagePosition */, 0 /* sideStageUid */, - 0 /* dragInstanceId */, - mLoggerSessionId.getId()); - } - - /** - * Logs when the apps in splitscreen are swapped. - */ - public void logSwap(@SplitPosition int mainStagePosition, int mainStageUid, - @SplitPosition int sideStagePosition, int sideStageUid, boolean isLandscape) { - if (mLoggerSessionId == null) { - // Ignore changes until we've started logging the session - return; - } - - updateMainStageState(getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape), - mainStageUid); - updateSideStageState(getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape), - sideStageUid); - FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED, - FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__SWAP, - 0 /* enterReason */, - 0 /* exitReason */, - 0f /* splitRatio */, - mLastMainStagePosition, - mLastMainStageUid, - mLastSideStagePosition, - mLastSideStageUid, - 0 /* dragInstanceId */, - mLoggerSessionId.getId()); - } - - private boolean updateMainStageState(int mainStagePosition, int mainStageUid) { - boolean changed = (mLastMainStagePosition != mainStagePosition) - || (mLastMainStageUid != mainStageUid); - if (!changed) { - return false; - } - - mLastMainStagePosition = mainStagePosition; - mLastMainStageUid = mainStageUid; - return true; - } - - private boolean updateSideStageState(int sideStagePosition, int sideStageUid) { - boolean changed = (mLastSideStagePosition != sideStagePosition) - || (mLastSideStageUid != sideStageUid); - if (!changed) { - return false; - } - - mLastSideStagePosition = sideStagePosition; - mLastSideStageUid = sideStageUid; - return true; - } - - private boolean updateSplitRatioState(float splitRatio) { - boolean changed = Float.compare(mLastSplitRatio, splitRatio) != 0; - if (!changed) { - return false; - } - - mLastSplitRatio = splitRatio; - return true; - } - - public int getDragEnterReasonFromSplitPosition(@SplitPosition int position, - boolean isLandscape) { - if (isLandscape) { - return position == SPLIT_POSITION_TOP_OR_LEFT - ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_LEFT - : FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_RIGHT; - } else { - return position == SPLIT_POSITION_TOP_OR_LEFT - ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_TOP - : FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_BOTTOM; - } - } - - private int getMainStagePositionFromSplitPosition(@SplitPosition int position, - boolean isLandscape) { - if (position == SPLIT_POSITION_UNDEFINED) { - return 0; - } - if (isLandscape) { - return position == SPLIT_POSITION_TOP_OR_LEFT - ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__LEFT - : FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__RIGHT; - } else { - return position == SPLIT_POSITION_TOP_OR_LEFT - ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__TOP - : FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__BOTTOM; - } - } - - private int getSideStagePositionFromSplitPosition(@SplitPosition int position, - boolean isLandscape) { - if (position == SPLIT_POSITION_UNDEFINED) { - return 0; - } - if (isLandscape) { - return position == SPLIT_POSITION_TOP_OR_LEFT - ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__LEFT - : FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__RIGHT; - } else { - return position == SPLIT_POSITION_TOP_OR_LEFT - ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__TOP - : FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__BOTTOM; - } - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java deleted file mode 100644 index ac25c7510931..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java +++ /dev/null @@ -1,1333 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.stagesplit; - -import static android.app.ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; -import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; -import static android.view.WindowManager.TRANSIT_OPEN; -import static android.view.WindowManager.TRANSIT_TO_BACK; -import static android.view.WindowManager.TRANSIT_TO_FRONT; -import static android.view.WindowManager.transitTypeToString; - -import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_DOES_NOT_SUPPORT_MULTIWINDOW; -import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED; -import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED; -import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DRAG_DIVIDER; -import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME; -import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED_SHOW_ON_TOP; -import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; -import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; -import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; -import static com.android.wm.shell.stagesplit.SplitScreen.STAGE_TYPE_MAIN; -import static com.android.wm.shell.stagesplit.SplitScreen.STAGE_TYPE_SIDE; -import static com.android.wm.shell.stagesplit.SplitScreen.STAGE_TYPE_UNDEFINED; -import static com.android.wm.shell.stagesplit.SplitScreen.stageTypeToString; -import static com.android.wm.shell.stagesplit.SplitScreenTransitions.FLAG_IS_DIVIDER_BAR; -import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS; -import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS_SNAP; -import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE; -import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN; -import static com.android.wm.shell.transition.Transitions.isClosingType; -import static com.android.wm.shell.transition.Transitions.isOpeningType; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.app.ActivityManager; -import android.app.ActivityOptions; -import android.app.ActivityTaskManager; -import android.app.PendingIntent; -import android.app.WindowConfiguration; -import android.content.Context; -import android.content.Intent; -import android.graphics.Rect; -import android.hardware.devicestate.DeviceStateManager; -import android.os.Bundle; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; -import android.util.Slog; -import android.view.IRemoteAnimationFinishedCallback; -import android.view.IRemoteAnimationRunner; -import android.view.RemoteAnimationAdapter; -import android.view.RemoteAnimationTarget; -import android.view.SurfaceControl; -import android.view.SurfaceSession; -import android.view.WindowManager; -import android.window.DisplayAreaInfo; -import android.window.RemoteTransition; -import android.window.TransitionInfo; -import android.window.TransitionRequestInfo; -import android.window.WindowContainerToken; -import android.window.WindowContainerTransaction; - -import com.android.internal.R; -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.logging.InstanceId; -import com.android.internal.protolog.common.ProtoLog; -import com.android.wm.shell.RootTaskDisplayAreaOrganizer; -import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.common.DisplayImeController; -import com.android.wm.shell.common.DisplayInsetsController; -import com.android.wm.shell.common.SyncTransactionQueue; -import com.android.wm.shell.common.TransactionPool; -import com.android.wm.shell.common.split.SplitLayout; -import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; -import com.android.wm.shell.common.split.SplitWindowManager; -import com.android.wm.shell.protolog.ShellProtoLogGroup; -import com.android.wm.shell.transition.Transitions; - -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import javax.inject.Provider; - -/** - * Coordinates the staging (visibility, sizing, ...) of the split-screen {@link MainStage} and - * {@link SideStage} stages. - * Some high-level rules: - * - The {@link StageCoordinator} is only considered active if the {@link SideStage} contains at - * least one child task. - * - The {@link MainStage} should only have children if the coordinator is active. - * - The {@link SplitLayout} divider is only visible if both the {@link MainStage} - * and {@link SideStage} are visible. - * - The {@link MainStage} configuration is fullscreen when the {@link SideStage} isn't visible. - * This rules are mostly implemented in {@link #onStageVisibilityChanged(StageListenerImpl)} and - * {@link #onStageHasChildrenChanged(StageListenerImpl).} - */ -class StageCoordinator implements SplitLayout.SplitLayoutHandler, - RootTaskDisplayAreaOrganizer.RootTaskDisplayAreaListener, Transitions.TransitionHandler { - - private static final String TAG = StageCoordinator.class.getSimpleName(); - - /** internal value for mDismissTop that represents no dismiss */ - private static final int NO_DISMISS = -2; - - private final SurfaceSession mSurfaceSession = new SurfaceSession(); - - private final MainStage mMainStage; - private final StageListenerImpl mMainStageListener = new StageListenerImpl(); - private final StageTaskUnfoldController mMainUnfoldController; - private final SideStage mSideStage; - private final StageListenerImpl mSideStageListener = new StageListenerImpl(); - private final StageTaskUnfoldController mSideUnfoldController; - @SplitPosition - private int mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT; - - private final int mDisplayId; - private SplitLayout mSplitLayout; - private boolean mDividerVisible; - private final SyncTransactionQueue mSyncQueue; - private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer; - private final ShellTaskOrganizer mTaskOrganizer; - private DisplayAreaInfo mDisplayAreaInfo; - private final Context mContext; - private final List<SplitScreen.SplitScreenListener> mListeners = new ArrayList<>(); - private final DisplayImeController mDisplayImeController; - private final DisplayInsetsController mDisplayInsetsController; - private final SplitScreenTransitions mSplitTransitions; - private final SplitscreenEventLogger mLogger; - private boolean mExitSplitScreenOnHide; - private boolean mKeyguardOccluded; - - // TODO(b/187041611): remove this flag after totally deprecated legacy split - /** Whether the device is supporting legacy split or not. */ - private boolean mUseLegacySplit; - - @SplitScreen.StageType private int mDismissTop = NO_DISMISS; - - /** The target stage to dismiss to when unlock after folded. */ - @SplitScreen.StageType private int mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED; - - private final Runnable mOnTransitionAnimationComplete = () -> { - // If still playing, let it finish. - if (!isSplitScreenVisible()) { - // Update divider state after animation so that it is still around and positioned - // properly for the animation itself. - setDividerVisibility(false); - mSplitLayout.resetDividerPosition(); - } - mDismissTop = NO_DISMISS; - }; - - private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks = - new SplitWindowManager.ParentContainerCallbacks() { - @Override - public void attachToParentSurface(SurfaceControl.Builder b) { - mRootTDAOrganizer.attachToDisplayArea(mDisplayId, b); - } - - @Override - public void onLeashReady(SurfaceControl leash) { - mSyncQueue.runInSync(t -> applyDividerVisibility(t)); - } - }; - - StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue, - RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer, - DisplayImeController displayImeController, - DisplayInsetsController displayInsetsController, Transitions transitions, - TransactionPool transactionPool, SplitscreenEventLogger logger, - Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) { - mContext = context; - mDisplayId = displayId; - mSyncQueue = syncQueue; - mRootTDAOrganizer = rootTDAOrganizer; - mTaskOrganizer = taskOrganizer; - mLogger = logger; - mMainUnfoldController = unfoldControllerProvider.get().orElse(null); - mSideUnfoldController = unfoldControllerProvider.get().orElse(null); - - mMainStage = new MainStage( - mTaskOrganizer, - mDisplayId, - mMainStageListener, - mSyncQueue, - mSurfaceSession, - mMainUnfoldController); - mSideStage = new SideStage( - mContext, - mTaskOrganizer, - mDisplayId, - mSideStageListener, - mSyncQueue, - mSurfaceSession, - mSideUnfoldController); - mDisplayImeController = displayImeController; - mDisplayInsetsController = displayInsetsController; - mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSideStage); - mRootTDAOrganizer.registerListener(displayId, this); - final DeviceStateManager deviceStateManager = - mContext.getSystemService(DeviceStateManager.class); - deviceStateManager.registerCallback(taskOrganizer.getExecutor(), - new DeviceStateManager.FoldStateListener(mContext, this::onFoldedStateChanged)); - mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions, - mOnTransitionAnimationComplete); - transitions.addHandler(this); - } - - @VisibleForTesting - StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue, - RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer, - MainStage mainStage, SideStage sideStage, DisplayImeController displayImeController, - DisplayInsetsController displayInsetsController, SplitLayout splitLayout, - Transitions transitions, TransactionPool transactionPool, - SplitscreenEventLogger logger, - Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) { - mContext = context; - mDisplayId = displayId; - mSyncQueue = syncQueue; - mRootTDAOrganizer = rootTDAOrganizer; - mTaskOrganizer = taskOrganizer; - mMainStage = mainStage; - mSideStage = sideStage; - mDisplayImeController = displayImeController; - mDisplayInsetsController = displayInsetsController; - mRootTDAOrganizer.registerListener(displayId, this); - mSplitLayout = splitLayout; - mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions, - mOnTransitionAnimationComplete); - mMainUnfoldController = unfoldControllerProvider.get().orElse(null); - mSideUnfoldController = unfoldControllerProvider.get().orElse(null); - mLogger = logger; - transitions.addHandler(this); - } - - @VisibleForTesting - SplitScreenTransitions getSplitTransitions() { - return mSplitTransitions; - } - - boolean isSplitScreenVisible() { - return mSideStageListener.mVisible && mMainStageListener.mVisible; - } - - boolean moveToSideStage(ActivityManager.RunningTaskInfo task, - @SplitPosition int sideStagePosition) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); - setSideStagePosition(sideStagePosition, wct); - mMainStage.activate(getMainStageBounds(), wct); - mSideStage.addTask(task, getSideStageBounds(), wct); - mSyncQueue.queue(wct); - mSyncQueue.runInSync( - t -> updateSurfaceBounds(null /* layout */, t, false /* applyResizingOffset */)); - return true; - } - - boolean removeFromSideStage(int taskId) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); - - /** - * {@link MainStage} will be deactivated in {@link #onStageHasChildrenChanged} if the - * {@link SideStage} no longer has children. - */ - final boolean result = mSideStage.removeTask(taskId, - mMainStage.isActive() ? mMainStage.mRootTaskInfo.token : null, - wct); - mTaskOrganizer.applyTransaction(wct); - return result; - } - - void setSideStageOutline(boolean enable) { - mSideStage.enableOutline(enable); - } - - /** Starts 2 tasks in one transition. */ - void startTasks(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId, - @Nullable Bundle sideOptions, @SplitPosition int sidePosition, - @Nullable RemoteTransition remoteTransition) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); - mainOptions = mainOptions != null ? mainOptions : new Bundle(); - sideOptions = sideOptions != null ? sideOptions : new Bundle(); - setSideStagePosition(sidePosition, wct); - - // Build a request WCT that will launch both apps such that task 0 is on the main stage - // while task 1 is on the side stage. - mMainStage.activate(getMainStageBounds(), wct); - mSideStage.setBounds(getSideStageBounds(), wct); - - // Make sure the launch options will put tasks in the corresponding split roots - addActivityOptions(mainOptions, mMainStage); - addActivityOptions(sideOptions, mSideStage); - - // Add task launch requests - wct.startTask(mainTaskId, mainOptions); - wct.startTask(sideTaskId, sideOptions); - - mSplitTransitions.startEnterTransition( - TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this); - } - - /** Starts 2 tasks in one legacy transition. */ - void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions, - int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition, - RemoteAnimationAdapter adapter) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); - // Need to add another wrapper here in shell so that we can inject the divider bar - // and also manage the process elevation via setRunningRemote - IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() { - @Override - public void onAnimationStart(@WindowManager.TransitionOldType int transit, - RemoteAnimationTarget[] apps, - RemoteAnimationTarget[] wallpapers, - RemoteAnimationTarget[] nonApps, - final IRemoteAnimationFinishedCallback finishedCallback) { - RemoteAnimationTarget[] augmentedNonApps = - new RemoteAnimationTarget[nonApps.length + 1]; - for (int i = 0; i < nonApps.length; ++i) { - augmentedNonApps[i] = nonApps[i]; - } - augmentedNonApps[augmentedNonApps.length - 1] = getDividerBarLegacyTarget(); - try { - ActivityTaskManager.getService().setRunningRemoteTransitionDelegate( - adapter.getCallingApplication()); - adapter.getRunner().onAnimationStart(transit, apps, wallpapers, nonApps, - finishedCallback); - } catch (RemoteException e) { - Slog.e(TAG, "Error starting remote animation", e); - } - } - - @Override - public void onAnimationCancelled() { - try { - adapter.getRunner().onAnimationCancelled(); - } catch (RemoteException e) { - Slog.e(TAG, "Error starting remote animation", e); - } - } - }; - RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter( - wrapper, adapter.getDuration(), adapter.getStatusBarTransitionDelay()); - - if (mainOptions == null) { - mainOptions = ActivityOptions.makeRemoteAnimation(wrappedAdapter).toBundle(); - } else { - ActivityOptions mainActivityOptions = ActivityOptions.fromBundle(mainOptions); - mainActivityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter)); - } - - sideOptions = sideOptions != null ? sideOptions : new Bundle(); - setSideStagePosition(sidePosition, wct); - - // Build a request WCT that will launch both apps such that task 0 is on the main stage - // while task 1 is on the side stage. - mMainStage.activate(getMainStageBounds(), wct); - mSideStage.setBounds(getSideStageBounds(), wct); - - // Make sure the launch options will put tasks in the corresponding split roots - addActivityOptions(mainOptions, mMainStage); - addActivityOptions(sideOptions, mSideStage); - - // Add task launch requests - wct.startTask(mainTaskId, mainOptions); - wct.startTask(sideTaskId, sideOptions); - - // Using legacy transitions, so we can't use blast sync since it conflicts. - mTaskOrganizer.applyTransaction(wct); - } - - public void startIntent(PendingIntent intent, Intent fillInIntent, - @SplitScreen.StageType int stage, @SplitPosition int position, - @androidx.annotation.Nullable Bundle options, - @Nullable RemoteTransition remoteTransition) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); - options = resolveStartStage(stage, position, options, wct); - wct.sendPendingIntent(intent, fillInIntent, options); - mSplitTransitions.startEnterTransition( - TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct, remoteTransition, this); - } - - Bundle resolveStartStage(@SplitScreen.StageType int stage, - @SplitPosition int position, @androidx.annotation.Nullable Bundle options, - @androidx.annotation.Nullable WindowContainerTransaction wct) { - switch (stage) { - case STAGE_TYPE_UNDEFINED: { - // Use the stage of the specified position is valid. - if (position != SPLIT_POSITION_UNDEFINED) { - if (position == getSideStagePosition()) { - options = resolveStartStage(STAGE_TYPE_SIDE, position, options, wct); - } else { - options = resolveStartStage(STAGE_TYPE_MAIN, position, options, wct); - } - } else { - // Exit split-screen and launch fullscreen since stage wasn't specified. - prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct); - } - break; - } - case STAGE_TYPE_SIDE: { - if (position != SPLIT_POSITION_UNDEFINED) { - setSideStagePosition(position, wct); - } else { - position = getSideStagePosition(); - } - if (options == null) { - options = new Bundle(); - } - updateActivityOptions(options, position); - break; - } - case STAGE_TYPE_MAIN: { - if (position != SPLIT_POSITION_UNDEFINED) { - // Set the side stage opposite of what we want to the main stage. - final int sideStagePosition = position == SPLIT_POSITION_TOP_OR_LEFT - ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT; - setSideStagePosition(sideStagePosition, wct); - } else { - position = getMainStagePosition(); - } - if (options == null) { - options = new Bundle(); - } - updateActivityOptions(options, position); - break; - } - default: - throw new IllegalArgumentException("Unknown stage=" + stage); - } - - return options; - } - - @SplitPosition - int getSideStagePosition() { - return mSideStagePosition; - } - - @SplitPosition - int getMainStagePosition() { - return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT - ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT; - } - - void setSideStagePosition(@SplitPosition int sideStagePosition, - @Nullable WindowContainerTransaction wct) { - setSideStagePosition(sideStagePosition, true /* updateBounds */, wct); - } - - private void setSideStagePosition(@SplitPosition int sideStagePosition, boolean updateBounds, - @Nullable WindowContainerTransaction wct) { - if (mSideStagePosition == sideStagePosition) return; - mSideStagePosition = sideStagePosition; - sendOnStagePositionChanged(); - - if (mSideStageListener.mVisible && updateBounds) { - if (wct == null) { - // onLayoutSizeChanged builds/applies a wct with the contents of updateWindowBounds. - onLayoutSizeChanged(mSplitLayout); - } else { - updateWindowBounds(mSplitLayout, wct); - updateUnfoldBounds(); - } - } - } - - void setSideStageVisibility(boolean visible) { - if (mSideStageListener.mVisible == visible) return; - - final WindowContainerTransaction wct = new WindowContainerTransaction(); - mSideStage.setVisibility(visible, wct); - mTaskOrganizer.applyTransaction(wct); - } - - void onKeyguardOccludedChanged(boolean occluded) { - // Do not exit split directly, because it needs to wait for task info update to determine - // which task should remain on top after split dismissed. - mKeyguardOccluded = occluded; - } - - void onKeyguardVisibilityChanged(boolean showing) { - if (!showing && mMainStage.isActive() - && mTopStageAfterFoldDismiss != STAGE_TYPE_UNDEFINED) { - exitSplitScreen(mTopStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage, - SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED); - } - } - - void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) { - mExitSplitScreenOnHide = exitSplitScreenOnHide; - } - - void exitSplitScreen(int toTopTaskId, int exitReason) { - StageTaskListener childrenToTop = null; - if (mMainStage.containsTask(toTopTaskId)) { - childrenToTop = mMainStage; - } else if (mSideStage.containsTask(toTopTaskId)) { - childrenToTop = mSideStage; - } - - final WindowContainerTransaction wct = new WindowContainerTransaction(); - if (childrenToTop != null) { - childrenToTop.reorderChild(toTopTaskId, true /* onTop */, wct); - } - applyExitSplitScreen(childrenToTop, wct, exitReason); - } - - private void exitSplitScreen(StageTaskListener childrenToTop, int exitReason) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); - applyExitSplitScreen(childrenToTop, wct, exitReason); - } - - private void applyExitSplitScreen( - StageTaskListener childrenToTop, - WindowContainerTransaction wct, int exitReason) { - mSideStage.removeAllTasks(wct, childrenToTop == mSideStage); - mMainStage.deactivate(wct, childrenToTop == mMainStage); - mTaskOrganizer.applyTransaction(wct); - mSyncQueue.runInSync(t -> t - .setWindowCrop(mMainStage.mRootLeash, null) - .setWindowCrop(mSideStage.mRootLeash, null)); - // Hide divider and reset its position. - setDividerVisibility(false); - mSplitLayout.resetDividerPosition(); - mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED; - if (childrenToTop != null) { - logExitToStage(exitReason, childrenToTop == mMainStage); - } else { - logExit(exitReason); - } - } - - /** - * Unlike exitSplitScreen, this takes a stagetype vs an actual stage-reference and populates - * an existing WindowContainerTransaction (rather than applying immediately). This is intended - * to be used when exiting split might be bundled with other window operations. - */ - void prepareExitSplitScreen(@SplitScreen.StageType int stageToTop, - @NonNull WindowContainerTransaction wct) { - mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE); - mMainStage.deactivate(wct, stageToTop == STAGE_TYPE_MAIN); - } - - void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) { - outTopOrLeftBounds.set(mSplitLayout.getBounds1()); - outBottomOrRightBounds.set(mSplitLayout.getBounds2()); - } - - private void addActivityOptions(Bundle opts, StageTaskListener stage) { - opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, stage.mRootTaskInfo.token); - } - - void updateActivityOptions(Bundle opts, @SplitPosition int position) { - addActivityOptions(opts, position == mSideStagePosition ? mSideStage : mMainStage); - } - - void registerSplitScreenListener(SplitScreen.SplitScreenListener listener) { - if (mListeners.contains(listener)) return; - mListeners.add(listener); - sendStatusToListener(listener); - } - - void unregisterSplitScreenListener(SplitScreen.SplitScreenListener listener) { - mListeners.remove(listener); - } - - void sendStatusToListener(SplitScreen.SplitScreenListener listener) { - listener.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition()); - listener.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition()); - listener.onSplitVisibilityChanged(isSplitScreenVisible()); - mSideStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_SIDE); - mMainStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_MAIN); - } - - private void sendOnStagePositionChanged() { - for (int i = mListeners.size() - 1; i >= 0; --i) { - final SplitScreen.SplitScreenListener l = mListeners.get(i); - l.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition()); - l.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition()); - } - } - - private void onStageChildTaskStatusChanged(StageListenerImpl stageListener, int taskId, - boolean present, boolean visible) { - int stage; - if (present) { - stage = stageListener == mSideStageListener ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN; - } else { - // No longer on any stage - stage = STAGE_TYPE_UNDEFINED; - } - if (stage == STAGE_TYPE_MAIN) { - mLogger.logMainStageAppChange(getMainStagePosition(), mMainStage.getTopChildTaskUid(), - mSplitLayout.isLandscape()); - } else { - mLogger.logSideStageAppChange(getSideStagePosition(), mSideStage.getTopChildTaskUid(), - mSplitLayout.isLandscape()); - } - - for (int i = mListeners.size() - 1; i >= 0; --i) { - mListeners.get(i).onTaskStageChanged(taskId, stage, visible); - } - } - - private void sendSplitVisibilityChanged() { - for (int i = mListeners.size() - 1; i >= 0; --i) { - final SplitScreen.SplitScreenListener l = mListeners.get(i); - l.onSplitVisibilityChanged(mDividerVisible); - } - - if (mMainUnfoldController != null && mSideUnfoldController != null) { - mMainUnfoldController.onSplitVisibilityChanged(mDividerVisible); - mSideUnfoldController.onSplitVisibilityChanged(mDividerVisible); - } - } - - private void onStageRootTaskAppeared(StageListenerImpl stageListener) { - if (mMainStageListener.mHasRootTask && mSideStageListener.mHasRootTask) { - mUseLegacySplit = mContext.getResources().getBoolean(R.bool.config_useLegacySplit); - final WindowContainerTransaction wct = new WindowContainerTransaction(); - // Make the stages adjacent to each other so they occlude what's behind them. - wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token, - true /* moveTogether */); - - // Only sets side stage as launch-adjacent-flag-root when the device is not using legacy - // split to prevent new split behavior confusing users. - if (!mUseLegacySplit) { - wct.setLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token); - } - - mTaskOrganizer.applyTransaction(wct); - } - } - - private void onStageRootTaskVanished(StageListenerImpl stageListener) { - if (stageListener == mMainStageListener || stageListener == mSideStageListener) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); - // Deactivate the main stage if it no longer has a root task. - mMainStage.deactivate(wct); - - if (!mUseLegacySplit) { - wct.clearLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token); - } - - mTaskOrganizer.applyTransaction(wct); - } - } - - private void setDividerVisibility(boolean visible) { - if (mDividerVisible == visible) return; - mDividerVisible = visible; - if (visible) { - mSplitLayout.init(); - updateUnfoldBounds(); - } else { - mSplitLayout.release(); - } - sendSplitVisibilityChanged(); - } - - private void onStageVisibilityChanged(StageListenerImpl stageListener) { - final boolean sideStageVisible = mSideStageListener.mVisible; - final boolean mainStageVisible = mMainStageListener.mVisible; - final boolean bothStageVisible = sideStageVisible && mainStageVisible; - final boolean bothStageInvisible = !sideStageVisible && !mainStageVisible; - final boolean sameVisibility = sideStageVisible == mainStageVisible; - // Only add or remove divider when both visible or both invisible to avoid sometimes we only - // got one stage visibility changed for a moment and it will cause flicker. - if (sameVisibility) { - setDividerVisibility(bothStageVisible); - } - - if (bothStageInvisible) { - if (mExitSplitScreenOnHide - // Don't dismiss staged split when both stages are not visible due to sleeping display, - // like the cases keyguard showing or screen off. - || (!mMainStage.mRootTaskInfo.isSleeping && !mSideStage.mRootTaskInfo.isSleeping)) { - exitSplitScreen(null /* childrenToTop */, - SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME); - } - } else if (mKeyguardOccluded) { - // At least one of the stages is visible while keyguard occluded. Dismiss split because - // there's show-when-locked activity showing on top of keyguard. Also make sure the - // task contains show-when-locked activity remains on top after split dismissed. - final StageTaskListener toTop = - mainStageVisible ? mMainStage : (sideStageVisible ? mSideStage : null); - exitSplitScreen(toTop, SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED_SHOW_ON_TOP); - } - - mSyncQueue.runInSync(t -> { - // Same above, we only set root tasks and divider leash visibility when both stage - // change to visible or invisible to avoid flicker. - if (sameVisibility) { - t.setVisibility(mSideStage.mRootLeash, bothStageVisible) - .setVisibility(mMainStage.mRootLeash, bothStageVisible); - applyDividerVisibility(t); - applyOutlineVisibility(t); - } - }); - } - - private void applyDividerVisibility(SurfaceControl.Transaction t) { - final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash(); - if (dividerLeash == null) { - return; - } - - if (mDividerVisible) { - t.show(dividerLeash) - .setLayer(dividerLeash, Integer.MAX_VALUE) - .setPosition(dividerLeash, - mSplitLayout.getDividerBounds().left, - mSplitLayout.getDividerBounds().top); - } else { - t.hide(dividerLeash); - } - } - - private void applyOutlineVisibility(SurfaceControl.Transaction t) { - final SurfaceControl outlineLeash = mSideStage.getOutlineLeash(); - if (outlineLeash == null) { - return; - } - - if (mDividerVisible) { - t.show(outlineLeash).setLayer(outlineLeash, Integer.MAX_VALUE); - } else { - t.hide(outlineLeash); - } - } - - private void onStageHasChildrenChanged(StageListenerImpl stageListener) { - final boolean hasChildren = stageListener.mHasChildren; - final boolean isSideStage = stageListener == mSideStageListener; - if (!hasChildren) { - if (isSideStage && mMainStageListener.mVisible) { - // Exit to main stage if side stage no longer has children. - exitSplitScreen(mMainStage, SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED); - } else if (!isSideStage && mSideStageListener.mVisible) { - // Exit to side stage if main stage no longer has children. - exitSplitScreen(mSideStage, SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED); - } - } else if (isSideStage) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); - // Make sure the main stage is active. - mMainStage.activate(getMainStageBounds(), wct); - mSideStage.setBounds(getSideStageBounds(), wct); - mTaskOrganizer.applyTransaction(wct); - } - if (!mLogger.hasStartedSession() && mMainStageListener.mHasChildren - && mSideStageListener.mHasChildren) { - mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(), - getMainStagePosition(), mMainStage.getTopChildTaskUid(), - getSideStagePosition(), mSideStage.getTopChildTaskUid(), - mSplitLayout.isLandscape()); - } - } - - @VisibleForTesting - IBinder onSnappedToDismissTransition(boolean mainStageToTop) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); - prepareExitSplitScreen(mainStageToTop ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE, wct); - return mSplitTransitions.startSnapToDismiss(wct, this); - } - - @Override - public void onSnappedToDismiss(boolean bottomOrRight) { - final boolean mainStageToTop = - bottomOrRight ? mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT - : mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT; - if (ENABLE_SHELL_TRANSITIONS) { - onSnappedToDismissTransition(mainStageToTop); - return; - } - exitSplitScreen(mainStageToTop ? mMainStage : mSideStage, - SPLITSCREEN_UICHANGED__EXIT_REASON__DRAG_DIVIDER); - } - - @Override - public void onDoubleTappedDivider() { - setSideStagePosition(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT - ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT, null /* wct */); - mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(), - getSideStagePosition(), mSideStage.getTopChildTaskUid(), - mSplitLayout.isLandscape()); - } - - @Override - public void onLayoutPositionChanging(SplitLayout layout) { - mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t, true /* applyResizingOffset */)); - } - - @Override - public void onLayoutSizeChanging(SplitLayout layout) { - mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t, true /* applyResizingOffset */)); - mSideStage.setOutlineVisibility(false); - } - - @Override - public void onLayoutSizeChanged(SplitLayout layout) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); - updateWindowBounds(layout, wct); - updateUnfoldBounds(); - mSyncQueue.queue(wct); - mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t, false /* applyResizingOffset */)); - mSideStage.setOutlineVisibility(true); - mLogger.logResize(mSplitLayout.getDividerPositionAsFraction()); - } - - private void updateUnfoldBounds() { - if (mMainUnfoldController != null && mSideUnfoldController != null) { - mMainUnfoldController.onLayoutChanged(getMainStageBounds()); - mSideUnfoldController.onLayoutChanged(getSideStageBounds()); - } - } - - /** - * Populates `wct` with operations that match the split windows to the current layout. - * To match relevant surfaces, make sure to call updateSurfaceBounds after `wct` is applied - */ - private void updateWindowBounds(SplitLayout layout, WindowContainerTransaction wct) { - final StageTaskListener topLeftStage = - mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage; - final StageTaskListener bottomRightStage = - mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage; - layout.applyTaskChanges(wct, topLeftStage.mRootTaskInfo, bottomRightStage.mRootTaskInfo); - } - - void updateSurfaceBounds(@Nullable SplitLayout layout, @NonNull SurfaceControl.Transaction t, - boolean applyResizingOffset) { - final StageTaskListener topLeftStage = - mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage; - final StageTaskListener bottomRightStage = - mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage; - (layout != null ? layout : mSplitLayout).applySurfaceChanges(t, topLeftStage.mRootLeash, - bottomRightStage.mRootLeash, topLeftStage.mDimLayer, bottomRightStage.mDimLayer, - applyResizingOffset); - } - - @Override - public int getSplitItemPosition(WindowContainerToken token) { - if (token == null) { - return SPLIT_POSITION_UNDEFINED; - } - - if (token.equals(mMainStage.mRootTaskInfo.getToken())) { - return getMainStagePosition(); - } else if (token.equals(mSideStage.mRootTaskInfo.getToken())) { - return getSideStagePosition(); - } - - return SPLIT_POSITION_UNDEFINED; - } - - @Override - public void setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout) { - final StageTaskListener topLeftStage = - mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage; - final StageTaskListener bottomRightStage = - mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage; - final WindowContainerTransaction wct = new WindowContainerTransaction(); - layout.applyLayoutOffsetTarget(wct, offsetX, offsetY, topLeftStage.mRootTaskInfo, - bottomRightStage.mRootTaskInfo); - mTaskOrganizer.applyTransaction(wct); - } - - @Override - public void onDisplayAreaAppeared(DisplayAreaInfo displayAreaInfo) { - mDisplayAreaInfo = displayAreaInfo; - if (mSplitLayout == null) { - mSplitLayout = new SplitLayout(TAG + "SplitDivider", mContext, - mDisplayAreaInfo.configuration, this, mParentContainerCallbacks, - mDisplayImeController, mTaskOrganizer, SplitLayout.PARALLAX_DISMISSING); - mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout); - - if (mMainUnfoldController != null && mSideUnfoldController != null) { - mMainUnfoldController.init(); - mSideUnfoldController.init(); - } - } - } - - @Override - public void onDisplayAreaVanished(DisplayAreaInfo displayAreaInfo) { - throw new IllegalStateException("Well that was unexpected..."); - } - - @Override - public void onDisplayAreaInfoChanged(DisplayAreaInfo displayAreaInfo) { - mDisplayAreaInfo = displayAreaInfo; - if (mSplitLayout != null - && mSplitLayout.updateConfiguration(mDisplayAreaInfo.configuration) - && mMainStage.isActive()) { - onLayoutSizeChanged(mSplitLayout); - } - } - - private void onFoldedStateChanged(boolean folded) { - mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED; - if (!folded) return; - - if (mMainStage.isFocused()) { - mTopStageAfterFoldDismiss = STAGE_TYPE_MAIN; - } else if (mSideStage.isFocused()) { - mTopStageAfterFoldDismiss = STAGE_TYPE_SIDE; - } - } - - private Rect getSideStageBounds() { - return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT - ? mSplitLayout.getBounds1() : mSplitLayout.getBounds2(); - } - - private Rect getMainStageBounds() { - return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT - ? mSplitLayout.getBounds2() : mSplitLayout.getBounds1(); - } - - /** - * Get the stage that should contain this `taskInfo`. The stage doesn't necessarily contain - * this task (yet) so this can also be used to identify which stage to put a task into. - */ - private StageTaskListener getStageOfTask(ActivityManager.RunningTaskInfo taskInfo) { - // TODO(b/184679596): Find a way to either include task-org information in the transition, - // or synchronize task-org callbacks so we can use stage.containsTask - if (mMainStage.mRootTaskInfo != null - && taskInfo.parentTaskId == mMainStage.mRootTaskInfo.taskId) { - return mMainStage; - } else if (mSideStage.mRootTaskInfo != null - && taskInfo.parentTaskId == mSideStage.mRootTaskInfo.taskId) { - return mSideStage; - } - return null; - } - - @SplitScreen.StageType - private int getStageType(StageTaskListener stage) { - return stage == mMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE; - } - - @Override - public WindowContainerTransaction handleRequest(@NonNull IBinder transition, - @Nullable TransitionRequestInfo request) { - final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask(); - if (triggerTask == null) { - // still want to monitor everything while in split-screen, so return non-null. - return isSplitScreenVisible() ? new WindowContainerTransaction() : null; - } - - WindowContainerTransaction out = null; - final @WindowManager.TransitionType int type = request.getType(); - if (isSplitScreenVisible()) { - // try to handle everything while in split-screen, so return a WCT even if it's empty. - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " split is active so using split" - + "Transition to handle request. triggerTask=%d type=%s mainChildren=%d" - + " sideChildren=%d", triggerTask.taskId, transitTypeToString(type), - mMainStage.getChildCount(), mSideStage.getChildCount()); - out = new WindowContainerTransaction(); - final StageTaskListener stage = getStageOfTask(triggerTask); - if (stage != null) { - // dismiss split if the last task in one of the stages is going away - if (isClosingType(type) && stage.getChildCount() == 1) { - // The top should be the opposite side that is closing: - mDismissTop = getStageType(stage) == STAGE_TYPE_MAIN - ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN; - } - } else { - if (triggerTask.getActivityType() == ACTIVITY_TYPE_HOME && isOpeningType(type)) { - // Going home so dismiss both. - mDismissTop = STAGE_TYPE_UNDEFINED; - } - } - if (mDismissTop != NO_DISMISS) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition " - + " deduced Dismiss from request. toTop=%s", - stageTypeToString(mDismissTop)); - prepareExitSplitScreen(mDismissTop, out); - mSplitTransitions.mPendingDismiss = transition; - } - } else { - // Not in split mode, so look for an open into a split stage just so we can whine and - // complain about how this isn't a supported operation. - if ((type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT)) { - if (getStageOfTask(triggerTask) != null) { - throw new IllegalStateException("Entering split implicitly with only one task" - + " isn't supported."); - } - } - } - return out; - } - - @Override - public boolean startAnimation(@NonNull IBinder transition, - @NonNull TransitionInfo info, - @NonNull SurfaceControl.Transaction startTransaction, - @NonNull SurfaceControl.Transaction finishTransaction, - @NonNull Transitions.TransitionFinishCallback finishCallback) { - if (transition != mSplitTransitions.mPendingDismiss - && transition != mSplitTransitions.mPendingEnter) { - // Not entering or exiting, so just do some house-keeping and validation. - - // If we're not in split-mode, just abort so something else can handle it. - if (!isSplitScreenVisible()) return false; - - for (int iC = 0; iC < info.getChanges().size(); ++iC) { - final TransitionInfo.Change change = info.getChanges().get(iC); - final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); - if (taskInfo == null || !taskInfo.hasParentTask()) continue; - final StageTaskListener stage = getStageOfTask(taskInfo); - if (stage == null) continue; - if (isOpeningType(change.getMode())) { - if (!stage.containsTask(taskInfo.taskId)) { - Log.w(TAG, "Expected onTaskAppeared on " + stage + " to have been called" - + " with " + taskInfo.taskId + " before startAnimation()."); - } - } else if (isClosingType(change.getMode())) { - if (stage.containsTask(taskInfo.taskId)) { - Log.w(TAG, "Expected onTaskVanished on " + stage + " to have been called" - + " with " + taskInfo.taskId + " before startAnimation()."); - } - } - } - if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) { - // TODO(shell-transitions): Implement a fallback behavior for now. - throw new IllegalStateException("Somehow removed the last task in a stage" - + " outside of a proper transition"); - // This can happen in some pathological cases. For example: - // 1. main has 2 tasks [Task A (Single-task), Task B], side has one task [Task C] - // 2. Task B closes itself and starts Task A in LAUNCH_ADJACENT at the same time - // In this case, the result *should* be that we leave split. - // TODO(b/184679596): Find a way to either include task-org information in - // the transition, or synchronize task-org callbacks. - } - - // Use normal animations. - return false; - } - - boolean shouldAnimate = true; - if (mSplitTransitions.mPendingEnter == transition) { - shouldAnimate = startPendingEnterAnimation(transition, info, startTransaction); - } else if (mSplitTransitions.mPendingDismiss == transition) { - shouldAnimate = startPendingDismissAnimation(transition, info, startTransaction); - } - if (!shouldAnimate) return false; - - mSplitTransitions.playAnimation(transition, info, startTransaction, finishTransaction, - finishCallback, mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token); - return true; - } - - private boolean startPendingEnterAnimation(@NonNull IBinder transition, - @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) { - if (info.getType() == TRANSIT_SPLIT_SCREEN_PAIR_OPEN) { - // First, verify that we actually have opened 2 apps in split. - TransitionInfo.Change mainChild = null; - TransitionInfo.Change sideChild = null; - for (int iC = 0; iC < info.getChanges().size(); ++iC) { - final TransitionInfo.Change change = info.getChanges().get(iC); - final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); - if (taskInfo == null || !taskInfo.hasParentTask()) continue; - final @SplitScreen.StageType int stageType = getStageType(getStageOfTask(taskInfo)); - if (stageType == STAGE_TYPE_MAIN) { - mainChild = change; - } else if (stageType == STAGE_TYPE_SIDE) { - sideChild = change; - } - } - if (mainChild == null || sideChild == null) { - throw new IllegalStateException("Launched 2 tasks in split, but didn't receive" - + " 2 tasks in transition. Possibly one of them failed to launch"); - // TODO: fallback logic. Probably start a new transition to exit split before - // applying anything here. Ideally consolidate with transition-merging. - } - - // Update local states (before animating). - setDividerVisibility(true); - setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, false /* updateBounds */, - null /* wct */); - setSplitsVisible(true); - - addDividerBarToTransition(info, t, true /* show */); - - // Make some noise if things aren't totally expected. These states shouldn't effect - // transitions locally, but remotes (like Launcher) may get confused if they were - // depending on listener callbacks. This can happen because task-organizer callbacks - // aren't serialized with transition callbacks. - // TODO(b/184679596): Find a way to either include task-org information in - // the transition, or synchronize task-org callbacks. - if (!mMainStage.containsTask(mainChild.getTaskInfo().taskId)) { - Log.w(TAG, "Expected onTaskAppeared on " + mMainStage - + " to have been called with " + mainChild.getTaskInfo().taskId - + " before startAnimation()."); - } - if (!mSideStage.containsTask(sideChild.getTaskInfo().taskId)) { - Log.w(TAG, "Expected onTaskAppeared on " + mSideStage - + " to have been called with " + sideChild.getTaskInfo().taskId - + " before startAnimation()."); - } - return true; - } else { - // TODO: other entry method animations - throw new RuntimeException("Unsupported split-entry"); - } - } - - private boolean startPendingDismissAnimation(@NonNull IBinder transition, - @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) { - // Make some noise if things aren't totally expected. These states shouldn't effect - // transitions locally, but remotes (like Launcher) may get confused if they were - // depending on listener callbacks. This can happen because task-organizer callbacks - // aren't serialized with transition callbacks. - // TODO(b/184679596): Find a way to either include task-org information in - // the transition, or synchronize task-org callbacks. - if (mMainStage.getChildCount() != 0) { - final StringBuilder tasksLeft = new StringBuilder(); - for (int i = 0; i < mMainStage.getChildCount(); ++i) { - tasksLeft.append(i != 0 ? ", " : ""); - tasksLeft.append(mMainStage.mChildrenTaskInfo.keyAt(i)); - } - Log.w(TAG, "Expected onTaskVanished on " + mMainStage - + " to have been called with [" + tasksLeft.toString() - + "] before startAnimation()."); - } - if (mSideStage.getChildCount() != 0) { - final StringBuilder tasksLeft = new StringBuilder(); - for (int i = 0; i < mSideStage.getChildCount(); ++i) { - tasksLeft.append(i != 0 ? ", " : ""); - tasksLeft.append(mSideStage.mChildrenTaskInfo.keyAt(i)); - } - Log.w(TAG, "Expected onTaskVanished on " + mSideStage - + " to have been called with [" + tasksLeft.toString() - + "] before startAnimation()."); - } - - // Update local states. - setSplitsVisible(false); - // Wait until after animation to update divider - - if (info.getType() == TRANSIT_SPLIT_DISMISS_SNAP) { - // Reset crops so they don't interfere with subsequent launches - t.setWindowCrop(mMainStage.mRootLeash, null); - t.setWindowCrop(mSideStage.mRootLeash, null); - } - - if (mDismissTop == STAGE_TYPE_UNDEFINED) { - // Going home (dismissing both splits) - - // TODO: Have a proper remote for this. Until then, though, reset state and use the - // normal animation stuff (which falls back to the normal launcher remote). - t.hide(mSplitLayout.getDividerLeash()); - setDividerVisibility(false); - mSplitTransitions.mPendingDismiss = null; - return false; - } - - addDividerBarToTransition(info, t, false /* show */); - // We're dismissing split by moving the other one to fullscreen. - // Since we don't have any animations for this yet, just use the internal example - // animations. - return true; - } - - private void addDividerBarToTransition(@NonNull TransitionInfo info, - @NonNull SurfaceControl.Transaction t, boolean show) { - final SurfaceControl leash = mSplitLayout.getDividerLeash(); - final TransitionInfo.Change barChange = new TransitionInfo.Change(null /* token */, leash); - final Rect bounds = mSplitLayout.getDividerBounds(); - barChange.setStartAbsBounds(bounds); - barChange.setEndAbsBounds(bounds); - barChange.setMode(show ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK); - barChange.setFlags(FLAG_IS_DIVIDER_BAR); - // Technically this should be order-0, but this is running after layer assignment - // and it's a special case, so just add to end. - info.addChange(barChange); - // Be default, make it visible. The remote animator can adjust alpha if it plans to animate. - if (show) { - t.setAlpha(leash, 1.f); - t.setLayer(leash, Integer.MAX_VALUE); - t.setPosition(leash, bounds.left, bounds.top); - t.show(leash); - } - } - - RemoteAnimationTarget getDividerBarLegacyTarget() { - final Rect bounds = mSplitLayout.getDividerBounds(); - return new RemoteAnimationTarget(-1 /* taskId */, -1 /* mode */, - mSplitLayout.getDividerLeash(), false /* isTranslucent */, null /* clipRect */, - null /* contentInsets */, Integer.MAX_VALUE /* prefixOrderIndex */, - new android.graphics.Point(0, 0) /* position */, bounds, bounds, - new WindowConfiguration(), true, null /* startLeash */, null /* startBounds */, - null /* taskInfo */, false /* allowEnterPip */, TYPE_DOCK_DIVIDER); - } - - RemoteAnimationTarget getOutlineLegacyTarget() { - final Rect bounds = mSideStage.mRootTaskInfo.configuration.windowConfiguration.getBounds(); - // Leverage TYPE_DOCK_DIVIDER type when wrapping outline remote animation target in order to - // distinguish as a split auxiliary target in Launcher. - return new RemoteAnimationTarget(-1 /* taskId */, -1 /* mode */, - mSideStage.getOutlineLeash(), false /* isTranslucent */, null /* clipRect */, - null /* contentInsets */, Integer.MAX_VALUE /* prefixOrderIndex */, - new android.graphics.Point(0, 0) /* position */, bounds, bounds, - new WindowConfiguration(), true, null /* startLeash */, null /* startBounds */, - null /* taskInfo */, false /* allowEnterPip */, TYPE_DOCK_DIVIDER); - } - - @Override - public void dump(@NonNull PrintWriter pw, String prefix) { - final String innerPrefix = prefix + " "; - final String childPrefix = innerPrefix + " "; - pw.println(prefix + TAG + " mDisplayId=" + mDisplayId); - pw.println(innerPrefix + "mDividerVisible=" + mDividerVisible); - pw.println(innerPrefix + "MainStage"); - pw.println(childPrefix + "isActive=" + mMainStage.isActive()); - mMainStageListener.dump(pw, childPrefix); - pw.println(innerPrefix + "SideStage"); - mSideStageListener.dump(pw, childPrefix); - pw.println(innerPrefix + "mSplitLayout=" + mSplitLayout); - } - - /** - * Directly set the visibility of both splits. This assumes hasChildren matches visibility. - * This is intended for batch use, so it assumes other state management logic is already - * handled. - */ - private void setSplitsVisible(boolean visible) { - mMainStageListener.mVisible = mSideStageListener.mVisible = visible; - mMainStageListener.mHasChildren = mSideStageListener.mHasChildren = visible; - } - - /** - * Sets drag info to be logged when splitscreen is next entered. - */ - public void logOnDroppedToSplit(@SplitPosition int position, InstanceId dragSessionId) { - mLogger.enterRequestedByDrag(position, dragSessionId); - } - - /** - * Logs the exit of splitscreen. - */ - private void logExit(int exitReason) { - mLogger.logExit(exitReason, - SPLIT_POSITION_UNDEFINED, 0 /* mainStageUid */, - SPLIT_POSITION_UNDEFINED, 0 /* sideStageUid */, - mSplitLayout.isLandscape()); - } - - /** - * Logs the exit of splitscreen to a specific stage. This must be called before the exit is - * executed. - */ - private void logExitToStage(int exitReason, boolean toMainStage) { - mLogger.logExit(exitReason, - toMainStage ? getMainStagePosition() : SPLIT_POSITION_UNDEFINED, - toMainStage ? mMainStage.getTopChildTaskUid() : 0 /* mainStageUid */, - !toMainStage ? getSideStagePosition() : SPLIT_POSITION_UNDEFINED, - !toMainStage ? mSideStage.getTopChildTaskUid() : 0 /* sideStageUid */, - mSplitLayout.isLandscape()); - } - - class StageListenerImpl implements StageTaskListener.StageListenerCallbacks { - boolean mHasRootTask = false; - boolean mVisible = false; - boolean mHasChildren = false; - - @Override - public void onRootTaskAppeared() { - mHasRootTask = true; - StageCoordinator.this.onStageRootTaskAppeared(this); - } - - @Override - public void onStatusChanged(boolean visible, boolean hasChildren) { - if (!mHasRootTask) return; - - if (mHasChildren != hasChildren) { - mHasChildren = hasChildren; - StageCoordinator.this.onStageHasChildrenChanged(this); - } - if (mVisible != visible) { - mVisible = visible; - StageCoordinator.this.onStageVisibilityChanged(this); - } - } - - @Override - public void onChildTaskStatusChanged(int taskId, boolean present, boolean visible) { - StageCoordinator.this.onStageChildTaskStatusChanged(this, taskId, present, visible); - } - - @Override - public void onRootTaskVanished() { - reset(); - StageCoordinator.this.onStageRootTaskVanished(this); - } - - @Override - public void onNoLongerSupportMultiWindow() { - if (mMainStage.isActive()) { - StageCoordinator.this.exitSplitScreen(null /* childrenToTop */, - SPLITSCREEN_UICHANGED__EXIT_REASON__APP_DOES_NOT_SUPPORT_MULTIWINDOW); - } - } - - private void reset() { - mHasRootTask = false; - mVisible = false; - mHasChildren = false; - } - - public void dump(@NonNull PrintWriter pw, String prefix) { - pw.println(prefix + "mHasRootTask=" + mHasRootTask); - pw.println(prefix + "mVisible=" + mVisible); - pw.println(prefix + "mHasChildren=" + mHasChildren); - } - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskListener.java deleted file mode 100644 index 7b679580fa87..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskListener.java +++ /dev/null @@ -1,298 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.stagesplit; - -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; -import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; - -import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS; - -import android.annotation.CallSuper; -import android.annotation.Nullable; -import android.app.ActivityManager; -import android.graphics.Point; -import android.graphics.Rect; -import android.util.SparseArray; -import android.view.SurfaceControl; -import android.view.SurfaceSession; -import android.window.WindowContainerTransaction; - -import androidx.annotation.NonNull; - -import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.common.SurfaceUtils; -import com.android.wm.shell.common.SyncTransactionQueue; - -import java.io.PrintWriter; - -/** - * Base class that handle common task org. related for split-screen stages. - * Note that this class and its sub-class do not directly perform hierarchy operations. - * They only serve to hold a collection of tasks and provide APIs like - * {@link #setBounds(Rect, WindowContainerTransaction)} for the centralized {@link StageCoordinator} - * to perform operations in-sync with other containers. - * - * @see StageCoordinator - */ -class StageTaskListener implements ShellTaskOrganizer.TaskListener { - private static final String TAG = StageTaskListener.class.getSimpleName(); - - protected static final int[] CONTROLLED_ACTIVITY_TYPES = {ACTIVITY_TYPE_STANDARD}; - protected static final int[] CONTROLLED_WINDOWING_MODES = - {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED}; - protected static final int[] CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE = - {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED, WINDOWING_MODE_MULTI_WINDOW}; - - /** Callback interface for listening to changes in a split-screen stage. */ - public interface StageListenerCallbacks { - void onRootTaskAppeared(); - - void onStatusChanged(boolean visible, boolean hasChildren); - - void onChildTaskStatusChanged(int taskId, boolean present, boolean visible); - - void onRootTaskVanished(); - void onNoLongerSupportMultiWindow(); - } - - private final StageListenerCallbacks mCallbacks; - private final SurfaceSession mSurfaceSession; - protected final SyncTransactionQueue mSyncQueue; - - protected ActivityManager.RunningTaskInfo mRootTaskInfo; - protected SurfaceControl mRootLeash; - protected SurfaceControl mDimLayer; - protected SparseArray<ActivityManager.RunningTaskInfo> mChildrenTaskInfo = new SparseArray<>(); - private final SparseArray<SurfaceControl> mChildrenLeashes = new SparseArray<>(); - - private final StageTaskUnfoldController mStageTaskUnfoldController; - - StageTaskListener(ShellTaskOrganizer taskOrganizer, int displayId, - StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue, - SurfaceSession surfaceSession, - @Nullable StageTaskUnfoldController stageTaskUnfoldController) { - mCallbacks = callbacks; - mSyncQueue = syncQueue; - mSurfaceSession = surfaceSession; - mStageTaskUnfoldController = stageTaskUnfoldController; - taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this); - } - - int getChildCount() { - return mChildrenTaskInfo.size(); - } - - boolean containsTask(int taskId) { - return mChildrenTaskInfo.contains(taskId); - } - - /** - * Returns the top activity uid for the top child task. - */ - int getTopChildTaskUid() { - for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) { - final ActivityManager.RunningTaskInfo info = mChildrenTaskInfo.valueAt(i); - if (info.topActivityInfo == null) { - continue; - } - return info.topActivityInfo.applicationInfo.uid; - } - return 0; - } - - /** @return {@code true} if this listener contains the currently focused task. */ - boolean isFocused() { - if (mRootTaskInfo == null) { - return false; - } - - if (mRootTaskInfo.isFocused) { - return true; - } - - for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) { - if (mChildrenTaskInfo.valueAt(i).isFocused) { - return true; - } - } - - return false; - } - - @Override - @CallSuper - public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { - if (mRootTaskInfo == null && !taskInfo.hasParentTask()) { - mRootLeash = leash; - mRootTaskInfo = taskInfo; - mCallbacks.onRootTaskAppeared(); - sendStatusChanged(); - mSyncQueue.runInSync(t -> { - t.hide(mRootLeash); - mDimLayer = - SurfaceUtils.makeDimLayer(t, mRootLeash, "Dim layer", mSurfaceSession); - }); - } else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) { - final int taskId = taskInfo.taskId; - mChildrenLeashes.put(taskId, leash); - mChildrenTaskInfo.put(taskId, taskInfo); - updateChildTaskSurface(taskInfo, leash, true /* firstAppeared */); - mCallbacks.onChildTaskStatusChanged(taskId, true /* present */, taskInfo.isVisible); - if (ENABLE_SHELL_TRANSITIONS) { - // Status is managed/synchronized by the transition lifecycle. - return; - } - sendStatusChanged(); - } else { - throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo - + "\n mRootTaskInfo: " + mRootTaskInfo); - } - - if (mStageTaskUnfoldController != null) { - mStageTaskUnfoldController.onTaskAppeared(taskInfo, leash); - } - } - - @Override - @CallSuper - public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { - if (!taskInfo.supportsMultiWindow) { - // Leave split screen if the task no longer supports multi window. - mCallbacks.onNoLongerSupportMultiWindow(); - return; - } - if (mRootTaskInfo.taskId == taskInfo.taskId) { - mRootTaskInfo = taskInfo; - } else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) { - mChildrenTaskInfo.put(taskInfo.taskId, taskInfo); - mCallbacks.onChildTaskStatusChanged(taskInfo.taskId, true /* present */, - taskInfo.isVisible); - if (!ENABLE_SHELL_TRANSITIONS) { - updateChildTaskSurface( - taskInfo, mChildrenLeashes.get(taskInfo.taskId), false /* firstAppeared */); - } - } else { - throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo - + "\n mRootTaskInfo: " + mRootTaskInfo); - } - if (ENABLE_SHELL_TRANSITIONS) { - // Status is managed/synchronized by the transition lifecycle. - return; - } - sendStatusChanged(); - } - - @Override - @CallSuper - public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { - final int taskId = taskInfo.taskId; - if (mRootTaskInfo.taskId == taskId) { - mCallbacks.onRootTaskVanished(); - mSyncQueue.runInSync(t -> t.remove(mDimLayer)); - mRootTaskInfo = null; - } else if (mChildrenTaskInfo.contains(taskId)) { - mChildrenTaskInfo.remove(taskId); - mChildrenLeashes.remove(taskId); - mCallbacks.onChildTaskStatusChanged(taskId, false /* present */, taskInfo.isVisible); - if (ENABLE_SHELL_TRANSITIONS) { - // Status is managed/synchronized by the transition lifecycle. - return; - } - sendStatusChanged(); - } else { - throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo - + "\n mRootTaskInfo: " + mRootTaskInfo); - } - - if (mStageTaskUnfoldController != null) { - mStageTaskUnfoldController.onTaskVanished(taskInfo); - } - } - - @Override - public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) { - b.setParent(findTaskSurface(taskId)); - } - - @Override - public void reparentChildSurfaceToTask(int taskId, SurfaceControl sc, - SurfaceControl.Transaction t) { - t.reparent(sc, findTaskSurface(taskId)); - } - - private SurfaceControl findTaskSurface(int taskId) { - if (mRootTaskInfo.taskId == taskId) { - return mRootLeash; - } else if (mChildrenLeashes.contains(taskId)) { - return mChildrenLeashes.get(taskId); - } else { - throw new IllegalArgumentException("There is no surface for taskId=" + taskId); - } - } - - void setBounds(Rect bounds, WindowContainerTransaction wct) { - wct.setBounds(mRootTaskInfo.token, bounds); - } - - void reorderChild(int taskId, boolean onTop, WindowContainerTransaction wct) { - if (!containsTask(taskId)) { - return; - } - wct.reorder(mChildrenTaskInfo.get(taskId).token, onTop /* onTop */); - } - - void setVisibility(boolean visible, WindowContainerTransaction wct) { - wct.reorder(mRootTaskInfo.token, visible /* onTop */); - } - - void onSplitScreenListenerRegistered(SplitScreen.SplitScreenListener listener, - @SplitScreen.StageType int stage) { - for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) { - int taskId = mChildrenTaskInfo.keyAt(i); - listener.onTaskStageChanged(taskId, stage, - mChildrenTaskInfo.get(taskId).isVisible); - } - } - - private void updateChildTaskSurface(ActivityManager.RunningTaskInfo taskInfo, - SurfaceControl leash, boolean firstAppeared) { - final Point taskPositionInParent = taskInfo.positionInParent; - mSyncQueue.runInSync(t -> { - t.setWindowCrop(leash, null); - t.setPosition(leash, taskPositionInParent.x, taskPositionInParent.y); - if (firstAppeared && !ENABLE_SHELL_TRANSITIONS) { - t.setAlpha(leash, 1f); - t.setMatrix(leash, 1, 0, 0, 1); - t.show(leash); - } - }); - } - - private void sendStatusChanged() { - mCallbacks.onStatusChanged(mRootTaskInfo.isVisible, mChildrenTaskInfo.size() > 0); - } - - @Override - @CallSuper - public void dump(@NonNull PrintWriter pw, String prefix) { - final String innerPrefix = prefix + " "; - final String childPrefix = innerPrefix + " "; - pw.println(prefix + this); - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskUnfoldController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskUnfoldController.java deleted file mode 100644 index 62b9da6d4715..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskUnfoldController.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.stagesplit; - -import static android.view.Display.DEFAULT_DISPLAY; - -import android.animation.RectEvaluator; -import android.animation.TypeEvaluator; -import android.annotation.NonNull; -import android.app.ActivityManager; -import android.content.Context; -import android.graphics.Rect; -import android.util.SparseArray; -import android.view.InsetsSource; -import android.view.InsetsState; -import android.view.SurfaceControl; - -import com.android.internal.policy.ScreenDecorationsUtils; -import com.android.wm.shell.common.DisplayInsetsController; -import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener; -import com.android.wm.shell.common.TransactionPool; -import com.android.wm.shell.unfold.ShellUnfoldProgressProvider; -import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener; -import com.android.wm.shell.unfold.UnfoldBackgroundController; - -import java.util.concurrent.Executor; - -/** - * Controls transformations of the split screen task surfaces in response - * to the unfolding/folding action on foldable devices - */ -public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChangedListener { - - private static final TypeEvaluator<Rect> RECT_EVALUATOR = new RectEvaluator(new Rect()); - private static final float CROPPING_START_MARGIN_FRACTION = 0.05f; - - private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>(); - private final ShellUnfoldProgressProvider mUnfoldProgressProvider; - private final DisplayInsetsController mDisplayInsetsController; - private final UnfoldBackgroundController mBackgroundController; - private final Executor mExecutor; - private final int mExpandedTaskBarHeight; - private final float mWindowCornerRadiusPx; - private final Rect mStageBounds = new Rect(); - private final TransactionPool mTransactionPool; - - private InsetsSource mTaskbarInsetsSource; - private boolean mBothStagesVisible; - - public StageTaskUnfoldController(@NonNull Context context, - @NonNull TransactionPool transactionPool, - @NonNull ShellUnfoldProgressProvider unfoldProgressProvider, - @NonNull DisplayInsetsController displayInsetsController, - @NonNull UnfoldBackgroundController backgroundController, - @NonNull Executor executor) { - mUnfoldProgressProvider = unfoldProgressProvider; - mTransactionPool = transactionPool; - mExecutor = executor; - mBackgroundController = backgroundController; - mDisplayInsetsController = displayInsetsController; - mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context); - mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.taskbar_frame_height); - } - - /** - * Initializes the controller, starts listening for the external events - */ - public void init() { - mUnfoldProgressProvider.addListener(mExecutor, this); - mDisplayInsetsController.addInsetsChangedListener(DEFAULT_DISPLAY, this); - } - - @Override - public void insetsChanged(InsetsState insetsState) { - mTaskbarInsetsSource = insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR); - for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) { - AnimationContext context = mAnimationContextByTaskId.valueAt(i); - context.update(); - } - } - - /** - * Called when split screen task appeared - * @param taskInfo info for the appeared task - * @param leash surface leash for the appeared task - */ - public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { - AnimationContext context = new AnimationContext(leash); - mAnimationContextByTaskId.put(taskInfo.taskId, context); - } - - /** - * Called when a split screen task vanished - * @param taskInfo info for the vanished task - */ - public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { - AnimationContext context = mAnimationContextByTaskId.get(taskInfo.taskId); - if (context != null) { - final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); - resetSurface(transaction, context); - transaction.apply(); - mTransactionPool.release(transaction); - } - mAnimationContextByTaskId.remove(taskInfo.taskId); - } - - @Override - public void onStateChangeProgress(float progress) { - if (mAnimationContextByTaskId.size() == 0 || !mBothStagesVisible) return; - - final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); - mBackgroundController.ensureBackground(transaction); - - for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) { - AnimationContext context = mAnimationContextByTaskId.valueAt(i); - - context.mCurrentCropRect.set(RECT_EVALUATOR - .evaluate(progress, context.mStartCropRect, context.mEndCropRect)); - - transaction.setWindowCrop(context.mLeash, context.mCurrentCropRect) - .setCornerRadius(context.mLeash, mWindowCornerRadiusPx); - } - - transaction.apply(); - - mTransactionPool.release(transaction); - } - - @Override - public void onStateChangeFinished() { - resetTransformations(); - } - - /** - * Called when split screen visibility changes - * @param bothStagesVisible true if both stages of the split screen are visible - */ - public void onSplitVisibilityChanged(boolean bothStagesVisible) { - mBothStagesVisible = bothStagesVisible; - if (!bothStagesVisible) { - resetTransformations(); - } - } - - /** - * Called when split screen stage bounds changed - * @param bounds new bounds for this stage - */ - public void onLayoutChanged(Rect bounds) { - mStageBounds.set(bounds); - - for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) { - final AnimationContext context = mAnimationContextByTaskId.valueAt(i); - context.update(); - } - } - - private void resetTransformations() { - final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); - - for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) { - final AnimationContext context = mAnimationContextByTaskId.valueAt(i); - resetSurface(transaction, context); - } - mBackgroundController.removeBackground(transaction); - transaction.apply(); - - mTransactionPool.release(transaction); - } - - private void resetSurface(SurfaceControl.Transaction transaction, AnimationContext context) { - transaction - .setWindowCrop(context.mLeash, null) - .setCornerRadius(context.mLeash, 0.0F); - } - - private class AnimationContext { - final SurfaceControl mLeash; - final Rect mStartCropRect = new Rect(); - final Rect mEndCropRect = new Rect(); - final Rect mCurrentCropRect = new Rect(); - - private AnimationContext(SurfaceControl leash) { - this.mLeash = leash; - update(); - } - - private void update() { - mStartCropRect.set(mStageBounds); - - if (mTaskbarInsetsSource != null) { - // Only insets the cropping window with taskbar when taskbar is expanded - if (mTaskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) { - mStartCropRect.inset(mTaskbarInsetsSource - .calculateVisibleInsets(mStartCropRect)); - } - } - - // Offset to surface coordinates as layout bounds are in screen coordinates - mStartCropRect.offsetTo(0, 0); - - mEndCropRect.set(mStartCropRect); - - int maxSize = Math.max(mEndCropRect.width(), mEndCropRect.height()); - int margin = (int) (maxSize * CROPPING_START_MARGIN_FRACTION); - mStartCropRect.inset(margin, margin, margin, margin); - } - } -} 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 54d62edf2570..a0e176c7ea68 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 @@ -261,7 +261,8 @@ public class StartingSurfaceDrawer { WindowManager.LayoutParams.TYPE_APPLICATION_STARTING); params.setFitInsetsSides(0); params.setFitInsetsTypes(0); - params.format = PixelFormat.TRANSLUCENT; + params.format = suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN + ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT; int windowFlags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; 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 new file mode 100644 index 000000000000..1ffe26df729f --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -0,0 +1,253 @@ +/* + * 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.transition; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.view.WindowManager.TRANSIT_TO_BACK; +import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; + +import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; +import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP; +import static com.android.wm.shell.splitscreen.StageCoordinator.FLAG_IS_DIVIDER_BAR; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.IBinder; +import android.view.SurfaceControl; +import android.view.WindowManager; +import android.window.TransitionInfo; +import android.window.TransitionRequestInfo; +import android.window.WindowContainerTransaction; + +import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.pip.PipTransitionController; +import com.android.wm.shell.protolog.ShellProtoLogGroup; +import com.android.wm.shell.splitscreen.StageCoordinator; + +import java.util.ArrayList; + +/** + * A handler for dealing with transitions involving multiple other handlers. For example: an + * activity in split-screen going into PiP. + */ +public class DefaultMixedHandler implements Transitions.TransitionHandler { + + private final Transitions mPlayer; + private final PipTransitionController mPipHandler; + private final StageCoordinator mSplitHandler; + + private static class MixedTransition { + static final int TYPE_ENTER_PIP_FROM_SPLIT = 1; + + final int mType; + final IBinder mTransition; + + Transitions.TransitionFinishCallback mFinishCallback = null; + + /** + * Mixed transitions are made up of multiple "parts". This keeps track of how many + * parts are currently animating. + */ + int mInFlightSubAnimations = 0; + + MixedTransition(int type, IBinder transition) { + mType = type; + mTransition = transition; + } + } + private final ArrayList<MixedTransition> mActiveTransitions = new ArrayList<>(); + + public DefaultMixedHandler(@NonNull Transitions player, + @NonNull PipTransitionController pipHandler, @NonNull StageCoordinator splitHandler) { + mPlayer = player; + mPipHandler = pipHandler; + mSplitHandler = splitHandler; + } + + @Nullable + @Override + public WindowContainerTransaction handleRequest(@NonNull IBinder transition, + @NonNull TransitionRequestInfo request) { + if (mPipHandler.requestHasPipEnter(request) && mSplitHandler.isSplitActive()) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a PiP-enter request while " + + "Split-Screen is active, so treat it as Mixed."); + if (request.getRemoteTransition() != null) { + throw new IllegalStateException("Unexpected remote transition in" + + "pip-enter-from-split request"); + } + mActiveTransitions.add(new MixedTransition(MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT, + transition)); + + WindowContainerTransaction out = new WindowContainerTransaction(); + mPipHandler.augmentRequest(transition, request, out); + mSplitHandler.addEnterOrExitIfNeeded(request, out); + return out; + } + return null; + } + + private TransitionInfo subCopy(@NonNull TransitionInfo info, + @WindowManager.TransitionType int newType) { + final TransitionInfo out = new TransitionInfo(newType, info.getFlags()); + for (int i = 0; i < info.getChanges().size(); ++i) { + out.getChanges().add(info.getChanges().get(i)); + } + out.setRootLeash(info.getRootLeash(), info.getRootOffset().x, info.getRootOffset().y); + out.setAnimationOptions(info.getAnimationOptions()); + return out; + } + + private boolean isHomeOpening(@NonNull TransitionInfo.Change change) { + return change.getTaskInfo() != null + && change.getTaskInfo().getActivityType() != ACTIVITY_TYPE_HOME; + } + + private boolean isWallpaper(@NonNull TransitionInfo.Change change) { + return (change.getFlags() & FLAG_IS_WALLPAPER) != 0; + } + + @Override + public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + MixedTransition mixed = null; + for (int i = mActiveTransitions.size() - 1; i >= 0; --i) { + if (mActiveTransitions.get(i).mTransition != transition) continue; + mixed = mActiveTransitions.remove(i); + break; + } + if (mixed == null) return false; + + if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) { + return animateEnterPipFromSplit(mixed, info, startTransaction, finishTransaction, + finishCallback); + } else { + throw new IllegalStateException("Starting mixed animation without a known mixed type? " + + mixed.mType); + } + } + + private boolean animateEnterPipFromSplit(@NonNull final MixedTransition mixed, + @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for " + + "entering PIP while Split-Screen is active."); + TransitionInfo.Change pipChange = null; + TransitionInfo.Change wallpaper = null; + final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK); + boolean homeIsOpening = false; + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + TransitionInfo.Change change = info.getChanges().get(i); + if (mPipHandler.isEnteringPip(change, info.getType())) { + if (pipChange != null) { + throw new IllegalStateException("More than 1 pip-entering changes in one" + + " transition? " + info); + } + pipChange = change; + // going backwards, so remove-by-index is fine. + everythingElse.getChanges().remove(i); + } else if (isHomeOpening(change)) { + homeIsOpening = true; + } else if (isWallpaper(change)) { + wallpaper = change; + } + } + if (pipChange == null) { + // um, something probably went wrong. + return false; + } + final boolean isGoingHome = homeIsOpening; + mixed.mFinishCallback = finishCallback; + Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> { + --mixed.mInFlightSubAnimations; + if (mixed.mInFlightSubAnimations > 0) return; + if (isGoingHome) { + mSplitHandler.onTransitionAnimationComplete(); + } + mixed.mFinishCallback.onTransitionFinished(wct, wctCB); + }; + if (isGoingHome) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animation is actually mixed " + + "since entering-PiP caused us to leave split and return home."); + // We need to split the transition into 2 parts: the pip part (animated by pip) + // and the dismiss-part (animated by launcher). + mixed.mInFlightSubAnimations = 2; + // immediately make the wallpaper visible (so that we don't see it pop-in during + // the time it takes to start recents animation (which is remote). + if (wallpaper != null) { + startTransaction.show(wallpaper.getLeash()).setAlpha(wallpaper.getLeash(), 1.f); + } + // make a new startTransaction because pip's startEnterAnimation "consumes" it so + // we need a separate one to send over to launcher. + SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction(); + // Let split update internal state for dismiss. + mSplitHandler.prepareDismissAnimation(STAGE_TYPE_UNDEFINED, + EXIT_REASON_CHILD_TASK_ENTER_PIP, everythingElse, otherStartT, + finishTransaction); + + // We are trying to accommodate launcher's close animation which can't handle the + // divider-bar, so if split-handler is closing the divider-bar, just hide it and remove + // from transition info. + for (int i = everythingElse.getChanges().size() - 1; i >= 0; --i) { + if ((everythingElse.getChanges().get(i).getFlags() & FLAG_IS_DIVIDER_BAR) != 0) { + everythingElse.getChanges().remove(i); + break; + } + } + + mPipHandler.startEnterAnimation(pipChange, startTransaction, finishTransaction, + finishCB); + // Dispatch the rest of the transition normally. This will most-likely be taken by + // recents or default handler. + mPlayer.dispatchTransition(mixed.mTransition, everythingElse, otherStartT, + finishTransaction, finishCB, this); + } else { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Not leaving split, so just " + + "forward animation to Pip-Handler."); + // This happens if the pip-ing activity is in a multi-activity task (and thus a + // new pip task is spawned). In this case, we don't actually exit split so we can + // just let pip transition handle the animation verbatim. + mixed.mInFlightSubAnimations = 1; + mPipHandler.startAnimation(mixed.mTransition, info, startTransaction, finishTransaction, + finishCB); + } + return true; + } + + @Override + public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + } + + @Override + public void onTransitionMerged(@NonNull IBinder transition) { + MixedTransition mixed = null; + for (int i = mActiveTransitions.size() - 1; i >= 0; --i) { + if (mActiveTransitions.get(i).mTransition != transition) continue; + mixed = mActiveTransitions.remove(i); + break; + } + if (mixed == null) return; + if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) { + mPipHandler.onTransitionMerged(transition); + } + } +} 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 9154226b7b22..c3eaa8ee1da0 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 @@ -24,6 +24,7 @@ import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS; import static android.app.ActivityOptions.ANIM_SCALE_UP; import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN; import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED; import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE; @@ -42,6 +43,7 @@ import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_RELAUNCH; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; +import static android.view.WindowManager.transitTypeToString; import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS; import static android.window.TransitionInfo.FLAG_IS_DISPLAY; import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION; @@ -470,8 +472,9 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } final Rect clipRect = Transitions.isClosingType(change.getMode()) - ? mRotator.getEndBoundsInStartRotation(change) - : change.getEndAbsBounds(); + ? new Rect(mRotator.getEndBoundsInStartRotation(change)) + : new Rect(change.getEndAbsBounds()); + clipRect.offsetTo(0, 0); if (delayedEdgeExtension) { // If the edge extension needs to happen after the startTransition has been @@ -684,6 +687,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { final Rect endBounds = Transitions.isClosingType(changeMode) ? mRotator.getEndBoundsInStartRotation(change) : change.getEndAbsBounds(); + final boolean isDream = + isTask && change.getTaskInfo().topActivityType == ACTIVITY_TYPE_DREAM; if (info.isKeyguardGoingAway()) { a = mTransitionAnimation.loadKeyguardExitAnimation(flags, @@ -726,7 +731,17 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } else { int animAttr = 0; boolean translucent = false; - if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_OPEN) { + if (isDream) { + if (type == TRANSIT_OPEN) { + animAttr = enter + ? R.styleable.WindowAnimation_dreamActivityOpenEnterAnimation + : R.styleable.WindowAnimation_dreamActivityOpenExitAnimation; + } else if (type == TRANSIT_CLOSE) { + animAttr = enter + ? 0 + : R.styleable.WindowAnimation_dreamActivityCloseExitAnimation; + } + } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_OPEN) { animAttr = enter ? R.styleable.WindowAnimation_wallpaperIntraOpenEnterAnimation : R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation; @@ -790,6 +805,11 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { a = mTransitionAnimation.loadDefaultAnimationAttr(animAttr, translucent); } } + + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, + "loadAnimation: anim=%s animAttr=0x%x type=%s isEntrance=%b", a, animAttr, + transitTypeToString(type), + enter); } if (a != null) { @@ -954,7 +974,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { private static void applyTransformation(long time, SurfaceControl.Transaction t, SurfaceControl leash, Animation anim, Transformation transformation, float[] matrix, - Point position, float cornerRadius, @Nullable Rect clipRect) { + Point position, float cornerRadius, @Nullable Rect immutableClipRect) { anim.getTransformation(time, transformation); if (position != null) { transformation.getMatrix().postTranslate(position.x, position.y); @@ -962,6 +982,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { t.setMatrix(leash, transformation.getMatrix(), matrix); t.setAlpha(leash, transformation.getAlpha()); + final Rect clipRect = immutableClipRect == null ? null : new Rect(immutableClipRect); Insets extensionInsets = Insets.min(transformation.getInsets(), Insets.NONE); if (!extensionInsets.equals(Insets.NONE) && clipRect != null && !clipRect.isEmpty()) { // Clip out any overflowing edge extension 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 435d67087f34..de0f47fa0a6b 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 @@ -23,6 +23,7 @@ import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; +import static android.window.TransitionInfo.FLAG_IS_INPUT_METHOD; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; @@ -287,12 +288,14 @@ public class Transitions implements RemoteCallable<Transitions> { finishT.setAlpha(leash, 1.f); } } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) { - // Wallpaper is a bit of an anomaly: it's visibility is tied to other WindowStates. - // As a result, we actually can't hide it's WindowToken because there may not be a - // transition associated with it becoming visible again. Fortunately, since it is - // always z-ordered to the back, we don't have to worry about it flickering to the - // front during reparenting, so the hide here isn't necessary for it. - if ((change.getFlags() & FLAG_IS_WALLPAPER) == 0) { + // Wallpaper/IME are anomalies: their visibility is tied to other WindowStates. + // As a result, we actually can't hide their WindowTokens because there may not be a + // transition associated with them becoming visible again. Fortunately, since + // wallpapers are always z-ordered to the back, we don't have to worry about it + // flickering to the front during reparenting. Similarly, the IME is reparented to + // the associated app, so its visibility is coupled. So, an explicit hide is not + // needed visually anyways. + if ((change.getFlags() & (FLAG_IS_WALLPAPER | FLAG_IS_INPUT_METHOD)) == 0) { finishT.hide(leash); } } @@ -309,13 +312,14 @@ public class Transitions implements RemoteCallable<Transitions> { if (info.getRootLeash().isValid()) { t.show(info.getRootLeash()); } + final int numChanges = info.getChanges().size(); // Put animating stuff above this line and put static stuff below it. - int zSplitLine = info.getChanges().size(); + final int zSplitLine = numChanges + 1; // changes should be ordered top-to-bottom in z - for (int i = info.getChanges().size() - 1; i >= 0; --i) { + for (int i = numChanges - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); final SurfaceControl leash = change.getLeash(); - final int mode = info.getChanges().get(i).getMode(); + final int mode = change.getMode(); // Don't reparent anything that isn't independent within its parents if (!TransitionInfo.isIndependent(change, info)) { @@ -329,26 +333,31 @@ public class Transitions implements RemoteCallable<Transitions> { t.setPosition(leash, change.getStartAbsBounds().left - info.getRootOffset().x, change.getStartAbsBounds().top - info.getRootOffset().y); } + final int layer; // Put all the OPEN/SHOW on top - if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) { + if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) { + // Wallpaper is always at the bottom. + layer = 0; + } else if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) { if (isOpening) { // put on top - t.setLayer(leash, zSplitLine + info.getChanges().size() - i); + layer = zSplitLine + numChanges - i; } else { // put on bottom - t.setLayer(leash, zSplitLine - i); + layer = zSplitLine - i; } } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) { if (isOpening) { // put on bottom and leave visible - t.setLayer(leash, zSplitLine - i); + layer = zSplitLine - i; } else { // put on top - t.setLayer(leash, zSplitLine + info.getChanges().size() - i); + layer = zSplitLine + numChanges - i; } } else { // CHANGE or other - t.setLayer(leash, zSplitLine + info.getChanges().size() - i); + layer = zSplitLine + numChanges - i; } + t.setLayer(leash, layer); } } @@ -377,6 +386,7 @@ public class Transitions implements RemoteCallable<Transitions> { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Invalid root leash (%s): %s", transitionToken, info); t.apply(); + finishT.apply(); onAbort(transitionToken); return; } @@ -400,6 +410,7 @@ public class Transitions implements RemoteCallable<Transitions> { } if (nonTaskChange && transferStartingWindow) { t.apply(); + finishT.apply(); // Treat this as an abort since we are bypassing any merge logic and effectively // finishing immediately. onAbort(transitionToken); @@ -435,33 +446,42 @@ public class Transitions implements RemoteCallable<Transitions> { playing.mToken, (wct, cb) -> onFinish(merging.mToken, wct, cb)); } - boolean startAnimation(@NonNull ActiveTransition active, TransitionHandler handler) { - return handler.startAnimation(active.mToken, active.mInfo, active.mStartT, active.mFinishT, - (wct, cb) -> onFinish(active.mToken, wct, cb)); - } - - void playTransition(@NonNull ActiveTransition active) { + private void playTransition(@NonNull ActiveTransition active) { setupAnimHierarchy(active.mInfo, active.mStartT, active.mFinishT); // If a handler already chose to run this animation, try delegating to it first. if (active.mHandler != null) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try firstHandler %s", active.mHandler); - if (startAnimation(active, active.mHandler)) { + boolean consumed = active.mHandler.startAnimation(active.mToken, active.mInfo, + active.mStartT, active.mFinishT, (wct, cb) -> onFinish(active.mToken, wct, cb)); + if (consumed) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by firstHandler"); return; } } - // Otherwise give every other handler a chance (in order) + // Otherwise give every other handler a chance + active.mHandler = dispatchTransition(active.mToken, active.mInfo, active.mStartT, + active.mFinishT, (wct, cb) -> onFinish(active.mToken, wct, cb), active.mHandler); + } + + /** + * Gives every handler (in order) a chance to animate until one consumes the transition. + * @return the handler which consumed the transition. + */ + TransitionHandler dispatchTransition(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT, + @NonNull TransitionFinishCallback finishCB, @Nullable TransitionHandler skip) { for (int i = mHandlers.size() - 1; i >= 0; --i) { - if (mHandlers.get(i) == active.mHandler) continue; + if (mHandlers.get(i) == skip) continue; ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try handler %s", mHandlers.get(i)); - if (startAnimation(active, mHandlers.get(i))) { + boolean consumed = mHandlers.get(i).startAnimation(transition, info, startT, finishT, + finishCB); + if (consumed) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by %s", mHandlers.get(i)); - active.mHandler = mHandlers.get(i); - return; + return mHandlers.get(i); } } throw new IllegalStateException( @@ -615,8 +635,9 @@ public class Transitions implements RemoteCallable<Transitions> { if (wct == null) { wct = new WindowContainerTransaction(); } - mDisplayController.getChangeController().dispatchOnRotateDisplay(wct, - change.getDisplayId(), change.getStartRotation(), change.getEndRotation()); + mDisplayController.getChangeController().dispatchOnDisplayChange(wct, + change.getDisplayId(), change.getStartRotation(), change.getEndRotation(), + null /* newDisplayAreaInfo */); } } active.mToken = mOrganizer.startTransition( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java new file mode 100644 index 000000000000..530d47416665 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java @@ -0,0 +1,219 @@ +/* + * 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.unfold; + +import android.annotation.NonNull; +import android.app.ActivityManager.RunningTaskInfo; +import android.app.TaskInfo; +import android.util.SparseArray; +import android.view.SurfaceControl; + +import com.android.wm.shell.common.TransactionPool; +import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener; +import com.android.wm.shell.unfold.animation.UnfoldTaskAnimator; + +import java.util.List; +import java.util.Optional; +import java.util.concurrent.Executor; + +import dagger.Lazy; + +/** + * Manages fold/unfold animations of tasks on foldable devices. + * When folding or unfolding a foldable device we play animations that + * transform task cropping/scaling/rounded corners. + * + * This controller manages: + * 1) Folding/unfolding when Shell transitions disabled + * 2) Folding when Shell transitions enabled, unfolding is managed by + * {@link com.android.wm.shell.unfold.UnfoldTransitionHandler} + */ +public class UnfoldAnimationController implements UnfoldListener { + + private final ShellUnfoldProgressProvider mUnfoldProgressProvider; + private final Executor mExecutor; + private final TransactionPool mTransactionPool; + private final List<UnfoldTaskAnimator> mAnimators; + private final Lazy<Optional<UnfoldTransitionHandler>> mUnfoldTransitionHandler; + + private final SparseArray<SurfaceControl> mTaskSurfaces = new SparseArray<>(); + private final SparseArray<UnfoldTaskAnimator> mAnimatorsByTaskId = new SparseArray<>(); + + public UnfoldAnimationController(@NonNull TransactionPool transactionPool, + @NonNull ShellUnfoldProgressProvider unfoldProgressProvider, + @NonNull List<UnfoldTaskAnimator> animators, + @NonNull Lazy<Optional<UnfoldTransitionHandler>> unfoldTransitionHandler, + @NonNull Executor executor) { + mUnfoldProgressProvider = unfoldProgressProvider; + mUnfoldTransitionHandler = unfoldTransitionHandler; + mTransactionPool = transactionPool; + mExecutor = executor; + mAnimators = animators; + } + + /** + * Initializes the controller, starts listening for the external events + */ + public void init() { + mUnfoldProgressProvider.addListener(mExecutor, this); + + for (int i = 0; i < mAnimators.size(); i++) { + final UnfoldTaskAnimator animator = mAnimators.get(i); + animator.init(); + animator.start(); + } + } + + /** + * Called when a task appeared + * @param taskInfo info for the appeared task + * @param leash surface leash for the appeared task + */ + public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { + mTaskSurfaces.put(taskInfo.taskId, leash); + + // Find the first matching animator + for (int i = 0; i < mAnimators.size(); i++) { + final UnfoldTaskAnimator animator = mAnimators.get(i); + if (animator.isApplicableTask(taskInfo)) { + mAnimatorsByTaskId.put(taskInfo.taskId, animator); + animator.onTaskAppeared(taskInfo, leash); + break; + } + } + } + + /** + * Called when task info changed + * @param taskInfo info for the changed task + */ + public void onTaskInfoChanged(RunningTaskInfo taskInfo) { + final UnfoldTaskAnimator animator = mAnimatorsByTaskId.get(taskInfo.taskId); + final boolean isCurrentlyApplicable = animator != null; + + if (isCurrentlyApplicable) { + final boolean isApplicable = animator.isApplicableTask(taskInfo); + if (isApplicable) { + // Still applicable, send update + animator.onTaskChanged(taskInfo); + } else { + // Became inapplicable + resetTask(animator, taskInfo); + animator.onTaskVanished(taskInfo); + mAnimatorsByTaskId.remove(taskInfo.taskId); + } + } else { + // Find the first matching animator + for (int i = 0; i < mAnimators.size(); i++) { + final UnfoldTaskAnimator currentAnimator = mAnimators.get(i); + if (currentAnimator.isApplicableTask(taskInfo)) { + // Became applicable + mAnimatorsByTaskId.put(taskInfo.taskId, currentAnimator); + + SurfaceControl leash = mTaskSurfaces.get(taskInfo.taskId); + currentAnimator.onTaskAppeared(taskInfo, leash); + break; + } + } + } + } + + /** + * Called when a task vanished + * @param taskInfo info for the vanished task + */ + public void onTaskVanished(RunningTaskInfo taskInfo) { + mTaskSurfaces.remove(taskInfo.taskId); + + final UnfoldTaskAnimator animator = mAnimatorsByTaskId.get(taskInfo.taskId); + final boolean isCurrentlyApplicable = animator != null; + + if (isCurrentlyApplicable) { + resetTask(animator, taskInfo); + animator.onTaskVanished(taskInfo); + mAnimatorsByTaskId.remove(taskInfo.taskId); + } + } + + @Override + public void onStateChangeStarted() { + if (mUnfoldTransitionHandler.get().get().willHandleTransition()) { + return; + } + + SurfaceControl.Transaction transaction = null; + for (int i = 0; i < mAnimators.size(); i++) { + final UnfoldTaskAnimator animator = mAnimators.get(i); + if (animator.hasActiveTasks()) { + if (transaction == null) transaction = mTransactionPool.acquire(); + animator.prepareStartTransaction(transaction); + } + } + + if (transaction != null) { + transaction.apply(); + mTransactionPool.release(transaction); + } + } + + @Override + public void onStateChangeProgress(float progress) { + if (mUnfoldTransitionHandler.get().get().willHandleTransition()) { + return; + } + + SurfaceControl.Transaction transaction = null; + for (int i = 0; i < mAnimators.size(); i++) { + final UnfoldTaskAnimator animator = mAnimators.get(i); + if (animator.hasActiveTasks()) { + if (transaction == null) transaction = mTransactionPool.acquire(); + animator.applyAnimationProgress(progress, transaction); + } + } + + if (transaction != null) { + transaction.apply(); + mTransactionPool.release(transaction); + } + } + + @Override + public void onStateChangeFinished() { + if (mUnfoldTransitionHandler.get().get().willHandleTransition()) { + return; + } + + final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); + + for (int i = 0; i < mAnimators.size(); i++) { + final UnfoldTaskAnimator animator = mAnimators.get(i); + animator.resetAllSurfaces(transaction); + animator.prepareFinishTransaction(transaction); + } + + transaction.apply(); + + mTransactionPool.release(transaction); + } + + private void resetTask(UnfoldTaskAnimator animator, TaskInfo taskInfo) { + final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); + animator.resetSurface(taskInfo, transaction); + transaction.apply(); + mTransactionPool.release(transaction); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java index 9faf454261d3..86ca292399cb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java @@ -79,7 +79,7 @@ public class UnfoldBackgroundController { } private float[] getBackgroundColor(Context context) { - int colorInt = context.getResources().getColor(R.color.unfold_transition_background); + int colorInt = context.getResources().getColor(R.color.taskbar_background); return new float[]{ (float) red(colorInt) / 255.0F, (float) green(colorInt) / 255.0F, 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 639603941c18..9bf32faa12bd 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 @@ -16,8 +16,6 @@ package com.android.wm.shell.unfold; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.view.WindowManager.TRANSIT_CHANGE; import android.os.IBinder; @@ -34,11 +32,19 @@ import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.transition.Transitions.TransitionFinishCallback; import com.android.wm.shell.transition.Transitions.TransitionHandler; import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener; +import com.android.wm.shell.unfold.animation.FullscreenUnfoldTaskAnimator; +import com.android.wm.shell.unfold.animation.SplitTaskUnfoldAnimator; +import com.android.wm.shell.unfold.animation.UnfoldTaskAnimator; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; +/** + * Transition handler that is responsible for animating app surfaces when unfolding of foldable + * devices. It does not handle the folding animation, which is done in + * {@link UnfoldAnimationController}. + */ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListener { private final ShellUnfoldProgressProvider mUnfoldProgressProvider; @@ -51,17 +57,26 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene @Nullable private IBinder mTransition; - private final List<TransitionInfo.Change> mAnimatedFullscreenTasks = new ArrayList<>(); + private final List<UnfoldTaskAnimator> mAnimators = new ArrayList<>(); public UnfoldTransitionHandler(ShellUnfoldProgressProvider unfoldProgressProvider, - TransactionPool transactionPool, Executor executor, Transitions transitions) { + FullscreenUnfoldTaskAnimator fullscreenUnfoldAnimator, + SplitTaskUnfoldAnimator splitUnfoldTaskAnimator, + TransactionPool transactionPool, + Executor executor, Transitions transitions) { mUnfoldProgressProvider = unfoldProgressProvider; mTransactionPool = transactionPool; mExecutor = executor; mTransitions = transitions; + + mAnimators.add(splitUnfoldTaskAnimator); + mAnimators.add(fullscreenUnfoldAnimator); } public void init() { + for (int i = 0; i < mAnimators.size(); i++) { + mAnimators.get(i).init(); + } mTransitions.addHandler(this); mUnfoldProgressProvider.addListener(mExecutor, this); } @@ -71,49 +86,69 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull TransitionFinishCallback finishCallback) { - if (transition != mTransition) return false; - startTransaction.apply(); - - mAnimatedFullscreenTasks.clear(); - info.getChanges().forEach(change -> { - final boolean allowedToAnimate = change.getTaskInfo() != null - && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_FULLSCREEN - && change.getTaskInfo().getActivityType() != ACTIVITY_TYPE_HOME - && change.getMode() == TRANSIT_CHANGE; - - if (allowedToAnimate) { - mAnimatedFullscreenTasks.add(change); + for (int i = 0; i < mAnimators.size(); i++) { + final UnfoldTaskAnimator animator = mAnimators.get(i); + animator.clearTasks(); + + info.getChanges().forEach(change -> { + if (change.getTaskInfo() != null + && change.getMode() == TRANSIT_CHANGE + && animator.isApplicableTask(change.getTaskInfo())) { + animator.onTaskAppeared(change.getTaskInfo(), change.getLeash()); + } + }); + + if (animator.hasActiveTasks()) { + animator.prepareStartTransaction(startTransaction); + animator.prepareFinishTransaction(finishTransaction); + animator.start(); } - }); + } + startTransaction.apply(); mFinishCallback = finishCallback; - mTransition = null; return true; } @Override public void onStateChangeProgress(float progress) { - mAnimatedFullscreenTasks.forEach(change -> { - final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); + if (mTransition == null) return; + + SurfaceControl.Transaction transaction = null; - // TODO: this is a placeholder animation, replace with a spec version in the next CLs - final float testScale = 0.8f + 0.2f * progress; - transaction.setScale(change.getLeash(), testScale, testScale); + for (int i = 0; i < mAnimators.size(); i++) { + final UnfoldTaskAnimator animator = mAnimators.get(i); + if (animator.hasActiveTasks()) { + if (transaction == null) { + transaction = mTransactionPool.acquire(); + } + + animator.applyAnimationProgress(progress, transaction); + } + } + + if (transaction != null) { transaction.apply(); mTransactionPool.release(transaction); - }); + } } @Override public void onStateChangeFinished() { - if (mFinishCallback != null) { - mFinishCallback.onTransitionFinished(null, null); - mFinishCallback = null; - mAnimatedFullscreenTasks.clear(); + if (mFinishCallback == null) return; + + for (int i = 0; i < mAnimators.size(); i++) { + final UnfoldTaskAnimator animator = mAnimators.get(i); + animator.clearTasks(); + animator.stop(); } + + mFinishCallback.onTransitionFinished(null, null); + mFinishCallback = null; + mTransition = null; } @Nullable @@ -127,4 +162,8 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene } return null; } + + public boolean willHandleTransition() { + return mTransition != null; + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenUnfoldController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java index aa3868cfca84..eab82f00e962 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenUnfoldController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,16 +14,16 @@ * limitations under the License. */ -package com.android.wm.shell.fullscreen; +package com.android.wm.shell.unfold.animation; -import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.util.MathUtils.lerp; import static android.view.Display.DEFAULT_DISPLAY; import android.animation.RectEvaluator; import android.animation.TypeEvaluator; import android.annotation.NonNull; -import android.app.ActivityManager; import android.app.TaskInfo; import android.content.Context; import android.graphics.Matrix; @@ -32,22 +32,26 @@ import android.util.SparseArray; import android.view.InsetsSource; import android.view.InsetsState; import android.view.SurfaceControl; +import android.view.SurfaceControl.Transaction; import com.android.internal.policy.ScreenDecorationsUtils; import com.android.wm.shell.common.DisplayInsetsController; -import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener; -import com.android.wm.shell.unfold.ShellUnfoldProgressProvider; -import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener; +import com.android.wm.shell.unfold.UnfoldAnimationController; import com.android.wm.shell.unfold.UnfoldBackgroundController; -import java.util.concurrent.Executor; - /** - * Controls full screen app unfold transition: animating cropping window and scaling when - * folding or unfolding a foldable device. + * This helper class contains logic that calculates scaling and cropping parameters + * for the folding/unfolding animation. As an input it receives TaskInfo objects and + * surfaces leashes and as an output it could fill surface transactions with required + * transformations. + * + * This class is used by + * {@link com.android.wm.shell.unfold.UnfoldTransitionHandler} and + * {@link UnfoldAnimationController}. They use independent + * instances of FullscreenUnfoldTaskAnimator. */ -public final class FullscreenUnfoldController implements UnfoldListener, - OnInsetsChangedListener { +public class FullscreenUnfoldTaskAnimator implements UnfoldTaskAnimator, + DisplayInsetsController.OnInsetsChangedListener { private static final float[] FLOAT_9 = new float[9]; private static final TypeEvaluator<Rect> RECT_EVALUATOR = new RectEvaluator(new Rect()); @@ -57,49 +61,77 @@ public final class FullscreenUnfoldController implements UnfoldListener, private static final float END_SCALE = 1f; private static final float START_SCALE = END_SCALE - VERTICAL_START_MARGIN * 2; - private final Executor mExecutor; - private final ShellUnfoldProgressProvider mProgressProvider; - private final DisplayInsetsController mDisplayInsetsController; - private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>(); + private final int mExpandedTaskBarHeight; + private final float mWindowCornerRadiusPx; + private final DisplayInsetsController mDisplayInsetsController; private final UnfoldBackgroundController mBackgroundController; private InsetsSource mTaskbarInsetsSource; - private final float mWindowCornerRadiusPx; - private final float mExpandedTaskBarHeight; - - private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction(); - - public FullscreenUnfoldController( - @NonNull Context context, - @NonNull Executor executor, + public FullscreenUnfoldTaskAnimator(Context context, @NonNull UnfoldBackgroundController backgroundController, - @NonNull ShellUnfoldProgressProvider progressProvider, - @NonNull DisplayInsetsController displayInsetsController - ) { - mExecutor = executor; - mProgressProvider = progressProvider; + DisplayInsetsController displayInsetsController) { mDisplayInsetsController = displayInsetsController; - mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context); + mBackgroundController = backgroundController; mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize( com.android.internal.R.dimen.taskbar_frame_height); - mBackgroundController = backgroundController; + mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context); } - /** - * Initializes the controller - */ public void init() { - mProgressProvider.addListener(mExecutor, this); mDisplayInsetsController.addInsetsChangedListener(DEFAULT_DISPLAY, this); } @Override - public void onStateChangeProgress(float progress) { - if (mAnimationContextByTaskId.size() == 0) return; + public void insetsChanged(InsetsState insetsState) { + mTaskbarInsetsSource = insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR); + for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) { + AnimationContext context = mAnimationContextByTaskId.valueAt(i); + context.update(mTaskbarInsetsSource, context.mTaskInfo); + } + } + + public boolean hasActiveTasks() { + return mAnimationContextByTaskId.size() > 0; + } + + @Override + public void onTaskAppeared(TaskInfo taskInfo, SurfaceControl leash) { + AnimationContext animationContext = new AnimationContext(leash, mTaskbarInsetsSource, + taskInfo); + mAnimationContextByTaskId.put(taskInfo.taskId, animationContext); + } + + @Override + public void onTaskChanged(TaskInfo taskInfo) { + AnimationContext animationContext = mAnimationContextByTaskId.get(taskInfo.taskId); + if (animationContext != null) { + animationContext.update(mTaskbarInsetsSource, taskInfo); + } + } + + @Override + public void onTaskVanished(TaskInfo taskInfo) { + mAnimationContextByTaskId.remove(taskInfo.taskId); + } - mBackgroundController.ensureBackground(mTransaction); + @Override + public void clearTasks() { + mAnimationContextByTaskId.clear(); + } + + @Override + public void resetSurface(TaskInfo taskInfo, Transaction transaction) { + final AnimationContext context = mAnimationContextByTaskId.get(taskInfo.taskId); + if (context != null) { + resetSurface(context, transaction); + } + } + + @Override + public void applyAnimationProgress(float progress, Transaction transaction) { + if (mAnimationContextByTaskId.size() == 0) return; for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) { final AnimationContext context = mAnimationContextByTaskId.valueAt(i); @@ -111,75 +143,41 @@ public final class FullscreenUnfoldController implements UnfoldListener, context.mMatrix.setScale(scale, scale, context.mCurrentCropRect.exactCenterX(), context.mCurrentCropRect.exactCenterY()); - mTransaction.setWindowCrop(context.mLeash, context.mCurrentCropRect) + transaction.setWindowCrop(context.mLeash, context.mCurrentCropRect) .setMatrix(context.mLeash, context.mMatrix, FLOAT_9) - .setCornerRadius(context.mLeash, mWindowCornerRadiusPx); + .setCornerRadius(context.mLeash, mWindowCornerRadiusPx) + .show(context.mLeash); } - - mTransaction.apply(); } @Override - public void onStateChangeFinished() { - for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) { - final AnimationContext context = mAnimationContextByTaskId.valueAt(i); - resetSurface(context); - } - - mBackgroundController.removeBackground(mTransaction); - mTransaction.apply(); + public void prepareStartTransaction(Transaction transaction) { + mBackgroundController.ensureBackground(transaction); } @Override - public void insetsChanged(InsetsState insetsState) { - mTaskbarInsetsSource = insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR); - for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) { - AnimationContext context = mAnimationContextByTaskId.valueAt(i); - context.update(mTaskbarInsetsSource, context.mTaskInfo); - } + public void prepareFinishTransaction(Transaction transaction) { + mBackgroundController.removeBackground(transaction); } - /** - * Called when a new matching task appeared - */ - public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { - AnimationContext animationContext = new AnimationContext(leash, mTaskbarInsetsSource, - taskInfo); - mAnimationContextByTaskId.put(taskInfo.taskId, animationContext); - } - - /** - * Called when matching task changed - */ - public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { - AnimationContext animationContext = mAnimationContextByTaskId.get(taskInfo.taskId); - if (animationContext != null) { - animationContext.update(mTaskbarInsetsSource, taskInfo); - } + @Override + public boolean isApplicableTask(TaskInfo taskInfo) { + return taskInfo != null && taskInfo.isVisible() + && taskInfo.realActivity != null // to filter out parents created by organizer + && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN + && taskInfo.getActivityType() != ACTIVITY_TYPE_HOME; } - /** - * Called when matching task vanished - */ - public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { - AnimationContext animationContext = mAnimationContextByTaskId.get(taskInfo.taskId); - if (animationContext != null) { - // PiP task has its own cleanup path, ignore surface reset to avoid conflict. - if (taskInfo.getWindowingMode() != WINDOWING_MODE_PINNED) { - resetSurface(animationContext); - } - mAnimationContextByTaskId.remove(taskInfo.taskId); - } - - if (mAnimationContextByTaskId.size() == 0) { - mBackgroundController.removeBackground(mTransaction); + @Override + public void resetAllSurfaces(Transaction transaction) { + for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) { + final AnimationContext context = mAnimationContextByTaskId.valueAt(i); + resetSurface(context, transaction); } - - mTransaction.apply(); } - private void resetSurface(AnimationContext context) { - mTransaction + private void resetSurface(AnimationContext context, Transaction transaction) { + transaction .setWindowCrop(context.mLeash, null) .setCornerRadius(context.mLeash, 0.0F) .setMatrix(context.mLeash, 1.0F, 0.0F, 0.0F, 1.0F) @@ -197,10 +195,9 @@ public final class FullscreenUnfoldController implements UnfoldListener, TaskInfo mTaskInfo; - private AnimationContext(SurfaceControl leash, - InsetsSource taskBarInsetsSource, - TaskInfo taskInfo) { - this.mLeash = leash; + private AnimationContext(SurfaceControl leash, InsetsSource taskBarInsetsSource, + TaskInfo taskInfo) { + mLeash = leash; update(taskBarInsetsSource, taskInfo); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskUnfoldController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java index 59eecb5db136..6e10ebe94c5d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskUnfoldController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,17 +14,19 @@ * limitations under the License. */ -package com.android.wm.shell.splitscreen; +package com.android.wm.shell.unfold.animation; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.view.Display.DEFAULT_DISPLAY; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; +import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN; +import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; import android.animation.RectEvaluator; import android.animation.TypeEvaluator; -import android.annotation.NonNull; -import android.app.ActivityManager; +import android.app.TaskInfo; import android.content.Context; import android.graphics.Insets; import android.graphics.Rect; @@ -32,67 +34,130 @@ import android.util.SparseArray; import android.view.InsetsSource; import android.view.InsetsState; import android.view.SurfaceControl; +import android.view.SurfaceControl.Transaction; import com.android.internal.policy.ScreenDecorationsUtils; import com.android.wm.shell.common.DisplayInsetsController; -import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener; -import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; -import com.android.wm.shell.unfold.ShellUnfoldProgressProvider; -import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener; +import com.android.wm.shell.splitscreen.SplitScreen; +import com.android.wm.shell.splitscreen.SplitScreen.SplitScreenListener; +import com.android.wm.shell.splitscreen.SplitScreenController; +import com.android.wm.shell.unfold.UnfoldAnimationController; import com.android.wm.shell.unfold.UnfoldBackgroundController; +import java.util.Optional; import java.util.concurrent.Executor; +import dagger.Lazy; + /** - * Controls transformations of the split screen task surfaces in response - * to the unfolding/folding action on foldable devices + * This helper class contains logic that calculates scaling and cropping parameters + * for the folding/unfolding animation. As an input it receives TaskInfo objects and + * surfaces leashes and as an output it could fill surface transactions with required + * transformations. + * + * This class is used by + * {@link com.android.wm.shell.unfold.UnfoldTransitionHandler} and + * {@link UnfoldAnimationController}. + * They use independent instances of SplitTaskUnfoldAnimator. */ -public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChangedListener { +public class SplitTaskUnfoldAnimator implements UnfoldTaskAnimator, + DisplayInsetsController.OnInsetsChangedListener, SplitScreenListener { private static final TypeEvaluator<Rect> RECT_EVALUATOR = new RectEvaluator(new Rect()); private static final float CROPPING_START_MARGIN_FRACTION = 0.05f; - private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>(); - private final ShellUnfoldProgressProvider mUnfoldProgressProvider; - private final DisplayInsetsController mDisplayInsetsController; - private final UnfoldBackgroundController mBackgroundController; private final Executor mExecutor; + private final DisplayInsetsController mDisplayInsetsController; + private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>(); private final int mExpandedTaskBarHeight; private final float mWindowCornerRadiusPx; - private final Rect mStageBounds = new Rect(); - private final TransactionPool mTransactionPool; + private final Lazy<Optional<SplitScreenController>> mSplitScreenController; + private final UnfoldBackgroundController mUnfoldBackgroundController; + + private final Rect mMainStageBounds = new Rect(); + private final Rect mSideStageBounds = new Rect(); + private final Rect mRootStageBounds = new Rect(); private InsetsSource mTaskbarInsetsSource; - private boolean mBothStagesVisible; - - public StageTaskUnfoldController(@NonNull Context context, - @NonNull TransactionPool transactionPool, - @NonNull ShellUnfoldProgressProvider unfoldProgressProvider, - @NonNull DisplayInsetsController displayInsetsController, - @NonNull UnfoldBackgroundController backgroundController, - @NonNull Executor executor) { - mUnfoldProgressProvider = unfoldProgressProvider; - mTransactionPool = transactionPool; - mExecutor = executor; - mBackgroundController = backgroundController; + + @SplitPosition + private int mMainStagePosition = SPLIT_POSITION_UNDEFINED; + @SplitPosition + private int mSideStagePosition = SPLIT_POSITION_UNDEFINED; + + public SplitTaskUnfoldAnimator(Context context, Executor executor, + Lazy<Optional<SplitScreenController>> splitScreenController, + UnfoldBackgroundController unfoldBackgroundController, + DisplayInsetsController displayInsetsController) { mDisplayInsetsController = displayInsetsController; - mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context); + mExecutor = executor; + mUnfoldBackgroundController = unfoldBackgroundController; + mSplitScreenController = splitScreenController; mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize( com.android.internal.R.dimen.taskbar_frame_height); + mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context); } - /** - * Initializes the controller, starts listening for the external events - */ + /** Initializes the animator, this should be called only once */ + @Override public void init() { - mUnfoldProgressProvider.addListener(mExecutor, this); mDisplayInsetsController.addInsetsChangedListener(DEFAULT_DISPLAY, this); } + /** + * Starts listening for split-screen changes and gets initial split-screen + * layout information through the listener + */ + @Override + public void start() { + mSplitScreenController.get().get().asSplitScreen() + .registerSplitScreenListener(this, mExecutor); + } + + /** + * Stops listening for the split-screen layout changes + */ + @Override + public void stop() { + mSplitScreenController.get().get().asSplitScreen() + .unregisterSplitScreenListener(this); + } + @Override public void insetsChanged(InsetsState insetsState) { mTaskbarInsetsSource = insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR); + updateContexts(); + } + + @Override + public void onTaskStageChanged(int taskId, int stage, boolean visible) { + final AnimationContext context = mAnimationContextByTaskId.get(taskId); + if (context != null) { + context.mStageType = stage; + context.update(); + } + } + + @Override + public void onStagePositionChanged(int stage, int position) { + if (stage == STAGE_TYPE_MAIN) { + mMainStagePosition = position; + } else { + mSideStagePosition = position; + } + updateContexts(); + } + + @Override + public void onSplitBoundsChanged(Rect rootBounds, Rect mainBounds, Rect sideBounds) { + mRootStageBounds.set(rootBounds); + mMainStageBounds.set(mainBounds); + mSideStageBounds.set(sideBounds); + updateContexts(); + } + + private void updateContexts() { for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) { AnimationContext context = mAnimationContextByTaskId.valueAt(i); context.update(); @@ -100,44 +165,73 @@ public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChange } /** - * Called when split screen task appeared - * @param taskInfo info for the appeared task - * @param leash surface leash for the appeared task + * Register a split task in the animator + * @param taskInfo info of the task + * @param leash the surface of the task */ - public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { - // Only handle child task surface here. - if (!taskInfo.hasParentTask()) return; - + @Override + public void onTaskAppeared(TaskInfo taskInfo, SurfaceControl leash) { AnimationContext context = new AnimationContext(leash); mAnimationContextByTaskId.put(taskInfo.taskId, context); } /** - * Called when a split screen task vanished - * @param taskInfo info for the vanished task + * Unregister the task from the unfold animation + * @param taskInfo info of the task + */ + @Override + public void onTaskVanished(TaskInfo taskInfo) { + mAnimationContextByTaskId.remove(taskInfo.taskId); + } + + @Override + public boolean isApplicableTask(TaskInfo taskInfo) { + return taskInfo.hasParentTask() + && taskInfo.isVisible + && taskInfo.realActivity != null // to filter out parents created by organizer + && taskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW; + } + + /** + * Clear all registered tasks */ - public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { - if (!taskInfo.hasParentTask()) return; + @Override + public void clearTasks() { + mAnimationContextByTaskId.clear(); + } + /** + * Reset transformations of the task that could have been applied by the animator + * @param taskInfo task to reset + * @param transaction a transaction to write the changes to + */ + @Override + public void resetSurface(TaskInfo taskInfo, Transaction transaction) { AnimationContext context = mAnimationContextByTaskId.get(taskInfo.taskId); if (context != null) { - final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); resetSurface(transaction, context); - transaction.apply(); - mTransactionPool.release(transaction); } - mAnimationContextByTaskId.remove(taskInfo.taskId); } + /** + * Reset all surface transformation that could have been introduced by the animator + * @param transaction to write changes to + */ @Override - public void onStateChangeProgress(float progress) { - if (mAnimationContextByTaskId.size() == 0 || !mBothStagesVisible) return; - - final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); - mBackgroundController.ensureBackground(transaction); + public void resetAllSurfaces(Transaction transaction) { + for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) { + final AnimationContext context = mAnimationContextByTaskId.valueAt(i); + resetSurface(transaction, context); + } + } + @Override + public void applyAnimationProgress(float progress, Transaction transaction) { for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) { AnimationContext context = mAnimationContextByTaskId.valueAt(i); + if (context.mStageType == STAGE_TYPE_UNDEFINED) { + continue; + } context.mCurrentCropRect.set(RECT_EVALUATOR .evaluate(progress, context.mStartCropRect, context.mEndCropRect)); @@ -145,53 +239,25 @@ public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChange transaction.setWindowCrop(context.mLeash, context.mCurrentCropRect) .setCornerRadius(context.mLeash, mWindowCornerRadiusPx); } - - transaction.apply(); - - mTransactionPool.release(transaction); } @Override - public void onStateChangeFinished() { - resetTransformations(); + public void prepareStartTransaction(Transaction transaction) { + mUnfoldBackgroundController.ensureBackground(transaction); + mSplitScreenController.get().get().updateSplitScreenSurfaces(transaction); } - /** - * Called when split screen visibility changes - * @param bothStagesVisible true if both stages of the split screen are visible - */ - public void onSplitVisibilityChanged(boolean bothStagesVisible) { - mBothStagesVisible = bothStagesVisible; - if (!bothStagesVisible) { - resetTransformations(); - } + @Override + public void prepareFinishTransaction(Transaction transaction) { + mUnfoldBackgroundController.removeBackground(transaction); } /** - * Called when split screen stage bounds changed - * @param bounds new bounds for this stage + * @return true if there are tasks to animate */ - public void onLayoutChanged(Rect bounds, @SplitPosition int splitPosition, - boolean isLandscape) { - mStageBounds.set(bounds); - - for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) { - final AnimationContext context = mAnimationContextByTaskId.valueAt(i); - context.update(splitPosition, isLandscape); - } - } - - private void resetTransformations() { - final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); - - for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) { - final AnimationContext context = mAnimationContextByTaskId.valueAt(i); - resetSurface(transaction, context); - } - mBackgroundController.removeBackground(transaction); - transaction.apply(); - - mTransactionPool.release(transaction); + @Override + public boolean hasActiveTasks() { + return mAnimationContextByTaskId.size() > 0; } private void resetSurface(SurfaceControl.Transaction transaction, AnimationContext context) { @@ -202,26 +268,24 @@ public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChange private class AnimationContext { final SurfaceControl mLeash; + final Rect mStartCropRect = new Rect(); final Rect mEndCropRect = new Rect(); final Rect mCurrentCropRect = new Rect(); - private @SplitPosition int mSplitPosition = SPLIT_POSITION_UNDEFINED; - private boolean mIsLandscape = false; + @SplitScreen.StageType + int mStageType = STAGE_TYPE_UNDEFINED; private AnimationContext(SurfaceControl leash) { - this.mLeash = leash; - update(); - } - - private void update(@SplitPosition int splitPosition, boolean isLandscape) { - this.mSplitPosition = splitPosition; - this.mIsLandscape = isLandscape; + mLeash = leash; update(); } private void update() { - mStartCropRect.set(mStageBounds); + final Rect stageBounds = mStageType == STAGE_TYPE_MAIN + ? mMainStageBounds : mSideStageBounds; + + mStartCropRect.set(stageBounds); boolean taskbarExpanded = isTaskbarExpanded(); if (taskbarExpanded) { @@ -239,7 +303,8 @@ public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChange // Sides adjacent to split bar or task bar are not be animated. Insets margins; - if (mIsLandscape) { // Left and right splits. + final boolean isLandscape = mRootStageBounds.width() > mRootStageBounds.height(); + if (isLandscape) { // Left and right splits. margins = getLandscapeMargins(margin, taskbarExpanded); } else { // Top and bottom splits. margins = getPortraitMargins(margin, taskbarExpanded); @@ -251,7 +316,9 @@ public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChange int left = margin; int right = margin; int bottom = taskbarExpanded ? 0 : margin; // Taskbar margin. - if (mSplitPosition == SPLIT_POSITION_TOP_OR_LEFT) { + final int splitPosition = mStageType == STAGE_TYPE_MAIN + ? mMainStagePosition : mSideStagePosition; + if (splitPosition == SPLIT_POSITION_TOP_OR_LEFT) { right = 0; // Divider margin. } else { left = 0; // Divider margin. @@ -262,7 +329,9 @@ public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChange private Insets getPortraitMargins(int margin, boolean taskbarExpanded) { int bottom = margin; int top = margin; - if (mSplitPosition == SPLIT_POSITION_TOP_OR_LEFT) { + final int splitPosition = mStageType == STAGE_TYPE_MAIN + ? mMainStagePosition : mSideStagePosition; + if (splitPosition == SPLIT_POSITION_TOP_OR_LEFT) { bottom = 0; // Divider margin. } else { // Bottom split. top = 0; // Divider margin. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/UnfoldTaskAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/UnfoldTaskAnimator.java new file mode 100644 index 000000000000..e1e366301b46 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/UnfoldTaskAnimator.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.unfold.animation; + +import android.app.TaskInfo; +import android.view.SurfaceControl; +import android.view.SurfaceControl.Transaction; + +/** + * Interface for classes that handle animations of tasks when folding or unfolding + * foldable devices. + */ +public interface UnfoldTaskAnimator { + /** + * Initializes the animator, this should be called once in the lifetime of the animator + */ + default void init() {} + + /** + * Starts the animator, it might start listening for some events from the system. + * Applying animation should be done only when animator is started. + * Animator could be started/stopped several times. + */ + default void start() {} + + /** + * Stops the animator, it could unsubscribe from system events. + */ + default void stop() {} + + /** + * If this method returns true then task updates will be propagated to + * the animator using the onTaskAppeared/Changed/Vanished callbacks. + * @return true if this task should be animated by this animator + */ + default boolean isApplicableTask(TaskInfo taskInfo) { + return false; + } + + /** + * Called whenever a task applicable to this animator appeared + * (isApplicableTask returns true for this task) + * + * @param taskInfo info of the appeared task + * @param leash surface of the task + */ + default void onTaskAppeared(TaskInfo taskInfo, SurfaceControl leash) {} + + /** + * Called whenever a task applicable to this animator changed + * @param taskInfo info of the changed task + */ + default void onTaskChanged(TaskInfo taskInfo) {} + + /** + * Called whenever a task applicable to this animator vanished + * @param taskInfo info of the vanished task + */ + default void onTaskVanished(TaskInfo taskInfo) {} + + /** + * @return true if there tasks that could be potentially animated + */ + default boolean hasActiveTasks() { + return false; + } + + /** + * Clears all registered tasks in the animator + */ + default void clearTasks() {} + + /** + * Apply task surfaces transformations based on the current unfold progress + * @param progress unfold transition progress + * @param transaction to write changes to + */ + default void applyAnimationProgress(float progress, Transaction transaction) {} + + /** + * Apply task surfaces transformations that should be set before starting the animation + * @param transaction to write changes to + */ + default void prepareStartTransaction(Transaction transaction) {} + + /** + * Apply task surfaces transformations that should be set after finishing the animation + * @param transaction to write changes to + */ + default void prepareFinishTransaction(Transaction transaction) {} + + /** + * Resets task surface to its initial transformation + * @param transaction to write changes to + */ + default void resetSurface(TaskInfo taskInfo, Transaction transaction) {} + + /** + * Resets all task surfaces to their initial transformations + * @param transaction to write changes to + */ + default void resetAllSurfaces(Transaction transaction) {} +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreenListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/qualifier/UnfoldShellTransition.java index 46e4299f99fa..4c868305dcdb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreenListener.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/qualifier/UnfoldShellTransition.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,20 +14,16 @@ * limitations under the License. */ -package com.android.wm.shell.stagesplit; +package com.android.wm.shell.unfold.qualifier; -/** - * Listener interface that Launcher attaches to SystemUI to get split-screen callbacks. - */ -oneway interface ISplitScreenListener { +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; - /** - * Called when the stage position changes. - */ - void onStagePositionChanged(int stage, int position); +import javax.inject.Qualifier; - /** - * Called when a task changes stages. - */ - void onTaskStageChanged(int taskId, int stage, boolean visible); -}
\ No newline at end of file +/** + * Indicates that this class is used for the shell unfold transition + */ +@Qualifier +@Retention(RetentionPolicy.RUNTIME) +public @interface UnfoldShellTransition {} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/qualifier/UnfoldTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/qualifier/UnfoldTransition.java new file mode 100644 index 000000000000..4d2b3e6f899b --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/qualifier/UnfoldTransition.java @@ -0,0 +1,30 @@ +/* + * 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.unfold.qualifier; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.inject.Qualifier; + +/** + * Indicates that this class is used for unfold transition implemented + * without using Shell transitions + */ +@Qualifier +@Retention(RetentionPolicy.RUNTIME) +public @interface UnfoldTransition {} 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 603d05d78fc0..2cff1714aff6 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 @@ -30,7 +30,7 @@ import androidx.annotation.Nullable; public class GroupedRecentTaskInfo implements Parcelable { public @NonNull ActivityManager.RecentTaskInfo mTaskInfo1; public @Nullable ActivityManager.RecentTaskInfo mTaskInfo2; - public @Nullable StagedSplitBounds mStagedSplitBounds; + public @Nullable SplitBounds mSplitBounds; public GroupedRecentTaskInfo(@NonNull ActivityManager.RecentTaskInfo task1) { this(task1, null, null); @@ -38,24 +38,24 @@ public class GroupedRecentTaskInfo implements Parcelable { public GroupedRecentTaskInfo(@NonNull ActivityManager.RecentTaskInfo task1, @Nullable ActivityManager.RecentTaskInfo task2, - @Nullable StagedSplitBounds stagedSplitBounds) { + @Nullable SplitBounds splitBounds) { mTaskInfo1 = task1; mTaskInfo2 = task2; - mStagedSplitBounds = stagedSplitBounds; + mSplitBounds = splitBounds; } GroupedRecentTaskInfo(Parcel parcel) { mTaskInfo1 = parcel.readTypedObject(ActivityManager.RecentTaskInfo.CREATOR); mTaskInfo2 = parcel.readTypedObject(ActivityManager.RecentTaskInfo.CREATOR); - mStagedSplitBounds = parcel.readTypedObject(StagedSplitBounds.CREATOR); + mSplitBounds = parcel.readTypedObject(SplitBounds.CREATOR); } @Override public String toString() { String taskString = "Task1: " + getTaskInfo(mTaskInfo1) + ", Task2: " + getTaskInfo(mTaskInfo2); - if (mStagedSplitBounds != null) { - taskString += ", SplitBounds: " + mStagedSplitBounds.toString(); + if (mSplitBounds != null) { + taskString += ", SplitBounds: " + mSplitBounds.toString(); } return taskString; } @@ -76,7 +76,7 @@ public class GroupedRecentTaskInfo implements Parcelable { public void writeToParcel(Parcel parcel, int flags) { parcel.writeTypedObject(mTaskInfo1, flags); parcel.writeTypedObject(mTaskInfo2, flags); - parcel.writeTypedObject(mStagedSplitBounds, flags); + parcel.writeTypedObject(mSplitBounds, flags); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/StagedSplitBounds.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java index a0c84cc33ebd..e90389764af3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/StagedSplitBounds.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java @@ -25,7 +25,7 @@ import java.util.Objects; * Container of various information needed to display split screen * tasks/leashes/etc in Launcher */ -public class StagedSplitBounds implements Parcelable { +public class SplitBounds implements Parcelable { public final Rect leftTopBounds; public final Rect rightBottomBounds; /** This rect represents the actual gap between the two apps */ @@ -43,7 +43,7 @@ public class StagedSplitBounds implements Parcelable { public final int leftTopTaskId; public final int rightBottomTaskId; - public StagedSplitBounds(Rect leftTopBounds, Rect rightBottomBounds, + public SplitBounds(Rect leftTopBounds, Rect rightBottomBounds, int leftTopTaskId, int rightBottomTaskId) { this.leftTopBounds = leftTopBounds; this.rightBottomBounds = rightBottomBounds; @@ -66,7 +66,7 @@ public class StagedSplitBounds implements Parcelable { topTaskPercent = this.leftTopBounds.height() / (float) rightBottomBounds.bottom; } - public StagedSplitBounds(Parcel parcel) { + public SplitBounds(Parcel parcel) { leftTopBounds = parcel.readTypedObject(Rect.CREATOR); rightBottomBounds = parcel.readTypedObject(Rect.CREATOR); visualDividerBounds = parcel.readTypedObject(Rect.CREATOR); @@ -96,11 +96,11 @@ public class StagedSplitBounds implements Parcelable { @Override public boolean equals(Object obj) { - if (!(obj instanceof StagedSplitBounds)) { + if (!(obj instanceof SplitBounds)) { return false; } // Only need to check the base fields (the other fields are derived from these) - final StagedSplitBounds other = (StagedSplitBounds) obj; + final SplitBounds other = (SplitBounds) obj; return Objects.equals(leftTopBounds, other.leftTopBounds) && Objects.equals(rightBottomBounds, other.rightBottomBounds) && leftTopTaskId == other.leftTopTaskId @@ -120,15 +120,15 @@ public class StagedSplitBounds implements Parcelable { + "AppsVertical? " + appsStackedVertically; } - public static final Creator<StagedSplitBounds> CREATOR = new Creator<StagedSplitBounds>() { + public static final Creator<SplitBounds> CREATOR = new Creator<SplitBounds>() { @Override - public StagedSplitBounds createFromParcel(Parcel in) { - return new StagedSplitBounds(in); + public SplitBounds createFromParcel(Parcel in) { + return new SplitBounds(in); } @Override - public StagedSplitBounds[] newArray(int size) { - return new StagedSplitBounds[size]; + public SplitBounds[] newArray(int size) { + return new SplitBounds[size]; } }; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java new file mode 100644 index 000000000000..6d28d73996f0 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java @@ -0,0 +1,174 @@ +/* + * 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.windowdecor; + +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; + +import android.app.ActivityManager; +import android.app.ActivityManager.RunningTaskInfo; +import android.app.ActivityTaskManager; +import android.content.Context; +import android.os.Handler; +import android.view.MotionEvent; +import android.view.SurfaceControl; +import android.view.View; +import android.window.WindowContainerToken; +import android.window.WindowContainerTransaction; + +import com.android.wm.shell.R; +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.SyncTransactionQueue; + +/** + * View model for the window decoration with a caption and shadows. Works with + * {@link CaptionWindowDecoration}. + */ +public class CaptionWindowDecorViewModel implements WindowDecorViewModel<CaptionWindowDecoration> { + private final ActivityTaskManager mActivityTaskManager; + private final ShellTaskOrganizer mTaskOrganizer; + private final Context mContext; + private final Handler mMainHandler; + private final DisplayController mDisplayController; + private final SyncTransactionQueue mSyncQueue; + + public CaptionWindowDecorViewModel( + Context context, + Handler mainHandler, + ShellTaskOrganizer taskOrganizer, + DisplayController displayController, + SyncTransactionQueue syncQueue) { + mContext = context; + mMainHandler = mainHandler; + mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class); + mTaskOrganizer = taskOrganizer; + mDisplayController = displayController; + mSyncQueue = syncQueue; + } + + @Override + public CaptionWindowDecoration createWindowDecoration( + ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface) { + final CaptionWindowDecoration windowDecoration = new CaptionWindowDecoration( + mContext, + mDisplayController, + mTaskOrganizer, + taskInfo, + taskSurface, + mMainHandler, + mSyncQueue); + TaskPositioner taskPositioner = new TaskPositioner(mTaskOrganizer, windowDecoration); + CaptionTouchEventListener touchEventListener = + new CaptionTouchEventListener(taskInfo, taskPositioner); + windowDecoration.setCaptionListeners(touchEventListener, touchEventListener); + windowDecoration.setDragResizeCallback(taskPositioner); + onTaskInfoChanged(taskInfo, windowDecoration); + return windowDecoration; + } + + @Override + public void onTaskInfoChanged(RunningTaskInfo taskInfo, CaptionWindowDecoration decoration) { + decoration.relayout(taskInfo); + + int statusBarColor = taskInfo.taskDescription.getStatusBarColor(); + decoration.setCaptionColor(statusBarColor); + } + + private class CaptionTouchEventListener implements + View.OnClickListener, View.OnTouchListener { + + private final int mTaskId; + private final WindowContainerToken mTaskToken; + private final DragResizeCallback mDragResizeCallback; + + private int mDragPointerId = -1; + + private CaptionTouchEventListener( + RunningTaskInfo taskInfo, DragResizeCallback dragResizeCallback) { + mTaskId = taskInfo.taskId; + mTaskToken = taskInfo.token; + mDragResizeCallback = dragResizeCallback; + } + + @Override + public void onClick(View v) { + int id = v.getId(); + if (id == R.id.close_window) { + mActivityTaskManager.removeTask(mTaskId); + } else if (id == R.id.maximize_window) { + WindowContainerTransaction wct = new WindowContainerTransaction(); + RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId); + int targetWindowingMode = taskInfo.getWindowingMode() != WINDOWING_MODE_FULLSCREEN + ? WINDOWING_MODE_FULLSCREEN : WINDOWING_MODE_FREEFORM; + int displayWindowingMode = + taskInfo.configuration.windowConfiguration.getDisplayWindowingMode(); + wct.setWindowingMode(mTaskToken, + targetWindowingMode == displayWindowingMode + ? WINDOWING_MODE_UNDEFINED : targetWindowingMode); + if (targetWindowingMode == WINDOWING_MODE_FULLSCREEN) { + wct.setBounds(mTaskToken, null); + } + mSyncQueue.queue(wct); + } + } + + @Override + public boolean onTouch(View v, MotionEvent e) { + if (v.getId() != R.id.caption) { + return false; + } + handleEventForMove(e); + + if (e.getAction() != MotionEvent.ACTION_DOWN) { + return false; + } + RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId); + if (taskInfo.isFocused) { + return false; + } + WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.reorder(mTaskToken, true /* onTop */); + mSyncQueue.queue(wct); + return true; + } + + private void handleEventForMove(MotionEvent e) { + switch (e.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + mDragPointerId = e.getPointerId(0); + mDragResizeCallback.onDragResizeStart( + 0 /* ctrlType */, e.getRawX(0), e.getRawY(0)); + break; + case MotionEvent.ACTION_MOVE: { + int dragPointerIdx = e.findPointerIndex(mDragPointerId); + mDragResizeCallback.onDragResizeMove( + e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)); + break; + } + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: { + int dragPointerIdx = e.findPointerIndex(mDragPointerId); + mDragResizeCallback.onDragResizeEnd( + e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)); + break; + } + } + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java new file mode 100644 index 000000000000..572516d2f8c9 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java @@ -0,0 +1,199 @@ +/* + * 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.windowdecor; + +import android.app.ActivityManager; +import android.app.WindowConfiguration; +import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.Color; +import android.graphics.Rect; +import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.VectorDrawable; +import android.os.Handler; +import android.view.SurfaceControl; +import android.view.View; +import android.window.WindowContainerTransaction; + +import com.android.wm.shell.R; +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.SyncTransactionQueue; + +/** + * Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with + * {@link CaptionWindowDecorViewModel}. The caption bar contains maximize and close buttons. + * + * {@link CaptionWindowDecorViewModel} can change the color of the caption bar based on the foremost + * app's request through {@link #setCaptionColor(int)}, in which it changes the foreground color of + * caption buttons according to the luminance of the background. + * + * The shadow's thickness is 20dp when the window is in focus and 5dp when the window isn't. + */ +public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearLayout> { + // The thickness of shadows of a window that has focus in DIP. + private static final int DECOR_SHADOW_FOCUSED_THICKNESS_IN_DIP = 20; + // The thickness of shadows of a window that doesn't have focus in DIP. + private static final int DECOR_SHADOW_UNFOCUSED_THICKNESS_IN_DIP = 5; + + // Height of button (32dp) + 2 * margin (5dp each) + private static final int DECOR_CAPTION_HEIGHT_IN_DIP = 42; + private static final int RESIZE_HANDLE_IN_DIP = 30; + + private static final Rect EMPTY_OUTSET = new Rect(); + private static final Rect RESIZE_HANDLE_OUTSET = new Rect( + RESIZE_HANDLE_IN_DIP, RESIZE_HANDLE_IN_DIP, RESIZE_HANDLE_IN_DIP, RESIZE_HANDLE_IN_DIP); + + private final Handler mHandler; + private final SyncTransactionQueue mSyncQueue; + + private View.OnClickListener mOnCaptionButtonClickListener; + private View.OnTouchListener mOnCaptionTouchListener; + private DragResizeCallback mDragResizeCallback; + + private DragResizeInputListener mDragResizeListener; + + private final WindowDecoration.RelayoutResult<WindowDecorLinearLayout> mResult = + new WindowDecoration.RelayoutResult<>(); + + CaptionWindowDecoration( + Context context, + DisplayController displayController, + ShellTaskOrganizer taskOrganizer, + ActivityManager.RunningTaskInfo taskInfo, + SurfaceControl taskSurface, + Handler handler, + SyncTransactionQueue syncQueue) { + super(context, displayController, taskOrganizer, taskInfo, taskSurface); + + mHandler = handler; + mSyncQueue = syncQueue; + } + + void setCaptionListeners( + View.OnClickListener onCaptionButtonClickListener, + View.OnTouchListener onCaptionTouchListener) { + mOnCaptionButtonClickListener = onCaptionButtonClickListener; + mOnCaptionTouchListener = onCaptionTouchListener; + } + + void setDragResizeCallback(DragResizeCallback dragResizeCallback) { + mDragResizeCallback = dragResizeCallback; + } + + void relayout(ActivityManager.RunningTaskInfo taskInfo) { + final int shadowRadiusDp = taskInfo.isFocused + ? DECOR_SHADOW_FOCUSED_THICKNESS_IN_DIP : DECOR_SHADOW_UNFOCUSED_THICKNESS_IN_DIP; + final boolean isFreeform = mTaskInfo.configuration.windowConfiguration.getWindowingMode() + == WindowConfiguration.WINDOWING_MODE_FREEFORM; + final boolean isDragResizeable = isFreeform && mTaskInfo.isResizeable; + final Rect outset = isDragResizeable ? RESIZE_HANDLE_OUTSET : EMPTY_OUTSET; + + WindowDecorLinearLayout oldRootView = mResult.mRootView; + final SurfaceControl oldDecorationSurface = mDecorationContainerSurface; + final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + final WindowContainerTransaction wct = new WindowContainerTransaction(); + relayout(taskInfo, R.layout.caption_window_decoration, oldRootView, + DECOR_CAPTION_HEIGHT_IN_DIP, outset, shadowRadiusDp, t, wct, mResult); + taskInfo = null; // Clear it just in case we use it accidentally + + mSyncQueue.runInSync(transaction -> { + transaction.merge(t); + t.close(); + + mTaskOrganizer.applyTransaction(wct); + }); + + if (mResult.mRootView == null) { + // This means the task is hidden. Nothing is set up in this case including the + // decoration surface. + return; + } + if (oldRootView != mResult.mRootView) { + setupRootView(); + } + + if (!isDragResizeable) { + closeDragResizeListener(); + return; + } + + if (oldDecorationSurface != mDecorationContainerSurface) { + closeDragResizeListener(); + mDragResizeListener = new DragResizeInputListener( + mContext, + mHandler, + mDisplay.getDisplayId(), + mDecorationContainerSurface, + mDragResizeCallback); + } + + mDragResizeListener.setGeometry( + mResult.mWidth, mResult.mHeight, (int) (mResult.mDensity * RESIZE_HANDLE_IN_DIP)); + } + + /** + * Sets up listeners when a new root view is created. + */ + private void setupRootView() { + View caption = mResult.mRootView.findViewById(R.id.caption); + + caption.setOnTouchListener(mOnCaptionTouchListener); + View maximize = caption.findViewById(R.id.maximize_window); + maximize.setOnClickListener(mOnCaptionButtonClickListener); + View close = caption.findViewById(R.id.close_window); + close.setOnClickListener(mOnCaptionButtonClickListener); + } + + void setCaptionColor(int captionColor) { + if (mResult.mRootView == null) { + return; + } + + View caption = mResult.mRootView.findViewById(R.id.caption); + GradientDrawable captionDrawable = (GradientDrawable) caption.getBackground(); + captionDrawable.setColor(captionColor); + + int buttonTintColorRes = + Color.valueOf(captionColor).luminance() < 0.5 + ? R.color.decor_button_light_color + : R.color.decor_button_dark_color; + ColorStateList buttonTintColor = + caption.getResources().getColorStateList(buttonTintColorRes, null /* theme */); + View maximize = caption.findViewById(R.id.maximize_window); + VectorDrawable maximizeBackground = (VectorDrawable) maximize.getBackground(); + maximizeBackground.setTintList(buttonTintColor); + + View close = caption.findViewById(R.id.close_window); + VectorDrawable closeBackground = (VectorDrawable) close.getBackground(); + closeBackground.setTintList(buttonTintColor); + } + + private void closeDragResizeListener() { + if (mDragResizeListener == null) { + return; + } + mDragResizeListener.close(); + mDragResizeListener = null; + } + + @Override + public void close() { + closeDragResizeListener(); + super.close(); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeCallback.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeCallback.java new file mode 100644 index 000000000000..ee160a15df19 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeCallback.java @@ -0,0 +1,46 @@ +/* + * 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.windowdecor; + +/** + * Callback called when receiving drag-resize or drag-move related input events. + */ +public interface DragResizeCallback { + /** + * Called when a drag resize starts. + * + * @param ctrlType {@link TaskPositioner.CtrlType} indicating the direction of resizing, use + * {@code 0} to indicate it's a move + * @param x x coordinate in window decoration coordinate system where the drag resize starts + * @param y y coordinate in window decoration coordinate system where the drag resize starts + */ + void onDragResizeStart(@TaskPositioner.CtrlType int ctrlType, float x, float y); + + /** + * Called when the pointer moves during a drag resize. + * @param x x coordinate in window decoration coordinate system of the new pointer location + * @param y y coordinate in window decoration coordinate system of the new pointer location + */ + void onDragResizeMove(float x, float y); + + /** + * Called when a drag resize stops. + * @param x x coordinate in window decoration coordinate system where the drag resize stops + * @param y y coordinate in window decoration coordinate system where the drag resize stops + */ + void onDragResizeEnd(float x, float y); +} 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 new file mode 100644 index 000000000000..c6bbb027c8e6 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java @@ -0,0 +1,288 @@ +/* + * 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.windowdecor; + +import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; + +import android.content.Context; +import android.graphics.Rect; +import android.graphics.Region; +import android.hardware.input.InputManager; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.RemoteException; +import android.view.IWindowSession; +import android.view.InputChannel; +import android.view.InputEvent; +import android.view.InputEventReceiver; +import android.view.MotionEvent; +import android.view.PointerIcon; +import android.view.SurfaceControl; +import android.view.WindowManagerGlobal; + +import com.android.internal.view.BaseIWindow; + +/** + * An input event listener registered to InputDispatcher to receive input events on task edges and + * convert them to drag resize requests. + */ +class DragResizeInputListener implements AutoCloseable { + private static final String TAG = "DragResizeInputListener"; + + private final IWindowSession mWindowSession = WindowManagerGlobal.getWindowSession(); + private final Handler mHandler; + private final InputManager mInputManager; + + private final int mDisplayId; + private final BaseIWindow mFakeWindow; + private final IBinder mFocusGrantToken; + private final SurfaceControl mDecorationSurface; + private final InputChannel mInputChannel; + private final TaskResizeInputEventReceiver mInputEventReceiver; + private final com.android.wm.shell.windowdecor.DragResizeCallback mCallback; + + private int mWidth; + private int mHeight; + private int mResizeHandleThickness; + + private int mDragPointerId = -1; + + DragResizeInputListener( + Context context, + Handler handler, + int displayId, + SurfaceControl decorationSurface, + DragResizeCallback callback) { + mInputManager = context.getSystemService(InputManager.class); + mHandler = handler; + mDisplayId = displayId; + mDecorationSurface = decorationSurface; + // Use a fake window as the backing surface is a container layer and we don't want to create + // a buffer layer for it so we can't use ViewRootImpl. + mFakeWindow = new BaseIWindow(); + mFakeWindow.setSession(mWindowSession); + mFocusGrantToken = new Binder(); + mInputChannel = new InputChannel(); + try { + mWindowSession.grantInputChannel( + mDisplayId, + new SurfaceControl(mDecorationSurface, TAG), + mFakeWindow, + null /* hostInputToken */, + FLAG_NOT_FOCUSABLE, + PRIVATE_FLAG_TRUSTED_OVERLAY, + TYPE_APPLICATION, + mFocusGrantToken, + TAG + " of " + decorationSurface.toString(), + mInputChannel); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + + mInputEventReceiver = new TaskResizeInputEventReceiver(mInputChannel, mHandler.getLooper()); + mCallback = callback; + } + + /** + * Updates geometry of this drag resize handler. Needs to be called every time there is a size + * change to notify the input event receiver it's ready to take the next input event. Otherwise + * it'll keep batching move events and the drag resize process is stalled. + * + * This is also used to update the touch regions of this handler every event dispatched here is + * a potential resize request. + * + * @param width The width of the drag resize handler in pixels, including resize handle + * thickness. That is task width + 2 * resize handle thickness. + * @param height The height of the drag resize handler in pixels, including resize handle + * thickness. That is task height + 2 * resize handle thickness. + * @param resizeHandleThickness The thickness of the resize handle in pixels. + */ + void setGeometry(int width, int height, int resizeHandleThickness) { + if (mWidth == width && mHeight == height + && mResizeHandleThickness == resizeHandleThickness) { + return; + } + + mWidth = width; + mHeight = height; + mResizeHandleThickness = resizeHandleThickness; + + Region touchRegion = new Region(); + final Rect topInputBounds = new Rect(0, 0, mWidth, mResizeHandleThickness); + touchRegion.union(topInputBounds); + + final Rect leftInputBounds = new Rect(0, mResizeHandleThickness, + mResizeHandleThickness, mHeight - mResizeHandleThickness); + touchRegion.union(leftInputBounds); + + final Rect rightInputBounds = new Rect( + mWidth - mResizeHandleThickness, mResizeHandleThickness, + mWidth, mHeight - mResizeHandleThickness); + touchRegion.union(rightInputBounds); + + final Rect bottomInputBounds = new Rect(0, mHeight - mResizeHandleThickness, + mWidth, mHeight); + touchRegion.union(bottomInputBounds); + + try { + mWindowSession.updateInputChannel( + mInputChannel.getToken(), + mDisplayId, + new SurfaceControl( + mDecorationSurface, "DragResizeInputListener#setTouchRegion"), + FLAG_NOT_FOCUSABLE, + PRIVATE_FLAG_TRUSTED_OVERLAY, + touchRegion); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + + // This marks all relevant components have handled the previous resize event and can take + // the next one now. + mInputEventReceiver.onHandledLastResizeEvent(); + } + + @Override + public void close() { + mInputChannel.dispose(); + try { + mWindowSession.remove(mFakeWindow); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + private class TaskResizeInputEventReceiver extends InputEventReceiver { + private boolean mWaitingForLastResizeEventHandled; + + private TaskResizeInputEventReceiver(InputChannel inputChannel, Looper looper) { + super(inputChannel, looper); + } + + private void onHandledLastResizeEvent() { + mWaitingForLastResizeEventHandled = false; + consumeBatchedInputEvents(-1); + } + + @Override + public void onBatchedInputEventPending(int source) { + // InputEventReceiver keeps continuous move events in a batched event until explicitly + // consuming it or an incompatible event shows up (likely an up event in this case). We + // continue to keep move events in the next batched event until we receive a geometry + // update so that we don't put too much pressure on the framework with excessive number + // of input events if it can't handle them fast enough. It's more responsive to always + // resize the task to the latest received coordinates. + if (!mWaitingForLastResizeEventHandled) { + consumeBatchedInputEvents(-1); + } + } + + @Override + public void onInputEvent(InputEvent inputEvent) { + finishInputEvent(inputEvent, handleInputEvent(inputEvent)); + } + + private boolean handleInputEvent(InputEvent inputEvent) { + if (!(inputEvent instanceof MotionEvent)) { + return false; + } + + MotionEvent e = (MotionEvent) inputEvent; + switch (e.getActionMasked()) { + case MotionEvent.ACTION_DOWN: { + mDragPointerId = e.getPointerId(0); + mCallback.onDragResizeStart( + calculateCtrlType(e.getX(0), e.getY(0)), e.getRawX(0), e.getRawY(0)); + mWaitingForLastResizeEventHandled = false; + break; + } + case MotionEvent.ACTION_MOVE: { + int dragPointerIndex = e.findPointerIndex(mDragPointerId); + mCallback.onDragResizeMove( + e.getRawX(dragPointerIndex), e.getRawY(dragPointerIndex)); + mWaitingForLastResizeEventHandled = true; + break; + } + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: { + int dragPointerIndex = e.findPointerIndex(mDragPointerId); + mCallback.onDragResizeEnd( + e.getRawX(dragPointerIndex), e.getRawY(dragPointerIndex)); + mWaitingForLastResizeEventHandled = false; + mDragPointerId = -1; + break; + } + case MotionEvent.ACTION_HOVER_ENTER: + case MotionEvent.ACTION_HOVER_MOVE: { + updateCursorType(e.getXCursorPosition(), e.getYCursorPosition()); + break; + } + case MotionEvent.ACTION_HOVER_EXIT: + mInputManager.setPointerIconType(PointerIcon.TYPE_DEFAULT); + break; + } + return true; + } + + @TaskPositioner.CtrlType + private int calculateCtrlType(float x, float y) { + int ctrlType = 0; + if (x < mResizeHandleThickness) { + ctrlType |= TaskPositioner.CTRL_TYPE_LEFT; + } + if (x > mWidth - mResizeHandleThickness) { + ctrlType |= TaskPositioner.CTRL_TYPE_RIGHT; + } + if (y < mResizeHandleThickness) { + ctrlType |= TaskPositioner.CTRL_TYPE_TOP; + } + if (y > mHeight - mResizeHandleThickness) { + ctrlType |= TaskPositioner.CTRL_TYPE_BOTTOM; + } + return ctrlType; + } + + private void updateCursorType(float x, float y) { + @TaskPositioner.CtrlType int ctrlType = calculateCtrlType(x, y); + + int cursorType = PointerIcon.TYPE_DEFAULT; + switch (ctrlType) { + case TaskPositioner.CTRL_TYPE_LEFT: + case TaskPositioner.CTRL_TYPE_RIGHT: + cursorType = PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW; + break; + case TaskPositioner.CTRL_TYPE_TOP: + case TaskPositioner.CTRL_TYPE_BOTTOM: + cursorType = PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW; + break; + case TaskPositioner.CTRL_TYPE_LEFT | TaskPositioner.CTRL_TYPE_TOP: + case TaskPositioner.CTRL_TYPE_RIGHT | TaskPositioner.CTRL_TYPE_BOTTOM: + cursorType = PointerIcon.TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW; + break; + case TaskPositioner.CTRL_TYPE_LEFT | TaskPositioner.CTRL_TYPE_BOTTOM: + case TaskPositioner.CTRL_TYPE_RIGHT | TaskPositioner.CTRL_TYPE_TOP: + cursorType = PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW; + break; + } + mInputManager.setPointerIconType(cursorType); + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskFocusStateConsumer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskFocusStateConsumer.java new file mode 100644 index 000000000000..1c61802bbd5c --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskFocusStateConsumer.java @@ -0,0 +1,21 @@ +/* + * 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.windowdecor; + +interface TaskFocusStateConsumer { + void setTaskFocusState(boolean focused); +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java new file mode 100644 index 000000000000..280569b05d87 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java @@ -0,0 +1,99 @@ +/* + * 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.windowdecor; + +import android.annotation.IntDef; +import android.graphics.PointF; +import android.graphics.Rect; +import android.window.WindowContainerTransaction; + +import com.android.wm.shell.ShellTaskOrganizer; + +class TaskPositioner implements DragResizeCallback { + + @IntDef({CTRL_TYPE_LEFT, CTRL_TYPE_RIGHT, CTRL_TYPE_TOP, CTRL_TYPE_BOTTOM}) + @interface CtrlType {} + + static final int CTRL_TYPE_LEFT = 1; + static final int CTRL_TYPE_RIGHT = 2; + static final int CTRL_TYPE_TOP = 4; + static final int CTRL_TYPE_BOTTOM = 8; + + private final ShellTaskOrganizer mTaskOrganizer; + private final WindowDecoration mWindowDecoration; + + private final Rect mTaskBoundsAtDragStart = new Rect(); + private final PointF mResizeStartPoint = new PointF(); + private final Rect mResizeTaskBounds = new Rect(); + + private int mCtrlType; + + TaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration) { + mTaskOrganizer = taskOrganizer; + mWindowDecoration = windowDecoration; + } + + @Override + public void onDragResizeStart(int ctrlType, float x, float y) { + mCtrlType = ctrlType; + + mTaskBoundsAtDragStart.set( + mWindowDecoration.mTaskInfo.configuration.windowConfiguration.getBounds()); + mResizeStartPoint.set(x, y); + } + + @Override + public void onDragResizeMove(float x, float y) { + changeBounds(x, y); + } + + @Override + public void onDragResizeEnd(float x, float y) { + changeBounds(x, y); + + mCtrlType = 0; + mTaskBoundsAtDragStart.setEmpty(); + mResizeStartPoint.set(0, 0); + } + + private void changeBounds(float x, float y) { + float deltaX = x - mResizeStartPoint.x; + mResizeTaskBounds.set(mTaskBoundsAtDragStart); + if ((mCtrlType & CTRL_TYPE_LEFT) != 0) { + mResizeTaskBounds.left += deltaX; + } + if ((mCtrlType & CTRL_TYPE_RIGHT) != 0) { + mResizeTaskBounds.right += deltaX; + } + float deltaY = y - mResizeStartPoint.y; + if ((mCtrlType & CTRL_TYPE_TOP) != 0) { + mResizeTaskBounds.top += deltaY; + } + if ((mCtrlType & CTRL_TYPE_BOTTOM) != 0) { + mResizeTaskBounds.bottom += deltaY; + } + if (mCtrlType == 0) { + mResizeTaskBounds.offset((int) deltaX, (int) deltaY); + } + + if (!mResizeTaskBounds.isEmpty()) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.setBounds(mWindowDecoration.mTaskInfo.token, mResizeTaskBounds); + mTaskOrganizer.applyTransaction(wct); + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorLinearLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorLinearLayout.java new file mode 100644 index 000000000000..6d8001a2f92b --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorLinearLayout.java @@ -0,0 +1,72 @@ +/* + * 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.windowdecor; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.LinearLayout; + +import androidx.annotation.Nullable; + +import com.android.wm.shell.R; + +/** + * A {@link LinearLayout} that takes an additional task focused drawable state. The new state is + * used to select the correct background color for views in the window decoration. + */ +public class WindowDecorLinearLayout extends LinearLayout implements TaskFocusStateConsumer { + private static final int[] TASK_FOCUSED_STATE = { R.attr.state_task_focused }; + + private boolean mIsTaskFocused; + + public WindowDecorLinearLayout(Context context) { + super(context); + } + + public WindowDecorLinearLayout(Context context, + @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public WindowDecorLinearLayout(Context context, @Nullable AttributeSet attrs, + int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public WindowDecorLinearLayout(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + public void setTaskFocusState(boolean focused) { + mIsTaskFocused = focused; + + refreshDrawableState(); + } + + @Override + protected int[] onCreateDrawableState(int extraSpace) { + if (!mIsTaskFocused) { + return super.onCreateDrawableState(extraSpace); + } + + final int[] states = super.onCreateDrawableState(extraSpace + 1); + mergeDrawableStates(states, TASK_FOCUSED_STATE); + return states; + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java new file mode 100644 index 000000000000..6f9ceff722ac --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java @@ -0,0 +1,49 @@ +/* + * 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.windowdecor; + +import android.app.ActivityManager; +import android.view.SurfaceControl; + +/** + * The interface used by some {@link com.android.wm.shell.ShellTaskOrganizer.TaskListener} to help + * customize {@link WindowDecoration}. Its implementations are responsible to interpret user's + * interactions with UI widgets in window decorations and send corresponding requests to system + * servers. + * + * @param <T> The actual decoration type + */ +public interface WindowDecorViewModel<T extends AutoCloseable> { + + /** + * Creates a window decoration for the given task. + * + * @param taskInfo the initial task info of the task + * @param taskSurface the surface of the task + * @return the window decoration object + */ + T createWindowDecoration(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface); + + /** + * Notifies a task info update on the given task, with the window decoration created previously + * for this task by {@link #createWindowDecoration}. + * + * @param taskInfo the new task info of the task + * @param windowDecoration the window decoration created for the task + */ + void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo, T windowDecoration); +} 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 new file mode 100644 index 000000000000..0c5022832162 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java @@ -0,0 +1,270 @@ +/* + * 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.windowdecor; + +import android.app.ActivityManager.RunningTaskInfo; +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.graphics.Point; +import android.graphics.Rect; +import android.util.DisplayMetrics; +import android.view.Display; +import android.view.InsetsState; +import android.view.LayoutInflater; +import android.view.SurfaceControl; +import android.view.SurfaceControlViewHost; +import android.view.View; +import android.view.ViewRootImpl; +import android.view.WindowManager; +import android.view.WindowlessWindowManager; +import android.window.WindowContainerTransaction; + +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.DisplayController; + +/** + * Manages a container surface and a windowless window to show window decoration. Responsible to + * update window decoration window state and layout parameters on task info changes and so that + * window decoration is in correct state and bounds. + * + * The container surface is a child of the task display area in the same display, so that window + * decorations can be drawn out of the task bounds and receive input events from out of the task + * bounds to support drag resizing. + * + * The windowless window that hosts window decoration is positioned in front of all activities, to + * allow the foreground activity to draw its own background behind window decorations, such as + * the window captions. + * + * @param <T> The type of the root view + */ +public class WindowDecoration<T extends View & TaskFocusStateConsumer> implements AutoCloseable { + private static final int[] CAPTION_INSETS_TYPES = { InsetsState.ITYPE_CAPTION_BAR }; + + /** + * System-wide context. Only used to create context with overridden configurations. + */ + final Context mContext; + final DisplayController mDisplayController; + final ShellTaskOrganizer mTaskOrganizer; + + RunningTaskInfo mTaskInfo; + final SurfaceControl mTaskSurface; + + Display mDisplay; + Context mDecorWindowContext; + SurfaceControl mDecorationContainerSurface; + SurfaceControl mTaskBackgroundSurface; + + private CaptionWindowManager mCaptionWindowManager; + private SurfaceControlViewHost mViewHost; + + private final Rect mCaptionInsetsRect = new Rect(); + private final Rect mTaskSurfaceCrop = new Rect(); + private final float[] mTmpColor = new float[3]; + + WindowDecoration( + Context context, + DisplayController displayController, + ShellTaskOrganizer taskOrganizer, + RunningTaskInfo taskInfo, + SurfaceControl taskSurface) { + mContext = context; + mDisplayController = displayController; + mTaskOrganizer = taskOrganizer; + mTaskInfo = taskInfo; + mTaskSurface = taskSurface; + + mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId); + mDecorWindowContext = mContext.createConfigurationContext(mTaskInfo.getConfiguration()); + + // Put caption under task surface because ViewRootImpl sets the destination frame of + // windowless window layers and BLASTBufferQueue#update() doesn't support offset. + mCaptionWindowManager = + new CaptionWindowManager(mTaskInfo.getConfiguration(), mTaskSurface); + } + + void relayout(RunningTaskInfo taskInfo, int layoutResId, T rootView, float captionHeightDp, + Rect outsetsDp, float shadowRadiusDp, SurfaceControl.Transaction t, + WindowContainerTransaction wct, RelayoutResult<T> outResult) { + outResult.reset(); + + final Configuration oldTaskConfig = mTaskInfo.getConfiguration(); + if (taskInfo != null) { + mTaskInfo = taskInfo; + } + + if (!mTaskInfo.isVisible) { + close(); + t.hide(mTaskSurface); + return; + } + + if (rootView == null && layoutResId == 0) { + throw new IllegalArgumentException("layoutResId and rootView can't both be invalid."); + } + + outResult.mRootView = rootView; + rootView = null; // Clear it just in case we use it accidentally + final Configuration taskConfig = mTaskInfo.getConfiguration(); + if (oldTaskConfig.densityDpi != taskConfig.densityDpi + || mDisplay.getDisplayId() != mTaskInfo.displayId) { + close(); + + mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId); + mDecorWindowContext = mContext.createConfigurationContext(taskConfig); + if (layoutResId != 0) { + outResult.mRootView = + (T) LayoutInflater.from(mDecorWindowContext).inflate(layoutResId, null); + } + } + + if (outResult.mRootView == null) { + outResult.mRootView = + (T) LayoutInflater.from(mDecorWindowContext).inflate(layoutResId, null); + } + + // DecorationContainerSurface + if (mDecorationContainerSurface == null) { + final SurfaceControl.Builder builder = new SurfaceControl.Builder(); + mDecorationContainerSurface = builder + .setName("Decor container of Task=" + mTaskInfo.taskId) + .setContainerLayer() + .setParent(mTaskSurface) + .build(); + + t.setTrustedOverlay(mDecorationContainerSurface, true); + } + + final Rect taskBounds = taskConfig.windowConfiguration.getBounds(); + outResult.mDensity = taskConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE; + final int decorContainerOffsetX = -(int) (outsetsDp.left * outResult.mDensity); + final int decorContainerOffsetY = -(int) (outsetsDp.top * outResult.mDensity); + outResult.mWidth = taskBounds.width() + + (int) (outsetsDp.right * outResult.mDensity) + - decorContainerOffsetX; + outResult.mHeight = taskBounds.height() + + (int) (outsetsDp.bottom * outResult.mDensity) + - decorContainerOffsetY; + t.setPosition(mDecorationContainerSurface, decorContainerOffsetX, decorContainerOffsetY) + .setWindowCrop(mDecorationContainerSurface, outResult.mWidth, outResult.mHeight) + .setLayer(mDecorationContainerSurface, mTaskInfo.numActivities + 1) + .show(mDecorationContainerSurface); + + // TaskBackgroundSurface + if (mTaskBackgroundSurface == null) { + final SurfaceControl.Builder builder = new SurfaceControl.Builder(); + mTaskBackgroundSurface = builder + .setName("Background of Task=" + mTaskInfo.taskId) + .setEffectLayer() + .setParent(mTaskSurface) + .build(); + } + + float shadowRadius = outResult.mDensity * shadowRadiusDp; + int backgroundColorInt = mTaskInfo.taskDescription.getBackgroundColor(); + mTmpColor[0] = Color.red(backgroundColorInt); + mTmpColor[1] = Color.green(backgroundColorInt); + mTmpColor[2] = Color.blue(backgroundColorInt); + t.setCrop(mTaskBackgroundSurface, taskBounds) + .setShadowRadius(mTaskBackgroundSurface, shadowRadius) + .setColor(mTaskBackgroundSurface, mTmpColor); + + // Caption view + mCaptionWindowManager.setConfiguration(taskConfig); + final int captionHeight = (int) Math.ceil(captionHeightDp * outResult.mDensity); + final WindowManager.LayoutParams lp = + new WindowManager.LayoutParams(taskBounds.width(), captionHeight, + WindowManager.LayoutParams.TYPE_APPLICATION, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT); + lp.setTitle("Caption of Task=" + mTaskInfo.taskId); + lp.setTrustedOverlay(); + if (mViewHost == null) { + mViewHost = new SurfaceControlViewHost(mDecorWindowContext, mDisplay, + mCaptionWindowManager, true); + mViewHost.setView(outResult.mRootView, lp); + } else { + mViewHost.relayout(lp); + } + + if (ViewRootImpl.CAPTION_ON_SHELL) { + outResult.mRootView.setTaskFocusState(mTaskInfo.isFocused); + + // Caption insets + mCaptionInsetsRect.set(taskBounds); + mCaptionInsetsRect.bottom = mCaptionInsetsRect.top + captionHeight; + wct.addRectInsetsProvider(mTaskInfo.token, mCaptionInsetsRect, CAPTION_INSETS_TYPES); + } else { + outResult.mRootView.setVisibility(View.GONE); + } + + // Task surface itself + Point taskPosition = mTaskInfo.positionInParent; + mTaskSurfaceCrop.set( + decorContainerOffsetX, + decorContainerOffsetY, + outResult.mWidth + decorContainerOffsetX, + outResult.mHeight + decorContainerOffsetY); + t.setPosition(mTaskSurface, taskPosition.x, taskPosition.y) + .setCrop(mTaskSurface, mTaskSurfaceCrop) + .show(mTaskSurface); + } + + @Override + public void close() { + if (mViewHost != null) { + mViewHost.release(); + mViewHost = null; + } + + if (mDecorationContainerSurface != null) { + mDecorationContainerSurface.release(); + mDecorationContainerSurface = null; + } + + if (mTaskBackgroundSurface != null) { + mTaskBackgroundSurface.release(); + mTaskBackgroundSurface = null; + } + } + + static class RelayoutResult<T extends View & TaskFocusStateConsumer> { + int mWidth; + int mHeight; + float mDensity; + T mRootView; + + void reset() { + mWidth = 0; + mHeight = 0; + mDensity = 0; + mRootView = null; + } + } + + private static class CaptionWindowManager extends WindowlessWindowManager { + CaptionWindowManager(Configuration config, SurfaceControl rootSurface) { + super(config, rootSurface, null /* hostInputToken */); + } + + @Override + public void setConfiguration(Configuration configuration) { + super.setConfiguration(configuration); + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt index cb478c84c2b7..cba396a82a87 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt @@ -43,6 +43,84 @@ fun FlickerTestParameter.appPairsDividerBecomesVisible() { } } +fun FlickerTestParameter.splitScreenDividerBecomesVisible() { + layerBecomesVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) +} + +fun FlickerTestParameter.layerBecomesVisible( + component: FlickerComponentName +) { + assertLayers { + this.isInvisible(component) + .then() + .isVisible(component) + } +} + +fun FlickerTestParameter.layerIsVisibleAtEnd( + component: FlickerComponentName +) { + assertLayersEnd { + this.isVisible(component) + } +} + +fun FlickerTestParameter.splitAppLayerBoundsBecomesVisible( + rotation: Int, + component: FlickerComponentName, + splitLeftTop: Boolean +) { + assertLayers { + val dividerRegion = this.last().layer(SPLIT_SCREEN_DIVIDER_COMPONENT).visibleRegion.region + this.isInvisible(component) + .then() + .invoke("splitAppLayerBoundsBecomesVisible") { + it.visibleRegion(component).overlaps( + if (splitLeftTop) { + getSplitLeftTopRegion(dividerRegion, rotation) + } else { + getSplitRightBottomRegion(dividerRegion, rotation) + } + ) + } + } +} + +fun FlickerTestParameter.splitAppLayerBoundsIsVisibleAtEnd( + rotation: Int, + component: FlickerComponentName, + splitLeftTop: Boolean +) { + assertLayersEnd { + val dividerRegion = layer(SPLIT_SCREEN_DIVIDER_COMPONENT).visibleRegion.region + visibleRegion(component).overlaps( + if (splitLeftTop) { + getSplitLeftTopRegion(dividerRegion, rotation) + } else { + getSplitRightBottomRegion(dividerRegion, rotation) + } + ) + } +} + +fun FlickerTestParameter.appWindowBecomesVisible( + component: FlickerComponentName +) { + assertWm { + this.isAppWindowInvisible(component) + .then() + .isAppWindowVisible(component) + } +} + +fun FlickerTestParameter.appWindowIsVisibleAtEnd( + component: FlickerComponentName +) { + assertWmEnd { + this.isAppWindowVisible(component) + } +} + fun FlickerTestParameter.dockedStackDividerIsVisibleAtEnd() { assertLayersEnd { this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT) @@ -118,21 +196,53 @@ fun FlickerTestParameter.dockedStackSecondaryBoundsIsVisibleAtEnd( fun getPrimaryRegion(dividerRegion: Region, rotation: Int): Region { val displayBounds = WindowUtils.getDisplayBounds(rotation) return if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) { - Region.from(0, 0, displayBounds.bounds.right, - dividerRegion.bounds.top + WindowUtils.dockedStackDividerInset) + Region.from( + 0, 0, displayBounds.bounds.right, + dividerRegion.bounds.top + WindowUtils.dockedStackDividerInset + ) } else { - Region.from(0, 0, dividerRegion.bounds.left + WindowUtils.dockedStackDividerInset, - displayBounds.bounds.bottom) + Region.from( + 0, 0, dividerRegion.bounds.left + WindowUtils.dockedStackDividerInset, + displayBounds.bounds.bottom + ) } } fun getSecondaryRegion(dividerRegion: Region, rotation: Int): Region { val displayBounds = WindowUtils.getDisplayBounds(rotation) return if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) { - Region.from(0, dividerRegion.bounds.bottom - WindowUtils.dockedStackDividerInset, - displayBounds.bounds.right, displayBounds.bounds.bottom) + Region.from( + 0, dividerRegion.bounds.bottom - WindowUtils.dockedStackDividerInset, + displayBounds.bounds.right, displayBounds.bounds.bottom + ) } else { - Region.from(dividerRegion.bounds.right - WindowUtils.dockedStackDividerInset, 0, - displayBounds.bounds.right, displayBounds.bounds.bottom) + Region.from( + dividerRegion.bounds.right - WindowUtils.dockedStackDividerInset, 0, + displayBounds.bounds.right, displayBounds.bounds.bottom + ) } -}
\ No newline at end of file +} + +fun getSplitLeftTopRegion(dividerRegion: Region, rotation: Int): Region { + val displayBounds = WindowUtils.getDisplayBounds(rotation) + return if (displayBounds.width > displayBounds.height) { + Region.from(0, 0, dividerRegion.bounds.left, displayBounds.bounds.bottom) + } else { + Region.from(0, 0, displayBounds.bounds.right, dividerRegion.bounds.top) + } +} + +fun getSplitRightBottomRegion(dividerRegion: Region, rotation: Int): Region { + val displayBounds = WindowUtils.getDisplayBounds(rotation) + return if (displayBounds.width > displayBounds.height) { + Region.from( + dividerRegion.bounds.right, 0, displayBounds.bounds.right, + displayBounds.bounds.bottom + ) + } else { + Region.from( + 0, dividerRegion.bounds.bottom, displayBounds.bounds.right, + displayBounds.bounds.bottom + ) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt index 40891f36a5da..f56eb6e783aa 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt @@ -21,4 +21,5 @@ import com.android.server.wm.traces.common.FlickerComponentName const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui" val APP_PAIR_SPLIT_DIVIDER_COMPONENT = FlickerComponentName("", "AppPairSplitDivider#") -val DOCKED_STACK_DIVIDER_COMPONENT = FlickerComponentName("", "DockedStackDivider#")
\ No newline at end of file +val DOCKED_STACK_DIVIDER_COMPONENT = FlickerComponentName("", "DockedStackDivider#") +val SPLIT_SCREEN_DIVIDER_COMPONENT = FlickerComponentName("", "StageCoordinatorSplitDivider#") diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt deleted file mode 100644 index c9cab39b7d8b..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.apppairs - -import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group1 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.wm.shell.flicker.appPairsDividerIsInvisibleAtEnd -import com.android.wm.shell.flicker.helpers.AppPairsHelper -import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig -import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow -import org.junit.After -import org.junit.Before -import org.junit.FixMethodOrder -import org.junit.Ignore -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized - -/** - * Test cold launch app from launcher. When the device doesn't support non-resizable in multi window - * {@link Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW}, app pairs should not pair - * non-resizable apps. - * - * To run this test: `atest WMShellFlickerTests:AppPairsTestCannotPairNonResizeableApps` - */ -@RequiresDevice -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group1 -class AppPairsTestCannotPairNonResizeableApps( - testSpec: FlickerTestParameter -) : AppPairsTransition(testSpec) { - - override val transition: FlickerBuilder.() -> Unit - get() = { - super.transition(this) - transitions { - nonResizeableApp?.launchViaIntent(wmHelper) - // TODO pair apps through normal UX flow - executeShellCommand( - composePairsCommand(primaryTaskId, nonResizeableTaskId, pair = true)) - nonResizeableApp?.run { wmHelper.waitForFullScreenApp(nonResizeableApp.component) } - } - } - - @Before - override fun setup() { - super.setup() - setSupportsNonResizableMultiWindow(instrumentation, -1) - } - - @After - override fun teardown() { - super.teardown() - resetMultiWindowConfig(instrumentation) - } - - @Ignore - @Test - override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() - - @Ignore - @Test - override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() - - @Ignore - @Test - override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() - - @Ignore - @Test - fun appPairsDividerIsInvisibleAtEnd() = testSpec.appPairsDividerIsInvisibleAtEnd() - - @Ignore - @Test - fun onlyResizeableAppWindowVisible() { - val nonResizeableApp = nonResizeableApp - require(nonResizeableApp != null) { - "Non resizeable app not initialized" - } - testSpec.assertWmEnd { - isAppWindowVisible(nonResizeableApp.component) - isAppWindowInvisible(primaryApp.component) - } - } - - companion object { - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): List<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( - repetitions = AppPairsHelper.TEST_REPETITIONS) - } - } -}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt deleted file mode 100644 index 60c32c99d1ff..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.apppairs - -import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group1 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.wm.shell.flicker.APP_PAIR_SPLIT_DIVIDER_COMPONENT -import com.android.wm.shell.flicker.appPairsDividerIsVisibleAtEnd -import com.android.wm.shell.flicker.helpers.AppPairsHelper -import com.android.wm.shell.flicker.helpers.AppPairsHelper.Companion.waitAppsShown -import org.junit.FixMethodOrder -import org.junit.Ignore -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized - -/** - * Test cold launch app from launcher. - * To run this test: `atest WMShellFlickerTests:AppPairsTestPairPrimaryAndSecondaryApps` - */ -@RequiresDevice -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group1 -class AppPairsTestPairPrimaryAndSecondaryApps( - testSpec: FlickerTestParameter -) : AppPairsTransition(testSpec) { - override val transition: FlickerBuilder.() -> Unit - get() = { - super.transition(this) - transitions { - // TODO pair apps through normal UX flow - executeShellCommand( - composePairsCommand(primaryTaskId, secondaryTaskId, pair = true)) - waitAppsShown(primaryApp, secondaryApp) - } - } - - @Ignore - @Test - override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() - - @Ignore - @Test - override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() - - @Ignore - @Test - override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() - - @Ignore - @Test - fun appPairsDividerIsVisibleAtEnd() = testSpec.appPairsDividerIsVisibleAtEnd() - - @Ignore - @Test - fun bothAppWindowsVisible() { - testSpec.assertWmEnd { - isAppWindowVisible(primaryApp.component) - isAppWindowVisible(secondaryApp.component) - } - } - - @Ignore - @Test - fun appsEndingBounds() { - testSpec.assertLayersEnd { - val dividerRegion = layer(APP_PAIR_SPLIT_DIVIDER_COMPONENT).visibleRegion.region - visibleRegion(primaryApp.component) - .coversExactly(appPairsHelper.getPrimaryBounds(dividerRegion)) - visibleRegion(secondaryApp.component) - .coversExactly(appPairsHelper.getSecondaryBounds(dividerRegion)) - } - } - - companion object { - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): List<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( - repetitions = AppPairsHelper.TEST_REPETITIONS) - } - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt deleted file mode 100644 index 24869a802167..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.apppairs - -import android.view.Display -import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group1 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.traces.common.WindowManagerConditionsFactory -import com.android.wm.shell.flicker.appPairsDividerIsVisibleAtEnd -import com.android.wm.shell.flicker.helpers.AppPairsHelper -import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig -import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow -import org.junit.After -import org.junit.Before -import org.junit.FixMethodOrder -import org.junit.Ignore -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized - -/** - * Test cold launch app from launcher. When the device supports non-resizable in multi window - * {@link Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW}, app pairs can pair - * non-resizable apps. - * - * To run this test: `atest WMShellFlickerTests:AppPairsTestSupportPairNonResizeableApps` - */ -@RequiresDevice -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group1 -class AppPairsTestSupportPairNonResizeableApps( - testSpec: FlickerTestParameter -) : AppPairsTransition(testSpec) { - - override val transition: FlickerBuilder.() -> Unit - get() = { - super.transition(this) - transitions { - nonResizeableApp?.launchViaIntent(wmHelper) - // TODO pair apps through normal UX flow - executeShellCommand( - composePairsCommand(primaryTaskId, nonResizeableTaskId, pair = true)) - val waitConditions = mutableListOf( - WindowManagerConditionsFactory.isWindowVisible(primaryApp.component), - WindowManagerConditionsFactory.isLayerVisible(primaryApp.component), - WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY)) - - nonResizeableApp?.let { - waitConditions.add( - WindowManagerConditionsFactory.isWindowVisible(nonResizeableApp.component)) - waitConditions.add( - WindowManagerConditionsFactory.isLayerVisible(nonResizeableApp.component)) - } - wmHelper.waitFor(*waitConditions.toTypedArray()) - } - } - - @Before - override fun setup() { - super.setup() - setSupportsNonResizableMultiWindow(instrumentation, 1) - } - - @After - override fun teardown() { - super.teardown() - resetMultiWindowConfig(instrumentation) - } - - @Ignore - @Test - override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() - - @Ignore - @Test - override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() - - @Ignore - @Test - override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() - - @Ignore - @Test - fun appPairsDividerIsVisibleAtEnd() = testSpec.appPairsDividerIsVisibleAtEnd() - - @Ignore - @Test - fun bothAppWindowVisible() { - val nonResizeableApp = nonResizeableApp - require(nonResizeableApp != null) { - "Non resizeable app not initialized" - } - testSpec.assertWmEnd { - isAppWindowVisible(nonResizeableApp.component) - isAppWindowVisible(primaryApp.component) - } - } - - companion object { - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): List<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( - repetitions = AppPairsHelper.TEST_REPETITIONS) - } - } -}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt deleted file mode 100644 index 007415d19860..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.apppairs - -import android.os.SystemClock -import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group1 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.wm.shell.flicker.APP_PAIR_SPLIT_DIVIDER_COMPONENT -import com.android.wm.shell.flicker.appPairsDividerIsInvisibleAtEnd -import com.android.wm.shell.flicker.helpers.AppPairsHelper -import com.android.wm.shell.flicker.helpers.AppPairsHelper.Companion.waitAppsShown -import org.junit.FixMethodOrder -import org.junit.Ignore -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized - -/** - * Test cold launch app from launcher. - * To run this test: `atest WMShellFlickerTests:AppPairsTestUnpairPrimaryAndSecondaryApps` - */ -@RequiresDevice -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group1 -class AppPairsTestUnpairPrimaryAndSecondaryApps( - testSpec: FlickerTestParameter -) : AppPairsTransition(testSpec) { - override val transition: FlickerBuilder.() -> Unit - get() = { - super.transition(this) - setup { - eachRun { - executeShellCommand( - composePairsCommand(primaryTaskId, secondaryTaskId, pair = true)) - waitAppsShown(primaryApp, secondaryApp) - } - } - transitions { - // TODO pair apps through normal UX flow - executeShellCommand( - composePairsCommand(primaryTaskId, secondaryTaskId, pair = false)) - SystemClock.sleep(AppPairsHelper.TIMEOUT_MS) - } - } - - @Ignore - @Test - override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() - - @Ignore - @Test - override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() - - @Ignore - @Test - fun appPairsDividerIsInvisibleAtEnd() = testSpec.appPairsDividerIsInvisibleAtEnd() - - @Ignore - @Test - fun bothAppWindowsInvisible() { - testSpec.assertWmEnd { - isAppWindowInvisible(primaryApp.component) - isAppWindowInvisible(secondaryApp.component) - } - } - - @Ignore - @Test - fun appsStartingBounds() { - testSpec.assertLayersStart { - val dividerRegion = layer(APP_PAIR_SPLIT_DIVIDER_COMPONENT).visibleRegion.region - visibleRegion(primaryApp.component) - .coversExactly(appPairsHelper.getPrimaryBounds(dividerRegion)) - visibleRegion(secondaryApp.component) - .coversExactly(appPairsHelper.getSecondaryBounds(dividerRegion)) - } - } - - @Ignore - @Test - fun appsEndingBounds() { - testSpec.assertLayersEnd { - notContains(primaryApp.component) - notContains(secondaryApp.component) - } - } - - @Ignore - @Test - override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() - - companion object { - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): List<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( - repetitions = AppPairsHelper.TEST_REPETITIONS) - } - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt deleted file mode 100644 index 3e17948b4a84..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.apppairs - -import android.app.Instrumentation -import android.content.Context -import android.system.helpers.ActivityHelper -import androidx.test.platform.app.InstrumentationRegistry -import com.android.server.wm.flicker.FlickerBuilderProvider -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen -import com.android.server.wm.flicker.navBarLayerIsVisible -import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsVisible -import com.android.server.wm.flicker.statusBarLayerIsVisible -import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsVisible -import com.android.server.wm.traces.parser.toFlickerComponent -import com.android.wm.shell.flicker.helpers.AppPairsHelper -import com.android.wm.shell.flicker.helpers.BaseAppHelper -import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.getDevEnableNonResizableMultiWindow -import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setDevEnableNonResizableMultiWindow -import com.android.wm.shell.flicker.helpers.SplitScreenHelper -import com.android.wm.shell.flicker.testapp.Components -import org.junit.After -import org.junit.Before -import org.junit.Ignore -import org.junit.Test - -abstract class AppPairsTransition(protected val testSpec: FlickerTestParameter) { - protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - protected val context: Context = instrumentation.context - protected val activityHelper = ActivityHelper.getInstance() - protected val appPairsHelper = AppPairsHelper(instrumentation, - Components.SplitScreenActivity.LABEL, - Components.SplitScreenActivity.COMPONENT.toFlickerComponent()) - - protected val primaryApp = SplitScreenHelper.getPrimary(instrumentation) - protected val secondaryApp = SplitScreenHelper.getSecondary(instrumentation) - protected open val nonResizeableApp: SplitScreenHelper? = - SplitScreenHelper.getNonResizeable(instrumentation) - protected var primaryTaskId = "" - protected var secondaryTaskId = "" - protected var nonResizeableTaskId = "" - private var prevDevEnableNonResizableMultiWindow = 0 - - @Before - open fun setup() { - prevDevEnableNonResizableMultiWindow = getDevEnableNonResizableMultiWindow(context) - if (prevDevEnableNonResizableMultiWindow != 0) { - // Turn off the development option - setDevEnableNonResizableMultiWindow(context, 0) - } - } - - @After - open fun teardown() { - setDevEnableNonResizableMultiWindow(context, prevDevEnableNonResizableMultiWindow) - } - - @FlickerBuilderProvider - fun buildFlicker(): FlickerBuilder { - return FlickerBuilder(instrumentation).apply { - transition(this) - } - } - - internal open val transition: FlickerBuilder.() -> Unit - get() = { - setup { - test { - device.wakeUpAndGoToHomeScreen() - } - eachRun { - this.setRotation(testSpec.startRotation) - primaryApp.launchViaIntent(wmHelper) - secondaryApp.launchViaIntent(wmHelper) - nonResizeableApp?.launchViaIntent(wmHelper) - updateTasksId() - } - } - teardown { - eachRun { - executeShellCommand(composePairsCommand( - primaryTaskId, secondaryTaskId, pair = false)) - executeShellCommand(composePairsCommand( - primaryTaskId, nonResizeableTaskId, pair = false)) - primaryApp.exit(wmHelper) - secondaryApp.exit(wmHelper) - nonResizeableApp?.exit(wmHelper) - } - } - } - - protected fun updateTasksId() { - primaryTaskId = getTaskIdForActivity( - primaryApp.component.packageName, primaryApp.component.className).toString() - secondaryTaskId = getTaskIdForActivity( - secondaryApp.component.packageName, secondaryApp.component.className).toString() - val nonResizeableApp = nonResizeableApp - if (nonResizeableApp != null) { - nonResizeableTaskId = getTaskIdForActivity( - nonResizeableApp.component.packageName, - nonResizeableApp.component.className).toString() - } - } - - private fun getTaskIdForActivity(pkgName: String, activityName: String): Int { - return activityHelper.getTaskIdForActivity(pkgName, activityName) - } - - internal fun executeShellCommand(cmd: String) { - BaseAppHelper.executeShellCommand(instrumentation, cmd) - } - - internal fun composePairsCommand( - primaryApp: String, - secondaryApp: String, - pair: Boolean - ): String = buildString { - // dumpsys activity service SystemUIService WMShell {pair|unpair} ${TASK_ID_1} ${TASK_ID_2} - append("dumpsys activity service SystemUIService WMShell ") - if (pair) { - append("pair ") - } else { - append("unpair ") - } - append("$primaryApp $secondaryApp") - } - - @Ignore - @Test - open fun navBarLayerIsVisible() { - testSpec.navBarLayerIsVisible() - } - - @Ignore - @Test - open fun statusBarLayerIsVisible() { - testSpec.statusBarLayerIsVisible() - } - - @Ignore - @Test - open fun navBarWindowIsVisible() { - testSpec.navBarWindowIsVisible() - } - - @Ignore - @Test - open fun statusBarWindowIsVisible() { - testSpec.statusBarWindowIsVisible() - } - - @Ignore - @Test - open fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() - - @Ignore - @Test - open fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() -}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/OWNERS b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/OWNERS deleted file mode 100644 index 8446b37dbf06..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/OWNERS +++ /dev/null @@ -1,2 +0,0 @@ -# window manager > wm shell > Split Screen -# Bug component: 928697 diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt deleted file mode 100644 index b0c3ba20d948..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.apppairs - -import android.view.Surface -import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group1 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.setRotation -import com.android.wm.shell.flicker.appPairsDividerIsVisibleAtEnd -import com.android.wm.shell.flicker.appPairsPrimaryBoundsIsVisibleAtEnd -import com.android.wm.shell.flicker.appPairsSecondaryBoundsIsVisibleAtEnd -import com.android.wm.shell.flicker.helpers.AppPairsHelper.Companion.waitAppsShown -import com.android.wm.shell.flicker.helpers.SplitScreenHelper -import org.junit.FixMethodOrder -import org.junit.Ignore -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized - -/** - * Test open apps to app pairs and rotate. - * To run this test: `atest WMShellFlickerTests:RotateTwoLaunchedAppsInAppPairsMode` - */ -@RequiresDevice -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group1 -class RotateTwoLaunchedAppsInAppPairsMode( - testSpec: FlickerTestParameter -) : RotateTwoLaunchedAppsTransition(testSpec) { - override val transition: FlickerBuilder.() -> Unit - get() = { - super.transition(this) - transitions { - executeShellCommand(composePairsCommand( - primaryTaskId, secondaryTaskId, true /* pair */)) - waitAppsShown(primaryApp, secondaryApp) - setRotation(testSpec.endRotation) - } - } - - @Ignore - @Test - override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() - - @Ignore - @Test - override fun statusBarLayerIsVisible() = super.statusBarLayerIsVisible() - - @Ignore - @Test - fun bothAppWindowsVisible() { - testSpec.assertWmEnd { - isAppWindowVisible(primaryApp.component) - isAppWindowVisible(secondaryApp.component) - } - } - - @Ignore - @Test - fun appPairsDividerIsVisibleAtEnd() = testSpec.appPairsDividerIsVisibleAtEnd() - - @Ignore - @Test - fun appPairsPrimaryBoundsIsVisibleAtEnd() = - testSpec.appPairsPrimaryBoundsIsVisibleAtEnd(testSpec.endRotation, - primaryApp.component) - - @Ignore - @Test - fun appPairsSecondaryBoundsIsVisibleAtEnd() = - testSpec.appPairsSecondaryBoundsIsVisibleAtEnd(testSpec.endRotation, - secondaryApp.component) - - @Ignore - @Test - override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() - - companion object { - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( - repetitions = SplitScreenHelper.TEST_REPETITIONS, - supportedRotations = listOf(Surface.ROTATION_90, Surface.ROTATION_270) - ) - } - } -}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt deleted file mode 100644 index ae56c7732a4d..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.apppairs - -import android.view.Surface -import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group1 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.setRotation -import com.android.wm.shell.flicker.appPairsDividerIsVisibleAtEnd -import com.android.wm.shell.flicker.appPairsPrimaryBoundsIsVisibleAtEnd -import com.android.wm.shell.flicker.appPairsSecondaryBoundsIsVisibleAtEnd -import com.android.wm.shell.flicker.helpers.AppPairsHelper.Companion.waitAppsShown -import com.android.wm.shell.flicker.helpers.SplitScreenHelper -import org.junit.FixMethodOrder -import org.junit.Ignore -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized - -/** - * Test open apps to app pairs and rotate. - * To run this test: `atest WMShellFlickerTests:RotateTwoLaunchedAppsRotateAndEnterAppPairsMode` - */ -@RequiresDevice -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group1 -class RotateTwoLaunchedAppsRotateAndEnterAppPairsMode( - testSpec: FlickerTestParameter -) : RotateTwoLaunchedAppsTransition(testSpec) { - override val transition: FlickerBuilder.() -> Unit - get() = { - super.transition(this) - transitions { - this.setRotation(testSpec.endRotation) - executeShellCommand( - composePairsCommand(primaryTaskId, secondaryTaskId, pair = true)) - waitAppsShown(primaryApp, secondaryApp) - } - } - - @Ignore - @Test - fun appPairsDividerIsVisibleAtEnd() = testSpec.appPairsDividerIsVisibleAtEnd() - - @Ignore - @Test - override fun navBarWindowIsVisible() = super.navBarWindowIsVisible() - - @Ignore - @Test - override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() - - @Ignore - @Test - override fun statusBarWindowIsVisible() = super.statusBarWindowIsVisible() - - @Ignore - @Test - override fun statusBarLayerIsVisible() = super.statusBarLayerIsVisible() - - @Ignore - @Test - override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() - - @Ignore - @Test - fun bothAppWindowsVisible() { - testSpec.assertWmEnd { - isAppWindowVisible(primaryApp.component) - isAppWindowVisible(secondaryApp.component) - } - } - - @Ignore - @Test - fun appPairsPrimaryBoundsIsVisibleAtEnd() = - testSpec.appPairsPrimaryBoundsIsVisibleAtEnd(testSpec.endRotation, - primaryApp.component) - - @Ignore - @Test - fun appPairsSecondaryBoundsIsVisibleAtEnd() = - testSpec.appPairsSecondaryBoundsIsVisibleAtEnd(testSpec.endRotation, - secondaryApp.component) - - companion object { - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( - repetitions = SplitScreenHelper.TEST_REPETITIONS, - supportedRotations = listOf(Surface.ROTATION_90, Surface.ROTATION_270) - ) - } - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt deleted file mode 100644 index b1f1c9e539df..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.apppairs - -import android.view.Surface -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen -import com.android.wm.shell.flicker.helpers.BaseAppHelper.Companion.isShellTransitionsEnabled -import com.android.wm.shell.flicker.helpers.SplitScreenHelper -import org.junit.Assume.assumeFalse -import org.junit.Before -import org.junit.Ignore -import org.junit.Test - -abstract class RotateTwoLaunchedAppsTransition( - testSpec: FlickerTestParameter -) : AppPairsTransition(testSpec) { - override val nonResizeableApp: SplitScreenHelper? - get() = null - - override val transition: FlickerBuilder.() -> Unit - get() = { - setup { - test { - device.wakeUpAndGoToHomeScreen() - this.setRotation(Surface.ROTATION_0) - primaryApp.launchViaIntent(wmHelper) - secondaryApp.launchViaIntent(wmHelper) - updateTasksId() - } - } - teardown { - eachRun { - executeShellCommand(composePairsCommand( - primaryTaskId, secondaryTaskId, pair = false)) - primaryApp.exit(wmHelper) - secondaryApp.exit(wmHelper) - } - } - } - - @Before - override fun setup() { - // AppPairs hasn't been updated to Shell Transition. There will be conflict on rotation. - assumeFalse(isShellTransitionsEnabled()) - super.setup() - } - - @Ignore - @Test - override fun navBarLayerIsVisible() { - super.navBarLayerIsVisible() - } - - @Ignore - @Test - override fun navBarLayerRotatesAndScales() { - super.navBarLayerRotatesAndScales() - } -}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt index e9d438a569d5..f73d191b1917 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt @@ -39,7 +39,7 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper( ) { private val mediaSessionManager: MediaSessionManager get() = context.getSystemService(MediaSessionManager::class.java) - ?: error("Could not get MediaSessionManager") + ?: error("Could not get MediaSessionManager") private val mediaController: MediaController? get() = mediaSessionManager.getActiveSessions(null).firstOrNull { @@ -69,8 +69,10 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper( action: String? = null, stringExtras: Map<String, String> ) { - launchViaIntentAndWaitShown(wmHelper, expectedWindowName, action, stringExtras, - waitConditions = arrayOf(WindowManagerStateHelper.pipShownCondition)) + launchViaIntentAndWaitShown( + wmHelper, expectedWindowName, action, stringExtras, + waitConditions = arrayOf(WindowManagerStateHelper.pipShownCondition) + ) } /** @@ -85,7 +87,7 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper( // from "the bottom". repeat(FOCUS_ATTEMPTS) { uiDevice.findObject(selector)?.apply { if (isFocusedOrHasFocusedChild) return true } - ?: error("The object we try to focus on is gone.") + ?: error("The object we try to focus on is gone.") uiDevice.pressDPadDown() uiDevice.waitForIdle() @@ -100,29 +102,35 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper( // Wait on WMHelper or simply wait for 3 seconds wmHelper?.waitPipShown() ?: SystemClock.sleep(3_000) // when entering pip, the dismiss button is visible at the start. to ensure the pip - // animation is complete, wait until the pip dismiss button is no longer visible. + // animation is complete, wait until the pip dismiss button is no longer visible. // b/176822698: dismiss-only state will be removed in the future uiDevice.wait(Until.gone(By.res(SYSTEMUI_PACKAGE, "dismiss")), FIND_TIMEOUT) } + fun enableEnterPipOnUserLeaveHint() { + clickObject(ENTER_PIP_ON_USER_LEAVE_HINT) + } + fun clickStartMediaSessionButton() { clickObject(MEDIA_SESSION_START_RADIO_BUTTON_ID) } fun checkWithCustomActionsCheckbox() = uiDevice - .findObject(By.res(component.packageName, WITH_CUSTOM_ACTIONS_BUTTON_ID)) - ?.takeIf { it.isCheckable } - ?.apply { if (!isChecked) clickObject(WITH_CUSTOM_ACTIONS_BUTTON_ID) } - ?: error("'With custom actions' checkbox not found") + .findObject(By.res(component.packageName, WITH_CUSTOM_ACTIONS_BUTTON_ID)) + ?.takeIf { it.isCheckable } + ?.apply { if (!isChecked) clickObject(WITH_CUSTOM_ACTIONS_BUTTON_ID) } + ?: error("'With custom actions' checkbox not found") fun pauseMedia() = mediaController?.transportControls?.pause() - ?: error("No active media session found") + ?: error("No active media session found") fun stopMedia() = mediaController?.transportControls?.stop() - ?: error("No active media session found") + ?: error("No active media session found") - @Deprecated("Use PipAppHelper.closePipWindow(wmHelper) instead", - ReplaceWith("closePipWindow(wmHelper)")) + @Deprecated( + "Use PipAppHelper.closePipWindow(wmHelper) instead", + ReplaceWith("closePipWindow(wmHelper)") + ) fun closePipWindow() { if (isTelevision) { uiDevice.closeTvPipWindow() @@ -152,7 +160,7 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper( val dismissSelector = By.res(SYSTEMUI_PACKAGE, "dismiss") uiDevice.wait(Until.hasObject(dismissSelector), FIND_TIMEOUT) val dismissPipObject = uiDevice.findObject(dismissSelector) - ?: error("PIP window dismiss button not found") + ?: error("PIP window dismiss button not found") val dismissButtonBounds = dismissPipObject.visibleBounds uiDevice.click(dismissButtonBounds.centerX(), dismissButtonBounds.centerY()) } @@ -172,7 +180,7 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper( val expandSelector = By.res(SYSTEMUI_PACKAGE, "expand_button") uiDevice.wait(Until.hasObject(expandSelector), FIND_TIMEOUT) val expandPipObject = uiDevice.findObject(expandSelector) - ?: error("PIP window expand button not found") + ?: error("PIP window expand button not found") val expandButtonBounds = expandPipObject.visibleBounds uiDevice.click(expandButtonBounds.centerX(), expandButtonBounds.centerY()) wmHelper.waitPipGone() @@ -194,5 +202,6 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper( private const val ENTER_PIP_BUTTON_ID = "enter_pip" private const val WITH_CUSTOM_ACTIONS_BUTTON_ID = "with_custom_actions" private const val MEDIA_SESSION_START_RADIO_BUTTON_ID = "media_session_start" + private const val ENTER_PIP_ON_USER_LEAVE_HINT = "enter_pip_on_leave_manual" } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt index 0ec9b2d869a8..d4298b8f29a0 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt @@ -17,9 +17,18 @@ package com.android.wm.shell.flicker.helpers import android.app.Instrumentation -import android.content.res.Resources +import android.graphics.Point +import android.os.SystemClock +import android.view.InputDevice +import android.view.MotionEvent +import androidx.test.uiautomator.By +import androidx.test.uiautomator.BySelector +import androidx.test.uiautomator.UiDevice +import androidx.test.uiautomator.Until import com.android.server.wm.traces.common.FlickerComponentName import com.android.server.wm.traces.parser.toFlickerComponent +import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME import com.android.wm.shell.flicker.testapp.Components class SplitScreenHelper( @@ -31,25 +40,152 @@ class SplitScreenHelper( companion object { const val TEST_REPETITIONS = 1 const val TIMEOUT_MS = 3_000L + const val DRAG_DURATION_MS = 1_000L + const val NOTIFICATION_SCROLLER = "notification_stack_scroller" + const val GESTURE_STEP_MS = 16L - // TODO: remove all legacy split screen flicker tests when legacy split screen is fully - // deprecated. - fun isUsingLegacySplit(): Boolean = - Resources.getSystem().getBoolean(com.android.internal.R.bool.config_useLegacySplit) + private val notificationScrollerSelector: BySelector + get() = By.res(SYSTEM_UI_PACKAGE_NAME, NOTIFICATION_SCROLLER) + private val notificationContentSelector: BySelector + get() = By.text("Notification content") fun getPrimary(instrumentation: Instrumentation): SplitScreenHelper = - SplitScreenHelper(instrumentation, + SplitScreenHelper( + instrumentation, Components.SplitScreenActivity.LABEL, - Components.SplitScreenActivity.COMPONENT.toFlickerComponent()) + Components.SplitScreenActivity.COMPONENT.toFlickerComponent() + ) fun getSecondary(instrumentation: Instrumentation): SplitScreenHelper = - SplitScreenHelper(instrumentation, + SplitScreenHelper( + instrumentation, Components.SplitScreenSecondaryActivity.LABEL, - Components.SplitScreenSecondaryActivity.COMPONENT.toFlickerComponent()) + Components.SplitScreenSecondaryActivity.COMPONENT.toFlickerComponent() + ) fun getNonResizeable(instrumentation: Instrumentation): SplitScreenHelper = - SplitScreenHelper(instrumentation, + SplitScreenHelper( + instrumentation, Components.NonResizeableActivity.LABEL, - Components.NonResizeableActivity.COMPONENT.toFlickerComponent()) + Components.NonResizeableActivity.COMPONENT.toFlickerComponent() + ) + + fun getSendNotification(instrumentation: Instrumentation): SplitScreenHelper = + SplitScreenHelper( + instrumentation, + Components.SendNotificationActivity.LABEL, + Components.SendNotificationActivity.COMPONENT.toFlickerComponent() + ) + + fun dragFromNotificationToSplit( + instrumentation: Instrumentation, + device: UiDevice, + wmHelper: WindowManagerStateHelper + ) { + val displayBounds = wmHelper.currentState.layerState + .displays.firstOrNull { !it.isVirtual } + ?.layerStackSpace + ?: error("Display not found") + + // Pull down the notifications + device.swipe( + displayBounds.centerX(), 5, + displayBounds.centerX(), displayBounds.bottom, 20 /* steps */ + ) + SystemClock.sleep(TIMEOUT_MS) + + // Find the target notification + val notificationScroller = device.wait( + Until.findObject(notificationScrollerSelector), TIMEOUT_MS + ) + var notificationContent = notificationScroller.findObject(notificationContentSelector) + + while (notificationContent == null) { + device.swipe( + displayBounds.centerX(), displayBounds.centerY(), + displayBounds.centerX(), displayBounds.centerY() - 150, 20 /* steps */ + ) + notificationContent = notificationScroller.findObject(notificationContentSelector) + } + + // Drag to split + var dragStart = notificationContent.visibleCenter + var dragMiddle = Point(dragStart.x + 50, dragStart.y) + var dragEnd = Point(displayBounds.width / 4, displayBounds.width / 4) + val downTime = SystemClock.uptimeMillis() + + touch( + instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime, + TIMEOUT_MS, dragStart + ) + // It needs a horizontal movement to trigger the drag + touchMove( + instrumentation, downTime, SystemClock.uptimeMillis(), + DRAG_DURATION_MS, dragStart, dragMiddle + ) + touchMove( + instrumentation, downTime, SystemClock.uptimeMillis(), + DRAG_DURATION_MS, dragMiddle, dragEnd + ) + // Wait for a while to start splitting + SystemClock.sleep(TIMEOUT_MS) + touch( + instrumentation, MotionEvent.ACTION_UP, downTime, SystemClock.uptimeMillis(), + GESTURE_STEP_MS, dragEnd + ) + SystemClock.sleep(TIMEOUT_MS) + } + + fun touch( + instrumentation: Instrumentation, + action: Int, + downTime: Long, + eventTime: Long, + duration: Long, + point: Point + ) { + val motionEvent = MotionEvent.obtain( + downTime, eventTime, action, point.x.toFloat(), point.y.toFloat(), 0 + ) + motionEvent.source = InputDevice.SOURCE_TOUCHSCREEN + instrumentation.uiAutomation.injectInputEvent(motionEvent, true) + motionEvent.recycle() + SystemClock.sleep(duration) + } + + fun touchMove( + instrumentation: Instrumentation, + downTime: Long, + eventTime: Long, + duration: Long, + from: Point, + to: Point + ) { + val steps: Long = duration / GESTURE_STEP_MS + var currentTime = eventTime + var currentX = from.x.toFloat() + var currentY = from.y.toFloat() + val stepX = (to.x.toFloat() - from.x.toFloat()) / steps.toFloat() + val stepY = (to.y.toFloat() - from.y.toFloat()) / steps.toFloat() + + for (i in 1..steps) { + val motionMove = MotionEvent.obtain( + downTime, currentTime, MotionEvent.ACTION_MOVE, currentX, currentY, 0 + ) + motionMove.source = InputDevice.SOURCE_TOUCHSCREEN + instrumentation.uiAutomation.injectInputEvent(motionMove, true) + motionMove.recycle() + + currentTime += GESTURE_STEP_MS + if (i == steps - 1) { + currentX = to.x.toFloat() + currentY = to.y.toFloat() + } else { + currentX += stepX + currentY += stepY + } + SystemClock.sleep(GESTURE_STEP_MS) + } + } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt deleted file mode 100644 index c86a1229d8d8..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.legacysplitscreen - -import android.platform.test.annotations.Presubmit -import android.view.Surface -import android.view.WindowManagerPolicyConstants -import androidx.test.filters.FlakyTest -import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group4 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.launchSplitScreen -import com.android.server.wm.flicker.navBarWindowIsVisible -import com.android.server.wm.flicker.statusBarWindowIsVisible -import com.android.server.wm.traces.common.FlickerComponentName -import com.android.wm.shell.flicker.dockedStackDividerBecomesVisible -import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd -import com.android.wm.shell.flicker.helpers.SplitScreenHelper -import org.junit.FixMethodOrder -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized - -/** - * Test open activity and dock to primary split screen - * To run this test: `atest WMShellFlickerTests:EnterSplitScreenDockActivity` - */ -@Presubmit -@RequiresDevice -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group4 -class EnterSplitScreenDockActivity( - testSpec: FlickerTestParameter -) : LegacySplitScreenTransition(testSpec) { - override val transition: FlickerBuilder.() -> Unit - get() = { - super.transition(this) - transitions { - device.launchSplitScreen(wmHelper) - } - } - - override val ignoredWindows: List<FlickerComponentName> - get() = listOf(LAUNCHER_COMPONENT, LIVE_WALLPAPER_COMPONENT, - splitScreenApp.component, FlickerComponentName.SPLASH_SCREEN, - FlickerComponentName.SNAPSHOT, LAUNCHER_COMPONENT) - - @Presubmit - @Test - fun dockedStackPrimaryBoundsIsVisibleAtEnd() = - testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.startRotation, - splitScreenApp.component) - - @Presubmit - @Test - fun dockedStackDividerBecomesVisible() = testSpec.dockedStackDividerBecomesVisible() - - @Presubmit - @Test - fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() - - @Presubmit - @Test - fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() - - @Presubmit - @Test - fun appWindowIsVisible() { - testSpec.assertWmEnd { - isAppWindowVisible(splitScreenApp.component) - } - } - - @FlakyTest - @Test - override fun visibleLayersShownMoreThanOneConsecutiveEntry() = - super.visibleLayersShownMoreThanOneConsecutiveEntry() - - @Presubmit - @Test - override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - super.visibleWindowsShownMoreThanOneConsecutiveEntry() - - companion object { - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( - repetitions = SplitScreenHelper.TEST_REPETITIONS, - supportedRotations = listOf(Surface.ROTATION_0), // bugId = 179116910 - supportedNavigationModes = listOf( - WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY) - ) - } - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenFromDetachedRecentTask.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenFromDetachedRecentTask.kt deleted file mode 100644 index 2f9244be9c18..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenFromDetachedRecentTask.kt +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.legacysplitscreen - -import android.platform.test.annotations.Presubmit -import android.view.Surface -import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group4 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.launchSplitScreen -import com.android.server.wm.traces.common.FlickerComponentName -import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd -import com.android.wm.shell.flicker.helpers.SplitScreenHelper -import org.junit.FixMethodOrder -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized - -/** - * Test enter split screen from a detached recent task - * - * To run this test: `atest WMShellFlickerTests:EnterSplitScreenFromDetachedRecentTask` - */ -@RequiresDevice -@RunWith(Parameterized::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@Group4 -class EnterSplitScreenFromDetachedRecentTask( - testSpec: FlickerTestParameter -) : LegacySplitScreenTransition(testSpec) { - - override val transition: FlickerBuilder.() -> Unit - get() = { - cleanSetup(this) - setup { - eachRun { - splitScreenApp.launchViaIntent(wmHelper) - // Press back to remove the task, but it should still be shown in recent. - device.pressBack() - } - } - transitions { - device.launchSplitScreen(wmHelper) - } - } - - override val ignoredWindows: List<FlickerComponentName> - get() = listOf(LAUNCHER_COMPONENT, - FlickerComponentName.SPLASH_SCREEN, - FlickerComponentName.SNAPSHOT, - splitScreenApp.component) - - @Presubmit - @Test - fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd() - - @Presubmit - @Test - fun appWindowIsVisible() { - testSpec.assertWmEnd { - isAppWindowVisible(splitScreenApp.component) - } - } - - @Presubmit - @Test - override fun visibleLayersShownMoreThanOneConsecutiveEntry() = - super.visibleLayersShownMoreThanOneConsecutiveEntry() - - @Presubmit - @Test - override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - super.visibleWindowsShownMoreThanOneConsecutiveEntry() - - companion object { - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( - repetitions = SplitScreenHelper.TEST_REPETITIONS, - supportedRotations = listOf(Surface.ROTATION_0) // bugId = 179116910 - ) - } - } -}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt deleted file mode 100644 index 1740c3ec24ca..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.legacysplitscreen - -import android.platform.test.annotations.Presubmit -import android.view.Surface -import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group4 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.launchSplitScreen -import com.android.server.wm.flicker.helpers.reopenAppFromOverview -import com.android.server.wm.flicker.navBarWindowIsVisible -import com.android.server.wm.flicker.statusBarWindowIsVisible -import com.android.server.wm.traces.common.FlickerComponentName -import com.android.wm.shell.flicker.dockedStackDividerBecomesVisible -import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd -import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisibleAtEnd -import com.android.wm.shell.flicker.helpers.SplitScreenHelper -import org.junit.FixMethodOrder -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized - -/** - * Test open activity to primary split screen and dock secondary activity to side - * To run this test: `atest WMShellFlickerTests:EnterSplitScreenLaunchToSide` - */ -@RequiresDevice -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group4 -class EnterSplitScreenLaunchToSide( - testSpec: FlickerTestParameter -) : LegacySplitScreenTransition(testSpec) { - override val transition: FlickerBuilder.() -> Unit - get() = { - super.transition(this) - transitions { - device.launchSplitScreen(wmHelper) - device.reopenAppFromOverview(wmHelper) - } - } - - override val ignoredWindows: List<FlickerComponentName> - get() = listOf(LAUNCHER_COMPONENT, splitScreenApp.component, - secondaryApp.component, FlickerComponentName.SPLASH_SCREEN, - FlickerComponentName.SNAPSHOT) - - @Presubmit - @Test - fun dockedStackPrimaryBoundsIsVisibleAtEnd() = - testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.startRotation, - splitScreenApp.component) - - @Presubmit - @Test - fun dockedStackSecondaryBoundsIsVisibleAtEnd() = - testSpec.dockedStackSecondaryBoundsIsVisibleAtEnd(testSpec.startRotation, - secondaryApp.component) - - @Presubmit - @Test - fun dockedStackDividerBecomesVisible() = testSpec.dockedStackDividerBecomesVisible() - - @Presubmit - @Test - fun appWindowBecomesVisible() { - testSpec.assertWm { - // when the app is launched, first the activity becomes visible, then the - // SnapshotStartingWindow appears and then the app window becomes visible. - // Because we log WM once per frame, sometimes the activity and the window - // become visible in the same entry, sometimes not, thus it is not possible to - // assert the visibility of the activity here - this.isAppWindowInvisible(secondaryApp.component) - .then() - // during re-parenting, the window may disappear and reappear from the - // trace, this occurs because we log only 1x per frame - .notContains(secondaryApp.component, isOptional = true) - .then() - .isAppWindowVisible(secondaryApp.component) - } - } - - @Presubmit - @Test - fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() - - @Presubmit - @Test - fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() - - @Presubmit - @Test - override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - super.visibleWindowsShownMoreThanOneConsecutiveEntry() - - companion object { - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( - repetitions = SplitScreenHelper.TEST_REPETITIONS, - supportedRotations = listOf(Surface.ROTATION_0) // bugId = 175687842 - ) - } - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt deleted file mode 100644 index 4c063b918e96..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.legacysplitscreen - -import android.platform.test.annotations.Presubmit -import android.view.Surface -import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group4 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.canSplitScreen -import com.android.server.wm.traces.common.FlickerComponentName -import com.android.wm.shell.flicker.dockedStackDividerNotExistsAtEnd -import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig -import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow -import com.android.wm.shell.flicker.helpers.SplitScreenHelper -import org.junit.After -import org.junit.Assert -import org.junit.Before -import org.junit.FixMethodOrder -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized - -/** - * Test enter split screen from non-resizable activity. When the device doesn't support - * non-resizable in multi window, there should be no button to enter split screen for non-resizable - * activity. - * - * To run this test: `atest WMShellFlickerTests:EnterSplitScreenNotSupportNonResizable` - */ -@RequiresDevice -@RunWith(Parameterized::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@Group4 -class EnterSplitScreenNotSupportNonResizable( - testSpec: FlickerTestParameter -) : LegacySplitScreenTransition(testSpec) { - - override val transition: FlickerBuilder.() -> Unit - get() = { - cleanSetup(this) - setup { - eachRun { - nonResizeableApp.launchViaIntent(wmHelper) - } - } - transitions { - if (device.canSplitScreen(wmHelper)) { - Assert.fail("Non-resizeable app should not enter split screen") - } - } - } - - override val ignoredWindows: List<FlickerComponentName> - get() = listOf(LAUNCHER_COMPONENT, - FlickerComponentName.SPLASH_SCREEN, - FlickerComponentName.SNAPSHOT, - nonResizeableApp.component, - splitScreenApp.component) - - @Before - override fun setup() { - super.setup() - setSupportsNonResizableMultiWindow(instrumentation, -1) - } - - @After - override fun teardown() { - super.teardown() - resetMultiWindowConfig(instrumentation) - } - - @Presubmit - @Test - fun dockedStackDividerNotExistsAtEnd() = testSpec.dockedStackDividerNotExistsAtEnd() - - @Presubmit - @Test - override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - super.visibleWindowsShownMoreThanOneConsecutiveEntry() - - companion object { - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( - repetitions = SplitScreenHelper.TEST_REPETITIONS, - supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668 - } - } -}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt deleted file mode 100644 index f75dee619564..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.legacysplitscreen - -import android.platform.test.annotations.Presubmit -import android.view.Surface -import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group2 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.launchSplitScreen -import com.android.server.wm.traces.common.FlickerComponentName -import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd -import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig -import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow -import com.android.wm.shell.flicker.helpers.SplitScreenHelper -import org.junit.After -import org.junit.Before -import org.junit.FixMethodOrder -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized - -/** - * Test enter split screen from non-resizable activity. When the device supports - * non-resizable in multi window, there should be a button to enter split screen for non-resizable - * activity. - * - * To run this test: `atest WMShellFlickerTests:EnterSplitScreenSupportNonResizable` - */ -@RequiresDevice -@RunWith(Parameterized::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@Group2 -class EnterSplitScreenSupportNonResizable( - testSpec: FlickerTestParameter -) : LegacySplitScreenTransition(testSpec) { - - override val transition: FlickerBuilder.() -> Unit - get() = { - cleanSetup(this) - setup { - eachRun { - nonResizeableApp.launchViaIntent(wmHelper) - } - } - transitions { - device.launchSplitScreen(wmHelper) - } - } - - override val ignoredWindows: List<FlickerComponentName> - get() = listOf(LAUNCHER_COMPONENT, - FlickerComponentName.SPLASH_SCREEN, - FlickerComponentName.SNAPSHOT, - nonResizeableApp.component, - splitScreenApp.component) - - @Before - override fun setup() { - super.setup() - setSupportsNonResizableMultiWindow(instrumentation, 1) - } - - @After - override fun teardown() { - super.teardown() - resetMultiWindowConfig(instrumentation) - } - - @Presubmit - @Test - fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd() - - @Presubmit - @Test - fun appWindowIsVisible() { - testSpec.assertWmEnd { - isAppWindowVisible(nonResizeableApp.component) - } - } - - @Presubmit - @Test - override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - super.visibleWindowsShownMoreThanOneConsecutiveEntry() - - companion object { - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( - repetitions = SplitScreenHelper.TEST_REPETITIONS, - supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668 - } - } -}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt deleted file mode 100644 index ef7d65e8a732..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.legacysplitscreen - -import android.view.Surface -import androidx.test.filters.FlakyTest -import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group2 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.exitSplitScreenFromBottom -import com.android.server.wm.flicker.helpers.launchSplitScreen -import com.android.server.wm.flicker.navBarWindowIsVisible -import com.android.server.wm.flicker.statusBarWindowIsVisible -import com.android.server.wm.traces.common.FlickerComponentName -import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT -import com.android.wm.shell.flicker.helpers.SplitScreenHelper -import org.junit.FixMethodOrder -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized - -/** - * Test open resizeable activity split in primary, and drag divider to bottom exit split screen - * To run this test: `atest WMShellFlickerTests:ExitLegacySplitScreenFromBottom` - */ -@RequiresDevice -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group2 -class ExitLegacySplitScreenFromBottom( - testSpec: FlickerTestParameter -) : LegacySplitScreenTransition(testSpec) { - override val transition: FlickerBuilder.() -> Unit - get() = { - super.transition(this) - setup { - eachRun { - splitScreenApp.launchViaIntent(wmHelper) - device.launchSplitScreen(wmHelper) - } - } - teardown { - eachRun { - splitScreenApp.exit(wmHelper) - } - } - transitions { - device.exitSplitScreenFromBottom(wmHelper) - } - } - - override val ignoredWindows: List<FlickerComponentName> - get() = listOf(LAUNCHER_COMPONENT, FlickerComponentName.SPLASH_SCREEN, - splitScreenApp.component, secondaryApp.component, - FlickerComponentName.SNAPSHOT) - - @FlakyTest - @Test - fun layerBecomesInvisible() { - testSpec.assertLayers { - this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT) - .then() - .isInvisible(DOCKED_STACK_DIVIDER_COMPONENT) - } - } - - @FlakyTest - @Test - fun appWindowBecomesInVisible() { - testSpec.assertWm { - this.isAppWindowVisible(secondaryApp.component) - .then() - .isAppWindowInvisible(secondaryApp.component) - } - } - - @FlakyTest - @Test - fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() - - @FlakyTest - @Test - fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() - - @FlakyTest - @Test - override fun visibleLayersShownMoreThanOneConsecutiveEntry() = - super.visibleLayersShownMoreThanOneConsecutiveEntry() - - @FlakyTest - @Test - override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - super.visibleWindowsShownMoreThanOneConsecutiveEntry() - - companion object { - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( - repetitions = SplitScreenHelper.TEST_REPETITIONS, - supportedRotations = listOf(Surface.ROTATION_0) // b/175687842 - ) - } - } -}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt deleted file mode 100644 index d913a6d85d3d..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.legacysplitscreen - -import android.platform.test.annotations.Presubmit -import android.view.Surface -import androidx.test.filters.FlakyTest -import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group2 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.launchSplitScreen -import com.android.server.wm.flicker.helpers.reopenAppFromOverview -import com.android.server.wm.flicker.navBarWindowIsVisible -import com.android.server.wm.flicker.statusBarWindowIsVisible -import com.android.server.wm.traces.common.FlickerComponentName -import com.android.wm.shell.flicker.dockedStackDividerNotExistsAtEnd -import com.android.wm.shell.flicker.helpers.SplitScreenHelper -import org.junit.FixMethodOrder -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized - -/** - * Test dock activity to primary split screen, and open secondary to side, exit primary split - * and test secondary activity become full screen. - * To run this test: `atest WMShellFlickerTests:ExitPrimarySplitScreenShowSecondaryFullscreen` - */ -@RequiresDevice -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group2 -class ExitPrimarySplitScreenShowSecondaryFullscreen( - testSpec: FlickerTestParameter -) : LegacySplitScreenTransition(testSpec) { - override val transition: FlickerBuilder.() -> Unit - get() = { - super.transition(this) - teardown { - eachRun { - secondaryApp.exit(wmHelper) - } - } - transitions { - splitScreenApp.launchViaIntent(wmHelper) - secondaryApp.launchViaIntent(wmHelper) - device.launchSplitScreen(wmHelper) - device.reopenAppFromOverview(wmHelper) - // TODO(b/175687842) Can not find Split screen divider, use exit() instead - splitScreenApp.exit(wmHelper) - } - } - - override val ignoredWindows: List<FlickerComponentName> - get() = listOf(LAUNCHER_COMPONENT, FlickerComponentName.SPLASH_SCREEN, - splitScreenApp.component, secondaryApp.component, - FlickerComponentName.SNAPSHOT) - - @Presubmit - @Test - fun dockedStackDividerNotExistsAtEnd() = testSpec.dockedStackDividerNotExistsAtEnd() - - @FlakyTest - @Test - fun layerBecomesInvisible() { - testSpec.assertLayers { - this.isVisible(splitScreenApp.component) - .then() - .isInvisible(splitScreenApp.component) - } - } - - @FlakyTest - @Test - fun appWindowBecomesInVisible() { - testSpec.assertWm { - this.isAppWindowVisible(splitScreenApp.component) - .then() - .isAppWindowInvisible(splitScreenApp.component) - } - } - - @Presubmit - @Test - fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() - - @Presubmit - @Test - fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() - - @Presubmit - @Test - override fun visibleLayersShownMoreThanOneConsecutiveEntry() = - super.visibleLayersShownMoreThanOneConsecutiveEntry() - - @Presubmit - @Test - override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - super.visibleWindowsShownMoreThanOneConsecutiveEntry() - - companion object { - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( - repetitions = SplitScreenHelper.TEST_REPETITIONS, - supportedRotations = listOf(Surface.ROTATION_0) // bugId = 179116910 - ) - } - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt deleted file mode 100644 index f3ff7b156aaf..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.legacysplitscreen - -import android.platform.test.annotations.Presubmit -import android.view.Surface -import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group2 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.launchSplitScreen -import com.android.server.wm.traces.common.FlickerComponentName -import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT -import com.android.wm.shell.flicker.dockedStackDividerNotExistsAtEnd -import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig -import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow -import com.android.wm.shell.flicker.helpers.SplitScreenHelper -import org.junit.After -import org.junit.Before -import org.junit.FixMethodOrder -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized - -/** - * Test launch non-resizable activity via intent in split screen mode. When the device does not - * support non-resizable in multi window, it should trigger exit split screen. - * To run this test: `atest WMShellFlickerTests:LegacySplitScreenFromIntentNotSupportNonResizable` - */ -@RequiresDevice -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group2 -class LegacySplitScreenFromIntentNotSupportNonResizable( - testSpec: FlickerTestParameter -) : LegacySplitScreenTransition(testSpec) { - - override val transition: FlickerBuilder.() -> Unit - get() = { - cleanSetup(this) - setup { - eachRun { - splitScreenApp.launchViaIntent(wmHelper) - device.launchSplitScreen(wmHelper) - } - } - transitions { - nonResizeableApp.launchViaIntent(wmHelper) - wmHelper.waitForAppTransitionIdle() - } - } - - override val ignoredWindows: List<FlickerComponentName> - get() = listOf(DOCKED_STACK_DIVIDER_COMPONENT, LAUNCHER_COMPONENT, LETTERBOX_COMPONENT, - nonResizeableApp.component, splitScreenApp.component, - FlickerComponentName.SPLASH_SCREEN, - FlickerComponentName.SNAPSHOT) - - @Before - override fun setup() { - super.setup() - setSupportsNonResizableMultiWindow(instrumentation, -1) - } - - @After - override fun teardown() { - super.teardown() - resetMultiWindowConfig(instrumentation) - } - - @Presubmit - @Test - fun resizableAppLayerBecomesInvisible() { - testSpec.assertLayers { - this.isVisible(splitScreenApp.component) - .then() - .isInvisible(splitScreenApp.component) - } - } - - @Presubmit - @Test - fun nonResizableAppLayerBecomesVisible() { - testSpec.assertLayers { - this.notContains(nonResizeableApp.component) - .then() - .isInvisible(nonResizeableApp.component) - .then() - .isVisible(nonResizeableApp.component) - } - } - - /** - * Assets that [splitScreenApp] exists at the start of the trace and, once it becomes - * invisible, it remains invisible until the end of the trace. - */ - @Presubmit - @Test - fun resizableAppWindowBecomesInvisible() { - testSpec.assertWm { - // when the activity gets PAUSED the window may still be marked as visible - // it will be updated in the next log entry. This occurs because we record 1x - // per frame, thus ignore activity check here - this.isAppWindowVisible(splitScreenApp.component) - .then() - // immediately after the window (after onResume and before perform relayout) - // the activity is invisible. This may or not be logged, since we record 1x - // per frame, thus ignore activity check here - .isAppWindowInvisible(splitScreenApp.component) - } - } - - /** - * Assets that [nonResizeableApp] doesn't exist at the start of the trace, then - * [nonResizeableApp] is created (visible or not) and, once [nonResizeableApp] becomes - * visible, it remains visible until the end of the trace. - */ - @Presubmit - @Test - fun nonResizableAppWindowBecomesVisible() { - testSpec.assertWm { - this.notContains(nonResizeableApp.component) - .then() - // we log once per frame, upon logging, window may be visible or not depending - // on what was processed until that moment. Both behaviors are correct - .isAppWindowInvisible(nonResizeableApp.component, isOptional = true) - .then() - // immediately after the window (after onResume and before perform relayout) - // the activity is invisible. This may or not be logged, since we record 1x - // per frame, thus ignore activity check here - .isAppWindowVisible(nonResizeableApp.component) - } - } - - /** - * Asserts that both the app window and the activity are visible at the end of the trace - */ - @Presubmit - @Test - fun nonResizableAppWindowBecomesVisibleAtEnd() { - testSpec.assertWmEnd { - isAppWindowVisible(nonResizeableApp.component) - } - } - - @Presubmit - @Test - fun dockedStackDividerNotExistsAtEnd() = testSpec.dockedStackDividerNotExistsAtEnd() - - @Presubmit - @Test - fun onlyNonResizableAppWindowIsVisibleAtEnd() { - testSpec.assertWmEnd { - isAppWindowInvisible(splitScreenApp.component) - isAppWindowVisible(nonResizeableApp.component) - } - } - - @Presubmit - @Test - override fun visibleLayersShownMoreThanOneConsecutiveEntry() = - super.visibleLayersShownMoreThanOneConsecutiveEntry() - - @Presubmit - @Test - override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - super.visibleWindowsShownMoreThanOneConsecutiveEntry() - - companion object { - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( - repetitions = SplitScreenHelper.TEST_REPETITIONS, - supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668 - } - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt deleted file mode 100644 index 42e707ab0850..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.legacysplitscreen - -import android.platform.test.annotations.Presubmit -import android.view.Surface -import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group2 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.launchSplitScreen -import com.android.server.wm.traces.common.FlickerComponentName -import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT -import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd -import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig -import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow -import com.android.wm.shell.flicker.helpers.SplitScreenHelper -import org.junit.After -import org.junit.Before -import org.junit.FixMethodOrder -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized - -/** - * Test launch non-resizable activity via intent in split screen mode. When the device supports - * non-resizable in multi window, it should show the non-resizable app in split screen. - * To run this test: `atest WMShellFlickerTests:LegacySplitScreenFromIntentSupportNonResizable` - */ -@RequiresDevice -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group2 -class LegacySplitScreenFromIntentSupportNonResizable( - testSpec: FlickerTestParameter -) : LegacySplitScreenTransition(testSpec) { - - override val transition: FlickerBuilder.() -> Unit - get() = { - cleanSetup(this) - setup { - eachRun { - splitScreenApp.launchViaIntent(wmHelper) - device.launchSplitScreen(wmHelper) - } - } - transitions { - nonResizeableApp.launchViaIntent(wmHelper) - wmHelper.waitForAppTransitionIdle() - } - } - - override val ignoredWindows: List<FlickerComponentName> - get() = listOf(DOCKED_STACK_DIVIDER_COMPONENT, LAUNCHER_COMPONENT, LETTERBOX_COMPONENT, - nonResizeableApp.component, splitScreenApp.component, - FlickerComponentName.SPLASH_SCREEN, - FlickerComponentName.SNAPSHOT) - - @Before - override fun setup() { - super.setup() - setSupportsNonResizableMultiWindow(instrumentation, 1) - } - - @After - override fun teardown() { - super.teardown() - resetMultiWindowConfig(instrumentation) - } - - @Presubmit - @Test - fun nonResizableAppLayerBecomesVisible() { - testSpec.assertLayers { - this.isInvisible(nonResizeableApp.component) - .then() - .isVisible(nonResizeableApp.component) - } - } - - /** - * Assets that [nonResizeableApp] doesn't exist at the start of the trace, then - * [nonResizeableApp] is created (visible or not) and, once [nonResizeableApp] becomes - * visible, it remains visible until the end of the trace. - */ - @Presubmit - @Test - fun nonResizableAppWindowBecomesVisible() { - testSpec.assertWm { - this.notContains(nonResizeableApp.component) - .then() - // we log once per frame, upon logging, window may be visible or not depending - // on what was processed until that moment. Both behaviors are correct - .isAppWindowInvisible(nonResizeableApp.component, isOptional = true) - .then() - // immediately after the window (after onResume and before perform relayout) - // the activity is invisible. This may or not be logged, since we record 1x - // per frame, thus ignore activity check here - .isAppWindowVisible(nonResizeableApp.component) - } - } - - @Presubmit - @Test - fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd() - - @Presubmit - @Test - fun bothAppsWindowsAreVisibleAtEnd() { - testSpec.assertWmEnd { - isAppWindowVisible(splitScreenApp.component) - isAppWindowVisible(nonResizeableApp.component) - } - } - - @Presubmit - @Test - override fun visibleLayersShownMoreThanOneConsecutiveEntry() = - super.visibleLayersShownMoreThanOneConsecutiveEntry() - - @Presubmit - @Test - override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - super.visibleWindowsShownMoreThanOneConsecutiveEntry() - - companion object { - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( - repetitions = SplitScreenHelper.TEST_REPETITIONS, - supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668 - } - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt deleted file mode 100644 index c1fba7d1530c..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.legacysplitscreen - -import android.platform.test.annotations.Presubmit -import android.view.Surface -import androidx.test.filters.FlakyTest -import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group2 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.launchSplitScreen -import com.android.server.wm.flicker.helpers.reopenAppFromOverview -import com.android.server.wm.traces.common.FlickerComponentName -import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT -import com.android.wm.shell.flicker.dockedStackDividerNotExistsAtEnd -import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig -import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow -import com.android.wm.shell.flicker.helpers.SplitScreenHelper -import org.junit.After -import org.junit.Before -import org.junit.FixMethodOrder -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized - -/** - * Test launch non-resizable activity via recent overview in split screen mode. When the device does - * not support non-resizable in multi window, it should trigger exit split screen. - * To run this test: `atest WMShellFlickerTests:LegacySplitScreenFromRecentNotSupportNonResizable` - */ -@RequiresDevice -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group2 -class LegacySplitScreenFromRecentNotSupportNonResizable( - testSpec: FlickerTestParameter -) : LegacySplitScreenTransition(testSpec) { - - override val transition: FlickerBuilder.() -> Unit - get() = { - cleanSetup(this) - setup { - eachRun { - nonResizeableApp.launchViaIntent(wmHelper) - splitScreenApp.launchViaIntent(wmHelper) - device.launchSplitScreen(wmHelper) - } - } - transitions { - device.reopenAppFromOverview(wmHelper) - } - } - - override val ignoredWindows: List<FlickerComponentName> - get() = listOf(DOCKED_STACK_DIVIDER_COMPONENT, LAUNCHER_COMPONENT, LETTERBOX_COMPONENT, - TOAST_COMPONENT, splitScreenApp.component, nonResizeableApp.component, - FlickerComponentName.SPLASH_SCREEN, - FlickerComponentName.SNAPSHOT) - - @Before - override fun setup() { - super.setup() - setSupportsNonResizableMultiWindow(instrumentation, -1) - } - - @After - override fun teardown() { - super.teardown() - resetMultiWindowConfig(instrumentation) - } - - @Presubmit - @Test - fun resizableAppLayerBecomesInvisible() { - testSpec.assertLayers { - this.isVisible(splitScreenApp.component) - .then() - .isInvisible(splitScreenApp.component) - } - } - - @Presubmit - @Test - fun nonResizableAppLayerBecomesVisible() { - testSpec.assertLayers { - this.isInvisible(nonResizeableApp.component) - .then() - .isVisible(nonResizeableApp.component) - } - } - - @Presubmit - @Test - fun resizableAppWindowBecomesInvisible() { - testSpec.assertWm { - // when the activity gets PAUSED the window may still be marked as visible - // it will be updated in the next log entry. This occurs because we record 1x - // per frame, thus ignore activity check here - this.isAppWindowVisible(splitScreenApp.component) - .then() - // immediately after the window (after onResume and before perform relayout) - // the activity is invisible. This may or not be logged, since we record 1x - // per frame, thus ignore activity check here - .isAppWindowInvisible(splitScreenApp.component) - } - } - - @FlakyTest - @Test - fun nonResizableAppWindowBecomesVisible() { - testSpec.assertWm { - this.isAppWindowInvisible(nonResizeableApp.component) - .then() - .isAppWindowVisible(nonResizeableApp.component) - } - } - - @Presubmit - @Test - fun dockedStackDividerNotExistsAtEnd() = testSpec.dockedStackDividerNotExistsAtEnd() - - @Presubmit - @Test - fun onlyNonResizableAppWindowIsVisibleAtEnd() { - testSpec.assertWmEnd { - isAppWindowInvisible(splitScreenApp.component) - isAppWindowVisible(nonResizeableApp.component) - } - } - - @Presubmit - @Test - override fun visibleLayersShownMoreThanOneConsecutiveEntry() = - super.visibleLayersShownMoreThanOneConsecutiveEntry() - - @Presubmit - @Test - override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - super.visibleWindowsShownMoreThanOneConsecutiveEntry() - - companion object { - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( - repetitions = SplitScreenHelper.TEST_REPETITIONS, - supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668 - } - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt deleted file mode 100644 index 6ac8683ac054..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.legacysplitscreen - -import android.platform.test.annotations.Presubmit -import android.view.Surface -import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group2 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.launchSplitScreen -import com.android.server.wm.flicker.helpers.reopenAppFromOverview -import com.android.server.wm.traces.common.FlickerComponentName -import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT -import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd -import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig -import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow -import com.android.wm.shell.flicker.helpers.SplitScreenHelper -import org.junit.After -import org.junit.Before -import org.junit.FixMethodOrder -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized - -/** - * Test launch non-resizable activity via recent overview in split screen mode. When the device - * supports non-resizable in multi window, it should show the non-resizable app in split screen. - * To run this test: `atest WMShellFlickerTests:LegacySplitScreenFromRecentSupportNonResizable` - */ -@RequiresDevice -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group2 -class LegacySplitScreenFromRecentSupportNonResizable( - testSpec: FlickerTestParameter -) : LegacySplitScreenTransition(testSpec) { - - override val transition: FlickerBuilder.() -> Unit - get() = { - cleanSetup(this) - setup { - eachRun { - nonResizeableApp.launchViaIntent(wmHelper) - splitScreenApp.launchViaIntent(wmHelper) - device.launchSplitScreen(wmHelper) - } - } - transitions { - device.reopenAppFromOverview(wmHelper) - } - } - - override val ignoredWindows: List<FlickerComponentName> - get() = listOf(DOCKED_STACK_DIVIDER_COMPONENT, LAUNCHER_COMPONENT, LETTERBOX_COMPONENT, - TOAST_COMPONENT, splitScreenApp.component, nonResizeableApp.component, - FlickerComponentName.SPLASH_SCREEN, - FlickerComponentName.SNAPSHOT) - - @Before - override fun setup() { - super.setup() - setSupportsNonResizableMultiWindow(instrumentation, 1) - } - - @After - override fun teardown() { - super.teardown() - resetMultiWindowConfig(instrumentation) - } - - @Presubmit - @Test - fun nonResizableAppLayerBecomesVisible() { - testSpec.assertLayers { - this.isInvisible(nonResizeableApp.component) - .then() - .isVisible(nonResizeableApp.component) - } - } - - @Presubmit - @Test - fun nonResizableAppWindowBecomesVisible() { - testSpec.assertWm { - // when the app is launched, first the activity becomes visible, then the - // SnapshotStartingWindow appears and then the app window becomes visible. - // Because we log WM once per frame, sometimes the activity and the window - // become visible in the same entry, sometimes not, thus it is not possible to - // assert the visibility of the activity here - this.isAppWindowInvisible(nonResizeableApp.component) - .then() - // during re-parenting, the window may disappear and reappear from the - // trace, this occurs because we log only 1x per frame - .notContains(nonResizeableApp.component, isOptional = true) - .then() - // if the window reappears after re-parenting it will most likely not - // be visible in the first log entry (because we log only 1x per frame) - .isAppWindowInvisible(nonResizeableApp.component, isOptional = true) - .then() - .isAppWindowVisible(nonResizeableApp.component) - } - } - - @Presubmit - @Test - fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd() - - @Presubmit - @Test - fun bothAppsWindowsAreVisibleAtEnd() { - testSpec.assertWmEnd { - isAppWindowVisible(splitScreenApp.component) - isAppWindowVisible(nonResizeableApp.component) - } - } - - @Presubmit - @Test - override fun visibleLayersShownMoreThanOneConsecutiveEntry() = - super.visibleLayersShownMoreThanOneConsecutiveEntry() - - @Presubmit - @Test - override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - super.visibleWindowsShownMoreThanOneConsecutiveEntry() - - companion object { - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( - repetitions = SplitScreenHelper.TEST_REPETITIONS, - supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668 - } - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt deleted file mode 100644 index b01f41c9e2ec..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.legacysplitscreen - -import android.view.Surface -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview -import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen - -abstract class LegacySplitScreenRotateTransition( - testSpec: FlickerTestParameter -) : LegacySplitScreenTransition(testSpec) { - override val transition: FlickerBuilder.() -> Unit - get() = { - setup { - eachRun { - device.wakeUpAndGoToHomeScreen() - device.openQuickStepAndClearRecentAppsFromOverview(wmHelper) - secondaryApp.launchViaIntent(wmHelper) - splitScreenApp.launchViaIntent(wmHelper) - } - } - teardown { - eachRun { - splitScreenApp.exit(wmHelper) - secondaryApp.exit(wmHelper) - this.setRotation(Surface.ROTATION_0) - } - } - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt deleted file mode 100644 index fb1004bda0cb..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.legacysplitscreen - -import android.platform.test.annotations.Presubmit -import android.view.Surface -import androidx.test.filters.FlakyTest -import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group2 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.entireScreenCovered -import com.android.server.wm.flicker.helpers.exitSplitScreen -import com.android.server.wm.flicker.helpers.launchSplitScreen -import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview -import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen -import com.android.server.wm.flicker.navBarLayerIsVisible -import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsVisible -import com.android.server.wm.flicker.statusBarLayerIsVisible -import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsVisible -import com.android.server.wm.traces.common.FlickerComponentName -import com.android.wm.shell.flicker.dockedStackDividerBecomesInvisible -import com.android.wm.shell.flicker.helpers.SimpleAppHelper -import org.junit.FixMethodOrder -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized - -/** - * Test open app to split screen. - * To run this test: `atest WMShellFlickerTests:LegacySplitScreenToLauncher` - */ -@RequiresDevice -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group2 -class LegacySplitScreenToLauncher( - testSpec: FlickerTestParameter -) : LegacySplitScreenTransition(testSpec) { - private val testApp = SimpleAppHelper(instrumentation) - - override val transition: FlickerBuilder.() -> Unit - get() = { - setup { - test { - device.wakeUpAndGoToHomeScreen() - device.openQuickStepAndClearRecentAppsFromOverview(wmHelper) - } - eachRun { - testApp.launchViaIntent(wmHelper) - this.setRotation(testSpec.endRotation) - device.launchSplitScreen(wmHelper) - device.waitForIdle() - } - } - teardown { - eachRun { - testApp.exit(wmHelper) - } - } - transitions { - device.exitSplitScreen() - } - } - - override val ignoredWindows: List<FlickerComponentName> - get() = listOf(LAUNCHER_COMPONENT, FlickerComponentName.SPLASH_SCREEN, - FlickerComponentName.SNAPSHOT) - - @Presubmit - @Test - fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() - - @Presubmit - @Test - fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() - - @Presubmit - @Test - fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible() - - @Presubmit - @Test - fun entireScreenCovered() = testSpec.entireScreenCovered() - - @Presubmit - @Test - fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() - - @FlakyTest(bugId = 206753786) - @Test - fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() - - @Presubmit - @Test - fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible() - - @FlakyTest - @Test - fun dockedStackDividerBecomesInvisible() = testSpec.dockedStackDividerBecomesInvisible() - - @FlakyTest - @Test - fun layerBecomesInvisible() { - testSpec.assertLayers { - this.isVisible(testApp.component) - .then() - .isInvisible(testApp.component) - } - } - - @FlakyTest - @Test - fun focusDoesNotChange() { - testSpec.assertEventLog { - this.focusDoesNotChange() - } - } - - @Presubmit - @Test - override fun visibleLayersShownMoreThanOneConsecutiveEntry() = - super.visibleLayersShownMoreThanOneConsecutiveEntry() - - @Presubmit - @Test - override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - super.visibleWindowsShownMoreThanOneConsecutiveEntry() - - companion object { - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - // b/161435597 causes the test not to work on 90 degrees - return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0)) - } - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt deleted file mode 100644 index a4a1f617e497..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.legacysplitscreen - -import android.app.Instrumentation -import android.content.Context -import android.support.test.launcherhelper.LauncherStrategyFactory -import android.view.Surface -import androidx.test.filters.FlakyTest -import androidx.test.platform.app.InstrumentationRegistry -import com.android.server.wm.flicker.FlickerBuilderProvider -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview -import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen -import com.android.server.wm.traces.common.FlickerComponentName -import com.android.wm.shell.flicker.helpers.BaseAppHelper.Companion.isShellTransitionsEnabled -import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.getDevEnableNonResizableMultiWindow -import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setDevEnableNonResizableMultiWindow -import com.android.wm.shell.flicker.helpers.SplitScreenHelper -import org.junit.After -import org.junit.Assume.assumeFalse -import org.junit.Assume.assumeTrue -import org.junit.Before -import org.junit.Test - -abstract class LegacySplitScreenTransition(protected val testSpec: FlickerTestParameter) { - protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - protected val context: Context = instrumentation.context - protected val splitScreenApp = SplitScreenHelper.getPrimary(instrumentation) - protected val secondaryApp = SplitScreenHelper.getSecondary(instrumentation) - protected val nonResizeableApp = SplitScreenHelper.getNonResizeable(instrumentation) - protected val LAUNCHER_COMPONENT = FlickerComponentName("", - LauncherStrategyFactory.getInstance(instrumentation) - .launcherStrategy.supportedLauncherPackage) - private var prevDevEnableNonResizableMultiWindow = 0 - - @Before - open fun setup() { - // Only run legacy split tests when the system is using legacy split screen. - assumeTrue(SplitScreenHelper.isUsingLegacySplit()) - // Legacy split is having some issue with Shell transition, and will be deprecated soon. - assumeFalse(isShellTransitionsEnabled()) - prevDevEnableNonResizableMultiWindow = getDevEnableNonResizableMultiWindow(context) - if (prevDevEnableNonResizableMultiWindow != 0) { - // Turn off the development option - setDevEnableNonResizableMultiWindow(context, 0) - } - } - - @After - open fun teardown() { - setDevEnableNonResizableMultiWindow(context, prevDevEnableNonResizableMultiWindow) - } - - /** - * List of windows that are ignored when verifying that visible elements appear on 2 - * consecutive entries in the trace. - * - * b/182720234 - */ - open val ignoredWindows: List<FlickerComponentName> = listOf( - FlickerComponentName.SPLASH_SCREEN, - FlickerComponentName.SNAPSHOT) - - protected open val transition: FlickerBuilder.() -> Unit - get() = { - setup { - eachRun { - device.wakeUpAndGoToHomeScreen() - device.openQuickStepAndClearRecentAppsFromOverview(wmHelper) - secondaryApp.launchViaIntent(wmHelper) - splitScreenApp.launchViaIntent(wmHelper) - this.setRotation(testSpec.startRotation) - } - } - teardown { - eachRun { - secondaryApp.exit(wmHelper) - splitScreenApp.exit(wmHelper) - this.setRotation(Surface.ROTATION_0) - } - } - } - - @FlickerBuilderProvider - fun buildFlicker(): FlickerBuilder { - return FlickerBuilder(instrumentation).apply { - transition(this) - } - } - - internal open val cleanSetup: FlickerBuilder.() -> Unit - get() = { - setup { - eachRun { - device.wakeUpAndGoToHomeScreen() - device.openQuickStepAndClearRecentAppsFromOverview(wmHelper) - this.setRotation(testSpec.startRotation) - } - } - teardown { - eachRun { - nonResizeableApp.exit(wmHelper) - splitScreenApp.exit(wmHelper) - device.pressHome() - this.setRotation(Surface.ROTATION_0) - } - } - } - - @FlakyTest(bugId = 178447631) - @Test - open fun visibleWindowsShownMoreThanOneConsecutiveEntry() { - testSpec.assertWm { - this.visibleWindowsShownMoreThanOneConsecutiveEntry(ignoredWindows) - } - } - - @FlakyTest(bugId = 178447631) - @Test - open fun visibleLayersShownMoreThanOneConsecutiveEntry() { - testSpec.assertLayers { - this.visibleLayersShownMoreThanOneConsecutiveEntry(ignoredWindows) - } - } - - companion object { - internal val LIVE_WALLPAPER_COMPONENT = FlickerComponentName("", - "com.breel.wallpapers18.soundviz.wallpaper.variations.SoundVizWallpaperV2") - internal val LETTERBOX_COMPONENT = FlickerComponentName("", "Letterbox") - internal val TOAST_COMPONENT = FlickerComponentName("", "Toast") - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OWNERS b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OWNERS deleted file mode 100644 index 8446b37dbf06..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OWNERS +++ /dev/null @@ -1,2 +0,0 @@ -# window manager > wm shell > Split Screen -# Bug component: 928697 diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt deleted file mode 100644 index 087b21c544c5..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.legacysplitscreen - -import android.platform.test.annotations.Presubmit -import android.view.Surface -import androidx.test.filters.FlakyTest -import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group2 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.entireScreenCovered -import com.android.server.wm.flicker.helpers.launchSplitScreen -import com.android.server.wm.flicker.statusBarLayerIsVisible -import com.android.server.wm.traces.common.FlickerComponentName -import com.android.wm.shell.flicker.appPairsDividerBecomesVisible -import com.android.wm.shell.flicker.helpers.SplitScreenHelper -import org.junit.FixMethodOrder -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized - -/** - * Test open app to split screen. - * To run this test: `atest WMShellFlickerTests:OpenAppToLegacySplitScreen` - */ -@RequiresDevice -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group2 -class OpenAppToLegacySplitScreen( - testSpec: FlickerTestParameter -) : LegacySplitScreenTransition(testSpec) { - override val transition: FlickerBuilder.() -> Unit - get() = { - super.transition(this) - transitions { - device.launchSplitScreen(wmHelper) - wmHelper.waitForAppTransitionIdle() - } - } - - override val ignoredWindows: List<FlickerComponentName> - get() = listOf(LAUNCHER_COMPONENT, splitScreenApp.component, - FlickerComponentName.SPLASH_SCREEN, - FlickerComponentName.SNAPSHOT) - - @FlakyTest - @Test - fun appWindowBecomesVisible() { - testSpec.assertWm { - this.isAppWindowInvisible(splitScreenApp.component) - .then() - .isAppWindowVisible(splitScreenApp.component) - } - } - - @Presubmit - @Test - fun entireScreenCovered() = testSpec.entireScreenCovered() - - @Presubmit - @Test - fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible() - - @Presubmit - @Test - fun appPairsDividerBecomesVisible() = testSpec.appPairsDividerBecomesVisible() - - @FlakyTest - @Test - fun layerBecomesVisible() { - testSpec.assertLayers { - this.isInvisible(splitScreenApp.component) - .then() - .isVisible(splitScreenApp.component) - } - } - - @Presubmit - @Test - fun focusChanges() { - testSpec.assertEventLog { - this.focusChanges(splitScreenApp.`package`, - "recents_animation_input_consumer", "NexusLauncherActivity") - } - } - - @Presubmit - @Test - override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - super.visibleWindowsShownMoreThanOneConsecutiveEntry() - - companion object { - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( - repetitions = SplitScreenHelper.TEST_REPETITIONS, - supportedRotations = listOf(Surface.ROTATION_0) // bugId = 179116910 - ) - } - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt deleted file mode 100644 index e2da1a4565c0..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.legacysplitscreen - -import android.util.Rational -import android.view.Surface -import androidx.test.filters.FlakyTest -import androidx.test.filters.RequiresDevice -import androidx.test.uiautomator.By -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group2 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.entireScreenCovered -import com.android.server.wm.flicker.helpers.ImeAppHelper -import com.android.server.wm.flicker.helpers.WindowUtils -import com.android.server.wm.flicker.helpers.launchSplitScreen -import com.android.server.wm.flicker.helpers.resizeSplitScreen -import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen -import com.android.server.wm.flicker.navBarLayerIsVisible -import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsVisible -import com.android.server.wm.flicker.statusBarLayerIsVisible -import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsVisible -import com.android.server.wm.traces.common.region.Region -import com.android.server.wm.traces.parser.toFlickerComponent -import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT -import com.android.wm.shell.flicker.helpers.SimpleAppHelper -import com.android.wm.shell.flicker.testapp.Components -import org.junit.FixMethodOrder -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized - -/** - * Test split screen resizing window transitions. - * To run this test: `atest WMShellFlickerTests:ResizeLegacySplitScreen` - * - * Currently it runs only in 0 degrees because of b/156100803 - */ -@RequiresDevice -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -@FlakyTest(bugId = 159096424) -@Group2 -class ResizeLegacySplitScreen( - testSpec: FlickerTestParameter -) : LegacySplitScreenTransition(testSpec) { - private val testAppTop = SimpleAppHelper(instrumentation) - private val testAppBottom = ImeAppHelper(instrumentation) - - override val transition: FlickerBuilder.() -> Unit - get() = { - setup { - eachRun { - device.wakeUpAndGoToHomeScreen() - this.setRotation(testSpec.startRotation) - this.launcherStrategy.clearRecentAppsFromOverview() - testAppBottom.launchViaIntent(wmHelper) - device.pressHome() - testAppTop.launchViaIntent(wmHelper) - device.waitForIdle() - device.launchSplitScreen(wmHelper) - val snapshot = - device.findObject(By.res(device.launcherPackageName, "snapshot")) - snapshot.click() - testAppBottom.openIME(device) - device.pressBack() - device.resizeSplitScreen(startRatio) - } - } - teardown { - eachRun { - testAppTop.exit(wmHelper) - testAppBottom.exit(wmHelper) - } - } - transitions { - device.resizeSplitScreen(stopRatio) - } - } - - @Test - fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() - - @Test - fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() - - @FlakyTest(bugId = 156223549) - @Test - fun topAppWindowIsAlwaysVisible() { - testSpec.assertWm { - this.isAppWindowVisible(Components.SimpleActivity.COMPONENT.toFlickerComponent()) - } - } - - @FlakyTest(bugId = 156223549) - @Test - fun bottomAppWindowIsAlwaysVisible() { - testSpec.assertWm { - this.isAppWindowVisible(Components.ImeActivity.COMPONENT.toFlickerComponent()) - } - } - - @Test - fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible() - - @Test - fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible() - - @Test - fun entireScreenCovered() = testSpec.entireScreenCovered() - - @Test - fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() - - @FlakyTest(bugId = 206753786) - @Test - fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() - - @Test - fun topAppLayerIsAlwaysVisible() { - testSpec.assertLayers { - this.isVisible(Components.SimpleActivity.COMPONENT.toFlickerComponent()) - } - } - - @Test - fun bottomAppLayerIsAlwaysVisible() { - testSpec.assertLayers { - this.isVisible(Components.ImeActivity.COMPONENT.toFlickerComponent()) - } - } - - @Test - fun dividerLayerIsAlwaysVisible() { - testSpec.assertLayers { - this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT) - } - } - - @FlakyTest - @Test - fun appsStartingBounds() { - testSpec.assertLayersStart { - val displayBounds = WindowUtils.displayBounds - val dividerBounds = - layer(DOCKED_STACK_DIVIDER_COMPONENT).visibleRegion.region.bounds - - val topAppBounds = Region.from(0, 0, dividerBounds.right, - dividerBounds.top + WindowUtils.dockedStackDividerInset) - val bottomAppBounds = Region.from(0, - dividerBounds.bottom - WindowUtils.dockedStackDividerInset, - displayBounds.right, - displayBounds.bottom - WindowUtils.navigationBarFrameHeight) - visibleRegion(Components.SimpleActivity.COMPONENT.toFlickerComponent()) - .coversExactly(topAppBounds) - visibleRegion(Components.ImeActivity.COMPONENT.toFlickerComponent()) - .coversExactly(bottomAppBounds) - } - } - - @FlakyTest - @Test - fun appsEndingBounds() { - testSpec.assertLayersStart { - val displayBounds = WindowUtils.displayBounds - val dividerBounds = - layer(DOCKED_STACK_DIVIDER_COMPONENT).visibleRegion.region.bounds - - val topAppBounds = Region.from(0, 0, dividerBounds.right, - dividerBounds.top + WindowUtils.dockedStackDividerInset) - val bottomAppBounds = Region.from(0, - dividerBounds.bottom - WindowUtils.dockedStackDividerInset, - displayBounds.right, - displayBounds.bottom - WindowUtils.navigationBarFrameHeight) - - visibleRegion(Components.SimpleActivity.COMPONENT.toFlickerComponent()) - .coversExactly(topAppBounds) - visibleRegion(Components.ImeActivity.COMPONENT.toFlickerComponent()) - .coversExactly(bottomAppBounds) - } - } - - @Test - fun focusDoesNotChange() { - testSpec.assertEventLog { - focusDoesNotChange() - } - } - - companion object { - private val startRatio = Rational(1, 3) - private val stopRatio = Rational(2, 3) - - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0)) - .map { - val description = (startRatio.toString().replace("/", "-") + "_to_" + - stopRatio.toString().replace("/", "-")) - val newName = "${FlickerTestParameter.defaultName(it)}_$description" - FlickerTestParameter(it.config, nameOverride = newName) - } - } - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt deleted file mode 100644 index d703ea082c87..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.legacysplitscreen - -import android.platform.test.annotations.Presubmit -import android.view.Surface -import androidx.test.filters.FlakyTest -import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group2 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.launchSplitScreen -import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsVisible -import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsVisible -import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd -import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd -import com.android.wm.shell.flicker.helpers.SplitScreenHelper -import org.junit.FixMethodOrder -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized - -/** - * Test dock activity to primary split screen and rotate - * To run this test: `atest WMShellFlickerTests:RotateOneLaunchedAppAndEnterSplitScreen` - */ -@RequiresDevice -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group2 -class RotateOneLaunchedAppAndEnterSplitScreen( - testSpec: FlickerTestParameter -) : LegacySplitScreenRotateTransition(testSpec) { - override val transition: FlickerBuilder.() -> Unit - get() = { - super.transition(this) - transitions { - device.launchSplitScreen(wmHelper) - this.setRotation(testSpec.startRotation) - } - } - - @Presubmit - @Test - fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd() - - @Presubmit - @Test - fun dockedStackPrimaryBoundsIsVisibleAtEnd() = - testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.startRotation, - splitScreenApp.component) - - @Presubmit - @Test - fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() - - @FlakyTest(bugId = 206753786) - @Test - fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() - - @Presubmit - @Test - fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() - - @Presubmit - @Test - fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() - - @FlakyTest - @Test - fun appWindowBecomesVisible() { - testSpec.assertWm { - this.isAppWindowInvisible(splitScreenApp.component) - .then() - .isAppWindowVisible(splitScreenApp.component) - } - } - - @Presubmit - @Test - override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - super.visibleWindowsShownMoreThanOneConsecutiveEntry() - - companion object { - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(repetitions = SplitScreenHelper.TEST_REPETITIONS, - supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668 - } - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt deleted file mode 100644 index 6b1883914e59..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.legacysplitscreen - -import android.platform.test.annotations.Presubmit -import android.view.Surface -import androidx.test.filters.FlakyTest -import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group2 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.launchSplitScreen -import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsVisible -import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsVisible -import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd -import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd -import com.android.wm.shell.flicker.helpers.SplitScreenHelper -import org.junit.FixMethodOrder -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized - -/** - * Rotate - * To run this test: `atest WMShellFlickerTests:RotateOneLaunchedAppInSplitScreenMode` - */ -@RequiresDevice -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group2 -class RotateOneLaunchedAppInSplitScreenMode( - testSpec: FlickerTestParameter -) : LegacySplitScreenRotateTransition(testSpec) { - override val transition: FlickerBuilder.() -> Unit - get() = { - super.transition(this) - transitions { - this.setRotation(testSpec.startRotation) - device.launchSplitScreen(wmHelper) - } - } - - @Presubmit - @Test - fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd() - - @Presubmit - @Test - fun dockedStackPrimaryBoundsIsVisibleAtEnd() = testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd( - testSpec.startRotation, splitScreenApp.component) - - @Presubmit - @Test - fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() - - @FlakyTest(bugId = 206753786) - @Test - fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() - - @Presubmit - @Test - fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() - - @Presubmit - @Test - fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() - - @FlakyTest - @Test - fun appWindowBecomesVisible() { - testSpec.assertWm { - this.isAppWindowInvisible(splitScreenApp.component) - .then() - .isAppWindowVisible(splitScreenApp.component) - } - } - - @Presubmit - @Test - override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - super.visibleWindowsShownMoreThanOneConsecutiveEntry() - - companion object { - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( - repetitions = SplitScreenHelper.TEST_REPETITIONS, - supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668 - } - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt deleted file mode 100644 index acd658b5ba56..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.legacysplitscreen - -import android.platform.test.annotations.Presubmit -import android.view.Surface -import androidx.test.filters.FlakyTest -import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group2 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.launchSplitScreen -import com.android.server.wm.flicker.helpers.reopenAppFromOverview -import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsVisible -import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsVisible -import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd -import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd -import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisibleAtEnd -import com.android.wm.shell.flicker.helpers.SplitScreenHelper -import org.junit.FixMethodOrder -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized - -/** - * Test open app to split screen. - * To run this test: `atest WMShellFlickerTests:RotateTwoLaunchedAppAndEnterSplitScreen` - */ -@RequiresDevice -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group2 -class RotateTwoLaunchedAppAndEnterSplitScreen( - testSpec: FlickerTestParameter -) : LegacySplitScreenRotateTransition(testSpec) { - override val transition: FlickerBuilder.() -> Unit - get() = { - super.transition(this) - transitions { - this.setRotation(testSpec.startRotation) - device.launchSplitScreen(wmHelper) - device.reopenAppFromOverview(wmHelper) - } - } - - @Presubmit - @Test - fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd() - - @Presubmit - @Test - fun dockedStackPrimaryBoundsIsVisibleAtEnd() = - testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.startRotation, - splitScreenApp.component) - - @Presubmit - @Test - fun dockedStackSecondaryBoundsIsVisibleAtEnd() = - testSpec.dockedStackSecondaryBoundsIsVisibleAtEnd(testSpec.startRotation, - secondaryApp.component) - - @Presubmit - @Test - fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() - - @FlakyTest(bugId = 206753786) - @Test - fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() - - @Presubmit - @Test - fun appWindowBecomesVisible() { - testSpec.assertWm { - // when the app is launched, first the activity becomes visible, then the - // SnapshotStartingWindow appears and then the app window becomes visible. - // Because we log WM once per frame, sometimes the activity and the window - // become visible in the same entry, sometimes not, thus it is not possible to - // assert the visibility of the activity here - this.isAppWindowInvisible(secondaryApp.component) - .then() - // during re-parenting, the window may disappear and reappear from the - // trace, this occurs because we log only 1x per frame - .notContains(secondaryApp.component, isOptional = true) - .then() - // if the window reappears after re-parenting it will most likely not - // be visible in the first log entry (because we log only 1x per frame) - .isAppWindowInvisible(secondaryApp.component, isOptional = true) - .then() - .isAppWindowVisible(secondaryApp.component) - } - } - - @Presubmit - @Test - fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() - - @Presubmit - @Test - fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() - - @Presubmit - @Test - override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - super.visibleWindowsShownMoreThanOneConsecutiveEntry() - - companion object { - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( - repetitions = SplitScreenHelper.TEST_REPETITIONS, - supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668 - } - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt deleted file mode 100644 index b40be8b5f401..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.legacysplitscreen - -import android.platform.test.annotations.Presubmit -import android.view.Surface -import androidx.test.filters.FlakyTest -import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group2 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.launchSplitScreen -import com.android.server.wm.flicker.helpers.reopenAppFromOverview -import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsVisible -import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsVisible -import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd -import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd -import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisibleAtEnd -import com.android.wm.shell.flicker.helpers.SplitScreenHelper -import org.junit.FixMethodOrder -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized - -/** - * Test open app to split screen. - * To run this test: `atest WMShellFlickerTests:RotateTwoLaunchedAppInSplitScreenMode` - */ -@RequiresDevice -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group2 -class RotateTwoLaunchedAppInSplitScreenMode( - testSpec: FlickerTestParameter -) : LegacySplitScreenRotateTransition(testSpec) { - override val transition: FlickerBuilder.() -> Unit - get() = { - super.transition(this) - setup { - eachRun { - device.launchSplitScreen(wmHelper) - device.reopenAppFromOverview(wmHelper) - this.setRotation(testSpec.startRotation) - } - } - transitions { - this.setRotation(testSpec.startRotation) - } - } - - @Presubmit - @Test - fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd() - - @Presubmit - @Test - fun dockedStackPrimaryBoundsIsVisibleAtEnd() = - testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.startRotation, - splitScreenApp.component) - - @Presubmit - @Test - fun dockedStackSecondaryBoundsIsVisibleAtEnd() = - testSpec.dockedStackSecondaryBoundsIsVisibleAtEnd(testSpec.startRotation, - secondaryApp.component) - - @Presubmit - @Test - fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() - - @FlakyTest(bugId = 206753786) - @Test - fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() - - @FlakyTest - @Test - fun appWindowBecomesVisible() { - testSpec.assertWm { - this.isAppWindowInvisible(secondaryApp.component) - .then() - .isAppWindowVisible(secondaryApp.component) - } - } - - @Presubmit - @Test - fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() - - @Presubmit - @Test - fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() - - @Presubmit - @Test - override fun visibleLayersShownMoreThanOneConsecutiveEntry() = - super.visibleLayersShownMoreThanOneConsecutiveEntry() - - @Presubmit - @Test - override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - super.visibleWindowsShownMoreThanOneConsecutiveEntry() - - companion object { - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( - repetitions = SplitScreenHelper.TEST_REPETITIONS, - supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668 - } - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt new file mode 100644 index 000000000000..e7d641e9c66e --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.pip + +import androidx.test.filters.RequiresDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.annotation.Group3 +import com.android.server.wm.flicker.dsl.FlickerBuilder +import org.junit.Assume +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test entering pip from an app via [onUserLeaveHint] and by navigating to home. + * + * To run this test: `atest WMShellFlickerTests:EnterPipOnUserLeaveHintTest` + * + * Actions: + * Launch an app in full screen + * Select "Via code behind" radio button + * Press Home button to put [pipApp] in pip mode + * + * Notes: + * 1. All assertions are inherited from [EnterPipTest] + * 2. Part of the test setup occurs automatically via + * [com.android.server.wm.flicker.TransitionRunnerWithRules], + * including configuring navigation mode, initial orientation and ensuring no + * apps are running before setup + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Group3 +class EnterPipOnUserLeaveHintTest(testSpec: FlickerTestParameter) : EnterPipTest(testSpec) { + protected val taplInstrumentation = LauncherInstrumentation() + /** + * Defines the transition used to run the test + */ + override val transition: FlickerBuilder.() -> Unit + get() = { + setupAndTeardown(this) + setup { + eachRun { + pipApp.launchViaIntent(wmHelper) + pipApp.enableEnterPipOnUserLeaveHint() + } + } + teardown { + eachRun { + pipApp.exit(wmHelper) + } + } + transitions { + taplInstrumentation.goHome() + } + } + + override fun pipAppLayerAlwaysVisible() { + if (!testSpec.isGesturalNavigation) super.pipAppLayerAlwaysVisible() else { + // pip layer in gesture nav will disappear during transition + testSpec.assertLayers { + this.isVisible(pipApp.component) + .then().isInvisible(pipApp.component) + .then().isVisible(pipApp.component) + } + } + } + + override fun pipLayerReduces() { + // in gestural nav the pip enters through alpha animation + Assume.assumeFalse(testSpec.isGesturalNavigation) + super.pipLayerReduces() + } + + override fun focusChanges() { + // in gestural nav the focus goes to different activity on swipe up + Assume.assumeFalse(testSpec.isGesturalNavigation) + super.focusChanges() + } + + override fun pipLayerRemainInsideVisibleBounds() { + if (!testSpec.isGesturalNavigation) super.pipLayerRemainInsideVisibleBounds() else { + // pip layer in gesture nav will disappear during transition + testSpec.assertLayersStart { + this.visibleRegion(pipApp.component).coversAtMost(displayBounds) + } + testSpec.assertLayersEnd { + this.visibleRegion(pipApp.component).coversAtMost(displayBounds) + } + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt index 0640ac526bd0..b4267ffced5c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt @@ -43,7 +43,7 @@ import org.junit.runners.Parameterized * * Notes: * 1. Some default assertions (e.g., nav bar, status bar and screen covered) - * are inherited [PipTransition] + * are inherited from [PipTransition] * 2. Part of the test setup occurs automatically via * [com.android.server.wm.flicker.TransitionRunnerWithRules], * including configuring navigation mode, initial orientation and ensuring no @@ -54,7 +54,7 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group3 -class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { +open class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { /** * Defines the transition used to run the test @@ -98,7 +98,7 @@ class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { */ @Presubmit @Test - fun pipAppLayerAlwaysVisible() { + open fun pipAppLayerAlwaysVisible() { testSpec.assertLayers { this.isVisible(pipApp.component) } @@ -122,7 +122,7 @@ class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { */ @Presubmit @Test - fun pipLayerRemainInsideVisibleBounds() { + open fun pipLayerRemainInsideVisibleBounds() { testSpec.assertLayersVisibleRegion(pipApp.component) { coversAtMost(displayBounds) } @@ -133,7 +133,7 @@ class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { */ @Presubmit @Test - fun pipLayerReduces() { + open fun pipLayerReduces() { val layerName = pipApp.component.toLayerName() testSpec.assertLayers { val pipLayerList = this.layers { it.name.contains(layerName) && it.isVisible } @@ -175,7 +175,7 @@ class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { */ @Presubmit @Test - fun focusChanges() { + open fun focusChanges() { testSpec.assertEventLog { this.focusChanges(pipApp.`package`, "NexusLauncherActivity") } @@ -192,8 +192,10 @@ class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { @JvmStatic fun getParams(): List<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0), - repetitions = 3) + .getConfigNonRotationTests( + supportedRotations = listOf(Surface.ROTATION_0), + repetitions = 3 + ) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt index 37e9344348d9..c6a705dacb8d 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt @@ -80,7 +80,17 @@ class ExitPipViaIntentTest(testSpec: FlickerTestParameter) : ExitPipToAppTransit /** {@inheritDoc} */ @FlakyTest(bugId = 206753786) @Test - override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() + override fun statusBarLayerRotatesScales() { + Assume.assumeFalse(isShellTransitionsEnabled) + super.statusBarLayerRotatesScales() + } + + @Presubmit + @Test + fun statusBarLayerRotatesScales_ShellTransit() { + Assume.assumeTrue(isShellTransitionsEnabled) + super.statusBarLayerRotatesScales() + } /** {@inheritDoc} */ @FlakyTest(bugId = 197726610) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt index 1a21d32f568c..fe51228230cb 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt @@ -16,7 +16,7 @@ package com.android.wm.shell.flicker.pip -import androidx.test.filters.FlakyTest +import android.platform.test.annotations.Presubmit import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter @@ -35,7 +35,6 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group4 -@FlakyTest(bugId = 217777115) class PipKeyboardTestShellTransit(testSpec: FlickerTestParameter) : PipKeyboardTest(testSpec) { @Before @@ -43,7 +42,7 @@ class PipKeyboardTestShellTransit(testSpec: FlickerTestParameter) : PipKeyboardT Assume.assumeTrue(isShellTransitionsEnabled) } - @FlakyTest(bugId = 214452854) + @Presubmit @Test override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt deleted file mode 100644 index 21175a0767a5..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.pip - -import android.platform.test.annotations.Presubmit -import android.view.Surface -import androidx.test.filters.FlakyTest -import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group4 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.launchSplitScreen -import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen -import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome -import com.android.wm.shell.flicker.helpers.BaseAppHelper.Companion.isShellTransitionsEnabled -import com.android.wm.shell.flicker.helpers.FixedAppHelper -import com.android.wm.shell.flicker.helpers.ImeAppHelper -import com.android.wm.shell.flicker.helpers.SplitScreenHelper -import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP -import org.junit.Assume.assumeFalse -import org.junit.Assume.assumeTrue -import org.junit.Before -import org.junit.FixMethodOrder -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized - -/** - * Test Pip with split-screen. - * To run this test: `atest WMShellFlickerTests:PipLegacySplitScreenTest` - */ -@RequiresDevice -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group4 -class PipLegacySplitScreenTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { - private val imeApp = ImeAppHelper(instrumentation) - private val testApp = FixedAppHelper(instrumentation) - - @Before - open fun setup() { - // Only run legacy split tests when the system is using legacy split screen. - assumeTrue(SplitScreenHelper.isUsingLegacySplit()) - // Legacy split is having some issue with Shell transition, and will be deprecated soon. - assumeFalse(isShellTransitionsEnabled()) - } - - override val transition: FlickerBuilder.() -> Unit - get() = { - setup { - test { - removeAllTasksButHome() - device.wakeUpAndGoToHomeScreen() - pipApp.launchViaIntent(stringExtras = mapOf(EXTRA_ENTER_PIP to "true"), - wmHelper = wmHelper) - } - } - transitions { - testApp.launchViaIntent(wmHelper) - device.launchSplitScreen(wmHelper) - imeApp.launchViaIntent(wmHelper) - } - teardown { - eachRun { - imeApp.exit(wmHelper) - testApp.exit(wmHelper) - } - test { - removeAllTasksButHome() - } - } - } - - /** {@inheritDoc} */ - @FlakyTest(bugId = 206753786) - @Test - override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() - - @FlakyTest(bugId = 161435597) - @Test - fun pipWindowInsideDisplayBounds() { - testSpec.assertWmVisibleRegion(pipApp.component) { - coversAtMost(displayBounds) - } - } - - @Presubmit - @Test - fun bothAppWindowsVisible() { - testSpec.assertWmEnd { - isAppWindowVisible(testApp.component) - isAppWindowVisible(imeApp.component) - doNotOverlap(testApp.component, imeApp.component) - } - } - - @FlakyTest(bugId = 161435597) - @Test - fun pipLayerInsideDisplayBounds() { - testSpec.assertLayersVisibleRegion(pipApp.component) { - coversAtMost(displayBounds) - } - } - - @Presubmit - @Test - fun bothAppLayersVisible() { - testSpec.assertLayersEnd { - visibleRegion(testApp.component).coversAtMost(displayBounds) - visibleRegion(imeApp.component).coversAtMost(displayBounds) - } - } - - @FlakyTest(bugId = 161435597) - @Test - override fun entireScreenCovered() = super.entireScreenCovered() - - companion object { - const val TEST_REPETITIONS = 2 - - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( - supportedRotations = listOf(Surface.ROTATION_0), - repetitions = TEST_REPETITIONS - ) - } - } -} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt index c1ee1a7cbb35..4618fb376f7f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt @@ -27,12 +27,10 @@ import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.helpers.WindowUtils -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.wm.shell.flicker.helpers.FixedAppHelper -import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -141,14 +139,6 @@ open class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testS @Presubmit @Test fun pipLayerRotates_StartingBounds() { - Assume.assumeFalse(isShellTransitionsEnabled) - pipLayerRotates_StartingBounds_internal() - } - - @FlakyTest(bugId = 228024285) - @Test - fun pipLayerRotates_StartingBounds_ShellTransit() { - Assume.assumeTrue(isShellTransitionsEnabled) pipLayerRotates_StartingBounds_internal() } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt new file mode 100644 index 000000000000..702710caded7 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.splitscreen + +import android.platform.test.annotations.Presubmit +import android.view.WindowManagerPolicyConstants +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.annotation.Group1 +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.wm.shell.flicker.appWindowBecomesVisible +import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd +import com.android.wm.shell.flicker.helpers.SplitScreenHelper +import com.android.wm.shell.flicker.layerBecomesVisible +import com.android.wm.shell.flicker.layerIsVisibleAtEnd +import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisible +import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd +import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible +import org.junit.Assume +import org.junit.Before +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test enter split screen by dragging app icon from all apps. + * This test is only for large screen devices. + * + * To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromAllApps` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Group1 +class EnterSplitScreenByDragFromAllApps( + testSpec: FlickerTestParameter +) : SplitScreenBase(testSpec) { + + @Before + open fun before() { + Assume.assumeTrue(taplInstrumentation.isTablet) + } + + override val transition: FlickerBuilder.() -> Unit + get() = { + super.transition(this) + setup { + eachRun { + taplInstrumentation.goHome() + primaryApp.launchViaIntent(wmHelper) + } + } + transitions { + taplInstrumentation.launchedAppState.taskbar + .openAllApps() + .getAppIcon(secondaryApp.appName) + .dragToSplitscreen(secondaryApp.component.packageName, + primaryApp.component.packageName) + } + } + + @Presubmit + @Test + fun dividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible() + + @Presubmit + @Test + fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp.component) + + @Presubmit + @Test + fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp.component) + + @Presubmit + @Test + fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd( + testSpec.endRotation, primaryApp.component, false /* splitLeftTop */) + + @Presubmit + @Test + fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible( + testSpec.endRotation, secondaryApp.component, true /* splitLeftTop */) + + @Presubmit + @Test + fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp.component) + + @Presubmit + @Test + fun secondaryAppWindowBecomesVisible() = + testSpec.appWindowBecomesVisible(secondaryApp.component) + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): List<FlickerTestParameter> { + return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( + repetitions = SplitScreenHelper.TEST_REPETITIONS, + // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. + supportedNavigationModes = + listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)) + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt new file mode 100644 index 000000000000..7323d992ecd4 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.splitscreen + +import android.platform.test.annotations.Presubmit +import android.view.WindowManagerPolicyConstants +import androidx.test.filters.RequiresDevice +import androidx.test.uiautomator.By +import androidx.test.uiautomator.Until +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.annotation.Group1 +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd +import com.android.wm.shell.flicker.helpers.SplitScreenHelper +import com.android.wm.shell.flicker.layerBecomesVisible +import com.android.wm.shell.flicker.layerIsVisibleAtEnd +import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisible +import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd +import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible +import org.junit.Assume +import org.junit.Before +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test enter split screen by dragging app icon from notification. + * This test is only for large screen devices. + * + * To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromNotification` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Group1 +class EnterSplitScreenByDragFromNotification( + testSpec: FlickerTestParameter +) : SplitScreenBase(testSpec) { + + private val sendNotificationApp = SplitScreenHelper.getSendNotification(instrumentation) + + @Before + fun before() { + Assume.assumeTrue(taplInstrumentation.isTablet) + } + + override val transition: FlickerBuilder.() -> Unit + get() = { + super.transition(this) + setup { + eachRun { + // Send a notification + sendNotificationApp.launchViaIntent(wmHelper) + val sendNotification = device.wait( + Until.findObject(By.text("Send Notification")), + SplitScreenHelper.TIMEOUT_MS + ) + sendNotification?.click() ?: error("Send notification button not found") + + taplInstrumentation.goHome() + primaryApp.launchViaIntent(wmHelper) + } + } + transitions { + SplitScreenHelper.dragFromNotificationToSplit(instrumentation, device, wmHelper) + } + teardown { + eachRun { + sendNotificationApp.exit(wmHelper) + } + } + } + + @Presubmit + @Test + fun dividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible() + + @Presubmit + @Test + fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp.component) + + @Presubmit + @Test + fun secondaryAppLayerBecomesVisible() = + testSpec.layerBecomesVisible(sendNotificationApp.component) + + @Presubmit + @Test + fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd( + testSpec.endRotation, primaryApp.component, false /* splitLeftTop */ + ) + + @Presubmit + @Test + fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible( + testSpec.endRotation, sendNotificationApp.component, true /* splitLeftTop */ + ) + + @Presubmit + @Test + fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp.component) + + @Presubmit + @Test + fun secondaryAppWindowIsVisibleAtEnd() = + testSpec.appWindowIsVisibleAtEnd(sendNotificationApp.component) + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): List<FlickerTestParameter> { + return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( + repetitions = SplitScreenHelper.TEST_REPETITIONS, + // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy. + supportedNavigationModes = + listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY) + ) + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt new file mode 100644 index 000000000000..52c2daf96a3c --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.splitscreen + +import android.app.Instrumentation +import android.content.Context +import androidx.test.platform.app.InstrumentationRegistry +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.FlickerBuilderProvider +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.setRotation +import com.android.wm.shell.flicker.helpers.SplitScreenHelper + +abstract class SplitScreenBase(protected val testSpec: FlickerTestParameter) { + protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + protected val taplInstrumentation = LauncherInstrumentation() + protected val context: Context = instrumentation.context + protected val primaryApp = SplitScreenHelper.getPrimary(instrumentation) + protected val secondaryApp = SplitScreenHelper.getSecondary(instrumentation) + + @FlickerBuilderProvider + fun buildFlicker(): FlickerBuilder { + return FlickerBuilder(instrumentation).apply { + transition(this) + } + } + + protected open val transition: FlickerBuilder.() -> Unit + get() = { + setup { + test { + taplInstrumentation.setEnableRotation(true) + setRotation(testSpec.startRotation) + taplInstrumentation.setExpectedRotation(testSpec.startRotation) + } + } + teardown { + eachRun { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml index bd98585b67ec..bc0b0b6292b4 100644 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml +++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml @@ -92,6 +92,17 @@ </intent-filter> </activity> + <activity android:name=".SendNotificationActivity" + android:taskAffinity="com.android.wm.shell.flicker.testapp.SendNotificationActivity" + android:theme="@style/CutoutShortEdges" + android:label="SendNotificationApp" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + <activity android:name=".NonResizeableActivity" android:resizeableActivity="false" android:taskAffinity="com.android.wm.shell.flicker.testapp.NonResizeableActivity" diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_notification.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_notification.xml new file mode 100644 index 000000000000..8d59b567e59b --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_notification.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:background="@android:color/black"> + + <Button + android:id="@+id/button_send_notification" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerHorizontal="true" + android:layout_centerVertical="true" + android:text="Send Notification" /> +</RelativeLayout> diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml index 909b77c87894..e9e7bb61660d 100644 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml +++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml @@ -44,6 +44,32 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" + android:checkedButton="@id/enter_pip_on_leave_disabled"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Enter PiP on home press"/> + + <RadioButton + android:id="@+id/enter_pip_on_leave_disabled" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Disabled" + android:onClick="onAutoPipSelected"/> + + <RadioButton + android:id="@+id/enter_pip_on_leave_manual" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Via code behind" + android:onClick="onAutoPipSelected"/> + </RadioGroup> + + <RadioGroup + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" android:checkedButton="@id/ratio_default"> <TextView diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java index 0ed59bdafd1d..a2b580da5898 100644 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java +++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java @@ -88,6 +88,12 @@ public class Components { PACKAGE_NAME + ".SplitScreenSecondaryActivity"); } + public static class SendNotificationActivity { + public static final String LABEL = "SendNotificationApp"; + public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME, + PACKAGE_NAME + ".SendNotificationActivity"); + } + public static class LaunchBubbleActivity { public static final String LABEL = "LaunchBubbleApp"; public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME, diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java index a6ba7823e22d..a39aa4dc7bde 100644 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java +++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java @@ -48,6 +48,7 @@ import android.view.View; import android.view.Window; import android.view.WindowManager; import android.widget.CheckBox; +import android.widget.RadioButton; import java.util.ArrayList; import java.util.Arrays; @@ -201,6 +202,17 @@ public class PipActivity extends Activity { super.onDestroy(); } + @Override + protected void onUserLeaveHint() { + // Only used when auto PiP is disabled. This is to simulate the behavior that an app + // supports regular PiP but not auto PiP. + final boolean manuallyEnterPip = + ((RadioButton) findViewById(R.id.enter_pip_on_leave_manual)).isChecked(); + if (manuallyEnterPip) { + enterPictureInPictureMode(); + } + } + private RemoteAction buildRemoteAction(Icon icon, String label, String action) { final Intent intent = new Intent(action); final PendingIntent pendingIntent = @@ -216,6 +228,17 @@ public class PipActivity extends Activity { enterPictureInPictureMode(mPipParamsBuilder.build()); } + public void onAutoPipSelected(View v) { + switch (v.getId()) { + case R.id.enter_pip_on_leave_manual: + // disable auto enter PiP + case R.id.enter_pip_on_leave_disabled: + mPipParamsBuilder.setAutoEnterEnabled(false); + setPictureInPictureParams(mPipParamsBuilder.build()); + break; + } + } + public void onRatioSelected(View v) { switch (v.getId()) { case R.id.ratio_default: diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SendNotificationActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SendNotificationActivity.java new file mode 100644 index 000000000000..8020ef2270a0 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SendNotificationActivity.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.testapp; + +import android.app.Activity; +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; + +public class SendNotificationActivity extends Activity { + private NotificationManager mNotificationManager; + private String mChannelId = "Channel id"; + private String mChannelName = "Channel name"; + private NotificationChannel mChannel; + private int mNotifyId = 0; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_notification); + findViewById(R.id.button_send_notification).setOnClickListener(this::sendNotification); + + mChannel = new NotificationChannel(mChannelId, mChannelName, + NotificationManager.IMPORTANCE_DEFAULT); + mNotificationManager = getSystemService(NotificationManager.class); + mNotificationManager.createNotificationChannel(mChannel); + } + + private void sendNotification(View v) { + PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, + new Intent(this, SendNotificationActivity.class), + PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE); + Notification notification = new Notification.Builder(this, mChannelId) + .setContentTitle("Notification App") + .setContentText("Notification content") + .setWhen(System.currentTimeMillis()) + .setSmallIcon(R.drawable.ic_message) + .setContentIntent(pendingIntent) + .build(); + + mNotificationManager.notify(mNotifyId, notification); + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java index a6caefe6d3e7..0b53c4069c3f 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 @@ -133,7 +133,7 @@ public class ShellTaskOrganizerTests { .when(mTaskOrganizerController).registerTaskOrganizer(any()); } catch (RemoteException e) {} mOrganizer = spy(new ShellTaskOrganizer(mTaskOrganizerController, mTestExecutor, mContext, - mCompatUI, Optional.empty())); + mCompatUI, Optional.empty(), Optional.empty())); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairTests.java deleted file mode 100644 index e73d9aaf190a..000000000000 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairTests.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.apppairs; - -import static android.view.Display.DEFAULT_DISPLAY; - -import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.app.ActivityManager; -import android.hardware.display.DisplayManager; - -import androidx.test.annotation.UiThreadTest; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.SmallTest; - -import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.ShellTestCase; -import com.android.wm.shell.TestRunningTaskInfoBuilder; -import com.android.wm.shell.common.DisplayController; -import com.android.wm.shell.common.SyncTransactionQueue; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -/** - * Tests for {@link AppPair} - * Build/Install/Run: - * atest WMShellUnitTests:AppPairTests - */ -@SmallTest -@RunWith(AndroidJUnit4.class) -public class AppPairTests extends ShellTestCase { - - private AppPairsController mController; - @Mock private SyncTransactionQueue mSyncQueue; - @Mock private ShellTaskOrganizer mTaskOrganizer; - @Mock private DisplayController mDisplayController; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - when(mDisplayController.getDisplayContext(anyInt())).thenReturn(mContext); - when(mDisplayController.getDisplay(anyInt())).thenReturn( - mContext.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY)); - mController = new TestAppPairsController( - mTaskOrganizer, - mSyncQueue, - mDisplayController); - spyOn(mController); - } - - @After - public void tearDown() {} - - @Test - @UiThreadTest - public void testContains() { - final ActivityManager.RunningTaskInfo task1 = new TestRunningTaskInfoBuilder().build(); - final ActivityManager.RunningTaskInfo task2 = new TestRunningTaskInfoBuilder().build(); - - final AppPair pair = mController.pairInner(task1, task2); - assertThat(pair.contains(task1.taskId)).isTrue(); - assertThat(pair.contains(task2.taskId)).isTrue(); - - pair.unpair(); - assertThat(pair.contains(task1.taskId)).isFalse(); - assertThat(pair.contains(task2.taskId)).isFalse(); - } - - @Test - @UiThreadTest - public void testVanishUnpairs() { - final ActivityManager.RunningTaskInfo task1 = new TestRunningTaskInfoBuilder().build(); - final ActivityManager.RunningTaskInfo task2 = new TestRunningTaskInfoBuilder().build(); - - final AppPair pair = mController.pairInner(task1, task2); - assertThat(pair.contains(task1.taskId)).isTrue(); - assertThat(pair.contains(task2.taskId)).isTrue(); - - pair.onTaskVanished(task1); - assertThat(pair.contains(task1.taskId)).isFalse(); - assertThat(pair.contains(task2.taskId)).isFalse(); - } - - @Test - @UiThreadTest - public void testOnTaskInfoChanged_notSupportsMultiWindow() { - final ActivityManager.RunningTaskInfo task1 = new TestRunningTaskInfoBuilder().build(); - final ActivityManager.RunningTaskInfo task2 = new TestRunningTaskInfoBuilder().build(); - - final AppPair pair = mController.pairInner(task1, task2); - assertThat(pair.contains(task1.taskId)).isTrue(); - assertThat(pair.contains(task2.taskId)).isTrue(); - - task1.supportsMultiWindow = false; - pair.onTaskInfoChanged(task1); - verify(mController).unpair(pair.getRootTaskId()); - } -} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsControllerTests.java deleted file mode 100644 index 505c153eff9c..000000000000 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsControllerTests.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.apppairs; - -import static android.view.Display.DEFAULT_DISPLAY; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.when; - -import android.app.ActivityManager; -import android.hardware.display.DisplayManager; - -import androidx.test.annotation.UiThreadTest; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.SmallTest; - -import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.ShellTestCase; -import com.android.wm.shell.TestRunningTaskInfoBuilder; -import com.android.wm.shell.common.DisplayController; -import com.android.wm.shell.common.SyncTransactionQueue; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -/** Tests for {@link AppPairsController} */ -@SmallTest -@RunWith(AndroidJUnit4.class) -public class AppPairsControllerTests extends ShellTestCase { - private TestAppPairsController mController; - private TestAppPairsPool mPool; - @Mock private SyncTransactionQueue mSyncQueue; - @Mock private ShellTaskOrganizer mTaskOrganizer; - @Mock private DisplayController mDisplayController; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - when(mDisplayController.getDisplayContext(anyInt())).thenReturn(mContext); - when(mDisplayController.getDisplay(anyInt())).thenReturn( - mContext.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY)); - mController = new TestAppPairsController( - mTaskOrganizer, - mSyncQueue, - mDisplayController); - mPool = mController.getPool(); - } - - @After - public void tearDown() {} - - @Test - @UiThreadTest - public void testPairUnpair() { - final ActivityManager.RunningTaskInfo task1 = new TestRunningTaskInfoBuilder().build(); - final ActivityManager.RunningTaskInfo task2 = new TestRunningTaskInfoBuilder().build(); - - final AppPair pair = mController.pairInner(task1, task2); - assertThat(pair.contains(task1.taskId)).isTrue(); - assertThat(pair.contains(task2.taskId)).isTrue(); - assertThat(mPool.poolSize()).isGreaterThan(0); - - mController.unpair(task2.taskId); - assertThat(pair.contains(task1.taskId)).isFalse(); - assertThat(pair.contains(task2.taskId)).isFalse(); - assertThat(mPool.poolSize()).isGreaterThan(1); - } - - @Test - @UiThreadTest - public void testUnpair_DontReleaseToPool() { - final ActivityManager.RunningTaskInfo task1 = new TestRunningTaskInfoBuilder().build(); - final ActivityManager.RunningTaskInfo task2 = new TestRunningTaskInfoBuilder().build(); - - final AppPair pair = mController.pairInner(task1, task2); - assertThat(pair.contains(task1.taskId)).isTrue(); - assertThat(pair.contains(task2.taskId)).isTrue(); - - mController.unpair(task2.taskId, false /* releaseToPool */); - assertThat(pair.contains(task1.taskId)).isFalse(); - assertThat(pair.contains(task2.taskId)).isFalse(); - assertThat(mPool.poolSize()).isEqualTo(1); - } -} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsPoolTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsPoolTests.java deleted file mode 100644 index a3f134ee97ed..000000000000 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsPoolTests.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.apppairs; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.when; - -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.SmallTest; - -import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.ShellTestCase; -import com.android.wm.shell.common.DisplayController; -import com.android.wm.shell.common.SyncTransactionQueue; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -/** Tests for {@link AppPairsPool} */ -@SmallTest -@RunWith(AndroidJUnit4.class) -public class AppPairsPoolTests extends ShellTestCase { - private TestAppPairsController mController; - private TestAppPairsPool mPool; - @Mock private SyncTransactionQueue mSyncQueue; - @Mock private ShellTaskOrganizer mTaskOrganizer; - @Mock private DisplayController mDisplayController; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - when(mDisplayController.getDisplayContext(anyInt())).thenReturn(mContext); - mController = new TestAppPairsController( - mTaskOrganizer, - mSyncQueue, - mDisplayController); - mPool = mController.getPool(); - } - - @After - public void tearDown() {} - - @Test - public void testInitialState() { - // Pool should always start off with at least 1 entry. - assertThat(mPool.poolSize()).isGreaterThan(0); - } - - @Test - public void testAcquireRelease() { - assertThat(mPool.poolSize()).isGreaterThan(0); - final AppPair appPair = mPool.acquire(); - assertThat(mPool.poolSize()).isGreaterThan(0); - mPool.release(appPair); - assertThat(mPool.poolSize()).isGreaterThan(1); - } -} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsController.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsController.java deleted file mode 100644 index 294bc1276291..000000000000 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsController.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.apppairs; - -import static org.mockito.Mockito.mock; - -import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.common.DisplayController; -import com.android.wm.shell.common.DisplayImeController; -import com.android.wm.shell.common.DisplayInsetsController; -import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.common.SyncTransactionQueue; - -public class TestAppPairsController extends AppPairsController { - private TestAppPairsPool mPool; - - public TestAppPairsController(ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue, - DisplayController displayController) { - super(organizer, syncQueue, displayController, mock(ShellExecutor.class), - mock(DisplayImeController.class), mock(DisplayInsetsController.class)); - mPool = new TestAppPairsPool(this); - setPairsPool(mPool); - } - - TestAppPairsPool getPool() { - return mPool; - } -} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsPool.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsPool.java deleted file mode 100644 index 1ee7fff44892..000000000000 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsPool.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.apppairs; - -import android.app.ActivityManager; - -import com.android.wm.shell.TestRunningTaskInfoBuilder; - -public class TestAppPairsPool extends AppPairsPool{ - TestAppPairsPool(AppPairsController controller) { - super(controller); - } - - @Override - void incrementPool() { - final AppPair entry = new AppPair(mController); - final ActivityManager.RunningTaskInfo info = - new TestRunningTaskInfoBuilder().build(); - entry.onTaskAppeared(info, null /* leash */); - release(entry); - } -} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandlerTest.java new file mode 100644 index 000000000000..44ff35466ae2 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandlerTest.java @@ -0,0 +1,142 @@ +/* + * 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.bubbles; + +import static android.view.MotionEvent.ACTION_CANCEL; +import static android.view.MotionEvent.ACTION_DOWN; +import static android.view.MotionEvent.ACTION_MOVE; +import static android.view.MotionEvent.ACTION_UP; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.floatThat; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +import android.os.SystemClock; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.MotionEvent; +import android.view.WindowManager; + +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.bubbles.BubblesNavBarMotionEventHandler.MotionEventListener; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Test {@link MotionEvent} handling in {@link BubblesNavBarMotionEventHandler}. + * Verifies that swipe events + */ +@SmallTest +@TestableLooper.RunWithLooper(setAsMainLooper = true) +@RunWith(AndroidTestingRunner.class) +public class BubblesNavBarMotionEventHandlerTest extends ShellTestCase { + + private BubblesNavBarMotionEventHandler mMotionEventHandler; + @Mock + private WindowManager mWindowManager; + @Mock + private Runnable mInterceptTouchRunnable; + @Mock + private MotionEventListener mMotionEventListener; + private long mMotionEventTime; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + TestableBubblePositioner positioner = new TestableBubblePositioner(getContext(), + mWindowManager); + mMotionEventHandler = new BubblesNavBarMotionEventHandler(getContext(), positioner, + mInterceptTouchRunnable, mMotionEventListener); + mMotionEventTime = SystemClock.uptimeMillis(); + } + + @Test + public void testMotionEvent_swipeUpInGestureZone_handled() { + mMotionEventHandler.onMotionEvent(newEvent(ACTION_DOWN, 0, 990)); + mMotionEventHandler.onMotionEvent(newEvent(ACTION_MOVE, 0, 690)); + mMotionEventHandler.onMotionEvent(newEvent(ACTION_MOVE, 0, 490)); + mMotionEventHandler.onMotionEvent(newEvent(ACTION_MOVE, 0, 390)); + mMotionEventHandler.onMotionEvent(newEvent(ACTION_UP, 0, 390)); + + verify(mMotionEventListener).onDown(0, 990); + verify(mMotionEventListener).onMove(0, -300); + verify(mMotionEventListener).onMove(0, -500); + verify(mMotionEventListener).onMove(0, -600); + // Check that velocity up is about 5000 + verify(mMotionEventListener).onUp(eq(0f), floatThat(f -> Math.round(f) == -5000)); + verifyZeroInteractions(mMotionEventListener); + verify(mInterceptTouchRunnable).run(); + } + + @Test + public void testMotionEvent_swipeUpOutsideGestureZone_ignored() { + mMotionEventHandler.onMotionEvent(newEvent(ACTION_DOWN, 0, 500)); + mMotionEventHandler.onMotionEvent(newEvent(ACTION_MOVE, 0, 100)); + mMotionEventHandler.onMotionEvent(newEvent(ACTION_UP, 0, 100)); + + verifyZeroInteractions(mMotionEventListener); + verifyZeroInteractions(mInterceptTouchRunnable); + } + + @Test + public void testMotionEvent_horizontalMoveMoreThanTouchSlop_handled() { + mMotionEventHandler.onMotionEvent(newEvent(ACTION_DOWN, 0, 990)); + mMotionEventHandler.onMotionEvent(newEvent(ACTION_MOVE, 100, 990)); + mMotionEventHandler.onMotionEvent(newEvent(ACTION_UP, 100, 990)); + + verify(mMotionEventListener).onDown(0, 990); + verify(mMotionEventListener).onMove(100, 0); + verify(mMotionEventListener).onUp(0, 0); + verifyZeroInteractions(mMotionEventListener); + verify(mInterceptTouchRunnable).run(); + } + + @Test + public void testMotionEvent_moveLessThanTouchSlop_ignored() { + mMotionEventHandler.onMotionEvent(newEvent(ACTION_DOWN, 0, 990)); + mMotionEventHandler.onMotionEvent(newEvent(ACTION_MOVE, 0, 989)); + mMotionEventHandler.onMotionEvent(newEvent(ACTION_UP, 0, 989)); + + verify(mMotionEventListener).onDown(0, 990); + verifyNoMoreInteractions(mMotionEventListener); + verifyZeroInteractions(mInterceptTouchRunnable); + } + + @Test + public void testMotionEvent_actionCancel_listenerNotified() { + mMotionEventHandler.onMotionEvent(newEvent(ACTION_DOWN, 0, 990)); + mMotionEventHandler.onMotionEvent(newEvent(ACTION_CANCEL, 0, 990)); + verify(mMotionEventListener).onDown(0, 990); + verify(mMotionEventListener).onCancel(); + verifyNoMoreInteractions(mMotionEventListener); + verifyZeroInteractions(mInterceptTouchRunnable); + } + + private MotionEvent newEvent(int actionDown, float x, float y) { + MotionEvent event = MotionEvent.obtain(0L, mMotionEventTime, actionDown, x, y, 0); + mMotionEventTime += 10; + return event; + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerTest.java new file mode 100644 index 000000000000..991913afbb90 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerTest.java @@ -0,0 +1,181 @@ +/* + * 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.bubbles.animation; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assume.assumeTrue; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.ViewConfiguration; +import android.view.WindowManager; + +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.bubbles.BubbleExpandedView; +import com.android.wm.shell.bubbles.TestableBubblePositioner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +public class ExpandedViewAnimationControllerTest extends ShellTestCase { + + private ExpandedViewAnimationController mController; + + @Mock + private WindowManager mWindowManager; + + @Mock + private BubbleExpandedView mMockExpandedView; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + TestableBubblePositioner positioner = new TestableBubblePositioner(getContext(), + mWindowManager); + mController = new ExpandedViewAnimationControllerImpl(getContext(), positioner); + + mController.setExpandedView(mMockExpandedView); + when(mMockExpandedView.getContentHeight()).thenReturn(1000); + } + + @Test + public void testUpdateDrag_expandedViewMovesUpAndClipped() { + // Drag by 50 pixels which corresponds to 10 pixels with overscroll + int dragDistance = 50; + int dampenedDistance = 10; + + mController.updateDrag(dragDistance); + + verify(mMockExpandedView).setTopClip(dampenedDistance); + verify(mMockExpandedView).setContentTranslationY(-dampenedDistance); + verify(mMockExpandedView).setManageButtonTranslationY(-dampenedDistance); + } + + @Test + public void testUpdateDrag_zOrderUpdates() { + mController.updateDrag(10); + mController.updateDrag(20); + + verify(mMockExpandedView, times(1)).setSurfaceZOrderedOnTop(true); + verify(mMockExpandedView, times(1)).setAnimating(true); + } + + @Test + public void testUpdateDrag_moveBackToZero_zOrderRestored() { + mController.updateDrag(50); + reset(mMockExpandedView); + mController.updateDrag(0); + mController.updateDrag(0); + + verify(mMockExpandedView, times(1)).setSurfaceZOrderedOnTop(false); + verify(mMockExpandedView, times(1)).setAnimating(false); + } + + @Test + public void testUpdateDrag_hapticFeedbackOnlyOnce() { + // Drag by 10 which is below the collapse threshold - no feedback + mController.updateDrag(10); + verify(mMockExpandedView, times(0)).performHapticFeedback(anyInt()); + // 150 takes it over the threshold - perform feedback + mController.updateDrag(150); + verify(mMockExpandedView, times(1)).performHapticFeedback(anyInt()); + // Continue dragging, no more feedback + mController.updateDrag(200); + verify(mMockExpandedView, times(1)).performHapticFeedback(anyInt()); + // Drag below threshold and over again - no more feedback + mController.updateDrag(10); + mController.updateDrag(150); + verify(mMockExpandedView, times(1)).performHapticFeedback(anyInt()); + } + + @Test + public void testShouldCollapse_doNotCollapseIfNotDragged() { + assertThat(mController.shouldCollapse()).isFalse(); + } + + @Test + public void testShouldCollapse_doNotCollapseIfVelocityDown() { + assumeTrue("Min fling velocity should be > 1 for this test", getMinFlingVelocity() > 1); + mController.setSwipeVelocity(getVelocityAboveMinFling()); + assertThat(mController.shouldCollapse()).isFalse(); + } + + @Test + public void tesShouldCollapse_doNotCollapseIfVelocityUpIsSmall() { + assumeTrue("Min fling velocity should be > 1 for this test", getMinFlingVelocity() > 1); + mController.setSwipeVelocity(-getVelocityBelowMinFling()); + assertThat(mController.shouldCollapse()).isFalse(); + } + + @Test + public void testShouldCollapse_collapseIfVelocityUpIsLarge() { + assumeTrue("Min fling velocity should be > 1 for this test", getMinFlingVelocity() > 1); + mController.setSwipeVelocity(-getVelocityAboveMinFling()); + assertThat(mController.shouldCollapse()).isTrue(); + } + + @Test + public void testShouldCollapse_collapseIfPastThreshold() { + mController.updateDrag(500); + assertThat(mController.shouldCollapse()).isTrue(); + } + + @Test + public void testReset() { + mController.updateDrag(100); + reset(mMockExpandedView); + mController.reset(); + verify(mMockExpandedView, atLeastOnce()).setAnimating(false); + verify(mMockExpandedView).setContentAlpha(1); + verify(mMockExpandedView).setBackgroundAlpha(1); + verify(mMockExpandedView).setManageButtonAlpha(1); + verify(mMockExpandedView).setManageButtonAlpha(1); + verify(mMockExpandedView).setTopClip(0); + verify(mMockExpandedView).setContentTranslationY(-0f); + verify(mMockExpandedView).setManageButtonTranslationY(-0f); + verify(mMockExpandedView).setBottomClip(0); + verify(mMockExpandedView).movePointerBy(0, 0); + assertThat(mController.shouldCollapse()).isFalse(); + } + + private int getVelocityBelowMinFling() { + return getMinFlingVelocity() - 1; + } + + private int getVelocityAboveMinFling() { + return getMinFlingVelocity() + 1; + } + + private int getMinFlingVelocity() { + return ViewConfiguration.get(getContext()).getScaledMinimumFlingVelocity(); + } +} 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 bb6026c36c97..7e6595f1abe2 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 @@ -182,8 +182,10 @@ public class DragAndDropPolicyTest { info.configuration.windowConfiguration.setActivityType(actType); info.configuration.windowConfiguration.setWindowingMode(winMode); info.isResizeable = true; - info.baseActivity = new ComponentName(getInstrumentation().getContext().getPackageName(), + info.baseActivity = new ComponentName(getInstrumentation().getContext(), ".ActivityWithMode" + winMode); + info.baseIntent = new Intent(); + info.baseIntent.setComponent(info.baseActivity); ActivityInfo activityInfo = new ActivityInfo(); activityInfo.packageName = info.baseActivity.getPackageName(); activityInfo.name = info.baseActivity.getClassName(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/fullscreen/FullscreenTaskListenerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/fullscreen/FullscreenTaskListenerTest.java deleted file mode 100644 index 4523e2c9cba5..000000000000 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/fullscreen/FullscreenTaskListenerTest.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.fullscreen; - -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; - -import static org.junit.Assume.assumeFalse; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - -import android.app.ActivityManager.RunningTaskInfo; -import android.app.WindowConfiguration; -import android.content.res.Configuration; -import android.graphics.Point; -import android.os.SystemProperties; -import android.view.SurfaceControl; - -import androidx.test.filters.SmallTest; - -import com.android.wm.shell.common.SyncTransactionQueue; -import com.android.wm.shell.recents.RecentTasksController; - -import org.junit.Before; -import org.junit.Test; -import org.mockito.InOrder; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.Optional; - -@SmallTest -public class FullscreenTaskListenerTest { - private static final boolean ENABLE_SHELL_TRANSITIONS = - SystemProperties.getBoolean("persist.wm.debug.shell_transit", false); - - @Mock - private SyncTransactionQueue mSyncQueue; - @Mock - private FullscreenUnfoldController mUnfoldController; - @Mock - private RecentTasksController mRecentTasksController; - @Mock - private SurfaceControl mSurfaceControl; - - private Optional<FullscreenUnfoldController> mFullscreenUnfoldController; - - private FullscreenTaskListener mListener; - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - mFullscreenUnfoldController = Optional.of(mUnfoldController); - mListener = new FullscreenTaskListener(mSyncQueue, mFullscreenUnfoldController, - Optional.empty()); - } - - @Test - public void testAnimatableTaskAppeared_notifiesUnfoldController() { - assumeFalse(ENABLE_SHELL_TRANSITIONS); - RunningTaskInfo info = createTaskInfo(/* visible */ true, /* taskId */ 0); - - mListener.onTaskAppeared(info, mSurfaceControl); - - verify(mUnfoldController).onTaskAppeared(eq(info), any()); - } - - @Test - public void testMultipleAnimatableTasksAppeared_notifiesUnfoldController() { - assumeFalse(ENABLE_SHELL_TRANSITIONS); - RunningTaskInfo animatable1 = createTaskInfo(/* visible */ true, /* taskId */ 0); - RunningTaskInfo animatable2 = createTaskInfo(/* visible */ true, /* taskId */ 1); - - mListener.onTaskAppeared(animatable1, mSurfaceControl); - mListener.onTaskAppeared(animatable2, mSurfaceControl); - - InOrder order = inOrder(mUnfoldController); - order.verify(mUnfoldController).onTaskAppeared(eq(animatable1), any()); - order.verify(mUnfoldController).onTaskAppeared(eq(animatable2), any()); - } - - @Test - public void testNonAnimatableTaskAppeared_doesNotNotifyUnfoldController() { - assumeFalse(ENABLE_SHELL_TRANSITIONS); - RunningTaskInfo info = createTaskInfo(/* visible */ false, /* taskId */ 0); - - mListener.onTaskAppeared(info, mSurfaceControl); - - verifyNoMoreInteractions(mUnfoldController); - } - - @Test - public void testNonAnimatableTaskChanged_doesNotNotifyUnfoldController() { - assumeFalse(ENABLE_SHELL_TRANSITIONS); - RunningTaskInfo info = createTaskInfo(/* visible */ false, /* taskId */ 0); - mListener.onTaskAppeared(info, mSurfaceControl); - - mListener.onTaskInfoChanged(info); - - verifyNoMoreInteractions(mUnfoldController); - } - - @Test - public void testNonAnimatableTaskVanished_doesNotNotifyUnfoldController() { - assumeFalse(ENABLE_SHELL_TRANSITIONS); - RunningTaskInfo info = createTaskInfo(/* visible */ false, /* taskId */ 0); - mListener.onTaskAppeared(info, mSurfaceControl); - - mListener.onTaskVanished(info); - - verifyNoMoreInteractions(mUnfoldController); - } - - @Test - public void testAnimatableTaskBecameInactive_notifiesUnfoldController() { - assumeFalse(ENABLE_SHELL_TRANSITIONS); - RunningTaskInfo animatableTask = createTaskInfo(/* visible */ true, /* taskId */ 0); - mListener.onTaskAppeared(animatableTask, mSurfaceControl); - RunningTaskInfo notAnimatableTask = createTaskInfo(/* visible */ false, /* taskId */ 0); - - mListener.onTaskInfoChanged(notAnimatableTask); - - verify(mUnfoldController).onTaskVanished(eq(notAnimatableTask)); - } - - @Test - public void testAnimatableTaskVanished_notifiesUnfoldController() { - assumeFalse(ENABLE_SHELL_TRANSITIONS); - RunningTaskInfo taskInfo = createTaskInfo(/* visible */ true, /* taskId */ 0); - mListener.onTaskAppeared(taskInfo, mSurfaceControl); - - mListener.onTaskVanished(taskInfo); - - verify(mUnfoldController).onTaskVanished(eq(taskInfo)); - } - - private RunningTaskInfo createTaskInfo(boolean visible, int taskId) { - final RunningTaskInfo info = spy(new RunningTaskInfo()); - info.isVisible = visible; - info.positionInParent = new Point(); - when(info.getWindowingMode()).thenReturn(WindowConfiguration.WINDOWING_MODE_FULLSCREEN); - final Configuration configuration = new Configuration(); - configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD); - when(info.getConfiguration()).thenReturn(configuration); - info.taskId = taskId; - return info; - } -} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java index 440a6f8fb59a..1eadeed7cd3b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java @@ -88,7 +88,7 @@ public class KidsModeTaskOrganizerTest { // NOTE: KidsModeTaskOrganizer should have a null CompatUIController. mOrganizer = spy(new KidsModeTaskOrganizer(mTaskOrganizerController, mTestExecutor, mHandler, mContext, mSyncTransactionQueue, mDisplayController, - mDisplayInsetsController, Optional.empty(), mObserver)); + mDisplayInsetsController, Optional.empty(), Optional.empty(), mObserver)); mOrganizer.initialize(mStartingWindowController); doReturn(mTransaction).when(mOrganizer).getWindowContainerTransaction(); doReturn(new InsetsState()).when(mDisplayController).getInsetsState(DEFAULT_DISPLAY); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java index ecf1c5d41864..6a6db8aa3c04 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java @@ -345,8 +345,8 @@ public class OneHandedControllerTest extends OneHandedTestCase { when(mMockSettingsUitl.getSettingsSwipeToNotificationEnabled(any(), anyInt())).thenReturn( false); final WindowContainerTransaction handlerWCT = new WindowContainerTransaction(); - mSpiedOneHandedController.onRotateDisplay(mDisplay.getDisplayId(), Surface.ROTATION_0, - Surface.ROTATION_90, handlerWCT); + mSpiedOneHandedController.onDisplayChange(mDisplay.getDisplayId(), Surface.ROTATION_0, + Surface.ROTATION_90, null /* newDisplayAreaInfo */, handlerWCT); verify(mMockDisplayAreaOrganizer, atLeastOnce()).onRotateDisplay(eq(mContext), eq(Surface.ROTATION_90), any(WindowContainerTransaction.class)); @@ -358,8 +358,8 @@ public class OneHandedControllerTest extends OneHandedTestCase { when(mMockSettingsUitl.getSettingsSwipeToNotificationEnabled(any(), anyInt())).thenReturn( false); final WindowContainerTransaction handlerWCT = new WindowContainerTransaction(); - mSpiedOneHandedController.onRotateDisplay(mDisplay.getDisplayId(), Surface.ROTATION_0, - Surface.ROTATION_90, handlerWCT); + mSpiedOneHandedController.onDisplayChange(mDisplay.getDisplayId(), Surface.ROTATION_0, + Surface.ROTATION_90, null /* newDisplayAreaInfo */, handlerWCT); verify(mMockDisplayAreaOrganizer, never()).onRotateDisplay(eq(mContext), eq(Surface.ROTATION_90), any(WindowContainerTransaction.class)); @@ -371,8 +371,8 @@ public class OneHandedControllerTest extends OneHandedTestCase { when(mMockSettingsUitl.getSettingsSwipeToNotificationEnabled(any(), anyInt())).thenReturn( true); final WindowContainerTransaction handlerWCT = new WindowContainerTransaction(); - mSpiedOneHandedController.onRotateDisplay(mDisplay.getDisplayId(), Surface.ROTATION_0, - Surface.ROTATION_90, handlerWCT); + mSpiedOneHandedController.onDisplayChange(mDisplay.getDisplayId(), Surface.ROTATION_0, + Surface.ROTATION_90, null /* newDisplayAreaInfo */, handlerWCT); verify(mMockDisplayAreaOrganizer, never()).onRotateDisplay(eq(mContext), eq(Surface.ROTATION_90), any(WindowContainerTransaction.class)); @@ -384,8 +384,8 @@ public class OneHandedControllerTest extends OneHandedTestCase { when(mMockSettingsUitl.getSettingsSwipeToNotificationEnabled(any(), anyInt())).thenReturn( false); final WindowContainerTransaction handlerWCT = new WindowContainerTransaction(); - mSpiedOneHandedController.onRotateDisplay(mDisplay.getDisplayId(), Surface.ROTATION_0, - Surface.ROTATION_90, handlerWCT); + mSpiedOneHandedController.onDisplayChange(mDisplay.getDisplayId(), Surface.ROTATION_0, + Surface.ROTATION_90, null /* newDisplayAreaInfo */, handlerWCT); verify(mMockDisplayAreaOrganizer, atLeastOnce()).onRotateDisplay(eq(mContext), eq(Surface.ROTATION_90), any(WindowContainerTransaction.class)); 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 df18133adcfb..abd55dd7d606 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 @@ -76,6 +76,7 @@ public class PipControllerTest extends ShellTestCase { @Mock private PhonePipMenuController mMockPhonePipMenuController; @Mock private PipAppOpsListener mMockPipAppOpsListener; @Mock private PipBoundsAlgorithm mMockPipBoundsAlgorithm; + @Mock private PipKeepClearAlgorithm mMockPipKeepClearAlgorithm; @Mock private PipSnapAlgorithm mMockPipSnapAlgorithm; @Mock private PipMediaController mMockPipMediaController; @Mock private PipTaskOrganizer mMockPipTaskOrganizer; @@ -101,7 +102,8 @@ public class PipControllerTest extends ShellTestCase { }).when(mMockExecutor).execute(any()); mPipController = new PipController(mContext, mMockDisplayController, mMockPipAppOpsListener, mMockPipBoundsAlgorithm, - mMockPipBoundsState, mMockPipMediaController, + mMockPipKeepClearAlgorithm, + mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper, mMockTaskStackListener, mPipParamsChangedForwarder, @@ -134,7 +136,8 @@ public class PipControllerTest extends ShellTestCase { assertNull(PipController.create(spyContext, mMockDisplayController, mMockPipAppOpsListener, mMockPipBoundsAlgorithm, - mMockPipBoundsState, mMockPipMediaController, + mMockPipKeepClearAlgorithm, + mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper, mMockTaskStackListener, mPipParamsChangedForwarder, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithmTest.java new file mode 100644 index 000000000000..e0f7e35f8d02 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithmTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.pip.phone; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import android.graphics.Rect; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.ShellTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Set; + +/** + * Unit tests against {@link PipKeepClearAlgorithm}. + */ +@RunWith(AndroidTestingRunner.class) +@SmallTest +@TestableLooper.RunWithLooper(setAsMainLooper = true) +public class PipKeepClearAlgorithmTest extends ShellTestCase { + + private PipKeepClearAlgorithm mPipKeepClearAlgorithm; + private static final Rect DISPLAY_BOUNDS = new Rect(0, 0, 1000, 1000); + + @Before + public void setUp() throws Exception { + mPipKeepClearAlgorithm = new PipKeepClearAlgorithm(); + } + + @Test + public void adjust_withCollidingRestrictedKeepClearAreas_movesBounds() { + final Rect inBounds = new Rect(0, 0, 100, 100); + final Rect keepClearRect = new Rect(50, 50, 150, 150); + + final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(keepClearRect), + Set.of(), DISPLAY_BOUNDS); + + assertFalse(outBounds.contains(keepClearRect)); + } + + @Test + public void adjust_withNonCollidingRestrictedKeepClearAreas_boundsDoNotChange() { + final Rect inBounds = new Rect(0, 0, 100, 100); + final Rect keepClearRect = new Rect(100, 100, 150, 150); + + final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(keepClearRect), + Set.of(), DISPLAY_BOUNDS); + + assertEquals(inBounds, outBounds); + } + + @Test + public void adjust_withCollidingUnrestrictedKeepClearAreas_boundsDoNotChange() { + // TODO(b/183746978): update this test to accommodate for the updated algorithm + final Rect inBounds = new Rect(0, 0, 100, 100); + final Rect keepClearRect = new Rect(50, 50, 150, 150); + + final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(), + Set.of(keepClearRect), DISPLAY_BOUNDS); + + assertEquals(inBounds, outBounds); + } + + @Test + public void adjust_withNonCollidingUnrestrictedKeepClearAreas_boundsDoNotChange() { + final Rect inBounds = new Rect(0, 0, 100, 100); + final Rect keepClearRect = new Rect(100, 100, 150, 150); + + final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(), + Set.of(keepClearRect), DISPLAY_BOUNDS); + + assertEquals(inBounds, outBounds); + } +} 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 50f6bd7b4927..2b4d1a6390c9 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 @@ -47,7 +47,7 @@ import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.util.GroupedRecentTaskInfo; -import com.android.wm.shell.util.StagedSplitBounds; +import com.android.wm.shell.util.SplitBounds; import org.junit.Before; import org.junit.Test; @@ -80,7 +80,7 @@ public class RecentTasksControllerTest extends ShellTestCase { mRecentTasksController = spy(new RecentTasksController(mContext, mTaskStackListener, mMainExecutor)); mShellTaskOrganizer = new ShellTaskOrganizer(mMainExecutor, mContext, - null /* sizeCompatUI */, Optional.of(mRecentTasksController)); + null /* sizeCompatUI */, Optional.empty(), Optional.of(mRecentTasksController)); } @Test @@ -89,7 +89,7 @@ public class RecentTasksControllerTest extends ShellTestCase { ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); setRawList(t1, t2); - mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, mock(StagedSplitBounds.class)); + mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, mock(SplitBounds.class)); verify(mRecentTasksController).notifyRecentTasksChanged(); reset(mRecentTasksController); @@ -104,10 +104,10 @@ public class RecentTasksControllerTest extends ShellTestCase { setRawList(t1, t2); // Verify only one update if the split info is the same - StagedSplitBounds bounds1 = new StagedSplitBounds(new Rect(0, 0, 50, 50), + SplitBounds bounds1 = new SplitBounds(new Rect(0, 0, 50, 50), new Rect(50, 50, 100, 100), t1.taskId, t2.taskId); mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, bounds1); - StagedSplitBounds bounds2 = new StagedSplitBounds(new Rect(0, 0, 50, 50), + SplitBounds bounds2 = new SplitBounds(new Rect(0, 0, 50, 50), new Rect(50, 50, 100, 100), t1.taskId, t2.taskId); mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, bounds2); verify(mRecentTasksController, times(1)).notifyRecentTasksChanged(); @@ -139,8 +139,8 @@ public class RecentTasksControllerTest extends ShellTestCase { setRawList(t1, t2, t3, t4, t5, t6); // Mark a couple pairs [t2, t4], [t3, t5] - StagedSplitBounds pair1Bounds = new StagedSplitBounds(new Rect(), new Rect(), 2, 4); - StagedSplitBounds pair2Bounds = new StagedSplitBounds(new Rect(), new Rect(), 3, 5); + SplitBounds pair1Bounds = new SplitBounds(new Rect(), new Rect(), 2, 4); + SplitBounds pair2Bounds = new SplitBounds(new Rect(), new Rect(), 3, 5); mRecentTasksController.addSplitPair(t2.taskId, t4.taskId, pair1Bounds); mRecentTasksController.addSplitPair(t3.taskId, t5.taskId, pair2Bounds); @@ -162,7 +162,7 @@ public class RecentTasksControllerTest extends ShellTestCase { setRawList(t1, t2, t3); // Add a pair - StagedSplitBounds pair1Bounds = new StagedSplitBounds(new Rect(), new Rect(), 2, 3); + SplitBounds pair1Bounds = new SplitBounds(new Rect(), new Rect(), 2, 3); mRecentTasksController.addSplitPair(t2.taskId, t3.taskId, pair1Bounds); reset(mRecentTasksController); @@ -245,15 +245,15 @@ public class RecentTasksControllerTest extends ShellTestCase { : -1; if (pair.mTaskInfo2 != null) { - assertNotNull(pair.mStagedSplitBounds); - int leftTopTaskId = pair.mStagedSplitBounds.leftTopTaskId; - int bottomRightTaskId = pair.mStagedSplitBounds.rightBottomTaskId; + assertNotNull(pair.mSplitBounds); + int leftTopTaskId = pair.mSplitBounds.leftTopTaskId; + int bottomRightTaskId = pair.mSplitBounds.rightBottomTaskId; // Unclear if pairs are ordered by split position, most likely not. assertTrue(leftTopTaskId == taskId1 || leftTopTaskId == pair.mTaskInfo2.taskId); assertTrue(bottomRightTaskId == taskId1 || bottomRightTaskId == pair.mTaskInfo2.taskId); } else { - assertNull(pair.mStagedSplitBounds); + assertNull(pair.mSplitBounds); } } assertTrue("Expected: " + Arrays.toString(expectedTaskIds) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/StagedSplitBoundsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java index ad73c56950bd..068a60a12f5f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/StagedSplitBoundsTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java @@ -9,7 +9,7 @@ import android.graphics.Rect; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.wm.shell.util.StagedSplitBounds; +import com.android.wm.shell.util.SplitBounds; import org.junit.Before; import org.junit.Test; @@ -17,7 +17,7 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) @SmallTest -public class StagedSplitBoundsTest { +public class SplitBoundsTest { private static final int DEVICE_WIDTH = 100; private static final int DEVICE_LENGTH = 200; private static final int DIVIDER_SIZE = 20; @@ -42,21 +42,21 @@ public class StagedSplitBoundsTest { @Test public void testVerticalStacked() { - StagedSplitBounds ssb = new StagedSplitBounds(mTopRect, mBottomRect, + SplitBounds ssb = new SplitBounds(mTopRect, mBottomRect, TASK_ID_1, TASK_ID_2); assertTrue(ssb.appsStackedVertically); } @Test public void testHorizontalStacked() { - StagedSplitBounds ssb = new StagedSplitBounds(mLeftRect, mRightRect, + SplitBounds ssb = new SplitBounds(mLeftRect, mRightRect, TASK_ID_1, TASK_ID_2); assertFalse(ssb.appsStackedVertically); } @Test public void testHorizontalDividerBounds() { - StagedSplitBounds ssb = new StagedSplitBounds(mTopRect, mBottomRect, + SplitBounds ssb = new SplitBounds(mTopRect, mBottomRect, TASK_ID_1, TASK_ID_2); Rect dividerBounds = ssb.visualDividerBounds; assertEquals(0, dividerBounds.left); @@ -67,7 +67,7 @@ public class StagedSplitBoundsTest { @Test public void testVerticalDividerBounds() { - StagedSplitBounds ssb = new StagedSplitBounds(mLeftRect, mRightRect, + SplitBounds ssb = new SplitBounds(mLeftRect, mRightRect, TASK_ID_1, TASK_ID_2); Rect dividerBounds = ssb.visualDividerBounds; assertEquals(DEVICE_WIDTH / 2 - DIVIDER_SIZE / 2, dividerBounds.left); @@ -78,7 +78,7 @@ public class StagedSplitBoundsTest { @Test public void testEqualVerticalTaskPercent() { - StagedSplitBounds ssb = new StagedSplitBounds(mTopRect, mBottomRect, + SplitBounds ssb = new SplitBounds(mTopRect, mBottomRect, TASK_ID_1, TASK_ID_2); float topPercentSpaceTaken = (float) (DEVICE_LENGTH / 2 - DIVIDER_SIZE / 2) / DEVICE_LENGTH; assertEquals(topPercentSpaceTaken, ssb.topTaskPercent, 0.01); @@ -86,7 +86,7 @@ public class StagedSplitBoundsTest { @Test public void testEqualHorizontalTaskPercent() { - StagedSplitBounds ssb = new StagedSplitBounds(mLeftRect, mRightRect, + SplitBounds ssb = new SplitBounds(mLeftRect, mRightRect, TASK_ID_1, TASK_ID_2); float leftPercentSpaceTaken = (float) (DEVICE_WIDTH / 2 - DIVIDER_SIZE / 2) / DEVICE_WIDTH; assertEquals(leftPercentSpaceTaken, ssb.leftTaskPercent, 0.01); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java index 0639ad5d0a62..68cb57c14d8c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java @@ -61,7 +61,7 @@ public class MainStageTests extends ShellTestCase { MockitoAnnotations.initMocks(this); mRootTaskInfo = new TestRunningTaskInfoBuilder().build(); mMainStage = new MainStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks, - mSyncQueue, mSurfaceSession, mIconProvider, null); + mSyncQueue, mSurfaceSession, mIconProvider); mMainStage.onTaskAppeared(mRootTaskInfo, mRootLeash); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java index a31aa58bdc26..3b42a48b5a40 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java @@ -66,7 +66,7 @@ public class SideStageTests extends ShellTestCase { MockitoAnnotations.initMocks(this); mRootTask = new TestRunningTaskInfoBuilder().build(); mSideStage = new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks, - mSyncQueue, mSurfaceSession, mIconProvider, null); + mSyncQueue, mSurfaceSession, mIconProvider); mSideStage.onTaskAppeared(mRootTask, mRootLeash); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java index eb9d3a11d285..a67853cfe745 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java @@ -40,8 +40,6 @@ import com.android.wm.shell.transition.Transitions; import java.util.Optional; -import javax.inject.Provider; - public class SplitTestUtils { static SplitLayout createMockSplitLayout() { @@ -74,12 +72,10 @@ public class SplitTestUtils { DisplayInsetsController insetsController, SplitLayout splitLayout, Transitions transitions, TransactionPool transactionPool, SplitscreenEventLogger logger, ShellExecutor mainExecutor, - Optional<RecentTasksController> recentTasks, - Provider<Optional<StageTaskUnfoldController>> unfoldController) { + Optional<RecentTasksController> recentTasks) { super(context, displayId, syncQueue, taskOrganizer, mainStage, sideStage, displayController, imeController, insetsController, splitLayout, - transitions, transactionPool, logger, mainExecutor, recentTasks, - unfoldController); + transitions, transactionPool, logger, mainExecutor, recentTasks); // Prepare root task for testing. mRootTask = new TestRunningTaskInfoBuilder().build(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java index ffaab652aa99..304ca66dd3bb 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java @@ -118,16 +118,16 @@ public class SplitTransitionTests extends ShellTestCase { mSplitLayout = SplitTestUtils.createMockSplitLayout(); mMainStage = new MainStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock( StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession, - mIconProvider, null); + mIconProvider); mMainStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface()); mSideStage = new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock( StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession, - mIconProvider, null); + mIconProvider); mSideStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface()); mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue, mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions, - mTransactionPool, mLogger, mMainExecutor, Optional.empty(), Optional::empty); + mTransactionPool, mLogger, mMainExecutor, Optional.empty()); mSplitScreenTransitions = mStageCoordinator.getSplitTransitions(); doAnswer((Answer<IBinder>) invocation -> mock(IBinder.class)) .when(mTransitions).startTransition(anyInt(), any(), any()); @@ -340,7 +340,7 @@ public class SplitTransitionTests extends ShellTestCase { TransitionInfo info = new TransitionInfo(TRANSIT_TO_BACK, 0); info.addChange(mainChange); info.addChange(sideChange); - IBinder transition = mSplitScreenTransitions.startDismissTransition(null, + IBinder transition = mSplitScreenTransitions.startDismissTransition( new WindowContainerTransaction(), mStageCoordinator, EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW, STAGE_TYPE_SIDE); boolean accepted = mStageCoordinator.startAnimation(transition, info, @@ -363,7 +363,7 @@ public class SplitTransitionTests extends ShellTestCase { TransitionInfo info = new TransitionInfo(TRANSIT_TO_BACK, 0); info.addChange(mainChange); info.addChange(sideChange); - IBinder transition = mSplitScreenTransitions.startDismissTransition(null, + IBinder transition = mSplitScreenTransitions.startDismissTransition( new WindowContainerTransaction(), mStageCoordinator, EXIT_REASON_DRAG_DIVIDER, STAGE_TYPE_SIDE); mMainStage.onTaskVanished(mMainChild); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java index 42d998f6b0ee..af2c495c85c5 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java @@ -34,6 +34,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -58,6 +59,7 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.split.SplitLayout; +import com.android.wm.shell.splitscreen.SplitScreen.SplitScreenListener; import com.android.wm.shell.transition.Transitions; import org.junit.Before; @@ -68,8 +70,6 @@ import org.mockito.MockitoAnnotations; import java.util.Optional; -import javax.inject.Provider; - /** * Tests for {@link StageCoordinator} */ @@ -85,10 +85,6 @@ public class StageCoordinatorTests extends ShellTestCase { @Mock private SideStage mSideStage; @Mock - private StageTaskUnfoldController mMainUnfoldController; - @Mock - private StageTaskUnfoldController mSideUnfoldController; - @Mock private SplitLayout mSplitLayout; @Mock private DisplayController mDisplayController; @@ -107,6 +103,7 @@ public class StageCoordinatorTests extends ShellTestCase { private final Rect mBounds1 = new Rect(10, 20, 30, 40); private final Rect mBounds2 = new Rect(5, 10, 15, 20); + private final Rect mRootBounds = new Rect(0, 0, 45, 60); private SurfaceSession mSurfaceSession = new SurfaceSession(); private SurfaceControl mRootLeash; @@ -119,11 +116,12 @@ public class StageCoordinatorTests extends ShellTestCase { mStageCoordinator = spy(new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue, mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions, mTransactionPool, mLogger, - mMainExecutor, Optional.empty(), new UnfoldControllerProvider())); + mMainExecutor, Optional.empty())); doNothing().when(mStageCoordinator).updateActivityOptions(any(), anyInt()); when(mSplitLayout.getBounds1()).thenReturn(mBounds1); when(mSplitLayout.getBounds2()).thenReturn(mBounds2); + when(mSplitLayout.getRootBounds()).thenReturn(mRootBounds); when(mSplitLayout.isLandscape()).thenReturn(false); mRootTask = new TestRunningTaskInfoBuilder().build(); @@ -168,13 +166,6 @@ public class StageCoordinatorTests extends ShellTestCase { } @Test - public void testRootTaskAppeared_initializesUnfoldControllers() { - verify(mMainUnfoldController).init(); - verify(mSideUnfoldController).init(); - verify(mStageCoordinator).onRootTaskAppeared(); - } - - @Test public void testRootTaskInfoChanged_updatesSplitLayout() { mStageCoordinator.onTaskInfoChanged(mRootTask); @@ -184,26 +175,25 @@ public class StageCoordinatorTests extends ShellTestCase { @Test public void testLayoutChanged_topLeftSplitPosition_updatesUnfoldStageBounds() { mStageCoordinator.setSideStagePosition(SPLIT_POSITION_TOP_OR_LEFT, null); - clearInvocations(mMainUnfoldController, mSideUnfoldController); + final SplitScreenListener listener = mock(SplitScreenListener.class); + mStageCoordinator.registerSplitScreenListener(listener); + clearInvocations(listener); mStageCoordinator.onLayoutSizeChanged(mSplitLayout); - verify(mMainUnfoldController).onLayoutChanged(mBounds2, SPLIT_POSITION_BOTTOM_OR_RIGHT, - false); - verify(mSideUnfoldController).onLayoutChanged(mBounds1, SPLIT_POSITION_TOP_OR_LEFT, false); + verify(listener).onSplitBoundsChanged(mRootBounds, mBounds2, mBounds1); } @Test public void testLayoutChanged_bottomRightSplitPosition_updatesUnfoldStageBounds() { mStageCoordinator.setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, null); - clearInvocations(mMainUnfoldController, mSideUnfoldController); + final SplitScreenListener listener = mock(SplitScreenListener.class); + mStageCoordinator.registerSplitScreenListener(listener); + clearInvocations(listener); mStageCoordinator.onLayoutSizeChanged(mSplitLayout); - verify(mMainUnfoldController).onLayoutChanged(mBounds1, SPLIT_POSITION_TOP_OR_LEFT, - false); - verify(mSideUnfoldController).onLayoutChanged(mBounds2, SPLIT_POSITION_BOTTOM_OR_RIGHT, - false); + verify(listener).onSplitBoundsChanged(mRootBounds, mBounds1, mBounds2); } @Test @@ -314,20 +304,4 @@ public class StageCoordinatorTests extends ShellTestCase { verify(mSplitLayout).applySurfaceChanges(any(), any(), any(), any(), any(), eq(false)); } - - private class UnfoldControllerProvider implements - Provider<Optional<StageTaskUnfoldController>> { - - private boolean isMain = true; - - @Override - public Optional<StageTaskUnfoldController> get() { - if (isMain) { - isMain = false; - return Optional.of(mMainUnfoldController); - } else { - return Optional.of(mSideUnfoldController); - } - } - } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java index 157c30bcb6c7..5ee8bf3006a3 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java @@ -25,7 +25,6 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeFalse; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -72,8 +71,6 @@ public final class StageTaskListenerTests extends ShellTestCase { private SyncTransactionQueue mSyncQueue; @Mock private IconProvider mIconProvider; - @Mock - private StageTaskUnfoldController mStageTaskUnfoldController; @Captor private ArgumentCaptor<SyncTransactionQueue.TransactionRunnable> mRunnableCaptor; private SurfaceSession mSurfaceSession = new SurfaceSession(); @@ -92,8 +89,7 @@ public final class StageTaskListenerTests extends ShellTestCase { mCallbacks, mSyncQueue, mSurfaceSession, - mIconProvider, - mStageTaskUnfoldController); + mIconProvider); mRootTask = new TestRunningTaskInfoBuilder().build(); mRootTask.parentTaskId = INVALID_TASK_ID; mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession).setName("test").build(); @@ -130,30 +126,6 @@ public final class StageTaskListenerTests extends ShellTestCase { verify(mCallbacks).onStatusChanged(eq(mRootTask.isVisible), eq(true)); } - @Test - public void testTaskAppeared_notifiesUnfoldListener() { - assumeFalse(ENABLE_SHELL_TRANSITIONS); - final ActivityManager.RunningTaskInfo task = - new TestRunningTaskInfoBuilder().setParentTaskId(mRootTask.taskId).build(); - - mStageTaskListener.onTaskAppeared(task, mSurfaceControl); - - verify(mStageTaskUnfoldController).onTaskAppeared(eq(task), eq(mSurfaceControl)); - } - - @Test - public void testTaskVanished_notifiesUnfoldListener() { - assumeFalse(ENABLE_SHELL_TRANSITIONS); - final ActivityManager.RunningTaskInfo task = - new TestRunningTaskInfoBuilder().setParentTaskId(mRootTask.taskId).build(); - mStageTaskListener.onTaskAppeared(task, mSurfaceControl); - clearInvocations(mStageTaskUnfoldController); - - mStageTaskListener.onTaskVanished(task); - - verify(mStageTaskUnfoldController).onTaskVanished(eq(task)); - } - @Test(expected = IllegalArgumentException.class) public void testUnknownTaskVanished() { final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldAnimationControllerTest.java new file mode 100644 index 000000000000..798208956180 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldAnimationControllerTest.java @@ -0,0 +1,341 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.unfold; + +import static com.android.wm.shell.unfold.UnfoldAnimationControllerTest.TestUnfoldTaskAnimator.UNSET; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.app.ActivityManager.RunningTaskInfo; +import android.app.TaskInfo; +import android.testing.AndroidTestingRunner; +import android.view.SurfaceControl; +import android.view.SurfaceControl.Transaction; + +import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.TestRunningTaskInfoBuilder; +import com.android.wm.shell.TestShellExecutor; +import com.android.wm.shell.common.TransactionPool; +import com.android.wm.shell.unfold.animation.UnfoldTaskAnimator; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.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.Predicate; + +/** + * Tests for {@link UnfoldAnimationController}. + * + * Build/Install/Run: + * atest WMShellUnitTests:UnfoldAnimationControllerTest + */ +@RunWith(AndroidTestingRunner.class) +public class UnfoldAnimationControllerTest extends ShellTestCase { + + @Mock + private TransactionPool mTransactionPool; + @Mock + private UnfoldTransitionHandler mUnfoldTransitionHandler; + @Mock + private SurfaceControl mLeash; + + private UnfoldAnimationController mUnfoldAnimationController; + + private final TestShellUnfoldProgressProvider mProgressProvider = + new TestShellUnfoldProgressProvider(); + private final TestShellExecutor mShellExecutor = new TestShellExecutor(); + + private final TestUnfoldTaskAnimator mTaskAnimator1 = new TestUnfoldTaskAnimator(); + private final TestUnfoldTaskAnimator mTaskAnimator2 = new TestUnfoldTaskAnimator(); + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mTransactionPool.acquire()).thenReturn(mock(SurfaceControl.Transaction.class)); + + final List<UnfoldTaskAnimator> animators = new ArrayList<>(); + animators.add(mTaskAnimator1); + animators.add(mTaskAnimator2); + mUnfoldAnimationController = new UnfoldAnimationController( + mTransactionPool, + mProgressProvider, + animators, + () -> Optional.of(mUnfoldTransitionHandler), + mShellExecutor + ); + } + + @Test + public void testAppearedMatchingTask_appliesUnfoldProgress() { + mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 2); + RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder() + .setWindowingMode(2).build(); + + mUnfoldAnimationController.onTaskAppeared(taskInfo, mLeash); + + mUnfoldAnimationController.onStateChangeProgress(0.5f); + assertThat(mTaskAnimator1.mLastAppliedProgress).isEqualTo(0.5f); + } + + @Test + public void testAppearedMatchingTaskTwoDifferentAnimators_appliesUnfoldProgressToBoth() { + mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 1); + mTaskAnimator2.setTaskMatcher((info) -> info.getWindowingMode() == 2); + RunningTaskInfo taskInfo1 = new TestRunningTaskInfoBuilder() + .setWindowingMode(1).build(); + RunningTaskInfo taskInfo2 = new TestRunningTaskInfoBuilder() + .setWindowingMode(2).build(); + + mUnfoldAnimationController.onTaskAppeared(taskInfo1, mLeash); + mUnfoldAnimationController.onTaskAppeared(taskInfo2, mLeash); + + mUnfoldAnimationController.onStateChangeProgress(0.5f); + assertThat(mTaskAnimator1.mLastAppliedProgress).isEqualTo(0.5f); + assertThat(mTaskAnimator2.mLastAppliedProgress).isEqualTo(0.5f); + } + + @Test + public void testAppearedNonMatchingTask_doesNotApplyUnfoldProgress() { + mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 2); + RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder() + .setWindowingMode(0).build(); + + mUnfoldAnimationController.onTaskAppeared(taskInfo, mLeash); + + mUnfoldAnimationController.onStateChangeProgress(0.5f); + assertThat(mTaskAnimator1.mLastAppliedProgress).isEqualTo(UNSET); + } + + @Test + public void testAppearedAndChangedToNonMatchingTask_doesNotApplyUnfoldProgress() { + mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 2); + RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder() + .setWindowingMode(2).build(); + + mUnfoldAnimationController.onTaskAppeared(taskInfo, mLeash); + taskInfo.configuration.windowConfiguration.setWindowingMode(0); + mUnfoldAnimationController.onTaskInfoChanged(taskInfo); + + mUnfoldAnimationController.onStateChangeProgress(0.5f); + assertThat(mTaskAnimator1.mLastAppliedProgress).isEqualTo(UNSET); + } + + @Test + public void testAppearedAndChangedToNonMatchingTaskAndBack_appliesUnfoldProgress() { + mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 2); + RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder() + .setWindowingMode(2).build(); + + mUnfoldAnimationController.onTaskAppeared(taskInfo, mLeash); + taskInfo.configuration.windowConfiguration.setWindowingMode(0); + mUnfoldAnimationController.onTaskInfoChanged(taskInfo); + taskInfo.configuration.windowConfiguration.setWindowingMode(2); + mUnfoldAnimationController.onTaskInfoChanged(taskInfo); + + mUnfoldAnimationController.onStateChangeProgress(0.5f); + assertThat(mTaskAnimator1.mLastAppliedProgress).isEqualTo(0.5f); + } + + @Test + public void testAppearedNonMatchingTaskAndChangedToMatching_appliesUnfoldProgress() { + mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 2); + RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder() + .setWindowingMode(0).build(); + + mUnfoldAnimationController.onTaskAppeared(taskInfo, mLeash); + taskInfo.configuration.windowConfiguration.setWindowingMode(2); + mUnfoldAnimationController.onTaskInfoChanged(taskInfo); + + mUnfoldAnimationController.onStateChangeProgress(0.5f); + assertThat(mTaskAnimator1.mLastAppliedProgress).isEqualTo(0.5f); + } + + @Test + public void testAppearedMatchingTaskAndChanged_appliesUnfoldProgress() { + mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 2); + RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder() + .setWindowingMode(2).build(); + + mUnfoldAnimationController.onTaskAppeared(taskInfo, mLeash); + mUnfoldAnimationController.onTaskInfoChanged(taskInfo); + + mUnfoldAnimationController.onStateChangeProgress(0.5f); + assertThat(mTaskAnimator1.mLastAppliedProgress).isEqualTo(0.5f); + } + + @Test + public void testShellTransitionRunning_doesNotApplyUnfoldProgress() { + when(mUnfoldTransitionHandler.willHandleTransition()).thenReturn(true); + mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 2); + RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder() + .setWindowingMode(2).build(); + + mUnfoldAnimationController.onTaskAppeared(taskInfo, mLeash); + + mUnfoldAnimationController.onStateChangeProgress(0.5f); + assertThat(mTaskAnimator1.mLastAppliedProgress).isEqualTo(UNSET); + } + + @Test + public void testApplicableTaskDisappeared_resetsSurface() { + mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 2); + RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder() + .setWindowingMode(2).build(); + mUnfoldAnimationController.onTaskAppeared(taskInfo, mLeash); + assertThat(mTaskAnimator1.mResetTasks).doesNotContain(taskInfo.taskId); + + mUnfoldAnimationController.onTaskVanished(taskInfo); + + assertThat(mTaskAnimator1.mResetTasks).contains(taskInfo.taskId); + } + + @Test + public void testNonApplicableTaskAppearedDisappeared_doesNotResetSurface() { + mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 2); + RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder() + .setWindowingMode(0).build(); + + mUnfoldAnimationController.onTaskAppeared(taskInfo, mLeash); + mUnfoldAnimationController.onTaskVanished(taskInfo); + + assertThat(mTaskAnimator1.mResetTasks).doesNotContain(taskInfo.taskId); + } + + @Test + public void testInit_initsAndStartsAnimators() { + mUnfoldAnimationController.init(); + + assertThat(mTaskAnimator1.mInitialized).isTrue(); + assertThat(mTaskAnimator1.mStarted).isTrue(); + } + + private static class TestShellUnfoldProgressProvider implements ShellUnfoldProgressProvider, + ShellUnfoldProgressProvider.UnfoldListener { + + private final List<UnfoldListener> mListeners = new ArrayList<>(); + + @Override + public void addListener(Executor executor, UnfoldListener listener) { + mListeners.add(listener); + } + + @Override + public void onStateChangeStarted() { + mListeners.forEach(UnfoldListener::onStateChangeStarted); + } + + @Override + public void onStateChangeProgress(float progress) { + mListeners.forEach(unfoldListener -> unfoldListener.onStateChangeProgress(progress)); + } + + @Override + public void onStateChangeFinished() { + mListeners.forEach(UnfoldListener::onStateChangeFinished); + } + } + + public static class TestUnfoldTaskAnimator implements UnfoldTaskAnimator { + + public static final float UNSET = -1f; + private Predicate<TaskInfo> mTaskMatcher = (info) -> false; + + Map<Integer, TaskInfo> mTasksMap = new HashMap<>(); + Set<Integer> mResetTasks = new HashSet<>(); + + boolean mInitialized = false; + boolean mStarted = false; + float mLastAppliedProgress = UNSET; + + @Override + public void init() { + mInitialized = true; + } + + @Override + public void start() { + mStarted = true; + } + + @Override + public void stop() { + mStarted = false; + } + + @Override + public boolean isApplicableTask(TaskInfo taskInfo) { + return mTaskMatcher.test(taskInfo); + } + + @Override + public void applyAnimationProgress(float progress, Transaction transaction) { + mLastAppliedProgress = progress; + } + + public void setTaskMatcher(Predicate<TaskInfo> taskMatcher) { + mTaskMatcher = taskMatcher; + } + + @Override + public void onTaskAppeared(TaskInfo taskInfo, SurfaceControl leash) { + mTasksMap.put(taskInfo.taskId, taskInfo); + } + + @Override + public void onTaskVanished(TaskInfo taskInfo) { + mTasksMap.remove(taskInfo.taskId); + } + + @Override + public void onTaskChanged(TaskInfo taskInfo) { + mTasksMap.put(taskInfo.taskId, taskInfo); + } + + @Override + public void resetSurface(TaskInfo taskInfo, Transaction transaction) { + mResetTasks.add(taskInfo.taskId); + } + + @Override + public void resetAllSurfaces(Transaction transaction) { + mTasksMap.values().forEach((t) -> mResetTasks.add(t.taskId)); + } + + @Override + public boolean hasActiveTasks() { + return mTasksMap.size() > 0; + } + + public List<TaskInfo> getCurrentTasks() { + return new ArrayList<>(mTasksMap.values()); + } + } +} diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp index 1e5be6c3eed7..4b0ddd2fa2ef 100644 --- a/libs/hwui/JankTracker.cpp +++ b/libs/hwui/JankTracker.cpp @@ -201,8 +201,9 @@ void JankTracker::finishFrame(FrameInfo& frame, std::unique_ptr<FrameMetricsRepo // If we are in triple buffering, we have enough buffers in queue to sustain a single frame // drop without jank, so adjust the frame interval to the deadline. if (isTripleBuffered) { - deadline += frameInterval; - frame.set(FrameInfoIndex::FrameDeadline) += frameInterval; + int64_t originalDeadlineDuration = deadline - frame[FrameInfoIndex::IntendedVsync]; + deadline = mNextFrameStartUnstuffed + originalDeadlineDuration; + frame.set(FrameInfoIndex::FrameDeadline) = deadline; } // If we hit the deadline, cool! diff --git a/libs/hwui/tests/unit/JankTrackerTests.cpp b/libs/hwui/tests/unit/JankTrackerTests.cpp index 5b397de36a86..b67e419e7d4a 100644 --- a/libs/hwui/tests/unit/JankTrackerTests.cpp +++ b/libs/hwui/tests/unit/JankTrackerTests.cpp @@ -195,3 +195,68 @@ TEST(JankTracker, doubleStuffedThenPauseThenJank) { ASSERT_EQ(3, container.get()->totalFrameCount()); ASSERT_EQ(2, container.get()->jankFrameCount()); } + +TEST(JankTracker, doubleStuffedTwoIntervalBehind) { + std::mutex mutex; + ProfileDataContainer container(mutex); + JankTracker jankTracker(&container); + std::unique_ptr<FrameMetricsReporter> reporter = std::make_unique<FrameMetricsReporter>(); + + uint64_t frameNumber = 0; + uint32_t surfaceId = 0; + + // First frame janks + FrameInfo* info = jankTracker.startFrame(); + info->set(FrameInfoIndex::IntendedVsync) = 100_ms; + info->set(FrameInfoIndex::Vsync) = 101_ms; + info->set(FrameInfoIndex::SwapBuffersCompleted) = 107_ms; + info->set(FrameInfoIndex::GpuCompleted) = 117_ms; + info->set(FrameInfoIndex::FrameCompleted) = 117_ms; + info->set(FrameInfoIndex::FrameInterval) = 16_ms; + info->set(FrameInfoIndex::FrameDeadline) = 116_ms; + jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId); + + ASSERT_EQ(1, container.get()->jankFrameCount()); + + // Second frame is long, but doesn't jank because double-stuffed. + // Second frame duration is between 1*interval ~ 2*interval + info = jankTracker.startFrame(); + info->set(FrameInfoIndex::IntendedVsync) = 116_ms; + info->set(FrameInfoIndex::Vsync) = 116_ms; + info->set(FrameInfoIndex::SwapBuffersCompleted) = 129_ms; + info->set(FrameInfoIndex::GpuCompleted) = 133_ms; + info->set(FrameInfoIndex::FrameCompleted) = 133_ms; + info->set(FrameInfoIndex::FrameInterval) = 16_ms; + info->set(FrameInfoIndex::FrameDeadline) = 132_ms; + jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId); + + ASSERT_EQ(1, container.get()->jankFrameCount()); + + // Third frame is even longer, cause a jank + // Third frame duration is between 2*interval ~ 3*interval + info = jankTracker.startFrame(); + info->set(FrameInfoIndex::IntendedVsync) = 132_ms; + info->set(FrameInfoIndex::Vsync) = 132_ms; + info->set(FrameInfoIndex::SwapBuffersCompleted) = 160_ms; + info->set(FrameInfoIndex::GpuCompleted) = 165_ms; + info->set(FrameInfoIndex::FrameCompleted) = 165_ms; + info->set(FrameInfoIndex::FrameInterval) = 16_ms; + info->set(FrameInfoIndex::FrameDeadline) = 148_ms; + jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId); + + ASSERT_EQ(2, container.get()->jankFrameCount()); + + // 4th frame is double-stuffed with a 2 * interval latency + // 4th frame duration is between 2*interval ~ 3*interval + info = jankTracker.startFrame(); + info->set(FrameInfoIndex::IntendedVsync) = 148_ms; + info->set(FrameInfoIndex::Vsync) = 148_ms; + info->set(FrameInfoIndex::SwapBuffersCompleted) = 170_ms; + info->set(FrameInfoIndex::GpuCompleted) = 181_ms; + info->set(FrameInfoIndex::FrameCompleted) = 181_ms; + info->set(FrameInfoIndex::FrameInterval) = 16_ms; + info->set(FrameInfoIndex::FrameDeadline) = 164_ms; + jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId); + + ASSERT_EQ(2, container.get()->jankFrameCount()); +} |