summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
Diffstat (limited to 'libs')
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java54
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java63
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java12
-rw-r--r--libs/WindowManager/Shell/OWNERS2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java146
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java46
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java31
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt63
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java51
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java37
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java91
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java36
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java40
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java153
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java21
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java140
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java78
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java447
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java31
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/OWNERS1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt111
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java18
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java14
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java11
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java38
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java8
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java17
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java10
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java8
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java65
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java78
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt92
-rw-r--r--libs/hwui/Properties.cpp7
-rw-r--r--libs/hwui/Properties.h4
-rw-r--r--libs/hwui/effects/GainmapRenderer.cpp253
-rw-r--r--libs/hwui/effects/GainmapRenderer.h5
-rw-r--r--libs/hwui/hwui/ImageDecoder.cpp11
-rw-r--r--libs/hwui/hwui/ImageDecoder.h2
-rw-r--r--libs/hwui/jni/ImageDecoder.cpp3
-rw-r--r--libs/hwui/jni/Shader.cpp21
-rw-r--r--libs/hwui/renderthread/CanvasContext.cpp2
-rw-r--r--libs/hwui/renderthread/RenderThread.cpp5
-rw-r--r--libs/hwui/renderthread/RenderThread.h1
-rw-r--r--libs/input/MouseCursorController.cpp58
-rw-r--r--libs/input/MouseCursorController.h6
-rw-r--r--libs/input/PointerController.cpp51
-rw-r--r--libs/input/PointerController.h4
-rw-r--r--libs/input/PointerControllerContext.h2
-rw-r--r--libs/input/tests/PointerController_test.cpp6
70 files changed, 1981 insertions, 743 deletions
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java b/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java
index 68ff806c6765..65955b1d9bcc 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java
@@ -21,6 +21,7 @@ import static androidx.window.util.ExtensionHelper.isZero;
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.graphics.Rect;
+import android.hardware.devicestate.DeviceStateManager;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -33,7 +34,8 @@ import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-/** A representation of a folding feature for both Extension and Sidecar.
+/**
+ * A representation of a folding feature for both Extension and Sidecar.
* For Sidecar this is the same as combining {@link androidx.window.sidecar.SidecarDeviceState} and
* {@link androidx.window.sidecar.SidecarDisplayFeature}. For Extensions this is the mirror of
* {@link androidx.window.extensions.layout.FoldingFeature}.
@@ -67,10 +69,11 @@ public final class CommonFoldingFeature {
public static final int COMMON_STATE_UNKNOWN = -1;
/**
- * A common state to represent a FLAT hinge. This is needed because the definitions in Sidecar
- * and Extensions do not match exactly.
+ * A common state that contains no folding features. For example, an in-folding device in the
+ * "closed" device state.
*/
- public static final int COMMON_STATE_FLAT = 3;
+ public static final int COMMON_STATE_NO_FOLDING_FEATURES = 1;
+
/**
* A common state to represent a HALF_OPENED hinge. This is needed because the definitions in
* Sidecar and Extensions do not match exactly.
@@ -78,9 +81,27 @@ public final class CommonFoldingFeature {
public static final int COMMON_STATE_HALF_OPENED = 2;
/**
- * The possible states for a folding hinge.
+ * A common state to represent a FLAT hinge. This is needed because the definitions in Sidecar
+ * and Extensions do not match exactly.
+ */
+ public static final int COMMON_STATE_FLAT = 3;
+
+ /**
+ * A common state where the hinge state should be derived using the base state from
+ * {@link DeviceStateManager.DeviceStateCallback#onBaseStateChanged(int)} instead of the
+ * emulated state. This is an internal state and must not be passed to clients.
*/
- @IntDef({COMMON_STATE_UNKNOWN, COMMON_STATE_FLAT, COMMON_STATE_HALF_OPENED})
+ public static final int COMMON_STATE_USE_BASE_STATE = 1000;
+
+ /**
+ * The possible states for a folding hinge. Common in this context means normalized between
+ * extensions and sidecar.
+ */
+ @IntDef({COMMON_STATE_UNKNOWN,
+ COMMON_STATE_NO_FOLDING_FEATURES,
+ COMMON_STATE_HALF_OPENED,
+ COMMON_STATE_FLAT,
+ COMMON_STATE_USE_BASE_STATE})
@Retention(RetentionPolicy.SOURCE)
public @interface State {
}
@@ -167,7 +188,7 @@ public final class CommonFoldingFeature {
}
String stateString = featureMatcher.group(6);
stateString = stateString == null ? "" : stateString;
- final int state;
+ @State final int state;
switch (stateString) {
case PATTERN_STATE_FLAT:
state = COMMON_STATE_FLAT;
@@ -191,8 +212,8 @@ public final class CommonFoldingFeature {
@NonNull
private final Rect mRect;
- CommonFoldingFeature(int type, int state, @NonNull Rect rect) {
- assertValidState(state);
+ CommonFoldingFeature(int type, @State int state, @NonNull Rect rect) {
+ assertReportableState(state);
this.mType = type;
this.mState = state;
if (rect.width() == 0 && rect.height() == 0) {
@@ -231,13 +252,22 @@ public final class CommonFoldingFeature {
}
@Override
+ public String toString() {
+ return "CommonFoldingFeature=[Type: " + mType + ", state: " + mState + "]";
+ }
+
+ @Override
public int hashCode() {
return Objects.hash(mType, mState, mRect);
}
- private static void assertValidState(@Nullable Integer state) {
- if (state != null && state != COMMON_STATE_FLAT
- && state != COMMON_STATE_HALF_OPENED && state != COMMON_STATE_UNKNOWN) {
+ /**
+ * Checks if the provided folding feature state should be reported to clients. See
+ * {@link androidx.window.extensions.layout.FoldingFeature}
+ */
+ private static void assertReportableState(@State int state) {
+ if (state != COMMON_STATE_FLAT && state != COMMON_STATE_HALF_OPENED
+ && state != COMMON_STATE_UNKNOWN) {
throw new IllegalArgumentException("Invalid state: " + state
+ "must be either COMMON_STATE_FLAT or COMMON_STATE_HALF_OPENED");
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
index 0bdf98c8680f..66f27f517ab3 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
@@ -19,6 +19,7 @@ package androidx.window.common;
import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_UNKNOWN;
+import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_USE_BASE_STATE;
import static androidx.window.common.CommonFoldingFeature.parseListFromString;
import android.annotation.NonNull;
@@ -52,13 +53,54 @@ public final class DeviceStateManagerFoldingFeatureProducer
DeviceStateManagerFoldingFeatureProducer.class.getSimpleName();
private static final boolean DEBUG = false;
+ /**
+ * Emulated device state {@link DeviceStateManager.DeviceStateCallback#onStateChanged(int)} to
+ * {@link CommonFoldingFeature.State} map.
+ */
private final SparseIntArray mDeviceStateToPostureMap = new SparseIntArray();
+ /**
+ * Emulated device state received via
+ * {@link DeviceStateManager.DeviceStateCallback#onStateChanged(int)}.
+ * "Emulated" states differ from "base" state in the sense that they may not correspond 1:1 with
+ * physical device states. They represent the state of the device when various software
+ * features and APIs are applied. The emulated states generally consist of all "base" states,
+ * but may have additional states such as "concurrent" or "rear display". Concurrent mode for
+ * example is activated via public API and can be active in both the "open" and "half folded"
+ * device states.
+ */
private int mCurrentDeviceState = INVALID_DEVICE_STATE;
+ /**
+ * Base device state received via
+ * {@link DeviceStateManager.DeviceStateCallback#onBaseStateChanged(int)}.
+ * "Base" in this context means the "physical" state of the device.
+ */
+ private int mCurrentBaseDeviceState = INVALID_DEVICE_STATE;
+
@NonNull
private final BaseDataProducer<String> mRawFoldSupplier;
+ private final DeviceStateCallback mDeviceStateCallback = new DeviceStateCallback() {
+ @Override
+ public void onStateChanged(int state) {
+ mCurrentDeviceState = state;
+ mRawFoldSupplier.getData(DeviceStateManagerFoldingFeatureProducer
+ .this::notifyFoldingFeatureChange);
+ }
+
+ @Override
+ public void onBaseStateChanged(int state) {
+ mCurrentBaseDeviceState = state;
+
+ if (mDeviceStateToPostureMap.get(mCurrentDeviceState)
+ == COMMON_STATE_USE_BASE_STATE) {
+ mRawFoldSupplier.getData(DeviceStateManagerFoldingFeatureProducer
+ .this::notifyFoldingFeatureChange);
+ }
+ }
+ };
+
public DeviceStateManagerFoldingFeatureProducer(@NonNull Context context,
@NonNull BaseDataProducer<String> rawFoldSupplier) {
mRawFoldSupplier = rawFoldSupplier;
@@ -92,12 +134,8 @@ public final class DeviceStateManagerFoldingFeatureProducer
}
if (mDeviceStateToPostureMap.size() > 0) {
- DeviceStateCallback deviceStateCallback = (state) -> {
- mCurrentDeviceState = state;
- mRawFoldSupplier.getData(this::notifyFoldingFeatureChange);
- };
Objects.requireNonNull(context.getSystemService(DeviceStateManager.class))
- .registerCallback(context.getMainExecutor(), deviceStateCallback);
+ .registerCallback(context.getMainExecutor(), mDeviceStateCallback);
}
}
@@ -178,11 +216,18 @@ public final class DeviceStateManagerFoldingFeatureProducer
}
private List<CommonFoldingFeature> calculateFoldingFeature(String displayFeaturesString) {
- final int globalHingeState = globalHingeState();
- return parseListFromString(displayFeaturesString, globalHingeState);
+ return parseListFromString(displayFeaturesString, currentHingeState());
}
- private int globalHingeState() {
- return mDeviceStateToPostureMap.get(mCurrentDeviceState, COMMON_STATE_UNKNOWN);
+ @CommonFoldingFeature.State
+ private int currentHingeState() {
+ @CommonFoldingFeature.State
+ int posture = mDeviceStateToPostureMap.get(mCurrentDeviceState, COMMON_STATE_UNKNOWN);
+
+ if (posture == CommonFoldingFeature.COMMON_STATE_USE_BASE_STATE) {
+ posture = mDeviceStateToPostureMap.get(mCurrentBaseDeviceState, COMMON_STATE_UNKNOWN);
+ }
+
+ return posture;
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
index 274dcaee5a43..575b0cea78f7 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
@@ -263,8 +263,10 @@ public class WindowAreaComponentImpl implements WindowAreaComponent,
return;
}
@WindowAreaStatus int currentStatus = getCurrentRearDisplayPresentationModeStatus();
+ DisplayMetrics metrics =
+ currentStatus == STATUS_UNSUPPORTED ? null : getRearDisplayMetrics();
consumer.accept(
- new RearDisplayPresentationStatus(currentStatus, getRearDisplayMetrics()));
+ new RearDisplayPresentationStatus(currentStatus, metrics));
}
}
@@ -408,6 +410,10 @@ public class WindowAreaComponentImpl implements WindowAreaComponent,
@GuardedBy("mLock")
private int getCurrentRearDisplayModeStatus() {
+ if (mRearDisplayState == INVALID_DEVICE_STATE) {
+ return WindowAreaComponent.STATUS_UNSUPPORTED;
+ }
+
if (mRearDisplaySessionStatus == WindowAreaComponent.SESSION_STATE_ACTIVE
|| !ArrayUtils.contains(mCurrentSupportedDeviceStates, mRearDisplayState)
|| isRearDisplayActive()) {
@@ -441,6 +447,10 @@ public class WindowAreaComponentImpl implements WindowAreaComponent,
@GuardedBy("mLock")
private int getCurrentRearDisplayPresentationModeStatus() {
+ if (mConcurrentDisplayState == INVALID_DEVICE_STATE) {
+ return WindowAreaComponent.STATUS_UNSUPPORTED;
+ }
+
if (mCurrentDeviceState == mConcurrentDisplayState
|| !ArrayUtils.contains(mCurrentSupportedDeviceStates, mConcurrentDisplayState)
|| isDeviceFolded()) {
diff --git a/libs/WindowManager/Shell/OWNERS b/libs/WindowManager/Shell/OWNERS
index 4b125904004a..852edef544b8 100644
--- a/libs/WindowManager/Shell/OWNERS
+++ b/libs/WindowManager/Shell/OWNERS
@@ -1,4 +1,4 @@
xutan@google.com
# Give submodule owners in shell resource approval
-per-file res*/*/*.xml = hwwang@google.com, lbill@google.com, madym@google.com
+per-file res*/*/*.xml = hwwang@google.com, jorgegil@google.com, lbill@google.com, madym@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 852fae695046..48fe65d3ce59 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
@@ -139,6 +139,30 @@ public class BubbleController implements ConfigurationChangeListener {
private static final boolean BUBBLE_BAR_ENABLED =
SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false);
+
+ /**
+ * Common interface to send updates to bubble views.
+ */
+ public interface BubbleViewCallback {
+ /** Called when the provided bubble should be removed. */
+ void removeBubble(Bubble removedBubble);
+ /** Called when the provided bubble should be added. */
+ void addBubble(Bubble addedBubble);
+ /** Called when the provided bubble should be updated. */
+ void updateBubble(Bubble updatedBubble);
+ /** Called when the provided bubble should be selected. */
+ void selectionChanged(BubbleViewProvider selectedBubble);
+ /** Called when the provided bubble's suppression state has changed. */
+ void suppressionChanged(Bubble bubble, boolean isSuppressed);
+ /** Called when the expansion state of bubbles has changed. */
+ void expansionChanged(boolean isExpanded);
+ /**
+ * Called when the order of the bubble list has changed. Depending on the expanded state
+ * the pointer might need to be updated.
+ */
+ void bubbleOrderChanged(List<Bubble> bubbleOrder, boolean updatePointer);
+ }
+
private final Context mContext;
private final BubblesImpl mImpl = new BubblesImpl();
private Bubbles.BubbleExpandListener mExpandListener;
@@ -162,7 +186,6 @@ public class BubbleController implements ConfigurationChangeListener {
// Used to post to main UI thread
private final ShellExecutor mMainExecutor;
private final Handler mMainHandler;
-
private final ShellExecutor mBackgroundExecutor;
private BubbleLogger mLogger;
@@ -1005,19 +1028,21 @@ public class BubbleController implements ConfigurationChangeListener {
* the bubble or bubble stack.
*
* Some notes:
- * - Only one app bubble is supported at a time
+ * - Only one app bubble is supported at a time, regardless of users. Multi-users support is
+ * tracked in b/273533235.
* - Calling this method with a different intent than the existing app bubble will do nothing
*
* @param intent the intent to display in the bubble expanded view.
+ * @param user the {@link UserHandle} of the user to start this activity for.
*/
- public void showOrHideAppBubble(Intent intent) {
+ public void showOrHideAppBubble(Intent intent, UserHandle user) {
if (intent == null || intent.getPackage() == null) {
Log.w(TAG, "App bubble failed to show, invalid intent: " + intent
+ ((intent != null) ? " with package: " + intent.getPackage() : " "));
return;
}
- PackageManager packageManager = getPackageManagerForUser(mContext, mCurrentUserId);
+ PackageManager packageManager = getPackageManagerForUser(mContext, user.getIdentifier());
if (!isResizableActivity(intent, packageManager, KEY_APP_BUBBLE)) return;
Bubble existingAppBubble = mBubbleData.getBubbleInStackWithKey(KEY_APP_BUBBLE);
@@ -1038,7 +1063,7 @@ public class BubbleController implements ConfigurationChangeListener {
}
} else {
// App bubble does not exist, lets add and expand it
- Bubble b = new Bubble(intent, UserHandle.of(mCurrentUserId), mMainExecutor);
+ Bubble b = new Bubble(intent, user, mMainExecutor);
b.setShouldAutoExpand(true);
inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false);
}
@@ -1320,6 +1345,58 @@ public class BubbleController implements ConfigurationChangeListener {
});
}
+ private final BubbleViewCallback mBubbleViewCallback = new BubbleViewCallback() {
+ @Override
+ public void removeBubble(Bubble removedBubble) {
+ if (mStackView != null) {
+ mStackView.removeBubble(removedBubble);
+ }
+ }
+
+ @Override
+ public void addBubble(Bubble addedBubble) {
+ if (mStackView != null) {
+ mStackView.addBubble(addedBubble);
+ }
+ }
+
+ @Override
+ public void updateBubble(Bubble updatedBubble) {
+ if (mStackView != null) {
+ mStackView.updateBubble(updatedBubble);
+ }
+ }
+
+ @Override
+ public void bubbleOrderChanged(List<Bubble> bubbleOrder, boolean updatePointer) {
+ if (mStackView != null) {
+ mStackView.updateBubbleOrder(bubbleOrder, updatePointer);
+ }
+ }
+
+ @Override
+ public void suppressionChanged(Bubble bubble, boolean isSuppressed) {
+ if (mStackView != null) {
+ mStackView.setBubbleSuppressed(bubble, isSuppressed);
+ }
+ }
+
+ @Override
+ public void expansionChanged(boolean isExpanded) {
+ if (mStackView != null) {
+ mStackView.setExpanded(isExpanded);
+ }
+ }
+
+ @Override
+ public void selectionChanged(BubbleViewProvider selectedBubble) {
+ if (mStackView != null) {
+ mStackView.setSelectedBubble(selectedBubble);
+ }
+
+ }
+ };
+
@SuppressWarnings("FieldCanBeLocal")
private final BubbleData.Listener mBubbleDataListener = new BubbleData.Listener() {
@@ -1342,7 +1419,8 @@ public class BubbleController implements ConfigurationChangeListener {
// Lazy load overflow bubbles from disk
loadOverflowBubblesFromDisk();
- mStackView.updateOverflowButtonDot();
+ // If bubbles in the overflow have a dot, make sure the overflow shows a dot
+ updateOverflowButtonDot();
// Update bubbles in overflow.
if (mOverflowListener != null) {
@@ -1357,9 +1435,7 @@ public class BubbleController implements ConfigurationChangeListener {
final Bubble bubble = removed.first;
@Bubbles.DismissReason final int reason = removed.second;
- if (mStackView != null) {
- mStackView.removeBubble(bubble);
- }
+ mBubbleViewCallback.removeBubble(bubble);
// Leave the notification in place if we're dismissing due to user switching, or
// because DND is suppressing the bubble. In both of those cases, we need to be able
@@ -1389,49 +1465,47 @@ public class BubbleController implements ConfigurationChangeListener {
}
mDataRepository.removeBubbles(mCurrentUserId, bubblesToBeRemovedFromRepository);
- if (update.addedBubble != null && mStackView != null) {
+ if (update.addedBubble != null) {
mDataRepository.addBubble(mCurrentUserId, update.addedBubble);
- mStackView.addBubble(update.addedBubble);
+ mBubbleViewCallback.addBubble(update.addedBubble);
}
- if (update.updatedBubble != null && mStackView != null) {
- mStackView.updateBubble(update.updatedBubble);
+ if (update.updatedBubble != null) {
+ mBubbleViewCallback.updateBubble(update.updatedBubble);
}
- if (update.suppressedBubble != null && mStackView != null) {
- mStackView.setBubbleSuppressed(update.suppressedBubble, true);
+ if (update.suppressedBubble != null) {
+ mBubbleViewCallback.suppressionChanged(update.suppressedBubble, true);
}
- if (update.unsuppressedBubble != null && mStackView != null) {
- mStackView.setBubbleSuppressed(update.unsuppressedBubble, false);
+ if (update.unsuppressedBubble != null) {
+ mBubbleViewCallback.suppressionChanged(update.unsuppressedBubble, false);
}
boolean collapseStack = update.expandedChanged && !update.expanded;
// At this point, the correct bubbles are inflated in the stack.
// Make sure the order in bubble data is reflected in bubble row.
- if (update.orderChanged && mStackView != null) {
+ if (update.orderChanged) {
mDataRepository.addBubbles(mCurrentUserId, update.bubbles);
// if the stack is going to be collapsed, do not update pointer position
// after reordering
- mStackView.updateBubbleOrder(update.bubbles, !collapseStack);
+ mBubbleViewCallback.bubbleOrderChanged(update.bubbles, !collapseStack);
}
if (collapseStack) {
- mStackView.setExpanded(false);
+ mBubbleViewCallback.expansionChanged(/* expanded= */ false);
mSysuiProxy.requestNotificationShadeTopUi(false, TAG);
}
- if (update.selectionChanged && mStackView != null) {
- mStackView.setSelectedBubble(update.selectedBubble);
+ if (update.selectionChanged) {
+ mBubbleViewCallback.selectionChanged(update.selectedBubble);
}
// Expanding? Apply this last.
if (update.expandedChanged && update.expanded) {
- if (mStackView != null) {
- mStackView.setExpanded(true);
- mSysuiProxy.requestNotificationShadeTopUi(true, TAG);
- }
+ mBubbleViewCallback.expansionChanged(/* expanded= */ true);
+ mSysuiProxy.requestNotificationShadeTopUi(true, TAG);
}
mSysuiProxy.notifyInvalidateNotifications("BubbleData.Listener.applyUpdate");
@@ -1442,6 +1516,19 @@ public class BubbleController implements ConfigurationChangeListener {
}
};
+ private void updateOverflowButtonDot() {
+ BubbleOverflow overflow = mBubbleData.getOverflow();
+ if (overflow == null) return;
+
+ for (Bubble b : mBubbleData.getOverflowBubbles()) {
+ if (b.showDot()) {
+ overflow.setShowDot(true);
+ return;
+ }
+ }
+ overflow.setShowDot(false);
+ }
+
private boolean handleDismissalInterception(BubbleEntry entry,
@Nullable List<BubbleEntry> children, IntConsumer removeCallback) {
if (isSummaryOfBubbles(entry)) {
@@ -1784,10 +1871,9 @@ public class BubbleController implements ConfigurationChangeListener {
}
@Override
- public void showOrHideAppBubble(Intent intent) {
- mMainExecutor.execute(() -> {
- BubbleController.this.showOrHideAppBubble(intent);
- });
+ public void showOrHideAppBubble(Intent intent, UserHandle user) {
+ mMainExecutor.execute(
+ () -> BubbleController.this.showOrHideAppBubble(intent, user));
}
@Override
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 ecddbda0fff4..e5a4362e5bf0 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
@@ -236,12 +236,17 @@ public class BubbleExpandedView extends LinearLayout {
fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
if (mBubble.isAppBubble()) {
- PendingIntent pi = PendingIntent.getActivity(mContext, 0,
+ Context context =
+ mContext.createContextAsUser(
+ mBubble.getUser(), Context.CONTEXT_RESTRICTED);
+ PendingIntent pi = PendingIntent.getActivity(
+ context,
+ /* requestCode= */ 0,
mBubble.getAppBubbleIntent()
.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT)
.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK),
PendingIntent.FLAG_IMMUTABLE,
- null);
+ /* options= */ null);
mTaskView.startActivity(pi, /* fillInIntent= */ null, options,
launchBounds);
} else if (!mIsOverflow && mBubble.hasMetadataShortcutId()) {
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 7d71089ef4fa..0b947c8b9b08 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
@@ -1359,16 +1359,6 @@ public class BubbleStackView extends FrameLayout
updateOverflowVisibility();
}
- void updateOverflowButtonDot() {
- for (Bubble b : mBubbleData.getOverflowBubbles()) {
- if (b.showDot()) {
- mBubbleOverflow.setShowDot(true);
- return;
- }
- }
- mBubbleOverflow.setShowDot(false);
- }
-
/**
* Handle theme changes.
*/
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 4c0a93fb9355..5555bec6a28e 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
@@ -129,12 +129,14 @@ public interface Bubbles {
* the bubble or bubble stack.
*
* Some notes:
- * - Only one app bubble is supported at a time
+ * - Only one app bubble is supported at a time, regardless of users. Multi-users support is
+ * tracked in b/273533235.
* - Calling this method with a different intent than the existing app bubble will do nothing
*
* @param intent the intent to display in the bubble expanded view.
+ * @param user the {@link UserHandle} of the user to start this activity for.
*/
- void showOrHideAppBubble(Intent intent);
+ void showOrHideAppBubble(Intent intent, UserHandle user);
/** @return true if the specified {@code taskId} corresponds to app bubble's taskId. */
boolean isAppBubbleTaskId(int taskId);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
index bf226283ae54..cb1a6e7ace6b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
@@ -22,10 +22,12 @@ import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE
import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_FOLDABLE;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.app.WindowConfiguration;
import android.content.Context;
import android.content.res.Configuration;
+import android.os.SystemProperties;
import android.util.ArraySet;
import android.view.Surface;
@@ -34,6 +36,8 @@ import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ShellInit;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@@ -49,8 +53,34 @@ import java.util.Set;
public class TabletopModeController implements
DevicePostureController.OnDevicePostureChangedListener,
DisplayController.OnDisplaysChangedListener {
+ /**
+ * When {@code true}, floating windows like PiP would auto move to the position
+ * specified by {@link #PREFER_TOP_HALF_IN_TABLETOP} when in tabletop mode.
+ */
+ private static final boolean ENABLE_MOVE_FLOATING_WINDOW_IN_TABLETOP =
+ SystemProperties.getBoolean(
+ "persist.wm.debug.enable_move_floating_window_in_tabletop", false);
+
+ /**
+ * Prefer the {@link #PREFERRED_TABLETOP_HALF_TOP} if this flag is enabled,
+ * {@link #PREFERRED_TABLETOP_HALF_BOTTOM} otherwise.
+ * See also {@link #getPreferredHalfInTabletopMode()}.
+ */
+ private static final boolean PREFER_TOP_HALF_IN_TABLETOP =
+ SystemProperties.getBoolean("persist.wm.debug.prefer_top_half_in_tabletop", true);
+
private static final long TABLETOP_MODE_DELAY_MILLIS = 1_000;
+ @IntDef(prefix = {"PREFERRED_TABLETOP_HALF_"}, value = {
+ PREFERRED_TABLETOP_HALF_TOP,
+ PREFERRED_TABLETOP_HALF_BOTTOM
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PreferredTabletopHalf {}
+
+ public static final int PREFERRED_TABLETOP_HALF_TOP = 0;
+ public static final int PREFERRED_TABLETOP_HALF_BOTTOM = 1;
+
private final Context mContext;
private final DevicePostureController mDevicePostureController;
@@ -132,6 +162,22 @@ public class TabletopModeController implements
}
}
+ /**
+ * @return {@code true} if floating windows like PiP would auto move to the position
+ * specified by {@link #getPreferredHalfInTabletopMode()} when in tabletop mode.
+ */
+ public boolean enableMoveFloatingWindowInTabletop() {
+ return ENABLE_MOVE_FLOATING_WINDOW_IN_TABLETOP;
+ }
+
+ /** @return Preferred half for floating windows like PiP when in tabletop mode. */
+ @PreferredTabletopHalf
+ public int getPreferredHalfInTabletopMode() {
+ return PREFER_TOP_HALF_IN_TABLETOP
+ ? PREFERRED_TABLETOP_HALF_TOP
+ : PREFERRED_TABLETOP_HALF_BOTTOM;
+ }
+
/** Register {@link OnTabletopModeChangedListener} to listen for tabletop mode change. */
public void registerOnTabletopModeChangedListener(
@NonNull OnTabletopModeChangedListener listener) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
index ab968563854c..3d1ed87f1305 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
@@ -31,6 +31,7 @@ import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipAppOpsListener;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipSnapAlgorithm;
@@ -70,7 +71,7 @@ public abstract class TvPipModule {
ShellInit shellInit,
ShellController shellController,
TvPipBoundsState tvPipBoundsState,
- PipSizeSpecHandler pipSizeSpecHandler,
+ PipDisplayLayoutState pipDisplayLayoutState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
TvPipBoundsController tvPipBoundsController,
PipAppOpsListener pipAppOpsListener,
@@ -91,7 +92,7 @@ public abstract class TvPipModule {
shellInit,
shellController,
tvPipBoundsState,
- pipSizeSpecHandler,
+ pipDisplayLayoutState,
tvPipBoundsAlgorithm,
tvPipBoundsController,
pipAppOpsListener,
@@ -141,14 +142,15 @@ public abstract class TvPipModule {
@WMSingleton
@Provides
static TvPipBoundsState provideTvPipBoundsState(Context context,
- PipSizeSpecHandler pipSizeSpecHandler) {
- return new TvPipBoundsState(context, pipSizeSpecHandler);
+ PipSizeSpecHandler pipSizeSpecHandler, PipDisplayLayoutState pipDisplayLayoutState) {
+ return new TvPipBoundsState(context, pipSizeSpecHandler, pipDisplayLayoutState);
}
@WMSingleton
@Provides
- static PipSizeSpecHandler providePipSizeSpecHelper(Context context) {
- return new PipSizeSpecHandler(context);
+ static PipSizeSpecHandler providePipSizeSpecHelper(Context context,
+ PipDisplayLayoutState pipDisplayLayoutState) {
+ return new PipSizeSpecHandler(context, pipDisplayLayoutState);
}
// Handler needed for loadDrawableAsync() in PipControlsViewController
@@ -203,7 +205,7 @@ public abstract class TvPipModule {
TvPipMenuController tvPipMenuController,
SyncTransactionQueue syncTransactionQueue,
TvPipBoundsState tvPipBoundsState,
- PipSizeSpecHandler pipSizeSpecHandler,
+ PipDisplayLayoutState pipDisplayLayoutState,
PipTransitionState pipTransitionState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
PipAnimationController pipAnimationController,
@@ -215,7 +217,7 @@ public abstract class TvPipModule {
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
@ShellMainThread ShellExecutor mainExecutor) {
return new TvPipTaskOrganizer(context,
- syncTransactionQueue, pipTransitionState, tvPipBoundsState, pipSizeSpecHandler,
+ syncTransactionQueue, pipTransitionState, tvPipBoundsState, pipDisplayLayoutState,
tvPipBoundsAlgorithm, tvPipMenuController, pipAnimationController,
pipSurfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder,
splitScreenControllerOptional, displayController, pipUiEventLogger,
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 948bf2d100f9..7a83d101578f 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
@@ -45,6 +45,7 @@ import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.common.TabletopModeController;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
@@ -65,6 +66,7 @@ import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipAppOpsListener;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipSnapAlgorithm;
@@ -344,6 +346,7 @@ public abstract class WMShellModule {
PhonePipKeepClearAlgorithm pipKeepClearAlgorithm,
PipBoundsState pipBoundsState,
PipSizeSpecHandler pipSizeSpecHandler,
+ PipDisplayLayoutState pipDisplayLayoutState,
PipMotionHelper pipMotionHelper,
PipMediaController pipMediaController,
PhonePipMenuController phonePipMenuController,
@@ -355,23 +358,24 @@ public abstract class WMShellModule {
TaskStackListenerImpl taskStackListener,
PipParamsChangedForwarder pipParamsChangedForwarder,
DisplayInsetsController displayInsetsController,
+ TabletopModeController pipTabletopController,
Optional<OneHandedController> oneHandedController,
@ShellMainThread ShellExecutor mainExecutor) {
return Optional.ofNullable(PipController.create(
context, shellInit, shellCommandHandler, shellController,
displayController, pipAnimationController, pipAppOpsListener, pipBoundsAlgorithm,
- pipKeepClearAlgorithm, pipBoundsState, pipSizeSpecHandler, pipMotionHelper,
- pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTransitionState,
- pipTouchHandler, pipTransitionController, windowManagerShellWrapper,
- taskStackListener, pipParamsChangedForwarder, displayInsetsController,
- oneHandedController, mainExecutor));
+ pipKeepClearAlgorithm, pipBoundsState, pipSizeSpecHandler, pipDisplayLayoutState,
+ pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer,
+ pipTransitionState, pipTouchHandler, pipTransitionController,
+ windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
+ displayInsetsController, pipTabletopController, oneHandedController, mainExecutor));
}
@WMSingleton
@Provides
static PipBoundsState providePipBoundsState(Context context,
- PipSizeSpecHandler pipSizeSpecHandler) {
- return new PipBoundsState(context, pipSizeSpecHandler);
+ PipSizeSpecHandler pipSizeSpecHandler, PipDisplayLayoutState pipDisplayLayoutState) {
+ return new PipBoundsState(context, pipSizeSpecHandler, pipDisplayLayoutState);
}
@WMSingleton
@@ -388,8 +392,9 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
- static PipSizeSpecHandler providePipSizeSpecHelper(Context context) {
- return new PipSizeSpecHandler(context);
+ static PipSizeSpecHandler providePipSizeSpecHelper(Context context,
+ PipDisplayLayoutState pipDisplayLayoutState) {
+ return new PipSizeSpecHandler(context, pipDisplayLayoutState);
}
@WMSingleton
@@ -446,7 +451,7 @@ public abstract class WMShellModule {
SyncTransactionQueue syncTransactionQueue,
PipTransitionState pipTransitionState,
PipBoundsState pipBoundsState,
- PipSizeSpecHandler pipSizeSpecHandler,
+ PipDisplayLayoutState pipDisplayLayoutState,
PipBoundsAlgorithm pipBoundsAlgorithm,
PhonePipMenuController menuPhoneController,
PipAnimationController pipAnimationController,
@@ -458,7 +463,7 @@ public abstract class WMShellModule {
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
@ShellMainThread ShellExecutor mainExecutor) {
return new PipTaskOrganizer(context,
- syncTransactionQueue, pipTransitionState, pipBoundsState, pipSizeSpecHandler,
+ syncTransactionQueue, pipTransitionState, pipBoundsState, pipDisplayLayoutState,
pipBoundsAlgorithm, menuPhoneController, pipAnimationController,
pipSurfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder,
splitScreenControllerOptional, displayController, pipUiEventLogger,
@@ -477,12 +482,12 @@ public abstract class WMShellModule {
static PipTransitionController providePipTransitionController(Context context,
ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, Transitions transitions,
PipAnimationController pipAnimationController, PipBoundsAlgorithm pipBoundsAlgorithm,
- PipBoundsState pipBoundsState, PipSizeSpecHandler pipSizeSpecHandler,
+ PipBoundsState pipBoundsState, PipDisplayLayoutState pipDisplayLayoutState,
PipTransitionState pipTransitionState, PhonePipMenuController pipMenuController,
PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
Optional<SplitScreenController> splitScreenOptional) {
return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
- pipBoundsState, pipSizeSpecHandler, pipTransitionState, pipMenuController,
+ pipBoundsState, pipDisplayLayoutState, pipTransitionState, pipMenuController,
pipBoundsAlgorithm, pipAnimationController, pipSurfaceTransactionHelper,
splitScreenOptional);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 73a740381090..31c5e33f21e3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -25,6 +25,7 @@ import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.app.WindowConfiguration.WindowingMode
import android.content.Context
import android.os.IBinder
+import android.os.SystemProperties
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
import android.view.WindowManager.TRANSIT_NONE
@@ -32,6 +33,7 @@ import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.TransitionInfo
import android.window.TransitionRequestInfo
+import android.window.WindowContainerToken
import android.window.WindowContainerTransaction
import androidx.annotation.BinderThread
import com.android.internal.protolog.common.ProtoLog
@@ -115,10 +117,7 @@ class DesktopTasksController(
val wct = WindowContainerTransaction()
// Bring other apps to front first
bringDesktopAppsToFront(wct)
-
- wct.setWindowingMode(task.getToken(), WINDOWING_MODE_FREEFORM)
- wct.reorder(task.getToken(), true /* onTop */)
-
+ addMoveToDesktopChanges(wct, task.token)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
} else {
@@ -136,8 +135,7 @@ class DesktopTasksController(
ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToFullscreen: %d", task.taskId)
val wct = WindowContainerTransaction()
- wct.setWindowingMode(task.getToken(), WINDOWING_MODE_FULLSCREEN)
- wct.setBounds(task.getToken(), null)
+ addMoveToFullscreenChanges(wct, task.token)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
} else {
@@ -234,8 +232,8 @@ class DesktopTasksController(
" taskId=%d",
task.taskId
)
- return WindowContainerTransaction().apply {
- setWindowingMode(task.token, WINDOWING_MODE_FREEFORM)
+ return WindowContainerTransaction().also { wct ->
+ addMoveToDesktopChanges(wct, task.token)
}
}
}
@@ -251,15 +249,44 @@ class DesktopTasksController(
" taskId=%d",
task.taskId
)
- return WindowContainerTransaction().apply {
- setWindowingMode(task.token, WINDOWING_MODE_FULLSCREEN)
- setBounds(task.token, null)
+ return WindowContainerTransaction().also { wct ->
+ addMoveToFullscreenChanges(wct, task.token)
}
}
}
return null
}
+ private fun addMoveToDesktopChanges(
+ wct: WindowContainerTransaction,
+ token: WindowContainerToken
+ ) {
+ wct.setWindowingMode(token, WINDOWING_MODE_FREEFORM)
+ wct.reorder(token, true /* onTop */)
+ if (isDesktopDensityOverrideSet()) {
+ wct.setDensityDpi(token, getDesktopDensityDpi())
+ }
+ }
+
+ private fun addMoveToFullscreenChanges(
+ wct: WindowContainerTransaction,
+ token: WindowContainerToken
+ ) {
+ wct.setWindowingMode(token, WINDOWING_MODE_FULLSCREEN)
+ wct.setBounds(token, null)
+ if (isDesktopDensityOverrideSet()) {
+ wct.setDensityDpi(token, getFullscreenDensityDpi())
+ }
+ }
+
+ private fun getFullscreenDensityDpi(): Int {
+ return context.resources.displayMetrics.densityDpi
+ }
+
+ private fun getDesktopDensityDpi(): Int {
+ return DESKTOP_DENSITY_OVERRIDE
+ }
+
/** Creates a new instance of the external interface to pass to another process. */
private fun createExternalInterface(): ExternalInterfaceBinder {
return IDesktopModeImpl(this)
@@ -318,4 +345,18 @@ class DesktopTasksController(
return result[0]
}
}
+
+ companion object {
+ private val DESKTOP_DENSITY_OVERRIDE =
+ SystemProperties.getInt("persist.wm.debug.desktop_mode_density", 0)
+ private val DESKTOP_DENSITY_ALLOWED_RANGE = (100..1000)
+
+ /**
+ * Check if desktop density override is enabled
+ */
+ @JvmStatic
+ fun isDesktopDensityOverrideSet(): Boolean {
+ return DESKTOP_DENSITY_OVERRIDE in DESKTOP_DENSITY_ALLOWED_RANGE
+ }
+ }
}
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 d094c229e0f8..998728d65e6a 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
@@ -129,6 +129,7 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
final State state = mTasks.get(taskInfo.taskId);
final Point oldPositionInParent = state.mTaskInfo.positionInParent;
+ boolean oldVisible = state.mTaskInfo.isVisible;
if (mWindowDecorViewModelOptional.isPresent()) {
mWindowDecorViewModelOptional.get().onTaskInfoChanged(taskInfo);
@@ -138,12 +139,18 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
updateRecentsForVisibleFullscreenTask(taskInfo);
final Point positionInParent = state.mTaskInfo.positionInParent;
- if (!oldPositionInParent.equals(state.mTaskInfo.positionInParent)) {
+ boolean positionInParentChanged = !oldPositionInParent.equals(positionInParent);
+ boolean becameVisible = !oldVisible && state.mTaskInfo.isVisible;
+
+ if (becameVisible || positionInParentChanged) {
mSyncQueue.runInSync(t -> {
if (!state.mLeash.isValid()) {
// Task vanished before sync completion
return;
}
+ if (becameVisible) {
+ t.show(state.mLeash);
+ }
t.setPosition(state.mLeash, positionInParent.x, positionInParent.y);
});
}
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 9796e4c29352..2d84d211e30a 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
@@ -267,6 +267,11 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
mLaunchRootTask = taskInfo;
}
+ if (mHomeTask != null && mHomeTask.taskId == taskInfo.taskId
+ && !taskInfo.equals(mHomeTask)) {
+ mHomeTask = taskInfo;
+ }
+
super.onTaskInfoChanged(taskInfo);
}
@@ -376,6 +381,7 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
final WindowContainerTransaction wct = getWindowContainerTransaction();
final Rect taskBounds = calculateBounds();
wct.setBounds(mLaunchRootTask.token, taskBounds);
+ wct.setBounds(mHomeTask.token, new Rect(0, 0, mDisplayWidth, mDisplayHeight));
mSyncQueue.queue(wct);
final SurfaceControl finalLeash = mLaunchRootLeash;
mSyncQueue.runInSync(t -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
index 2624ee536b58..d961d8658b98 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
@@ -70,4 +70,9 @@ interface IPip {
* Sets the next pip animation type to be the alpha animation.
*/
oneway void setPipAnimationTypeToAlpha() = 5;
+
+ /**
+ * Sets the height and visibility of the Launcher keep clear area.
+ */
+ oneway void setLauncherKeepClearAreaHeight(boolean visible, int height) = 6;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index 23f73f614294..fe8ede67c415 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -36,6 +36,7 @@ import android.window.TaskSnapshot;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
+import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.transition.Transitions;
@@ -372,7 +373,8 @@ public class PipAnimationController {
void setAppIconContentOverlay(Context context, Rect bounds, ActivityInfo activityInfo) {
reattachContentOverlay(
- new PipContentOverlay.PipAppIconOverlay(context, bounds, activityInfo));
+ new PipContentOverlay.PipAppIconOverlay(context, bounds,
+ () -> new IconProvider(context).getIcon(activityInfo)));
}
private void reattachContentOverlay(PipContentOverlay overlay) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
index 5be18d852990..f08742db8ebf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
@@ -30,7 +30,6 @@ import android.graphics.Rect;
import android.os.RemoteException;
import android.util.ArraySet;
import android.util.Size;
-import android.view.Display;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
@@ -44,7 +43,9 @@ import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
@@ -77,6 +78,7 @@ public class PipBoundsState {
private final @NonNull Rect mExpandedBounds = new Rect();
private final @NonNull Rect mNormalMovementBounds = new Rect();
private final @NonNull Rect mExpandedMovementBounds = new Rect();
+ private final @NonNull PipDisplayLayoutState mPipDisplayLayoutState;
private final Point mMaxSize = new Point();
private final Point mMinSize = new Point();
private final @NonNull Context mContext;
@@ -86,8 +88,6 @@ public class PipBoundsState {
private @Nullable PipReentryState mPipReentryState;
private final @Nullable PipSizeSpecHandler mPipSizeSpecHandler;
private @Nullable ComponentName mLastPipComponentName;
- private int mDisplayId = Display.DEFAULT_DISPLAY;
- private final @NonNull DisplayLayout mDisplayLayout = new DisplayLayout();
private final @NonNull MotionBoundsState mMotionBoundsState = new MotionBoundsState();
private boolean mIsImeShowing;
private int mImeHeight;
@@ -115,15 +115,23 @@ public class PipBoundsState {
* @see android.view.View#setPreferKeepClearRects
*/
private final Set<Rect> mUnrestrictedKeepClearAreas = new ArraySet<>();
+ /**
+ * Additional to {@link #mUnrestrictedKeepClearAreas}, allow the caller to append named bounds
+ * as unrestricted keep clear area. Values in this map would be appended to
+ * {@link #getUnrestrictedKeepClearAreas()} and this is meant for internal usage only.
+ */
+ private final Map<String, Rect> mNamedUnrestrictedKeepClearAreas = new HashMap<>();
private @Nullable Runnable mOnMinimalSizeChangeCallback;
private @Nullable TriConsumer<Boolean, Integer, Boolean> mOnShelfVisibilityChangeCallback;
private List<Consumer<Rect>> mOnPipExclusionBoundsChangeCallbacks = new ArrayList<>();
- public PipBoundsState(@NonNull Context context, PipSizeSpecHandler pipSizeSpecHandler) {
+ public PipBoundsState(@NonNull Context context, PipSizeSpecHandler pipSizeSpecHandler,
+ PipDisplayLayoutState pipDisplayLayoutState) {
mContext = context;
reloadResources();
mPipSizeSpecHandler = pipSizeSpecHandler;
+ mPipDisplayLayoutState = pipDisplayLayoutState;
}
/** Reloads the resources. */
@@ -290,31 +298,16 @@ public class PipBoundsState {
return mLastPipComponentName;
}
- /** Get the current display id. */
- public int getDisplayId() {
- return mDisplayId;
- }
-
- /** Set the current display id for the associated display layout. */
- public void setDisplayId(int displayId) {
- mDisplayId = displayId;
- }
-
/** Returns the display's bounds. */
@NonNull
public Rect getDisplayBounds() {
- return new Rect(0, 0, mDisplayLayout.width(), mDisplayLayout.height());
- }
-
- /** Update the display layout. */
- public void setDisplayLayout(@NonNull DisplayLayout displayLayout) {
- mDisplayLayout.set(displayLayout);
+ return mPipDisplayLayoutState.getDisplayBounds();
}
/** Get a copy of the display layout. */
@NonNull
public DisplayLayout getDisplayLayout() {
- return new DisplayLayout(mDisplayLayout);
+ return mPipDisplayLayoutState.getDisplayLayout();
}
@VisibleForTesting
@@ -393,6 +386,16 @@ public class PipBoundsState {
mUnrestrictedKeepClearAreas.addAll(unrestrictedAreas);
}
+ /** Add a named unrestricted keep clear area. */
+ public void addNamedUnrestrictedKeepClearArea(@NonNull String name, Rect unrestrictedArea) {
+ mNamedUnrestrictedKeepClearAreas.put(name, unrestrictedArea);
+ }
+
+ /** Remove a named unrestricted keep clear area. */
+ public void removeNamedUnrestrictedKeepClearArea(@NonNull String name) {
+ mNamedUnrestrictedKeepClearAreas.remove(name);
+ }
+
@NonNull
public Set<Rect> getRestrictedKeepClearAreas() {
return mRestrictedKeepClearAreas;
@@ -400,7 +403,10 @@ public class PipBoundsState {
@NonNull
public Set<Rect> getUnrestrictedKeepClearAreas() {
- return mUnrestrictedKeepClearAreas;
+ if (mNamedUnrestrictedKeepClearAreas.isEmpty()) return mUnrestrictedKeepClearAreas;
+ final Set<Rect> unrestrictedAreas = new ArraySet<>(mUnrestrictedKeepClearAreas);
+ unrestrictedAreas.addAll(mNamedUnrestrictedKeepClearAreas.values());
+ return unrestrictedAreas;
}
/**
@@ -568,7 +574,6 @@ public class PipBoundsState {
pw.println(innerPrefix + "mExpandedMovementBounds=" + mExpandedMovementBounds);
pw.println(innerPrefix + "mLastPipComponentName=" + mLastPipComponentName);
pw.println(innerPrefix + "mAspectRatio=" + mAspectRatio);
- pw.println(innerPrefix + "mDisplayId=" + mDisplayId);
pw.println(innerPrefix + "mStashedState=" + mStashedState);
pw.println(innerPrefix + "mStashOffset=" + mStashOffset);
pw.println(innerPrefix + "mIsImeShowing=" + mIsImeShowing);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
index 53bf42a3c911..d228dfbb7705 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
@@ -20,9 +20,6 @@ import static android.util.TypedValue.COMPLEX_UNIT_DIP;
import android.annotation.Nullable;
import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -35,6 +32,8 @@ import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.window.TaskSnapshot;
+import java.util.function.Supplier;
+
/**
* Represents the content overlay used during the entering PiP animation.
*/
@@ -177,7 +176,9 @@ public abstract class PipContentOverlay {
/** A {@link PipContentOverlay} shows app icon on solid color background. */
public static final class PipAppIconOverlay extends PipContentOverlay {
private static final String TAG = PipAppIconOverlay.class.getSimpleName();
- private static final int APP_ICON_SIZE_DP = 48;
+ // Align with the practical / reasonable launcher:iconImageSize as in
+ // vendor/unbundled_google/packages/NexusLauncher/res/xml/device_profiles.xml
+ private static final int APP_ICON_SIZE_DP = 66;
private final Context mContext;
private final int mAppIconSizePx;
@@ -187,14 +188,14 @@ public abstract class PipContentOverlay {
private Bitmap mBitmap;
- public PipAppIconOverlay(Context context, Rect appBounds, ActivityInfo activityInfo) {
+ public PipAppIconOverlay(Context context, Rect appBounds, Supplier<Drawable> iconSupplier) {
mContext = context;
mAppIconSizePx = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, APP_ICON_SIZE_DP,
context.getResources().getDisplayMetrics());
mAppBounds = new Rect(appBounds);
mBitmap = Bitmap.createBitmap(appBounds.width(), appBounds.height(),
Bitmap.Config.ARGB_8888);
- prepareAppIconOverlay(activityInfo);
+ prepareAppIconOverlay(iconSupplier);
mLeash = new SurfaceControl.Builder(new SurfaceSession())
.setCallsite(TAG)
.setName(LAYER_NAME)
@@ -237,7 +238,7 @@ public abstract class PipContentOverlay {
}
}
- private void prepareAppIconOverlay(ActivityInfo activityInfo) {
+ private void prepareAppIconOverlay(Supplier<Drawable> iconSupplier) {
final Canvas canvas = new Canvas();
canvas.setBitmap(mBitmap);
final TypedArray ta = mContext.obtainStyledAttributes(new int[] {
@@ -251,8 +252,7 @@ public abstract class PipContentOverlay {
} finally {
ta.recycle();
}
- final Drawable appIcon = loadActivityInfoIcon(activityInfo,
- mContext.getResources().getConfiguration().densityDpi);
+ final Drawable appIcon = iconSupplier.get();
final Rect appIconBounds = new Rect(
mAppBounds.centerX() - mAppIconSizePx / 2,
mAppBounds.centerY() - mAppIconSizePx / 2,
@@ -262,24 +262,5 @@ public abstract class PipContentOverlay {
appIcon.draw(canvas);
mBitmap = mBitmap.copy(Bitmap.Config.HARDWARE, false /* mutable */);
}
-
- // Copied from com.android.launcher3.icons.IconProvider#loadActivityInfoIcon
- private Drawable loadActivityInfoIcon(ActivityInfo ai, int density) {
- final int iconRes = ai.getIconResource();
- Drawable icon = null;
- // Get the preferred density icon from the app's resources
- if (density != 0 && iconRes != 0) {
- try {
- final Resources resources = mContext.getPackageManager()
- .getResourcesForApplication(ai.applicationInfo);
- icon = resources.getDrawableForDensity(iconRes, density);
- } catch (PackageManager.NameNotFoundException | Resources.NotFoundException exc) { }
- }
- // Get the default density icon
- if (icon == null) {
- icon = ai.loadIcon(mContext.getPackageManager());
- }
- return icon;
- }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java
new file mode 100644
index 000000000000..0f76af48199f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.view.Surface;
+
+import androidx.annotation.NonNull;
+
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.dagger.WMSingleton;
+
+import java.io.PrintWriter;
+
+import javax.inject.Inject;
+
+/**
+ * Acts as a source of truth for display related information for PIP.
+ */
+@WMSingleton
+public class PipDisplayLayoutState {
+ private static final String TAG = PipDisplayLayoutState.class.getSimpleName();
+
+ private Context mContext;
+ private int mDisplayId;
+ @NonNull private DisplayLayout mDisplayLayout;
+
+ @Inject
+ public PipDisplayLayoutState(Context context) {
+ mContext = context;
+ mDisplayLayout = new DisplayLayout();
+ }
+
+ /** Update the display layout. */
+ public void setDisplayLayout(@NonNull DisplayLayout displayLayout) {
+ mDisplayLayout.set(displayLayout);
+ }
+
+ /** Get a copy of the display layout. */
+ @NonNull
+ public DisplayLayout getDisplayLayout() {
+ return new DisplayLayout(mDisplayLayout);
+ }
+
+ /** Get the display bounds */
+ @NonNull
+ public Rect getDisplayBounds() {
+ return new Rect(0, 0, mDisplayLayout.width(), mDisplayLayout.height());
+ }
+
+ /**
+ * Apply a rotation to this layout and its parameters.
+ * @param targetRotation
+ */
+ public void rotateTo(@Surface.Rotation int targetRotation) {
+ mDisplayLayout.rotateTo(mContext.getResources(), targetRotation);
+ }
+
+ /** Get the current display id */
+ public int getDisplayId() {
+ return mDisplayId;
+ }
+
+ /** Set the current display id for the associated display layout. */
+ public void setDisplayId(int displayId) {
+ mDisplayId = displayId;
+ }
+
+ /** Dumps internal state. */
+ public void dump(PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ pw.println(prefix + TAG);
+ pw.println(innerPrefix + "mDisplayId=" + mDisplayId);
+ pw.println(innerPrefix + "getDisplayBounds=" + getDisplayBounds());
+ }
+}
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 e9d257139779..d5b9c5e8d8ff 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
@@ -79,13 +79,11 @@ import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ScreenshotUtils;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.pip.phone.PipMotionHelper;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.transition.Transitions;
@@ -128,7 +126,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
private final Context mContext;
private final SyncTransactionQueue mSyncTransactionQueue;
private final PipBoundsState mPipBoundsState;
- private final PipSizeSpecHandler mPipSizeSpecHandler;
+ private final PipDisplayLayoutState mPipDisplayLayoutState;
private final PipBoundsAlgorithm mPipBoundsAlgorithm;
private final @NonNull PipMenuController mPipMenuController;
private final PipAnimationController mPipAnimationController;
@@ -316,7 +314,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
@NonNull SyncTransactionQueue syncTransactionQueue,
@NonNull PipTransitionState pipTransitionState,
@NonNull PipBoundsState pipBoundsState,
- @NonNull PipSizeSpecHandler pipSizeSpecHandler,
+ @NonNull PipDisplayLayoutState pipDisplayLayoutState,
@NonNull PipBoundsAlgorithm boundsHandler,
@NonNull PipMenuController pipMenuController,
@NonNull PipAnimationController pipAnimationController,
@@ -332,7 +330,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mSyncTransactionQueue = syncTransactionQueue;
mPipTransitionState = pipTransitionState;
mPipBoundsState = pipBoundsState;
- mPipSizeSpecHandler = pipSizeSpecHandler;
+ mPipDisplayLayoutState = pipDisplayLayoutState;
mPipBoundsAlgorithm = boundsHandler;
mPipMenuController = pipMenuController;
mPipTransitionController = pipTransitionController;
@@ -653,7 +651,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
// If the displayId of the task is different than what PipBoundsHandler has, then update
// it. This is possible if we entered PiP on an external display.
- if (info.displayId != mPipBoundsState.getDisplayId()
+ if (info.displayId != mPipDisplayLayoutState.getDisplayId()
&& mOnDisplayIdChangeCallback != null) {
mOnDisplayIdChangeCallback.accept(info.displayId);
}
@@ -1179,6 +1177,20 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
}
/**
+ * Directly update the animator bounds.
+ */
+ public void updateAnimatorBounds(Rect bounds) {
+ final PipAnimationController.PipTransitionAnimator animator =
+ mPipAnimationController.getCurrentAnimator();
+ if (animator != null && animator.isRunning()) {
+ if (animator.getAnimationType() == ANIM_TYPE_BOUNDS) {
+ animator.updateEndValue(bounds);
+ }
+ animator.setDestinationBounds(bounds);
+ }
+ }
+
+ /**
* Handles all changes to the PictureInPictureParams.
*/
protected void applyNewPictureInPictureParams(@NonNull PictureInPictureParams params) {
@@ -1621,15 +1633,15 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
return animator;
}
- /** Computes destination bounds in old rotation and returns source hint rect if available. */
+ /** Computes destination bounds in old rotation and returns source hint rect if available.
+ *
+ * Note: updates the internal state of {@link PipDisplayLayoutState} by applying a rotation
+ * transformation onto the display layout.
+ */
private @Nullable Rect computeRotatedBounds(int rotationDelta, int direction,
Rect outDestinationBounds, Rect sourceHintRect) {
if (direction == TRANSITION_DIRECTION_TO_PIP) {
- DisplayLayout layoutCopy = mPipBoundsState.getDisplayLayout();
-
- layoutCopy.rotateTo(mContext.getResources(), mNextRotation);
- mPipBoundsState.setDisplayLayout(layoutCopy);
- mPipSizeSpecHandler.setDisplayLayout(layoutCopy);
+ mPipDisplayLayoutState.rotateTo(mNextRotation);
final Rect displayBounds = mPipBoundsState.getDisplayBounds();
outDestinationBounds.set(mPipBoundsAlgorithm.getEntryDestinationBounds());
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 a91a3424f3a0..45bb73bdc0d2 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
@@ -64,8 +64,6 @@ import androidx.annotation.Nullable;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellInit;
@@ -85,7 +83,7 @@ public class PipTransition extends PipTransitionController {
private final Context mContext;
private final PipTransitionState mPipTransitionState;
- private final PipSizeSpecHandler mPipSizeSpecHandler;
+ private final PipDisplayLayoutState mPipDisplayLayoutState;
private final int mEnterExitAnimationDuration;
private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
private final Optional<SplitScreenController> mSplitScreenOptional;
@@ -116,7 +114,7 @@ public class PipTransition extends PipTransitionController {
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@NonNull Transitions transitions,
PipBoundsState pipBoundsState,
- PipSizeSpecHandler pipSizeSpecHandler,
+ PipDisplayLayoutState pipDisplayLayoutState,
PipTransitionState pipTransitionState,
PipMenuController pipMenuController,
PipBoundsAlgorithm pipBoundsAlgorithm,
@@ -127,7 +125,7 @@ public class PipTransition extends PipTransitionController {
pipBoundsAlgorithm, pipAnimationController);
mContext = context;
mPipTransitionState = pipTransitionState;
- mPipSizeSpecHandler = pipSizeSpecHandler;
+ mPipDisplayLayoutState = pipDisplayLayoutState;
mEnterExitAnimationDuration = context.getResources()
.getInteger(R.integer.config_pipResizeAnimationDuration);
mSurfaceTransactionHelper = pipSurfaceTransactionHelper;
@@ -313,11 +311,7 @@ public class PipTransition extends PipTransitionController {
// initial state under the new rotation.
int rotationDelta = deltaRotation(startRotation, endRotation);
if (rotationDelta != Surface.ROTATION_0) {
- DisplayLayout layoutCopy = mPipBoundsState.getDisplayLayout();
-
- layoutCopy.rotateTo(mContext.getResources(), endRotation);
- mPipBoundsState.setDisplayLayout(layoutCopy);
- mPipSizeSpecHandler.setDisplayLayout(layoutCopy);
+ mPipDisplayLayoutState.rotateTo(endRotation);
final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
wct.setBounds(mRequestedEnterTask, destinationBounds);
@@ -398,7 +392,7 @@ public class PipTransition extends PipTransitionController {
// Launcher may update the Shelf height during the animation, which will update the
// destination bounds. Because this is in fixed rotation, We need to make sure the
// finishTransaction is using the updated bounds in the display rotation.
- final Rect displayBounds = mPipBoundsState.getDisplayBounds();
+ final Rect displayBounds = mPipDisplayLayoutState.getDisplayBounds();
final Rect finishBounds = new Rect(destinationBounds);
rotateBounds(finishBounds, displayBounds, mEndFixedRotation, displayRotation);
mSurfaceTransactionHelper.crop(mFinishTransaction, leash, finishBounds);
@@ -495,10 +489,11 @@ public class PipTransition extends PipTransitionController {
// Reparent the pip leash to the root with max layer so that we can animate it outside of
// parent crop, and make sure it is not covered by other windows.
final SurfaceControl pipLeash = pipChange.getLeash();
- startTransaction.reparent(pipLeash, info.getRootLeash());
+ final int rootIdx = TransitionUtil.rootIndexFor(pipChange, info);
+ startTransaction.reparent(pipLeash, info.getRoot(rootIdx).getLeash());
startTransaction.setLayer(pipLeash, Integer.MAX_VALUE);
// Note: because of this, the bounds to animate should be translated to the root coordinate.
- final Point offset = info.getRootOffset();
+ final Point offset = info.getRoot(rootIdx).getOffset();
final Rect currentBounds = mPipBoundsState.getBounds();
currentBounds.offset(-offset.x, -offset.y);
startTransaction.setPosition(pipLeash, currentBounds.left, currentBounds.top);
@@ -640,7 +635,7 @@ public class PipTransition extends PipTransitionController {
@NonNull TaskInfo taskInfo) {
startTransaction.apply();
finishTransaction.setWindowCrop(info.getChanges().get(0).getLeash(),
- mPipBoundsState.getDisplayBounds());
+ mPipDisplayLayoutState.getDisplayBounds());
mPipOrganizer.onExitPipFinished(taskInfo);
finishCallback.onTransitionFinished(null, null);
}
@@ -803,8 +798,15 @@ public class PipTransition extends PipTransitionController {
if (sourceHintRect == null) {
// We use content overlay when there is no source rect hint to enter PiP use bounds
// animation.
+ // TODO(b/272819817): cleanup the null-check and extra logging.
+ final boolean hasTopActivityInfo = taskInfo.topActivityInfo != null;
+ if (!hasTopActivityInfo) {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "%s: TaskInfo.topActivityInfo is null", TAG);
+ }
if (SystemProperties.getBoolean(
- "persist.wm.debug.enable_pip_app_icon_overlay", true)) {
+ "persist.wm.debug.enable_pip_app_icon_overlay", true)
+ && hasTopActivityInfo) {
animator.setAppIconContentOverlay(
mContext, currentBounds, taskInfo.topActivityInfo);
} else {
@@ -834,13 +836,9 @@ public class PipTransition extends PipTransitionController {
/** Computes destination bounds in old rotation and updates source hint rect if available. */
private void computeEnterPipRotatedBounds(int rotationDelta, int startRotation, int endRotation,
TaskInfo taskInfo, Rect outDestinationBounds, @Nullable Rect outSourceHintRect) {
- DisplayLayout layoutCopy = mPipBoundsState.getDisplayLayout();
-
- layoutCopy.rotateTo(mContext.getResources(), endRotation);
- mPipBoundsState.setDisplayLayout(layoutCopy);
- mPipSizeSpecHandler.setDisplayLayout(layoutCopy);
+ mPipDisplayLayoutState.rotateTo(endRotation);
- final Rect displayBounds = mPipBoundsState.getDisplayBounds();
+ final Rect displayBounds = mPipDisplayLayoutState.getDisplayBounds();
outDestinationBounds.set(mPipBoundsAlgorithm.getEntryDestinationBounds());
// Transform the destination bounds to current display coordinates.
rotateBounds(outDestinationBounds, displayBounds, endRotation, startRotation);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java
index c6b5ce93fd35..db6138a0891f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java
@@ -93,6 +93,11 @@ public class PipTransitionState {
return hasEnteredPip(mState);
}
+ /** Returns true if activity is currently entering PiP mode. */
+ public boolean isEnteringPip() {
+ return isEnteringPip(mState);
+ }
+
public void setInSwipePipToHomeTransition(boolean inSwipePipToHomeTransition) {
mInSwipePipToHomeTransition = inSwipePipToHomeTransition;
}
@@ -130,6 +135,11 @@ public class PipTransitionState {
return state == ENTERED_PIP;
}
+ /** Returns true if activity is currently entering PiP mode. */
+ public static boolean isEnteringPip(@TransitionState int state) {
+ return state == ENTERING_PIP;
+ }
+
public interface OnPipTransitionStateChangedListener {
void onPipTransitionStateChanged(@TransitionState int oldState,
@TransitionState int newState);
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 a1483a8dedae..ea2559af6142 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
@@ -43,6 +43,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.RemoteException;
import android.os.SystemProperties;
@@ -71,6 +72,7 @@ import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
+import com.android.wm.shell.common.TabletopModeController;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.onehanded.OneHandedController;
@@ -83,6 +85,7 @@ import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipAppOpsListener;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
@@ -115,6 +118,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb
UserChangeListener {
private static final String TAG = "PipController";
+ private static final String LAUNCHER_KEEP_CLEAR_AREA_TAG = "hotseat";
+
private static final long PIP_KEEP_CLEAR_AREAS_DELAY =
SystemProperties.getLong("persist.wm.debug.pip_keep_clear_areas_delay", 200);
@@ -138,12 +143,14 @@ public class PipController implements PipTransitionController.PipTransitionCallb
private PipKeepClearAlgorithmInterface mPipKeepClearAlgorithm;
private PipBoundsState mPipBoundsState;
private PipSizeSpecHandler mPipSizeSpecHandler;
+ private PipDisplayLayoutState mPipDisplayLayoutState;
private PipMotionHelper mPipMotionHelper;
private PipTouchHandler mTouchHandler;
private PipTransitionController mPipTransitionController;
private TaskStackListenerImpl mTaskStackListener;
private PipParamsChangedForwarder mPipParamsChangedForwarder;
private DisplayInsetsController mDisplayInsetsController;
+ private TabletopModeController mTabletopModeController;
private Optional<OneHandedController> mOneHandedController;
private final ShellCommandHandler mShellCommandHandler;
private final ShellController mShellController;
@@ -181,14 +188,20 @@ public class PipController implements PipTransitionController.PipTransitionCallb
// early bail out if the keep clear areas feature is disabled
return;
}
- // only move if already in pip, other transitions account for keep clear areas
- if (mPipTransitionState.hasEnteredPip()) {
+ // only move if we're in PiP or transitioning into PiP
+ if (!mPipTransitionState.shouldBlockResizeRequest()) {
Rect destBounds = mPipKeepClearAlgorithm.adjust(mPipBoundsState,
mPipBoundsAlgorithm);
// only move if the bounds are actually different
if (destBounds != mPipBoundsState.getBounds()) {
- mPipTaskOrganizer.scheduleAnimateResizePip(destBounds,
- mEnterAnimationDuration, null);
+ if (mPipTransitionState.hasEnteredPip()) {
+ // if already in PiP, schedule separate animation
+ mPipTaskOrganizer.scheduleAnimateResizePip(destBounds,
+ mEnterAnimationDuration, null);
+ } else if (mPipTransitionState.isEnteringPip()) {
+ // while entering PiP we just need to update animator bounds
+ mPipTaskOrganizer.updateAnimatorBounds(destBounds);
+ }
}
}
}
@@ -303,7 +316,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
@Override
public void onDisplayAdded(int displayId) {
- if (displayId != mPipBoundsState.getDisplayId()) {
+ if (displayId != mPipDisplayLayoutState.getDisplayId()) {
return;
}
onDisplayChanged(mDisplayController.getDisplayLayout(displayId),
@@ -312,7 +325,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
@Override
public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
- if (displayId != mPipBoundsState.getDisplayId()) {
+ if (displayId != mPipDisplayLayoutState.getDisplayId()) {
return;
}
onDisplayChanged(mDisplayController.getDisplayLayout(displayId),
@@ -322,7 +335,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
@Override
public void onKeepClearAreasChanged(int displayId, Set<Rect> restricted,
Set<Rect> unrestricted) {
- if (mPipBoundsState.getDisplayId() == displayId) {
+ if (mPipDisplayLayoutState.getDisplayId() == displayId) {
if (mEnablePipKeepClearAlgorithm) {
mPipBoundsState.setKeepClearAreas(restricted, unrestricted);
@@ -382,6 +395,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
PipKeepClearAlgorithmInterface pipKeepClearAlgorithm,
PipBoundsState pipBoundsState,
PipSizeSpecHandler pipSizeSpecHandler,
+ PipDisplayLayoutState pipDisplayLayoutState,
PipMotionHelper pipMotionHelper,
PipMediaController pipMediaController,
PhonePipMenuController phonePipMenuController,
@@ -393,6 +407,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
TaskStackListenerImpl taskStackListener,
PipParamsChangedForwarder pipParamsChangedForwarder,
DisplayInsetsController displayInsetsController,
+ TabletopModeController pipTabletopController,
Optional<OneHandedController> oneHandedController,
ShellExecutor mainExecutor) {
if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
@@ -404,10 +419,10 @@ public class PipController implements PipTransitionController.PipTransitionCallb
return new PipController(context, shellInit, shellCommandHandler, shellController,
displayController, pipAnimationController, pipAppOpsListener,
pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState, pipSizeSpecHandler,
- pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer,
- pipTransitionState, pipTouchHandler, pipTransitionController,
+ pipDisplayLayoutState, pipMotionHelper, pipMediaController, phonePipMenuController,
+ pipTaskOrganizer, pipTransitionState, pipTouchHandler, pipTransitionController,
windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
- displayInsetsController, oneHandedController, mainExecutor)
+ displayInsetsController, pipTabletopController, oneHandedController, mainExecutor)
.mImpl;
}
@@ -422,6 +437,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
PipKeepClearAlgorithmInterface pipKeepClearAlgorithm,
@NonNull PipBoundsState pipBoundsState,
PipSizeSpecHandler pipSizeSpecHandler,
+ @NonNull PipDisplayLayoutState pipDisplayLayoutState,
PipMotionHelper pipMotionHelper,
PipMediaController pipMediaController,
PhonePipMenuController phonePipMenuController,
@@ -433,6 +449,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
TaskStackListenerImpl taskStackListener,
PipParamsChangedForwarder pipParamsChangedForwarder,
DisplayInsetsController displayInsetsController,
+ TabletopModeController tabletopModeController,
Optional<OneHandedController> oneHandedController,
ShellExecutor mainExecutor
) {
@@ -448,6 +465,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mPipKeepClearAlgorithm = pipKeepClearAlgorithm;
mPipBoundsState = pipBoundsState;
mPipSizeSpecHandler = pipSizeSpecHandler;
+ mPipDisplayLayoutState = pipDisplayLayoutState;
mPipMotionHelper = pipMotionHelper;
mPipTaskOrganizer = pipTaskOrganizer;
mPipTransitionState = pipTransitionState;
@@ -465,6 +483,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
.getInteger(R.integer.config_pipEnterAnimationDuration);
mPipParamsChangedForwarder = pipParamsChangedForwarder;
mDisplayInsetsController = displayInsetsController;
+ mTabletopModeController = tabletopModeController;
shellInit.addInitCallback(this::onInit, this);
}
@@ -475,7 +494,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
INPUT_CONSUMER_PIP, mMainExecutor);
mPipTransitionController.registerPipTransitionCallback(this);
mPipTaskOrganizer.registerOnDisplayIdChangeCallback((int displayId) -> {
- mPipBoundsState.setDisplayId(displayId);
+ mPipDisplayLayoutState.setDisplayId(displayId);
onDisplayChanged(mDisplayController.getDisplayLayout(displayId),
false /* saveRestoreSnapFraction */);
});
@@ -515,11 +534,10 @@ public class PipController implements PipTransitionController.PipTransitionCallb
// Ensure that we have the display info in case we get calls to update the bounds before the
// listener calls back
- mPipBoundsState.setDisplayId(mContext.getDisplayId());
+ mPipDisplayLayoutState.setDisplayId(mContext.getDisplayId());
DisplayLayout layout = new DisplayLayout(mContext, mContext.getDisplay());
- mPipSizeSpecHandler.setDisplayLayout(layout);
- mPipBoundsState.setDisplayLayout(layout);
+ mPipDisplayLayoutState.setDisplayLayout(layout);
try {
mWindowManagerShellWrapper.addPinnedStackListener(mPinnedTaskListener);
@@ -614,12 +632,12 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
});
- mDisplayInsetsController.addInsetsChangedListener(mPipBoundsState.getDisplayId(),
+ mDisplayInsetsController.addInsetsChangedListener(mPipDisplayLayoutState.getDisplayId(),
new DisplayInsetsController.OnInsetsChangedListener() {
@Override
public void insetsChanged(InsetsState insetsState) {
- DisplayLayout pendingLayout =
- mDisplayController.getDisplayLayout(mPipBoundsState.getDisplayId());
+ DisplayLayout pendingLayout = mDisplayController
+ .getDisplayLayout(mPipDisplayLayoutState.getDisplayId());
if (mIsInFixedRotation
|| pendingLayout.rotation()
!= mPipBoundsState.getDisplayLayout().rotation()) {
@@ -627,8 +645,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb
return;
}
int oldMaxMovementBound = mPipBoundsState.getMovementBounds().bottom;
- onDisplayChangedUncheck(
- mDisplayController.getDisplayLayout(mPipBoundsState.getDisplayId()),
+ onDisplayChangedUncheck(mDisplayController
+ .getDisplayLayout(mPipDisplayLayoutState.getDisplayId()),
false /* saveRestoreSnapFraction */);
int newMaxMovementBound = mPipBoundsState.getMovementBounds().bottom;
if (!mEnablePipKeepClearAlgorithm) {
@@ -648,6 +666,42 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
});
+ mTabletopModeController.registerOnTabletopModeChangedListener((isInTabletopMode) -> {
+ if (!mTabletopModeController.enableMoveFloatingWindowInTabletop()) return;
+ final String tag = "tabletop-mode";
+ if (!isInTabletopMode) {
+ mPipBoundsState.removeNamedUnrestrictedKeepClearArea(tag);
+ return;
+ }
+
+ // To prepare for the entry bounds.
+ final Rect displayBounds = mPipBoundsState.getDisplayBounds();
+ if (mTabletopModeController.getPreferredHalfInTabletopMode()
+ == TabletopModeController.PREFERRED_TABLETOP_HALF_TOP) {
+ // Prefer top, avoid the bottom half of the display.
+ mPipBoundsState.addNamedUnrestrictedKeepClearArea(tag, new Rect(
+ displayBounds.left, displayBounds.centerY(),
+ displayBounds.right, displayBounds.bottom));
+ } else {
+ // Prefer bottom, avoid the top half of the display.
+ mPipBoundsState.addNamedUnrestrictedKeepClearArea(tag, new Rect(
+ displayBounds.left, displayBounds.top,
+ displayBounds.right, displayBounds.centerY()));
+ }
+
+ // Try to move the PiP window if we have entered PiP mode.
+ if (mPipTransitionState.hasEnteredPip()) {
+ final Rect pipBounds = mPipBoundsState.getBounds();
+ final Point edgeInsets = mPipSizeSpecHandler.getScreenEdgeInsets();
+ if ((pipBounds.height() + 2 * edgeInsets.y) > (displayBounds.height() / 2)) {
+ // PiP bounds is too big to fit either half, bail early.
+ return;
+ }
+ mMainExecutor.removeCallbacks(mMovePipInResponseToKeepClearAreasChangeCallback);
+ mMainExecutor.execute(mMovePipInResponseToKeepClearAreasChangeCallback);
+ }
+ });
+
mOneHandedController.ifPresent(controller -> {
controller.registerTransitionCallback(
new OneHandedTransitionCallback() {
@@ -714,7 +768,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
private void onDisplayChanged(DisplayLayout layout, boolean saveRestoreSnapFraction) {
- if (!mPipBoundsState.getDisplayLayout().isSameGeometry(layout)) {
+ if (!mPipDisplayLayoutState.getDisplayLayout().isSameGeometry(layout)) {
PipAnimationController.PipTransitionAnimator animator =
mPipAnimationController.getCurrentAnimator();
if (animator != null && animator.isRunning()) {
@@ -728,11 +782,10 @@ public class PipController implements PipTransitionController.PipTransitionCallb
private void onDisplayChangedUncheck(DisplayLayout layout, boolean saveRestoreSnapFraction) {
Runnable updateDisplayLayout = () -> {
final boolean fromRotation = Transitions.ENABLE_SHELL_TRANSITIONS
- && mPipBoundsState.getDisplayLayout().rotation() != layout.rotation();
+ && mPipDisplayLayoutState.getDisplayLayout().rotation() != layout.rotation();
// update the internal state of objects subscribed to display changes
- mPipSizeSpecHandler.setDisplayLayout(layout);
- mPipBoundsState.setDisplayLayout(layout);
+ mPipDisplayLayoutState.setDisplayLayout(layout);
final WindowContainerTransaction wct =
fromRotation ? new WindowContainerTransaction() : null;
@@ -756,11 +809,13 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mPipBoundsState.getStashedState());
// Scale PiP on density dpi change, so it appears to be the same size physically.
- final boolean densityDpiChanged = mPipBoundsState.getDisplayLayout().densityDpi() != 0
- && (mPipBoundsState.getDisplayLayout().densityDpi() != layout.densityDpi());
+ final boolean densityDpiChanged =
+ mPipDisplayLayoutState.getDisplayLayout().densityDpi() != 0
+ && (mPipDisplayLayoutState.getDisplayLayout().densityDpi()
+ != layout.densityDpi());
if (densityDpiChanged) {
final float scale = (float) layout.densityDpi()
- / mPipBoundsState.getDisplayLayout().densityDpi();
+ / mPipDisplayLayoutState.getDisplayLayout().densityDpi();
postChangeBounds.set(0, 0,
(int) (postChangeBounds.width() * scale),
(int) (postChangeBounds.height() * scale));
@@ -775,8 +830,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb
pipSnapAlgorithm.applySnapFraction(postChangeBounds, postChangeMovementBounds,
snapFraction, mPipBoundsState.getStashedState(),
mPipBoundsState.getStashOffset(),
- mPipBoundsState.getDisplayBounds(),
- mPipBoundsState.getDisplayLayout().stableInsets());
+ mPipDisplayLayoutState.getDisplayBounds(),
+ mPipDisplayLayoutState.getDisplayLayout().stableInsets());
if (densityDpiChanged) {
// Using PipMotionHelper#movePip directly here may cause race condition since
@@ -874,6 +929,19 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
}
+ private void setLauncherKeepClearAreaHeight(boolean visible, int height) {
+ if (visible) {
+ Rect rect = new Rect(
+ 0, mPipBoundsState.getDisplayBounds().bottom - height,
+ mPipBoundsState.getDisplayBounds().right,
+ mPipBoundsState.getDisplayBounds().bottom);
+ mPipBoundsState.addNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG, rect);
+ updatePipPositionForKeepClearAreas();
+ } else {
+ mPipBoundsState.removeNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG);
+ }
+ }
+
private void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {
mOnIsInPipStateChangedListener = callback;
if (mOnIsInPipStateChangedListener != null) {
@@ -1018,7 +1086,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
// Populate inset / normal bounds and DisplayInfo from mPipBoundsHandler before
// passing to mTouchHandler/mPipTaskOrganizer
final Rect outBounds = new Rect(toBounds);
- final int rotation = mPipBoundsState.getDisplayLayout().rotation();
+ final int rotation = mPipDisplayLayoutState.getDisplayLayout().rotation();
mPipBoundsAlgorithm.getInsetBounds(mTmpInsetBounds);
mPipBoundsState.setNormalBounds(mPipBoundsAlgorithm.getNormalBounds());
@@ -1042,11 +1110,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
private void onDisplayRotationChangedNotInPip(Context context, int toRotation) {
// Update the display layout, note that we have to do this on every rotation even if we
// aren't in PIP since we need to update the display layout to get the right resources
- DisplayLayout layoutCopy = mPipBoundsState.getDisplayLayout();
-
- layoutCopy.rotateTo(context.getResources(), toRotation);
- mPipBoundsState.setDisplayLayout(layoutCopy);
- mPipSizeSpecHandler.setDisplayLayout(layoutCopy);
+ mPipDisplayLayoutState.rotateTo(toRotation);
}
/**
@@ -1059,7 +1123,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
Rect outInsetBounds,
int displayId, int fromRotation, int toRotation, WindowContainerTransaction t) {
// Bail early if the event is not sent to current display
- if ((displayId != mPipBoundsState.getDisplayId()) || (fromRotation == toRotation)) {
+ if ((displayId != mPipDisplayLayoutState.getDisplayId()) || (fromRotation == toRotation)) {
return false;
}
@@ -1083,11 +1147,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mPipBoundsState.getStashedState());
// Update the display layout
- DisplayLayout layoutCopy = mPipBoundsState.getDisplayLayout();
-
- layoutCopy.rotateTo(context.getResources(), toRotation);
- mPipBoundsState.setDisplayLayout(layoutCopy);
- mPipSizeSpecHandler.setDisplayLayout(layoutCopy);
+ mPipDisplayLayoutState.rotateTo(toRotation);
// Calculate the stack bounds in the new orientation based on same fraction along the
// rotated movement bounds.
@@ -1095,8 +1155,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb
postChangeStackBounds, false /* adjustForIme */);
pipSnapAlgorithm.applySnapFraction(postChangeStackBounds, postChangeMovementBounds,
snapFraction, mPipBoundsState.getStashedState(), mPipBoundsState.getStashOffset(),
- mPipBoundsState.getDisplayBounds(),
- mPipBoundsState.getDisplayLayout().stableInsets());
+ mPipDisplayLayoutState.getDisplayBounds(),
+ mPipDisplayLayoutState.getDisplayLayout().stableInsets());
mPipBoundsAlgorithm.getInsetBounds(outInsetBounds);
outBounds.set(postChangeStackBounds);
@@ -1114,6 +1174,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mPipBoundsState.dump(pw, innerPrefix);
mPipInputConsumer.dump(pw, innerPrefix);
mPipSizeSpecHandler.dump(pw, innerPrefix);
+ mPipDisplayLayoutState.dump(pw, innerPrefix);
}
/**
@@ -1240,6 +1301,14 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
@Override
+ public void setLauncherKeepClearAreaHeight(boolean visible, int height) {
+ executeRemoteCallWithTaskPermission(mController, "setLauncherKeepClearAreaHeight",
+ (controller) -> {
+ controller.setLauncherKeepClearAreaHeight(visible, height);
+ });
+ }
+
+ @Override
public void setPipAnimationListener(IPipAnimationListener listener) {
executeRemoteCallWithTaskPermission(mController, "setPipAnimationListener",
(controller) -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
index d03d075b38af..23988a62735d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
@@ -31,6 +31,7 @@ import android.util.Size;
import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
import java.io.PrintWriter;
@@ -40,10 +41,9 @@ import java.io.PrintWriter;
public class PipSizeSpecHandler {
private static final String TAG = PipSizeSpecHandler.class.getSimpleName();
- @NonNull private final DisplayLayout mDisplayLayout = new DisplayLayout();
+ @NonNull private final PipDisplayLayoutState mPipDisplayLayoutState;
- @VisibleForTesting
- final SizeSpecSource mSizeSpecSourceImpl;
+ private final SizeSpecSource mSizeSpecSourceImpl;
/** The preferred minimum (and default minimum) size specified by apps. */
@Nullable private Size mOverrideMinSize;
@@ -361,8 +361,9 @@ public class PipSizeSpecHandler {
}
}
- public PipSizeSpecHandler(Context context) {
+ public PipSizeSpecHandler(Context context, PipDisplayLayoutState pipDisplayLayoutState) {
mContext = context;
+ mPipDisplayLayoutState = pipDisplayLayoutState;
boolean enablePipSizeLargeScreen = SystemProperties
.getBoolean("persist.wm.debug.enable_pip_size_large_screen", false);
@@ -403,15 +404,9 @@ public class PipSizeSpecHandler {
mSizeSpecSourceImpl.reloadResources();
}
- /** Returns the display's bounds. */
@NonNull
- public Rect getDisplayBounds() {
- return new Rect(0, 0, mDisplayLayout.width(), mDisplayLayout.height());
- }
-
- /** Update the display layout. */
- public void setDisplayLayout(@NonNull DisplayLayout displayLayout) {
- mDisplayLayout.set(displayLayout);
+ private Rect getDisplayBounds() {
+ return mPipDisplayLayoutState.getDisplayBounds();
}
public Point getScreenEdgeInsets() {
@@ -423,11 +418,12 @@ public class PipSizeSpecHandler {
*/
public Rect getInsetBounds() {
Rect insetBounds = new Rect();
- Rect insets = mDisplayLayout.stableInsets();
+ DisplayLayout displayLayout = mPipDisplayLayoutState.getDisplayLayout();
+ Rect insets = displayLayout.stableInsets();
insetBounds.set(insets.left + mScreenEdgeInsets.x,
insets.top + mScreenEdgeInsets.y,
- mDisplayLayout.width() - insets.right - mScreenEdgeInsets.x,
- mDisplayLayout.height() - insets.bottom - mScreenEdgeInsets.y);
+ displayLayout.width() - insets.right - mScreenEdgeInsets.x,
+ displayLayout.height() - insets.bottom - mScreenEdgeInsets.y);
return insetBounds;
}
@@ -522,8 +518,8 @@ public class PipSizeSpecHandler {
public void dump(PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
pw.println(prefix + TAG);
- pw.println(innerPrefix + "mSizeSpecSourceImpl=" + mSizeSpecSourceImpl.toString());
- pw.println(innerPrefix + "mDisplayLayout=" + mDisplayLayout);
+ pw.println(innerPrefix + "mSizeSpecSourceImpl=" + mSizeSpecSourceImpl);
pw.println(innerPrefix + "mOverrideMinSize=" + mOverrideMinSize);
+ pw.println(innerPrefix + "mScreenEdgeInsets=" + mScreenEdgeInsets);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
index 22b3f4987e82..e1737eccc6e1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
@@ -31,6 +31,7 @@ import android.view.View;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import java.lang.annotation.Retention;
@@ -75,8 +76,9 @@ public class TvPipBoundsState extends PipBoundsState {
private Insets mPipMenuTemporaryDecorInsets = Insets.NONE;
public TvPipBoundsState(@NonNull Context context,
- @NonNull PipSizeSpecHandler pipSizeSpecHandler) {
- super(context, pipSizeSpecHandler);
+ @NonNull PipSizeSpecHandler pipSizeSpecHandler,
+ @NonNull PipDisplayLayoutState pipDisplayLayoutState) {
+ super(context, pipSizeSpecHandler, pipDisplayLayoutState);
mContext = context;
updateDefaultGravity();
mPreviousCollapsedGravity = mDefaultGravity;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index a437a3bc2826..d73723cc02ff 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -51,11 +51,11 @@ import com.android.wm.shell.pip.PinnedStackListenerForwarder;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipAppOpsListener;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.ShellController;
@@ -119,7 +119,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
private final ShellController mShellController;
private final TvPipBoundsState mTvPipBoundsState;
- private final PipSizeSpecHandler mPipSizeSpecHandler;
+ private final PipDisplayLayoutState mPipDisplayLayoutState;
private final TvPipBoundsAlgorithm mTvPipBoundsAlgorithm;
private final TvPipBoundsController mTvPipBoundsController;
private final PipAppOpsListener mAppOpsListener;
@@ -154,7 +154,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
ShellInit shellInit,
ShellController shellController,
TvPipBoundsState tvPipBoundsState,
- PipSizeSpecHandler pipSizeSpecHandler,
+ PipDisplayLayoutState pipDisplayLayoutState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
TvPipBoundsController tvPipBoundsController,
PipAppOpsListener pipAppOpsListener,
@@ -174,7 +174,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
shellInit,
shellController,
tvPipBoundsState,
- pipSizeSpecHandler,
+ pipDisplayLayoutState,
tvPipBoundsAlgorithm,
tvPipBoundsController,
pipAppOpsListener,
@@ -196,7 +196,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
ShellInit shellInit,
ShellController shellController,
TvPipBoundsState tvPipBoundsState,
- PipSizeSpecHandler pipSizeSpecHandler,
+ PipDisplayLayoutState pipDisplayLayoutState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
TvPipBoundsController tvPipBoundsController,
PipAppOpsListener pipAppOpsListener,
@@ -220,10 +220,11 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
DisplayLayout layout = new DisplayLayout(context, context.getDisplay());
mTvPipBoundsState = tvPipBoundsState;
- mTvPipBoundsState.setDisplayLayout(layout);
- mTvPipBoundsState.setDisplayId(context.getDisplayId());
- mPipSizeSpecHandler = pipSizeSpecHandler;
- mPipSizeSpecHandler.setDisplayLayout(layout);
+
+ mPipDisplayLayoutState = pipDisplayLayoutState;
+ mPipDisplayLayoutState.setDisplayLayout(layout);
+ mPipDisplayLayoutState.setDisplayId(context.getDisplayId());
+
mTvPipBoundsAlgorithm = tvPipBoundsAlgorithm;
mTvPipBoundsController = tvPipBoundsController;
mTvPipBoundsController.setListener(this);
@@ -392,7 +393,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
@Override
public void onKeepClearAreasChanged(int displayId, Set<Rect> restricted,
Set<Rect> unrestricted) {
- if (mTvPipBoundsState.getDisplayId() == displayId) {
+ if (mPipDisplayLayoutState.getDisplayId() == displayId) {
boolean unrestrictedAreasChanged = !Objects.equals(unrestricted,
mTvPipBoundsState.getUnrestrictedKeepClearAreas());
mTvPipBoundsState.setKeepClearAreas(restricted, unrestricted);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
index be9b9361b359..f6856f15f16f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
@@ -28,6 +28,7 @@ import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipMenuController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
@@ -36,7 +37,6 @@ import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.pip.PipUtils;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.splitscreen.SplitScreenController;
import java.util.Objects;
@@ -51,7 +51,7 @@ public class TvPipTaskOrganizer extends PipTaskOrganizer {
@NonNull SyncTransactionQueue syncTransactionQueue,
@NonNull PipTransitionState pipTransitionState,
@NonNull PipBoundsState pipBoundsState,
- @NonNull PipSizeSpecHandler pipSizeSpecHandler,
+ @NonNull PipDisplayLayoutState pipDisplayLayoutState,
@NonNull PipBoundsAlgorithm boundsHandler,
@NonNull PipMenuController pipMenuController,
@NonNull PipAnimationController pipAnimationController,
@@ -63,8 +63,8 @@ public class TvPipTaskOrganizer extends PipTaskOrganizer {
@NonNull PipUiEventLogger pipUiEventLogger,
@NonNull ShellTaskOrganizer shellTaskOrganizer,
ShellExecutor mainExecutor) {
- super(context, syncTransactionQueue, pipTransitionState, pipBoundsState, pipSizeSpecHandler,
- boundsHandler, pipMenuController, pipAnimationController,
+ super(context, syncTransactionQueue, pipTransitionState, pipBoundsState,
+ pipDisplayLayoutState, boundsHandler, pipMenuController, pipAnimationController,
surfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder,
splitScreenOptional, displayController, pipUiEventLogger, shellTaskOrganizer,
mainExecutor);
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 e1c089550c2d..e09c3c9e4d3f 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
@@ -37,6 +37,7 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
import android.view.SurfaceControl;
@@ -128,6 +129,7 @@ class SplitScreenTransitions {
final int mode = info.getChanges().get(i).getMode();
if (mode == TRANSIT_CHANGE) {
+ final int rootIdx = TransitionUtil.rootIndexFor(change, info);
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());
@@ -135,7 +137,7 @@ class SplitScreenTransitions {
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.reparent(leash, info.getRoot(rootIdx).getLeash());
t.setLayer(leash, info.getChanges().size() - i);
// build the finish reparent/reposition
mFinishTransaction.reparent(leash, parentChange.getLeash());
@@ -145,8 +147,9 @@ class SplitScreenTransitions {
// 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);
+ final Point rootOffset = info.getRoot(rootIdx).getOffset();
+ startBounds.offset(-rootOffset.x, -rootOffset.y);
+ endBounds.offset(-rootOffset.x, -rootOffset.y);
startExampleResizeAnimation(leash, startBounds, endBounds);
}
boolean isRootOrSplitSideRoot = change.getParent() == 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 18a3849ea17d..a1eaf851da23 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
@@ -207,6 +207,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private boolean mIsDividerRemoteAnimating;
private boolean mIsDropEntering;
private boolean mIsExiting;
+ private boolean mIsRootTranslucent;
private DefaultMixedHandler mMixedHandler;
private final Toast mSplitUnsupportedToast;
@@ -258,37 +259,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
};
- private final SplitScreenTransitions.TransitionFinishedCallback
- mRecentTransitionFinishedCallback =
- new SplitScreenTransitions.TransitionFinishedCallback() {
- @Override
- public void onFinished(WindowContainerTransaction finishWct,
- SurfaceControl.Transaction finishT) {
- // 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 < finishWct.getHierarchyOps().size(); ++i) {
- final WindowContainerTransaction.HierarchyOp op =
- finishWct.getHierarchyOps().get(i);
- final IBinder container = op.getContainer();
- if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop()
- && (mMainStage.containsContainer(container)
- || mSideStage.containsContainer(container))) {
- updateSurfaceBounds(mSplitLayout, finishT,
- false /* applyResizingOffset */);
- setDividerVisibility(true, finishT);
- return;
- }
- }
-
- // Dismiss the split screen if it's not returning to split.
- prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, finishWct);
- setSplitsVisible(false);
- setDividerVisibility(false, finishT);
- logExit(EXIT_REASON_UNKNOWN);
- }
- };
-
protected StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
ShellTaskOrganizer taskOrganizer, DisplayController displayController,
DisplayImeController displayImeController,
@@ -387,6 +357,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return mMainStage.isActive();
}
+ /** Checks if `transition` is a pending enter-split transition. */
+ public boolean isPendingEnter(IBinder transition) {
+ return mSplitTransitions.isPendingEnter(transition);
+ }
+
@StageType
int getStageOfTask(int taskId) {
if (mMainStage.containsTask(taskId)) {
@@ -422,6 +397,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
}
+ if (!isSplitActive()) {
+ // prevent the fling divider to center transitioni if split screen didn't active.
+ mIsDropEntering = true;
+ }
+
setSideStagePosition(sideStagePosition, wct);
final WindowContainerTransaction evictWct = new WindowContainerTransaction();
targetStage.evictAllChildren(evictWct);
@@ -436,28 +416,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// reparent the task to an invisible split root will make the activity invisible. Reorder
// the root task to front to make the entering transition from pip to split smooth.
wct.reorder(mRootTaskInfo.token, true);
- wct.setForceTranslucent(mRootTaskInfo.token, true);
wct.reorder(targetStage.mRootTaskInfo.token, true);
- wct.setForceTranslucent(targetStage.mRootTaskInfo.token, true);
- // prevent the fling divider to center transition
- mIsDropEntering = true;
-
targetStage.addTask(task, wct);
- if (ENABLE_SHELL_TRANSITIONS) {
- prepareEnterSplitScreen(wct);
- mSplitTransitions.startEnterTransition(TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct,
- null, this, null /* consumedCallback */, (finishWct, finishT) -> {
- if (!evictWct.isEmpty()) {
- finishWct.merge(evictWct, true);
- }
- } /* finishedCallback */);
- } else {
- if (!evictWct.isEmpty()) {
- wct.merge(evictWct, true /* transfer */);
- }
- mTaskOrganizer.applyTransaction(wct);
+ if (!evictWct.isEmpty()) {
+ wct.merge(evictWct, true /* transfer */);
}
+ mTaskOrganizer.applyTransaction(wct);
return true;
}
@@ -716,7 +681,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitLayout.setDivideRatio(splitRatio);
updateWindowBounds(mSplitLayout, wct);
wct.reorder(mRootTaskInfo.token, true);
- wct.setForceTranslucent(mRootTaskInfo.token, false);
+ setRootForceTranslucent(false, wct);
// Make sure the launch options will put tasks in the corresponding split roots
mainOptions = mainOptions != null ? mainOptions : new Bundle();
@@ -923,7 +888,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
updateWindowBounds(mSplitLayout, wct);
wct.reorder(mRootTaskInfo.token, true);
- wct.setForceTranslucent(mRootTaskInfo.token, false);
+ setRootForceTranslucent(false, wct);
// TODO(b/268008375): Merge APIs to start a split pair into one.
if (mainTaskId != INVALID_TASK_ID) {
@@ -1351,7 +1316,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSideStage.removeAllTasks(wct, false /* toTop */);
mMainStage.deactivate(wct, false /* toTop */);
wct.reorder(mRootTaskInfo.token, false /* onTop */);
- wct.setForceTranslucent(mRootTaskInfo.token, true);
+ setRootForceTranslucent(true, wct);
wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
onTransitionAnimationComplete();
} else {
@@ -1383,7 +1348,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mMainStage.deactivate(finishedWCT, childrenToTop == mMainStage /* toTop */);
mSideStage.removeAllTasks(finishedWCT, childrenToTop == mSideStage /* toTop */);
finishedWCT.reorder(mRootTaskInfo.token, false /* toTop */);
- finishedWCT.setForceTranslucent(mRootTaskInfo.token, true);
+ setRootForceTranslucent(true, wct);
finishedWCT.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
mSyncQueue.queue(finishedWCT);
mSyncQueue.runInSync(at -> {
@@ -1505,7 +1470,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mMainStage.activate(wct, true /* includingTopTask */);
updateWindowBounds(mSplitLayout, wct);
wct.reorder(mRootTaskInfo.token, true);
- wct.setForceTranslucent(mRootTaskInfo.token, false);
+ setRootForceTranslucent(false, wct);
}
void finishEnterSplitScreen(SurfaceControl.Transaction t) {
@@ -1709,6 +1674,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mRootTaskInfo = null;
mRootTaskLeash = null;
+ mIsRootTranslucent = false;
}
@@ -1727,7 +1693,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// Make the stages adjacent to each other so they occlude what's behind them.
wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
wct.setLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token);
- wct.setForceTranslucent(mRootTaskInfo.token, true);
+ setRootForceTranslucent(true, wct);
mSplitLayout.getInvisibleBounds(mTempRect1);
wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
mSyncQueue.queue(wct);
@@ -1751,7 +1717,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSideStage.evictOtherChildren(wct, taskId);
updateWindowBounds(mSplitLayout, wct);
wct.reorder(mRootTaskInfo.token, true);
- wct.setForceTranslucent(mRootTaskInfo.token, false);
+ setRootForceTranslucent(false, wct);
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> {
@@ -1775,6 +1741,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mDisplayInsetsController.removeInsetsChangedListener(mDisplayId, mSplitLayout);
}
+ private void setRootForceTranslucent(boolean translucent, WindowContainerTransaction wct) {
+ if (mIsRootTranslucent == translucent) return;
+
+ mIsRootTranslucent = translucent;
+ wct.setForceTranslucent(mRootTaskInfo.token, translucent);
+ }
+
private void onStageVisibilityChanged(StageListenerImpl stageListener) {
// If split didn't active, just ignore this callback because we should already did these
// on #applyExitSplitScreen.
@@ -1801,12 +1774,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// Split entering background.
wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
true /* setReparentLeafTaskIfRelaunch */);
- if (!mMainStage.mRootTaskInfo.isSleeping && !mSideStage.mRootTaskInfo.isSleeping) {
- wct.setForceTranslucent(mRootTaskInfo.token, true);
- }
+ setRootForceTranslucent(true, wct);
} else {
wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
false /* setReparentLeafTaskIfRelaunch */);
+ setRootForceTranslucent(false, wct);
}
mSyncQueue.queue(wct);
@@ -1938,7 +1910,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mMainStage.activate(wct, true /* includingTopTask */);
updateWindowBounds(mSplitLayout, wct);
wct.reorder(mRootTaskInfo.token, true);
- wct.setForceTranslucent(mRootTaskInfo.token, false);
+ setRootForceTranslucent(false, wct);
}
mSyncQueue.queue(wct);
@@ -2266,11 +2238,16 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
} else if (isOpening && inFullscreen) {
final int activityType = triggerTask.getActivityType();
- if (activityType == ACTIVITY_TYPE_HOME
- || activityType == ACTIVITY_TYPE_RECENTS) {
- // Enter overview panel, so start recent transition.
- mSplitTransitions.setRecentTransition(transition, request.getRemoteTransition(),
- mRecentTransitionFinishedCallback);
+ if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) {
+ if (request.getRemoteTransition() != null) {
+ // starting recents/home, so don't handle this and let it fall-through to
+ // the remote handler.
+ return null;
+ }
+ // Need to use the old stuff for non-remote animations, otherwise we don't
+ // exit split-screen.
+ mSplitTransitions.setRecentTransition(transition, null /* remote */,
+ this::onRecentsInSplitAnimationFinish);
}
}
} else {
@@ -2400,7 +2377,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
shouldAnimate = startPendingEnterAnimation(
transition, info, startTransaction, finishTransaction);
} else if (mSplitTransitions.isPendingRecent(transition)) {
- shouldAnimate = startPendingRecentAnimation(transition, info, startTransaction);
+ onRecentsInSplitAnimationStart(startTransaction);
} else if (mSplitTransitions.isPendingDismiss(transition)) {
shouldAnimate = startPendingDismissAnimation(
mSplitTransitions.mPendingDismiss, info, startTransaction, finishTransaction);
@@ -2655,10 +2632,35 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return true;
}
- private boolean startPendingRecentAnimation(@NonNull IBinder transition,
- @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) {
+ /** Call this when starting the open-recents animation while split-screen is active. */
+ public void onRecentsInSplitAnimationStart(@NonNull SurfaceControl.Transaction t) {
setDividerVisibility(false, t);
- return true;
+ }
+
+ /** Call this when the recents animation during split-screen finishes. */
+ public void onRecentsInSplitAnimationFinish(WindowContainerTransaction finishWct,
+ SurfaceControl.Transaction finishT) {
+ // 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 < finishWct.getHierarchyOps().size(); ++i) {
+ final WindowContainerTransaction.HierarchyOp op =
+ finishWct.getHierarchyOps().get(i);
+ final IBinder container = op.getContainer();
+ if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop()
+ && (mMainStage.containsContainer(container)
+ || mSideStage.containsContainer(container))) {
+ updateSurfaceBounds(mSplitLayout, finishT,
+ false /* applyResizingOffset */);
+ setDividerVisibility(true, finishT);
+ return;
+ }
+ }
+
+ // Dismiss the split screen if it's not returning to split.
+ prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, finishWct);
+ setSplitsVisible(false);
+ setDividerVisibility(false, finishT);
+ logExit(EXIT_REASON_UNKNOWN);
}
private void addDividerBarToTransition(@NonNull TransitionInfo info,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 75112b62c1c6..d0948923dc6e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -18,6 +18,7 @@ package com.android.wm.shell.transition;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
@@ -25,6 +26,7 @@ import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
import static com.android.wm.shell.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.util.TransitionUtil.isOpeningType;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -68,14 +70,20 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
/** Pip was entered while handling an intent with its own remoteTransition. */
static final int TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE = 3;
+ /** Recents transition while split-screen active. */
+ static final int TYPE_RECENTS_DURING_SPLIT = 4;
+
/** The default animation for this mixed transition. */
static final int ANIM_TYPE_DEFAULT = 0;
/** For ENTER_PIP_FROM_SPLIT, indicates that this is a to-home animation. */
static final int ANIM_TYPE_GOING_HOME = 1;
+ /** For RECENTS_DURING_SPLIT, is set when this turns into a pair->pair task switch. */
+ static final int ANIM_TYPE_PAIR_TO_PAIR = 1;
+
final int mType;
- int mAnimType = 0;
+ int mAnimType = ANIM_TYPE_DEFAULT;
final IBinder mTransition;
Transitions.TransitionHandler mLeftoversHandler = null;
@@ -167,6 +175,27 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
mixed.mLeftoversHandler = handler.first;
mActiveTransitions.add(mixed);
return handler.second;
+ } else if (mSplitHandler.isSplitActive()
+ && isOpeningType(request.getType())
+ && request.getTriggerTask() != null
+ && request.getTriggerTask().getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+ && (request.getTriggerTask().getActivityType() == ACTIVITY_TYPE_HOME
+ || request.getTriggerTask().getActivityType() == ACTIVITY_TYPE_RECENTS)
+ && request.getRemoteTransition() != null) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
+ + "Split-Screen is active, so treat it as Mixed.");
+ Pair<Transitions.TransitionHandler, WindowContainerTransaction> handler =
+ mPlayer.dispatchRequest(transition, request, this);
+ if (handler == null) {
+ android.util.Log.e(Transitions.TAG, " No handler for remote? This is unexpected"
+ + ", there should at-least be RemoteHandler.");
+ return null;
+ }
+ final MixedTransition mixed = new MixedTransition(
+ MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition);
+ mixed.mLeftoversHandler = handler.first;
+ mActiveTransitions.add(mixed);
+ return handler.second;
}
return null;
}
@@ -179,7 +208,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
out.getChanges().add(info.getChanges().get(i));
}
}
- out.setRootLeash(info.getRootLeash(), info.getRootOffset().x, info.getRootOffset().y);
+ for (int i = 0; i < info.getRootCount(); ++i) {
+ out.addRoot(info.getRoot(i));
+ }
out.setAnimationOptions(info.getAnimationOptions());
return out;
}
@@ -214,6 +245,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
} else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
return animateOpenIntentWithRemoteAndPip(mixed, info, startTransaction,
finishTransaction, finishCallback);
+ } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
+ return animateRecentsDuringSplit(mixed, info, startTransaction, finishTransaction,
+ finishCallback);
} else {
mActiveTransitions.remove(mixed);
throw new IllegalStateException("Starting mixed animation without a known mixed type? "
@@ -439,12 +473,40 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
return true;
}
+ private boolean animateRecentsDuringSplit(@NonNull final MixedTransition mixed,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ // Split-screen is only interested in the recents transition finishing (and merging), so
+ // just wrap finish and start recents animation directly.
+ Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> {
+ mixed.mInFlightSubAnimations = 0;
+ mActiveTransitions.remove(mixed);
+ // If pair-to-pair switching, the post-recents clean-up isn't needed.
+ if (mixed.mAnimType != MixedTransition.ANIM_TYPE_PAIR_TO_PAIR) {
+ wct = wct != null ? wct : new WindowContainerTransaction();
+ mSplitHandler.onRecentsInSplitAnimationFinish(wct, finishTransaction);
+ }
+ mSplitHandler.onTransitionAnimationComplete();
+ finishCallback.onTransitionFinished(wct, wctCB);
+ };
+ mixed.mInFlightSubAnimations = 1;
+ mSplitHandler.onRecentsInSplitAnimationStart(startTransaction);
+ final boolean handled = mixed.mLeftoversHandler.startAnimation(mixed.mTransition, info,
+ startTransaction, finishTransaction, finishCB);
+ if (!handled) {
+ mActiveTransitions.remove(mixed);
+ }
+ return handled;
+ }
+
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
for (int i = 0; i < mActiveTransitions.size(); ++i) {
- if (mActiveTransitions.get(i) != mergeTarget) continue;
+ if (mActiveTransitions.get(i).mTransition != mergeTarget) continue;
MixedTransition mixed = mActiveTransitions.get(i);
if (mixed.mInFlightSubAnimations <= 0) {
// Already done, so no need to end it.
@@ -472,6 +534,14 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
finishCallback);
}
+ } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
+ if (mSplitHandler.isPendingEnter(transition)) {
+ // Recents -> enter-split means that we are switching from one pair to
+ // another pair.
+ mixed.mAnimType = MixedTransition.ANIM_TYPE_PAIR_TO_PAIR;
+ }
+ mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
+ finishCallback);
} else {
throw new IllegalStateException("Playing a mixed transition with unknown type? "
+ mixed.mType);
@@ -491,6 +561,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
if (mixed == null) return;
if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
mPipHandler.onTransitionConsumed(transition, aborted, finishT);
+ } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
+ mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index f66c26bb87e4..63c7969291a0 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
@@ -383,9 +383,10 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
continue;
}
// No default animation for this, so just update bounds/position.
+ final int rootIdx = TransitionUtil.rootIndexFor(change, info);
startTransaction.setPosition(change.getLeash(),
- change.getEndAbsBounds().left - info.getRootOffset().x,
- change.getEndAbsBounds().top - info.getRootOffset().y);
+ change.getEndAbsBounds().left - info.getRoot(rootIdx).getOffset().x,
+ change.getEndAbsBounds().top - info.getRoot(rootIdx).getOffset().y);
// Seamless display transition doesn't need to animate.
if (isSeamlessDisplayChange) continue;
if (isTask || (change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)
@@ -474,8 +475,10 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
}
if (backgroundColorForTransition != 0) {
- addBackgroundToTransition(info.getRootLeash(), backgroundColorForTransition,
- startTransaction, finishTransaction);
+ for (int i = 0; i < info.getRootCount(); ++i) {
+ addBackgroundToTransition(info.getRoot(i).getLeash(), backgroundColorForTransition,
+ startTransaction, finishTransaction);
+ }
}
if (postStartTransactionCallbacks.size() > 0) {
@@ -520,8 +523,10 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
private void startRotationAnimation(SurfaceControl.Transaction startTransaction,
TransitionInfo.Change change, TransitionInfo info, int animHint,
ArrayList<Animator> animations, Runnable onAnimFinish) {
+ final int rootIdx = TransitionUtil.rootIndexFor(change, info);
final ScreenRotationAnimation anim = new ScreenRotationAnimation(mContext, mSurfaceSession,
- mTransactionPool, startTransaction, change, info.getRootLeash(), animHint);
+ mTransactionPool, startTransaction, change, info.getRoot(rootIdx).getLeash(),
+ animHint);
// The rotation animation may consist of 3 animations: fade-out screenshot, fade-in real
// content, and background color. The item of "animGroup" will be removed if the sub
// animation is finished. Then if the list becomes empty, the rotation animation is done.
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 155990a40836..039bde95815e 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
@@ -75,11 +75,29 @@ import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
import java.util.Arrays;
-/** Plays transition animations */
+/**
+ * Plays transition animations. Within this player, each transition has a lifecycle.
+ * 1. When a transition is directly started or requested, it is added to "pending" state.
+ * 2. Once WMCore applies the transition and notifies, the transition moves to "ready" state.
+ * 3. When a transition starts animating, it is moved to the "active" state.
+ *
+ * Basically: --start--> PENDING --onTransitionReady--> READY --play--> ACTIVE --finish--> |
+ * --merge--> MERGED --^
+ *
+ * At the moment, only one transition can be animating at a time. While a transition is animating,
+ * transitions will be queued in the "ready" state for their turn. At the same time, whenever a
+ * transition makes it to the head of the "ready" queue, it will attempt to merge to with the
+ * "active" transition. If the merge succeeds, it will be moved to the "active" transition's
+ * "merged" and then the next "ready" transition can attempt to merge.
+ *
+ * Once the "active" transition animation is finished, it will be removed from the "active" list
+ * and then the next "ready" transition can play.
+ */
public class Transitions implements RemoteCallable<Transitions> {
static final String TAG = "ShellTransitions";
@@ -150,14 +168,22 @@ public class Transitions implements RemoteCallable<Transitions> {
private static final class ActiveTransition {
IBinder mToken;
TransitionHandler mHandler;
- boolean mMerged;
boolean mAborted;
TransitionInfo mInfo;
SurfaceControl.Transaction mStartT;
SurfaceControl.Transaction mFinishT;
+
+ /** Ordered list of transitions which have been merged into this one. */
+ private ArrayList<ActiveTransition> mMerged;
}
- /** Keeps track of currently playing transitions in the order of receipt. */
+ /** Keeps track of transitions which have been started, but aren't ready yet. */
+ private final ArrayList<ActiveTransition> mPendingTransitions = new ArrayList<>();
+
+ /** Keeps track of transitions which are ready to play but still waiting for their turn. */
+ private final ArrayList<ActiveTransition> mReadyTransitions = new ArrayList<>();
+
+ /** Keeps track of currently playing transitions. For now, there can only be 1 max. */
private final ArrayList<ActiveTransition> mActiveTransitions = new ArrayList<>();
public Transitions(@NonNull Context context,
@@ -322,7 +348,8 @@ public class Transitions implements RemoteCallable<Transitions> {
* will be executed when the last active transition is finished.
*/
public void runOnIdle(Runnable runnable) {
- if (mActiveTransitions.isEmpty()) {
+ if (mActiveTransitions.isEmpty() && mPendingTransitions.isEmpty()
+ && mReadyTransitions.isEmpty()) {
runnable.run();
} else {
mRunWhenIdleQueue.add(runnable);
@@ -389,8 +416,8 @@ public class Transitions implements RemoteCallable<Transitions> {
private static void setupAnimHierarchy(@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
boolean isOpening = isOpeningType(info.getType());
- if (info.getRootLeash().isValid()) {
- t.show(info.getRootLeash());
+ for (int i = 0; i < info.getRootCount(); ++i) {
+ t.show(info.getRoot(i).getLeash());
}
final int numChanges = info.getChanges().size();
// Put animating stuff above this line and put static stuff below it.
@@ -408,10 +435,12 @@ public class Transitions implements RemoteCallable<Transitions> {
boolean hasParent = change.getParent() != null;
+ final int rootIdx = TransitionUtil.rootIndexFor(change, info);
if (!hasParent) {
- t.reparent(leash, info.getRootLeash());
- t.setPosition(leash, change.getStartAbsBounds().left - info.getRootOffset().x,
- change.getStartAbsBounds().top - info.getRootOffset().y);
+ t.reparent(leash, info.getRoot(rootIdx).getLeash());
+ t.setPosition(leash,
+ change.getStartAbsBounds().left - info.getRoot(rootIdx).getOffset().x,
+ change.getStartAbsBounds().top - info.getRoot(rootIdx).getOffset().y);
}
final int layer;
// Put all the OPEN/SHOW on top
@@ -441,9 +470,9 @@ public class Transitions implements RemoteCallable<Transitions> {
}
}
- private int findActiveTransition(IBinder token) {
- for (int i = mActiveTransitions.size() - 1; i >= 0; --i) {
- if (mActiveTransitions.get(i).mToken == token) return i;
+ private static int findByToken(ArrayList<ActiveTransition> list, IBinder token) {
+ for (int i = list.size() - 1; i >= 0; --i) {
+ if (list.get(i).mToken == token) return i;
}
return -1;
}
@@ -481,14 +510,24 @@ public class Transitions implements RemoteCallable<Transitions> {
@NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s",
transitionToken, info);
- final int activeIdx = findActiveTransition(transitionToken);
+ final int activeIdx = findByToken(mPendingTransitions, transitionToken);
if (activeIdx < 0) {
- throw new IllegalStateException("Got transitionReady for non-active transition "
+ throw new IllegalStateException("Got transitionReady for non-pending transition "
+ transitionToken + ". expecting one of "
- + Arrays.toString(mActiveTransitions.stream().map(
+ + Arrays.toString(mPendingTransitions.stream().map(
+ activeTransition -> activeTransition.mToken).toArray()));
+ }
+ if (activeIdx > 0) {
+ Log.e(TAG, "Transition became ready out-of-order " + transitionToken + ". Expected"
+ + " order: " + Arrays.toString(mPendingTransitions.stream().map(
activeTransition -> activeTransition.mToken).toArray()));
}
- final ActiveTransition active = mActiveTransitions.get(activeIdx);
+ // Move from pending to ready
+ final ActiveTransition active = mPendingTransitions.remove(activeIdx);
+ mReadyTransitions.add(active);
+ active.mInfo = info;
+ active.mStartT = t;
+ active.mFinishT = finishT;
for (int i = 0; i < mObservers.size(); ++i) {
mObservers.get(i).onTransitionReady(transitionToken, info, t, finishT);
@@ -496,15 +535,6 @@ public class Transitions implements RemoteCallable<Transitions> {
if (info.getType() == TRANSIT_SLEEP) {
if (activeIdx > 0) {
- active.mInfo = info;
- active.mStartT = t;
- active.mFinishT = finishT;
- if (!info.getRootLeash().isValid()) {
- // Shell has some debug settings which makes calling binders with invalid
- // surfaces crash, so replace it with a "real" one.
- info.setRootLeash(new SurfaceControl.Builder().setName("Invalid")
- .setContainerLayer().build(), 0, 0);
- }
// Sleep starts a process of forcing all prior transitions to finish immediately
finishForSleep(null /* forceFinish */);
return;
@@ -513,14 +543,12 @@ public class Transitions implements RemoteCallable<Transitions> {
// Allow to notify keyguard un-occluding state to KeyguardService, which can happen while
// screen-off, so there might no visibility change involved.
- if (!info.getRootLeash().isValid() && info.getType() != TRANSIT_KEYGUARD_UNOCCLUDE) {
- // Invalid root-leash implies that the transition is empty/no-op, so just do
+ if (info.getRootCount() == 0 && info.getType() != TRANSIT_KEYGUARD_UNOCCLUDE) {
+ // No root-leashes implies that the transition is empty/no-op, so just do
// housekeeping and return.
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Invalid root leash (%s): %s",
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "No transition roots (%s): %s",
transitionToken, info);
- t.apply();
- finishT.apply();
- onAbort(transitionToken);
+ onAbort(active);
return;
}
@@ -546,40 +574,90 @@ public class Transitions implements RemoteCallable<Transitions> {
// changes are underneath another change.
|| ((info.getType() == TRANSIT_TO_BACK || info.getType() == TRANSIT_TO_FRONT)
&& allOccluded)) {
- t.apply();
- finishT.apply();
// Treat this as an abort since we are bypassing any merge logic and effectively
// finishing immediately.
- onAbort(transitionToken);
- releaseSurfaces(info);
+ onAbort(active);
return;
}
- active.mInfo = info;
- active.mStartT = t;
- active.mFinishT = finishT;
setupStartState(active.mInfo, active.mStartT, active.mFinishT);
- if (activeIdx > 0) {
- // This is now playing at the same time as an existing animation, so try merging it.
- attemptMergeTransition(mActiveTransitions.get(0), active);
+ if (mReadyTransitions.size() > 1) {
+ // There are already transitions waiting in the queue, so just return.
return;
}
- // The normal case, just play it.
- playTransition(active);
+ processReadyQueue();
}
- /**
- * Attempt to merge by delegating the transition start to the handler of the currently
- * playing transition.
- */
- void attemptMergeTransition(@NonNull ActiveTransition playing,
- @NonNull ActiveTransition merging) {
+ void processReadyQueue() {
+ if (mReadyTransitions.isEmpty()) {
+ // Check if idle.
+ if (mActiveTransitions.isEmpty() && mPendingTransitions.isEmpty()) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition "
+ + "animations finished");
+ // Run all runnables from the run-when-idle queue.
+ for (int i = 0; i < mRunWhenIdleQueue.size(); i++) {
+ mRunWhenIdleQueue.get(i).run();
+ }
+ mRunWhenIdleQueue.clear();
+ }
+ return;
+ }
+ final ActiveTransition ready = mReadyTransitions.get(0);
+ if (mActiveTransitions.isEmpty()) {
+ // The normal case, just play it (currently we only support 1 active transition).
+ mReadyTransitions.remove(0);
+ mActiveTransitions.add(ready);
+ if (ready.mAborted) {
+ // finish now since there's nothing to animate. Calls back into processReadyQueue
+ onFinish(ready, null, null);
+ return;
+ }
+ playTransition(ready);
+ // Attempt to merge any more queued-up transitions.
+ processReadyQueue();
+ return;
+ }
+ // An existing animation is playing, so see if we can merge.
+ final ActiveTransition playing = mActiveTransitions.get(0);
+ if (ready.mAborted) {
+ // record as merged since it is no-op. Calls back into processReadyQueue
+ onMerged(playing, ready);
+ return;
+ }
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition %s ready while"
+ " another transition %s is still animating. Notify the animating transition"
- + " in case they can be merged", merging.mToken, playing.mToken);
- playing.mHandler.mergeAnimation(merging.mToken, merging.mInfo, merging.mStartT,
- playing.mToken, (wct, cb) -> onFinish(merging.mToken, wct, cb));
+ + " in case they can be merged", ready.mToken, playing.mToken);
+ playing.mHandler.mergeAnimation(ready.mToken, ready.mInfo, ready.mStartT,
+ playing.mToken, (wct, cb) -> onMerged(playing, ready));
+ }
+
+ private void onMerged(@NonNull ActiveTransition playing, @NonNull ActiveTransition merged) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition was merged %s",
+ merged.mToken);
+ int readyIdx = 0;
+ if (mReadyTransitions.isEmpty() || mReadyTransitions.get(0) != merged) {
+ Log.e(TAG, "Merged transition out-of-order?");
+ readyIdx = mReadyTransitions.indexOf(merged);
+ if (readyIdx < 0) {
+ Log.e(TAG, "Merged a transition that is no-longer queued?");
+ return;
+ }
+ }
+ mReadyTransitions.remove(readyIdx);
+ if (playing.mMerged == null) {
+ playing.mMerged = new ArrayList<>();
+ }
+ playing.mMerged.add(merged);
+ // if it was aborted, then onConsumed has already been reported.
+ if (merged.mHandler != null && !merged.mAborted) {
+ merged.mHandler.onTransitionConsumed(merged.mToken, false /* abort */, merged.mFinishT);
+ }
+ for (int i = 0; i < mObservers.size(); ++i) {
+ mObservers.get(i).onTransitionMerged(merged.mToken, playing.mToken);
+ }
+ // See if we should merge another transition.
+ processReadyQueue();
}
private void playTransition(@NonNull ActiveTransition active) {
@@ -594,7 +672,7 @@ public class Transitions implements RemoteCallable<Transitions> {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try firstHandler %s",
active.mHandler);
boolean consumed = active.mHandler.startAnimation(active.mToken, active.mInfo,
- active.mStartT, active.mFinishT, (wct, cb) -> onFinish(active.mToken, wct, cb));
+ active.mStartT, active.mFinishT, (wct, cb) -> onFinish(active, wct, cb));
if (consumed) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by firstHandler");
return;
@@ -602,7 +680,7 @@ public class Transitions implements RemoteCallable<Transitions> {
}
// Otherwise give every other handler a chance
active.mHandler = dispatchTransition(active.mToken, active.mInfo, active.mStartT,
- active.mFinishT, (wct, cb) -> onFinish(active.mToken, wct, cb), active.mHandler);
+ active.mFinishT, (wct, cb) -> onFinish(active, wct, cb), active.mHandler);
}
/**
@@ -645,15 +723,28 @@ public class Transitions implements RemoteCallable<Transitions> {
return null;
}
- /** Special version of finish just for dealing with no-op/invalid transitions. */
- private void onAbort(IBinder transition) {
- onFinish(transition, null /* wct */, null /* wctCB */, true /* abort */);
- }
+ /** Aborts a transition. This will still queue it up to maintain order. */
+ private void onAbort(ActiveTransition transition) {
+ // apply immediately since they may be "parallel" operations: We currently we use abort for
+ // thing which are independent to other transitions (like starting-window transfer).
+ transition.mStartT.apply();
+ transition.mFinishT.apply();
+ transition.mAborted = true;
- private void onFinish(IBinder transition,
- @Nullable WindowContainerTransaction wct,
- @Nullable WindowContainerTransactionCallback wctCB) {
- onFinish(transition, wct, wctCB, false /* abort */);
+ if (transition.mHandler != null) {
+ // Notifies to clean-up the aborted transition.
+ transition.mHandler.onTransitionConsumed(
+ transition.mToken, true /* aborted */, null /* finishTransaction */);
+ }
+
+ releaseSurfaces(transition.mInfo);
+
+ // This still went into the queue (to maintain the correct finish ordering).
+ if (mReadyTransitions.size() > 1) {
+ // There are already transitions waiting in the queue, so just return.
+ return;
+ }
+ processReadyQueue();
}
/**
@@ -665,167 +756,97 @@ public class Transitions implements RemoteCallable<Transitions> {
info.releaseAnimSurfaces();
}
- private void onFinish(IBinder transition,
+ private void onFinish(ActiveTransition active,
@Nullable WindowContainerTransaction wct,
- @Nullable WindowContainerTransactionCallback wctCB,
- boolean abort) {
- int activeIdx = findActiveTransition(transition);
+ @Nullable WindowContainerTransactionCallback wctCB) {
+ int activeIdx = mActiveTransitions.indexOf(active);
if (activeIdx < 0) {
Log.e(TAG, "Trying to finish a non-running transition. Either remote crashed or "
- + " a handler didn't properly deal with a merge.", new RuntimeException());
+ + " a handler didn't properly deal with a merge. " + active.mToken,
+ new RuntimeException());
return;
- } else if (activeIdx > 0) {
- // This transition was merged.
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition was merged (abort=%b:"
- + " %s", abort, transition);
- final ActiveTransition active = mActiveTransitions.get(activeIdx);
- active.mMerged = true;
- active.mAborted = abort;
- if (active.mHandler != null) {
- active.mHandler.onTransitionConsumed(
- active.mToken, abort, abort ? null : active.mFinishT);
- }
- for (int i = 0; i < mObservers.size(); ++i) {
- mObservers.get(i).onTransitionMerged(
- active.mToken, mActiveTransitions.get(0).mToken);
- }
- return;
- }
- final ActiveTransition active = mActiveTransitions.get(activeIdx);
- active.mAborted = abort;
- if (active.mAborted && active.mHandler != null) {
- // Notifies to clean-up the aborted transition.
- active.mHandler.onTransitionConsumed(
- transition, true /* aborted */, null /* finishTransaction */);
+ } else if (activeIdx != 0) {
+ // Relevant right now since we only allow 1 active transition at a time.
+ Log.e(TAG, "Finishing a transition out of order. " + active.mToken);
}
+ mActiveTransitions.remove(activeIdx);
+
for (int i = 0; i < mObservers.size(); ++i) {
mObservers.get(i).onTransitionFinished(active.mToken, active.mAborted);
}
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
- "Transition animation finished (abort=%b), notifying core %s", abort, transition);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition animation finished "
+ + "(aborted=%b), notifying core %s", active.mAborted, active.mToken);
if (active.mStartT != null) {
// Applied by now, so clear immediately to remove any references. Do not set to null
// yet, though, since nullness is used later to disambiguate malformed transitions.
active.mStartT.clear();
}
- // Merge all relevant transactions together
+ // Merge all associated transactions together
SurfaceControl.Transaction fullFinish = active.mFinishT;
- for (int iA = activeIdx + 1; iA < mActiveTransitions.size(); ++iA) {
- final ActiveTransition toMerge = mActiveTransitions.get(iA);
- if (!toMerge.mMerged) break;
- // Include start. It will be a no-op if it was already applied. Otherwise, we need it
- // to maintain consistent state.
- if (toMerge.mStartT != null) {
- if (fullFinish == null) {
- fullFinish = toMerge.mStartT;
- } else {
- fullFinish.merge(toMerge.mStartT);
+ if (active.mMerged != null) {
+ for (int iM = 0; iM < active.mMerged.size(); ++iM) {
+ final ActiveTransition toMerge = active.mMerged.get(iM);
+ // Include start. It will be a no-op if it was already applied. Otherwise, we need
+ // it to maintain consistent state.
+ if (toMerge.mStartT != null) {
+ if (fullFinish == null) {
+ fullFinish = toMerge.mStartT;
+ } else {
+ fullFinish.merge(toMerge.mStartT);
+ }
}
- }
- if (toMerge.mFinishT != null) {
- if (fullFinish == null) {
- fullFinish = toMerge.mFinishT;
- } else {
- fullFinish.merge(toMerge.mFinishT);
+ if (toMerge.mFinishT != null) {
+ if (fullFinish == null) {
+ fullFinish = toMerge.mFinishT;
+ } else {
+ fullFinish.merge(toMerge.mFinishT);
+ }
}
}
}
if (fullFinish != null) {
fullFinish.apply();
}
- // Now perform all the finishes.
+ // Now perform all the finish callbacks (starting with the playing one and then all the
+ // transitions merged into it).
releaseSurfaces(active.mInfo);
- mActiveTransitions.remove(activeIdx);
- mOrganizer.finishTransition(transition, wct, wctCB);
- while (activeIdx < mActiveTransitions.size()) {
- if (!mActiveTransitions.get(activeIdx).mMerged) break;
- ActiveTransition merged = mActiveTransitions.remove(activeIdx);
- mOrganizer.finishTransition(merged.mToken, null /* wct */, null /* wctCB */);
- releaseSurfaces(merged.mInfo);
- }
- // sift through aborted transitions
- while (mActiveTransitions.size() > activeIdx
- && mActiveTransitions.get(activeIdx).mAborted) {
- ActiveTransition aborted = mActiveTransitions.remove(activeIdx);
- // Notifies to clean-up the aborted transition.
- if (aborted.mHandler != null) {
- aborted.mHandler.onTransitionConsumed(
- transition, true /* aborted */, null /* finishTransaction */);
+ mOrganizer.finishTransition(active.mToken, wct, wctCB);
+ if (active.mMerged != null) {
+ for (int iM = 0; iM < active.mMerged.size(); ++iM) {
+ ActiveTransition merged = active.mMerged.get(iM);
+ mOrganizer.finishTransition(merged.mToken, null /* wct */, null /* wctCB */);
+ releaseSurfaces(merged.mInfo);
}
- mOrganizer.finishTransition(aborted.mToken, null /* wct */, null /* wctCB */);
- for (int i = 0; i < mObservers.size(); ++i) {
- mObservers.get(i).onTransitionFinished(aborted.mToken, true);
- }
- releaseSurfaces(aborted.mInfo);
- }
- if (mActiveTransitions.size() <= activeIdx) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition animations "
- + "finished");
- // Run all runnables from the run-when-idle queue.
- for (int i = 0; i < mRunWhenIdleQueue.size(); i++) {
- mRunWhenIdleQueue.get(i).run();
- }
- mRunWhenIdleQueue.clear();
- return;
+ active.mMerged.clear();
}
- // Start animating the next active transition
- final ActiveTransition next = mActiveTransitions.get(activeIdx);
- if (next.mInfo == null) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Pending transition after one"
- + " finished, but it isn't ready yet.");
- return;
+
+ // Now that this is done, check the ready queue for more work.
+ processReadyQueue();
+ }
+
+ private boolean isTransitionKnown(IBinder token) {
+ for (int i = 0; i < mPendingTransitions.size(); ++i) {
+ if (mPendingTransitions.get(i).mToken == token) return true;
}
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Pending transitions after one"
- + " finished, so start the next one.");
- playTransition(next);
- // Now try to merge the rest of the transitions (re-acquire activeIdx since next may have
- // finished immediately)
- activeIdx = findActiveTransition(next.mToken);
- if (activeIdx < 0) {
- // This means 'next' finished immediately and thus re-entered this function. Since
- // that is the case, just return here since all relevant logic has already run in the
- // re-entered call.
- return;
+ for (int i = 0; i < mReadyTransitions.size(); ++i) {
+ if (mReadyTransitions.get(i).mToken == token) return true;
}
-
- // This logic is also convoluted because 'next' may finish immediately in response to any of
- // the merge requests (eg. if it decided to "cancel" itself).
- int mergeIdx = activeIdx + 1;
- while (mergeIdx < mActiveTransitions.size()) {
- ActiveTransition mergeCandidate = mActiveTransitions.get(mergeIdx);
- if (mergeCandidate.mAborted) {
- // transition was aborted, so we can skip for now (still leave it in the list
- // so that it gets cleaned-up in the right order).
- ++mergeIdx;
- continue;
- }
- if (mergeCandidate.mInfo == null) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition merge candidate"
- + " %s is not ready yet", mergeCandidate.mToken);
- // The later transition should not be merged if the prior one is not ready.
- return;
- }
- if (mergeCandidate.mMerged) {
- throw new IllegalStateException("Can't merge a transition after not-merging"
- + " a preceding one.");
- }
- attemptMergeTransition(next, mergeCandidate);
- mergeIdx = findActiveTransition(mergeCandidate.mToken);
- if (mergeIdx < 0) {
- // This means 'next' finished immediately and thus re-entered this function. Since
- // that is the case, just return here since all relevant logic has already run in
- // the re-entered call.
- return;
+ for (int i = 0; i < mActiveTransitions.size(); ++i) {
+ final ActiveTransition active = mActiveTransitions.get(i);
+ if (active.mToken == token) return true;
+ if (active.mMerged == null) continue;
+ for (int m = 0; m < active.mMerged.size(); ++m) {
+ if (active.mMerged.get(m).mToken == token) return true;
}
- ++mergeIdx;
}
+ return false;
}
void requestStartTransition(@NonNull IBinder transitionToken,
@Nullable TransitionRequestInfo request) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested: %s %s",
transitionToken, request);
- if (findActiveTransition(transitionToken) >= 0) {
+ if (isTransitionKnown(transitionToken)) {
throw new RuntimeException("Transition already started " + transitionToken);
}
final ActiveTransition active = new ActiveTransition();
@@ -858,15 +879,13 @@ public class Transitions implements RemoteCallable<Transitions> {
}
mOrganizer.startTransition(transitionToken, wct != null && wct.isEmpty() ? null : wct);
active.mToken = transitionToken;
- int insertIdx = 0;
- for (; insertIdx < mActiveTransitions.size(); ++insertIdx) {
- if (mActiveTransitions.get(insertIdx).mInfo == null) {
- // A `startNewTransition` was sent to WMCore, but wasn't acknowledged before WMCore
- // made this request, so insert this request beforehand to keep order in sync.
- break;
- }
- }
- mActiveTransitions.add(insertIdx, active);
+ // Currently, WMCore only does one transition at a time. If it makes a requestStart, it
+ // is already collecting that transition on core-side, so it will be the next one to
+ // become ready. There may already be pending transitions added as part of direct
+ // `startNewTransition` but if we have a request now, it means WM created the request
+ // transition before it acknowledged any of the pending `startNew` transitions. So, insert
+ // it at the front.
+ mPendingTransitions.add(0, active);
}
/** Start a new transition directly. */
@@ -875,7 +894,7 @@ public class Transitions implements RemoteCallable<Transitions> {
final ActiveTransition active = new ActiveTransition();
active.mHandler = handler;
active.mToken = mOrganizer.startNewTransition(type, wct);
- mActiveTransitions.add(active);
+ mPendingTransitions.add(active);
return active.mToken;
}
@@ -894,27 +913,38 @@ public class Transitions implements RemoteCallable<Transitions> {
* @param forceFinish When non-null, this is the transition that we last sent the SLEEP merge
* signal to -- so it will be force-finished if it's still running.
*/
- private void finishForSleep(@Nullable IBinder forceFinish) {
- if (mActiveTransitions.isEmpty() || mSleepHandler.mSleepTransitions.isEmpty()) {
+ private void finishForSleep(@Nullable ActiveTransition forceFinish) {
+ if ((mActiveTransitions.isEmpty() && mReadyTransitions.isEmpty())
+ || mSleepHandler.mSleepTransitions.isEmpty()) {
+ // Done finishing things.
+ // Prevent any weird leaks... shouldn't happen though.
+ mSleepHandler.mSleepTransitions.clear();
return;
}
- if (forceFinish != null && mActiveTransitions.get(0).mToken == forceFinish) {
+ if (forceFinish != null && mActiveTransitions.contains(forceFinish)) {
Log.e(TAG, "Forcing transition to finish due to sleep timeout: "
- + mActiveTransitions.get(0).mToken);
- onFinish(mActiveTransitions.get(0).mToken, null, null, true);
+ + forceFinish.mToken);
+ forceFinish.mAborted = true;
+ // Last notify of it being consumed. Note: mHandler should never be null,
+ // but check just to be safe.
+ if (forceFinish.mHandler != null) {
+ forceFinish.mHandler.onTransitionConsumed(
+ forceFinish.mToken, true /* aborted */, null /* finishTransaction */);
+ }
+ onFinish(forceFinish, null, null);
}
final SurfaceControl.Transaction dummyT = new SurfaceControl.Transaction();
while (!mActiveTransitions.isEmpty() && !mSleepHandler.mSleepTransitions.isEmpty()) {
final ActiveTransition playing = mActiveTransitions.get(0);
- int sleepIdx = findActiveTransition(mSleepHandler.mSleepTransitions.get(0));
+ int sleepIdx = findByToken(mReadyTransitions, mSleepHandler.mSleepTransitions.get(0));
if (sleepIdx >= 0) {
// Try to signal that we are sleeping by attempting to merge the sleep transition
// into the playing one.
- final ActiveTransition nextSleep = mActiveTransitions.get(sleepIdx);
+ final ActiveTransition nextSleep = mReadyTransitions.get(sleepIdx);
playing.mHandler.mergeAnimation(nextSleep.mToken, nextSleep.mInfo, dummyT,
playing.mToken, (wct, cb) -> {});
} else {
- Log.e(TAG, "Couldn't find sleep transition in active list: "
+ Log.e(TAG, "Couldn't find sleep transition in ready list: "
+ mSleepHandler.mSleepTransitions.get(0));
}
// it's possible to complete immediately. If that happens, just repeat the signal
@@ -922,8 +952,7 @@ public class Transitions implements RemoteCallable<Transitions> {
// finishing immediately.
if (!mActiveTransitions.isEmpty() && mActiveTransitions.get(0) == playing) {
// Give it a (very) short amount of time to process it before forcing.
- mMainExecutor.executeDelayed(
- () -> finishForSleep(playing.mToken), SLEEP_ALLOWANCE_MS);
+ mMainExecutor.executeDelayed(() -> finishForSleep(playing), SLEEP_ALLOWANCE_MS);
break;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
index 8c6e1e7f5f1b..7595c9617709 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
@@ -139,11 +139,12 @@ public class TransitionUtil {
// changes should be ordered top-to-bottom in z
final int mode = change.getMode();
- t.reparent(leash, info.getRootLeash());
+ final int rootIdx = TransitionUtil.rootIndexFor(change, info);
+ t.reparent(leash, info.getRoot(rootIdx).getLeash());
final Rect absBounds =
(mode == TRANSIT_OPEN) ? change.getEndAbsBounds() : change.getStartAbsBounds();
- t.setPosition(leash, absBounds.left - info.getRootOffset().x,
- absBounds.top - info.getRootOffset().y);
+ t.setPosition(leash, absBounds.left - info.getRoot(rootIdx).getOffset().x,
+ absBounds.top - info.getRoot(rootIdx).getOffset().y);
// Put all the OPEN/SHOW on top
if (TransitionUtil.isOpeningType(mode)) {
@@ -179,12 +180,13 @@ public class TransitionUtil {
// making leashes means we have to handle them specially.
return change.getLeash();
}
+ final int rootIdx = TransitionUtil.rootIndexFor(change, info);
SurfaceControl leashSurface = new SurfaceControl.Builder()
.setName(change.getLeash().toString() + "_transition-leash")
.setContainerLayer()
// Initial the surface visible to respect the visibility of the original surface.
.setHidden(false)
- .setParent(info.getRootLeash())
+ .setParent(info.getRoot(rootIdx).getLeash())
.build();
// Copied Transitions setup code (which expects bottom-to-top order, so we swap here)
setupLeash(leashSurface, change, info.getChanges().size() - order, info, t);
@@ -261,4 +263,18 @@ public class TransitionUtil {
target.setRotationChange(change.getEndRotation() - change.getStartRotation());
return target;
}
+
+ /**
+ * Finds the "correct" root idx for a change. The change's end display is prioritized, then
+ * the start display. If there is no display, it will fallback on the 0th root in the
+ * transition. There MUST be at-least 1 root in the transition (ie. it's not a no-op).
+ */
+ public static int rootIndexFor(@NonNull TransitionInfo.Change change,
+ @NonNull TransitionInfo info) {
+ int rootIdx = info.findRootIndex(change.getEndDisplayId());
+ if (rootIdx >= 0) return rootIdx;
+ rootIdx = info.findRootIndex(change.getStartDisplayId());
+ if (rootIdx >= 0) return rootIdx;
+ return 0;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 9224b3cbd798..6b7ca421f5ed 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -193,6 +193,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
private final DragDetector mDragDetector;
private int mDragPointerId = -1;
+ private boolean mIsDragging;
private CaptionTouchEventListener(
RunningTaskInfo taskInfo,
@@ -223,19 +224,15 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
if (v.getId() != R.id.caption) {
return false;
}
- mDragDetector.onMotionEvent(e);
-
- if (e.getAction() != MotionEvent.ACTION_DOWN) {
- return false;
- }
- final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
- if (taskInfo.isFocused) {
- return false;
+ if (e.getAction() == MotionEvent.ACTION_DOWN) {
+ final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
+ if (!taskInfo.isFocused) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.reorder(mTaskToken, true /* onTop */);
+ mSyncQueue.queue(wct);
+ }
}
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.reorder(mTaskToken, true /* onTop */);
- mSyncQueue.queue(wct);
- return true;
+ return mDragDetector.onMotionEvent(e);
}
/**
@@ -253,20 +250,24 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
mDragPointerId = e.getPointerId(0);
mDragPositioningCallback.onDragPositioningStart(
0 /* ctrlType */, e.getRawX(0), e.getRawY(0));
- break;
+ mIsDragging = false;
+ return false;
}
case MotionEvent.ACTION_MOVE: {
int dragPointerIdx = e.findPointerIndex(mDragPointerId);
mDragPositioningCallback.onDragPositioningMove(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
- break;
+ mIsDragging = true;
+ return true;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
int dragPointerIdx = e.findPointerIndex(mDragPointerId);
mDragPositioningCallback.onDragPositioningEnd(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
- break;
+ final boolean wasDragging = mIsDragging;
+ mIsDragging = false;
+ return wasDragging;
}
}
return true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 2aa6d1217fa6..6b45149e35a2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -223,6 +223,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private final DragPositioningCallback mDragPositioningCallback;
private final DragDetector mDragDetector;
+ private boolean mIsDragging;
private int mDragPointerId = -1;
private DesktopModeTouchEventListener(
@@ -273,23 +274,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
if (id != R.id.caption_handle && id != R.id.desktop_mode_caption) {
return false;
}
- switch (e.getAction()) {
- case MotionEvent.ACTION_DOWN:
- mDragDetector.onMotionEvent(e);
- final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
- if (taskInfo.isFocused) {
- return mDragDetector.isDragEvent();
- }
- return false;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- boolean res = mDragDetector.isDragEvent();
- mDragDetector.onMotionEvent(e);
- return res;
- default:
- mDragDetector.onMotionEvent(e);
- return mDragDetector.isDragEvent();
- }
+ return mDragDetector.onMotionEvent(e);
}
/**
@@ -313,13 +298,15 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mDragPointerId = e.getPointerId(0);
mDragPositioningCallback.onDragPositioningStart(
0 /* ctrlType */, e.getRawX(0), e.getRawY(0));
- break;
+ mIsDragging = false;
+ return false;
}
case MotionEvent.ACTION_MOVE: {
final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
mDragPositioningCallback.onDragPositioningMove(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
- break;
+ mIsDragging = true;
+ return true;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
@@ -336,7 +323,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
c -> c.moveToFullscreen(taskInfo));
}
}
- break;
+ final boolean wasDragging = mIsDragging;
+ mIsDragging = false;
+ return wasDragging;
}
}
return true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 72da1089c91c..3c0ef965f4f5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -23,6 +23,7 @@ import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Point;
@@ -46,6 +47,7 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.desktopmode.DesktopTasksController;
/**
* Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with
@@ -95,6 +97,17 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mDesktopActive = DesktopModeStatus.isActive(mContext);
}
+ @Override
+ protected Configuration getConfigurationWithOverrides(
+ ActivityManager.RunningTaskInfo taskInfo) {
+ Configuration configuration = taskInfo.getConfiguration();
+ if (DesktopTasksController.isDesktopDensityOverrideSet()) {
+ // Density is overridden for desktop tasks. Keep system density for window decoration.
+ configuration.densityDpi = mContext.getResources().getConfiguration().densityDpi;
+ }
+ return configuration;
+ }
+
void setCaptionListeners(
View.OnClickListener onCaptionButtonClickListener,
View.OnTouchListener onCaptionTouchListener) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
index cf1850b92373..65b5a7a17afe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
@@ -56,10 +56,15 @@ class DragDetector {
* {@link #mEventHandler} handles the previous down event if the event shouldn't be passed
*/
boolean onMotionEvent(MotionEvent ev) {
+ final boolean isTouchScreen =
+ (ev.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN;
+ if (!isTouchScreen) {
+ // Only touches generate noisy moves, so mouse/trackpad events don't need to filtered
+ // to take the slop threshold into consideration.
+ return mEventHandler.handleMotionEvent(ev);
+ }
switch (ev.getActionMasked()) {
case ACTION_DOWN: {
- // Only touch screens generate noisy moves.
- mIsDragEvent = (ev.getSource() & SOURCE_TOUCHSCREEN) != SOURCE_TOUCHSCREEN;
mDragPointerId = ev.getPointerId(0);
float rawX = ev.getRawX(0);
float rawY = ev.getRawY(0);
@@ -72,8 +77,12 @@ class DragDetector {
int dragPointerIndex = ev.findPointerIndex(mDragPointerId);
float dx = ev.getRawX(dragPointerIndex) - mInputDownPoint.x;
float dy = ev.getRawY(dragPointerIndex) - mInputDownPoint.y;
+ // Touches generate noisy moves, so only once the move is past the touch
+ // slop threshold should it be considered a drag.
mIsDragEvent = Math.hypot(dx, dy) > mTouchSlop;
}
+ // The event handler should only be notified about 'move' events if a drag has been
+ // detected.
if (mIsDragEvent) {
return mEventHandler.handleMotionEvent(ev);
} else {
@@ -94,10 +103,6 @@ class DragDetector {
mTouchSlop = touchSlop;
}
- boolean isDragEvent() {
- return mIsDragEvent;
- }
-
private void resetState() {
mIsDragEvent = false;
mInputDownPoint.set(0, 0);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/OWNERS
new file mode 100644
index 000000000000..4417209b85ed
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/OWNERS
@@ -0,0 +1 @@
+jorgegil@google.com
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
index a3d364a0068e..0bce3acecb3c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
@@ -40,6 +40,7 @@ class TaskPositioner implements DragPositioningCallback {
private final DisplayController mDisplayController;
private final WindowDecoration mWindowDecoration;
+ private final Rect mTempBounds = new Rect();
private final Rect mTaskBoundsAtDragStart = new Rect();
private final PointF mRepositionStartPoint = new PointF();
private final Rect mRepositionTaskBounds = new Rect();
@@ -117,17 +118,32 @@ class TaskPositioner implements DragPositioningCallback {
final float deltaX = x - mRepositionStartPoint.x;
final float deltaY = y - mRepositionStartPoint.y;
mRepositionTaskBounds.set(mTaskBoundsAtDragStart);
+
+ final Rect stableBounds = mTempBounds;
+ // Make sure the new resizing destination in any direction falls within the stable bounds.
+ // If not, set the bounds back to the old location that was valid to avoid conflicts with
+ // some regions such as the gesture area.
+ mDisplayController.getDisplayLayout(mWindowDecoration.mDisplay.getDisplayId())
+ .getStableBounds(stableBounds);
if ((mCtrlType & CTRL_TYPE_LEFT) != 0) {
- mRepositionTaskBounds.left += deltaX;
+ final int candidateLeft = mRepositionTaskBounds.left + (int) deltaX;
+ mRepositionTaskBounds.left = (candidateLeft > stableBounds.left)
+ ? candidateLeft : oldLeft;
}
if ((mCtrlType & CTRL_TYPE_RIGHT) != 0) {
- mRepositionTaskBounds.right += deltaX;
+ final int candidateRight = mRepositionTaskBounds.right + (int) deltaX;
+ mRepositionTaskBounds.right = (candidateRight < stableBounds.right)
+ ? candidateRight : oldRight;
}
if ((mCtrlType & CTRL_TYPE_TOP) != 0) {
- mRepositionTaskBounds.top += deltaY;
+ final int candidateTop = mRepositionTaskBounds.top + (int) deltaY;
+ mRepositionTaskBounds.top = (candidateTop > stableBounds.top)
+ ? candidateTop : oldTop;
}
if ((mCtrlType & CTRL_TYPE_BOTTOM) != 0) {
- mRepositionTaskBounds.bottom += deltaY;
+ final int candidateBottom = mRepositionTaskBounds.bottom + (int) deltaY;
+ mRepositionTaskBounds.bottom = (candidateBottom < stableBounds.bottom)
+ ? candidateBottom : oldBottom;
}
if (mCtrlType == CTRL_TYPE_UNDEFINED) {
mRepositionTaskBounds.offset((int) deltaX, (int) deltaY);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 7a7ac476879e..ddd3b440e1a6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -131,7 +131,17 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
mSurfaceControlViewHostFactory = surfaceControlViewHostFactory;
mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId);
- mDecorWindowContext = mContext.createConfigurationContext(mTaskInfo.getConfiguration());
+ mDecorWindowContext = mContext.createConfigurationContext(
+ getConfigurationWithOverrides(mTaskInfo));
+ }
+
+ /**
+ * Get {@link Configuration} from supplied {@link RunningTaskInfo}.
+ *
+ * Allows values to be overridden before returning the configuration.
+ */
+ protected Configuration getConfigurationWithOverrides(RunningTaskInfo taskInfo) {
+ return taskInfo.getConfiguration();
}
/**
@@ -165,7 +175,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
outResult.mRootView = rootView;
rootView = null; // Clear it just in case we use it accidentally
- final Configuration taskConfig = mTaskInfo.getConfiguration();
+ final Configuration taskConfig = getConfigurationWithOverrides(mTaskInfo);
if (oldTaskConfig.densityDpi != taskConfig.densityDpi
|| mDisplay == null
|| mDisplay.getDisplayId() != mTaskInfo.displayId) {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
new file mode 100644
index 000000000000..53ce3936fbe4
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.Postsubmit
+import android.tools.common.Rotation
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.graphics.Rect
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test the snapping of a PIP window via dragging, releasing, and checking its final location.
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class PipDragThenSnapTest(flicker: FlickerTest) : PipTransition(flicker){
+ // represents the direction in which the pip window should be snapping
+ private var willSnapRight: Boolean = true
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ val stringExtras: Map<String, String> =
+ mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true")
+
+ // cache the starting bounds here
+ val startBounds = Rect()
+
+ setup {
+ // Launch the PIP activity and wait for it to enter PiP mode
+ setRotation(Rotation.ROTATION_0)
+ RemoveAllTasksButHomeRule.removeAllTasksButHome()
+ pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras)
+
+ // get the initial region bounds and cache them
+ val initRegion = pipApp.getWindowRect(wmHelper)
+ startBounds
+ .set(initRegion.left, initRegion.top, initRegion.right, initRegion.bottom)
+
+ // drag the pip window away from the edge
+ pipApp.dragPipWindowAwayFromEdge(wmHelper, 50)
+
+ // determine the direction in which the snapping should occur
+ willSnapRight = pipApp.isCloserToRightEdge(wmHelper)
+ }
+ transitions {
+ // continue the transition until the PIP snaps
+ pipApp.waitForPipToSnapTo(wmHelper, startBounds)
+ }
+ }
+
+ /**
+ * Checks that the visible region area of [pipApp] moves to closest edge during the animation.
+ */
+ @Postsubmit
+ @Test
+ fun pipLayerMovesToClosestEdge() {
+ flicker.assertLayers {
+ val pipLayerList = layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
+ pipLayerList.zipWithNext { previous, current ->
+ if (willSnapRight) {
+ current.visibleRegion.isToTheRight(previous.visibleRegion.region)
+ } else {
+ previous.visibleRegion.isToTheRight(current.visibleRegion.region)
+ }
+ }
+ }
+ }
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(Rotation.ROTATION_0)
+ )
+ }
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java
index 35c374ddd974..26b787fa836c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java
@@ -30,6 +30,7 @@ import android.window.TransitionInfo;
*/
public class TransitionInfoBuilder {
final TransitionInfo mInfo;
+ static final int DISPLAY_ID = 0;
public TransitionInfoBuilder(@WindowManager.TransitionType int type) {
this(type, 0 /* flags */);
@@ -38,7 +39,7 @@ public class TransitionInfoBuilder {
public TransitionInfoBuilder(@WindowManager.TransitionType int type,
@WindowManager.TransitionFlags int flags) {
mInfo = new TransitionInfo(type, flags);
- mInfo.setRootLeash(createMockSurface(true /* valid */), 0, 0);
+ mInfo.addRootLeash(DISPLAY_ID, createMockSurface(true /* valid */), 0, 0);
}
public TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode,
@@ -61,6 +62,7 @@ public class TransitionInfoBuilder {
}
public TransitionInfoBuilder addChange(TransitionInfo.Change change) {
+ change.setDisplayId(DISPLAY_ID, DISPLAY_ID);
mInfo.addChange(change);
return this;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
index ec264a643785..addc2338144f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
@@ -55,25 +55,28 @@ public class PipBoundsAlgorithmTest extends ShellTestCase {
private static final float MAX_ASPECT_RATIO = 2f;
private static final int DEFAULT_MIN_EDGE_SIZE = 100;
+ /** The minimum possible size of the override min size's width or height */
+ private static final int OVERRIDABLE_MIN_SIZE = 40;
+
private PipBoundsAlgorithm mPipBoundsAlgorithm;
private DisplayInfo mDefaultDisplayInfo;
- private PipBoundsState mPipBoundsState;
- private PipSizeSpecHandler mPipSizeSpecHandler;
+ private PipBoundsState mPipBoundsState; private PipSizeSpecHandler mPipSizeSpecHandler;
+ private PipDisplayLayoutState mPipDisplayLayoutState;
@Before
public void setUp() throws Exception {
initializeMockResources();
- mPipSizeSpecHandler = new PipSizeSpecHandler(mContext);
- mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler);
+ mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
+ mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState);
+ mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler, mPipDisplayLayoutState);
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState,
new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {},
mPipSizeSpecHandler);
DisplayLayout layout =
new DisplayLayout(mDefaultDisplayInfo, mContext.getResources(), true, true);
- mPipBoundsState.setDisplayLayout(layout);
- mPipSizeSpecHandler.setDisplayLayout(layout);
+ mPipDisplayLayoutState.setDisplayLayout(layout);
}
private void initializeMockResources() {
@@ -88,6 +91,9 @@ public class PipBoundsAlgorithmTest extends ShellTestCase {
R.dimen.default_minimal_size_pip_resizable_task,
DEFAULT_MIN_EDGE_SIZE);
res.addOverride(
+ R.dimen.overridable_minimal_size_pip_resizable_task,
+ OVERRIDABLE_MIN_SIZE);
+ res.addOverride(
R.string.config_defaultPictureInPictureScreenEdgeInsets,
"16x16");
res.addOverride(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
index 341a451eeb43..f32000445ca9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
@@ -27,11 +27,13 @@ import android.content.ComponentName;
import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.testing.TestableResources;
import android.util.Size;
import androidx.test.filters.SmallTest;
import com.android.internal.util.function.TriConsumer;
+import com.android.wm.shell.R;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
@@ -52,13 +54,23 @@ public class PipBoundsStateTest extends ShellTestCase {
private static final Size DEFAULT_SIZE = new Size(10, 10);
private static final float DEFAULT_SNAP_FRACTION = 1.0f;
+ /** The minimum possible size of the override min size's width or height */
+ private static final int OVERRIDABLE_MIN_SIZE = 40;
+
private PipBoundsState mPipBoundsState;
private ComponentName mTestComponentName1;
private ComponentName mTestComponentName2;
@Before
public void setUp() {
- mPipBoundsState = new PipBoundsState(mContext, new PipSizeSpecHandler(mContext));
+ final TestableResources res = mContext.getOrCreateTestableResources();
+ res.addOverride(
+ R.dimen.overridable_minimal_size_pip_resizable_task,
+ OVERRIDABLE_MIN_SIZE);
+
+ PipDisplayLayoutState pipDisplayLayoutState = new PipDisplayLayoutState(mContext);
+ mPipBoundsState = new PipBoundsState(mContext,
+ new PipSizeSpecHandler(mContext, pipDisplayLayoutState), pipDisplayLayoutState);
mTestComponentName1 = new ComponentName(mContext, "component1");
mTestComponentName2 = new ComponentName(mContext, "component2");
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index e907cd3ca0ad..15bb10ed4f2b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -88,6 +88,7 @@ public class PipTaskOrganizerTest extends ShellTestCase {
private PipTransitionState mPipTransitionState;
private PipBoundsAlgorithm mPipBoundsAlgorithm;
private PipSizeSpecHandler mPipSizeSpecHandler;
+ private PipDisplayLayoutState mPipDisplayLayoutState;
private ComponentName mComponent1;
private ComponentName mComponent2;
@@ -97,15 +98,16 @@ public class PipTaskOrganizerTest extends ShellTestCase {
MockitoAnnotations.initMocks(this);
mComponent1 = new ComponentName(mContext, "component1");
mComponent2 = new ComponentName(mContext, "component2");
- mPipSizeSpecHandler = new PipSizeSpecHandler(mContext);
- mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler);
+ mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
+ mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState);
+ mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler, mPipDisplayLayoutState);
mPipTransitionState = new PipTransitionState();
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState,
new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {},
mPipSizeSpecHandler);
mMainExecutor = new TestShellExecutor();
mPipTaskOrganizer = new PipTaskOrganizer(mContext, mMockSyncTransactionQueue,
- mPipTransitionState, mPipBoundsState, mPipSizeSpecHandler,
+ mPipTransitionState, mPipBoundsState, mPipDisplayLayoutState,
mPipBoundsAlgorithm, mMockPhonePipMenuController, mMockPipAnimationController,
mMockPipSurfaceTransactionHelper, mMockPipTransitionController,
mMockPipParamsChangedForwarder, mMockOptionalSplitScreen, mMockDisplayController,
@@ -259,8 +261,7 @@ public class PipTaskOrganizerTest extends ShellTestCase {
final DisplayInfo info = new DisplayInfo();
DisplayLayout layout = new DisplayLayout(info,
mContext.getResources(), true, true);
- mPipBoundsState.setDisplayLayout(layout);
- mPipSizeSpecHandler.setDisplayLayout(layout);
+ mPipDisplayLayoutState.setDisplayLayout(layout);
mPipTaskOrganizer.setOneShotAnimationType(PipAnimationController.ANIM_TYPE_ALPHA);
mPipTaskOrganizer.setSurfaceControlTransactionFactory(
MockSurfaceControlHelper::createMockSurfaceControlTransaction);
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 4a68287a4486..108e273d75a5 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
@@ -53,12 +53,14 @@ import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.TabletopModeController;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipAppOpsListener;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipSnapAlgorithm;
@@ -108,11 +110,13 @@ public class PipControllerTest extends ShellTestCase {
@Mock private WindowManagerShellWrapper mMockWindowManagerShellWrapper;
@Mock private PipBoundsState mMockPipBoundsState;
@Mock private PipSizeSpecHandler mMockPipSizeSpecHandler;
+ @Mock private PipDisplayLayoutState mMockPipDisplayLayoutState;
@Mock private TaskStackListenerImpl mMockTaskStackListener;
@Mock private ShellExecutor mMockExecutor;
@Mock private Optional<OneHandedController> mMockOneHandedController;
@Mock private PipParamsChangedForwarder mMockPipParamsChangedForwarder;
@Mock private DisplayInsetsController mMockDisplayInsetsController;
+ @Mock private TabletopModeController mMockTabletopModeController;
@Mock private DisplayLayout mMockDisplayLayout1;
@Mock private DisplayLayout mMockDisplayLayout2;
@@ -130,11 +134,12 @@ public class PipControllerTest extends ShellTestCase {
mPipController = new PipController(mContext, mShellInit, mMockShellCommandHandler,
mShellController, mMockDisplayController, mMockPipAnimationController,
mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
- mMockPipBoundsState, mMockPipSizeSpecHandler, mMockPipMotionHelper,
- mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer,
- mMockPipTransitionState, mMockPipTouchHandler, mMockPipTransitionController,
- mMockWindowManagerShellWrapper, mMockTaskStackListener,
- mMockPipParamsChangedForwarder, mMockDisplayInsetsController,
+ mMockPipBoundsState, mMockPipSizeSpecHandler, mMockPipDisplayLayoutState,
+ mMockPipMotionHelper, mMockPipMediaController, mMockPhonePipMenuController,
+ mMockPipTaskOrganizer, mMockPipTransitionState, mMockPipTouchHandler,
+ mMockPipTransitionController, mMockWindowManagerShellWrapper,
+ mMockTaskStackListener, mMockPipParamsChangedForwarder,
+ mMockDisplayInsetsController, mMockTabletopModeController,
mMockOneHandedController, mMockExecutor);
mShellInit.init();
when(mMockPipBoundsAlgorithm.getSnapAlgorithm()).thenReturn(mMockPipSnapAlgorithm);
@@ -221,11 +226,12 @@ public class PipControllerTest extends ShellTestCase {
assertNull(PipController.create(spyContext, shellInit, mMockShellCommandHandler,
mShellController, mMockDisplayController, mMockPipAnimationController,
mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
- mMockPipBoundsState, mMockPipSizeSpecHandler, mMockPipMotionHelper,
- mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer,
- mMockPipTransitionState, mMockPipTouchHandler, mMockPipTransitionController,
- mMockWindowManagerShellWrapper, mMockTaskStackListener,
- mMockPipParamsChangedForwarder, mMockDisplayInsetsController,
+ mMockPipBoundsState, mMockPipSizeSpecHandler, mMockPipDisplayLayoutState,
+ mMockPipMotionHelper, mMockPipMediaController, mMockPhonePipMenuController,
+ mMockPipTaskOrganizer, mMockPipTransitionState, mMockPipTouchHandler,
+ mMockPipTransitionController, mMockWindowManagerShellWrapper,
+ mMockTaskStackListener, mMockPipParamsChangedForwarder,
+ mMockDisplayInsetsController, mMockTabletopModeController,
mMockOneHandedController, mMockExecutor));
}
@@ -283,8 +289,8 @@ public class PipControllerTest extends ShellTestCase {
when(mMockPipBoundsState.getMinSize()).thenReturn(new Point(1, 1));
when(mMockPipBoundsState.getMaxSize()).thenReturn(new Point(MAX_VALUE, MAX_VALUE));
when(mMockPipBoundsState.getBounds()).thenReturn(bounds);
- when(mMockPipBoundsState.getDisplayId()).thenReturn(displayId);
- when(mMockPipBoundsState.getDisplayLayout()).thenReturn(mMockDisplayLayout1);
+ when(mMockPipDisplayLayoutState.getDisplayId()).thenReturn(displayId);
+ when(mMockPipDisplayLayoutState.getDisplayLayout()).thenReturn(mMockDisplayLayout1);
when(mMockDisplayController.getDisplayLayout(displayId)).thenReturn(mMockDisplayLayout2);
when(mMockPipTaskOrganizer.isInPip()).thenReturn(true);
@@ -299,8 +305,8 @@ public class PipControllerTest extends ShellTestCase {
final int displayId = 1;
final Rect bounds = new Rect(0, 0, 10, 10);
when(mMockPipBoundsAlgorithm.getDefaultBounds()).thenReturn(bounds);
- when(mMockPipBoundsState.getDisplayId()).thenReturn(displayId);
- when(mMockPipBoundsState.getDisplayLayout()).thenReturn(mMockDisplayLayout1);
+ when(mMockPipDisplayLayoutState.getDisplayId()).thenReturn(displayId);
+ when(mMockPipDisplayLayoutState.getDisplayLayout()).thenReturn(mMockDisplayLayout1);
when(mMockDisplayController.getDisplayLayout(displayId)).thenReturn(mMockDisplayLayout2);
when(mMockPipTaskOrganizer.isInPip()).thenReturn(false);
@@ -314,7 +320,7 @@ public class PipControllerTest extends ShellTestCase {
public void onKeepClearAreasChanged_featureDisabled_pipBoundsStateDoesntChange() {
final int displayId = 1;
final Rect keepClearArea = new Rect(0, 0, 10, 10);
- when(mMockPipBoundsState.getDisplayId()).thenReturn(displayId);
+ when(mMockPipDisplayLayoutState.getDisplayId()).thenReturn(displayId);
mPipController.mDisplaysChangedListener.onKeepClearAreasChanged(
displayId, Set.of(keepClearArea), Set.of());
@@ -327,7 +333,7 @@ public class PipControllerTest extends ShellTestCase {
mPipController.setEnablePipKeepClearAlgorithm(true);
final int displayId = 1;
final Rect keepClearArea = new Rect(0, 0, 10, 10);
- when(mMockPipBoundsState.getDisplayId()).thenReturn(displayId);
+ when(mMockPipDisplayLayoutState.getDisplayId()).thenReturn(displayId);
mPipController.mDisplaysChangedListener.onKeepClearAreasChanged(
displayId, Set.of(keepClearArea), Set.of());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
index c7b9eb3d1074..5b62a940c074 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
@@ -37,6 +37,7 @@ import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
@@ -87,11 +88,14 @@ public class PipResizeGestureHandlerTest extends ShellTestCase {
private PipSizeSpecHandler mPipSizeSpecHandler;
+ private PipDisplayLayoutState mPipDisplayLayoutState;
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mPipSizeSpecHandler = new PipSizeSpecHandler(mContext);
- mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler);
+ mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
+ mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState);
+ mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler, mPipDisplayLayoutState);
final PipSnapAlgorithm pipSnapAlgorithm = new PipSnapAlgorithm();
final PipKeepClearAlgorithmInterface pipKeepClearAlgorithm =
new PipKeepClearAlgorithmInterface() {};
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
index d9ff7d1f1089..390c830069eb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
@@ -33,6 +33,7 @@ import android.view.DisplayInfo;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
import org.junit.After;
import org.junit.Assert;
@@ -74,6 +75,7 @@ public class PipSizeSpecHandlerTest extends ShellTestCase {
@Mock private Context mContext;
@Mock private Resources mResources;
+ private PipDisplayLayoutState mPipDisplayLayoutState;
private PipSizeSpecHandler mPipSizeSpecHandler;
/**
@@ -137,7 +139,6 @@ public class PipSizeSpecHandlerTest extends ShellTestCase {
@Before
public void setUp() {
initExpectedSizes();
- setUpStaticSystemPropertiesSession();
when(mResources.getDimensionPixelSize(anyInt())).thenReturn(DEFAULT_MIN_EDGE_SIZE);
when(mResources.getFloat(anyInt())).thenReturn(OPTIMIZED_ASPECT_RATIO);
@@ -148,11 +149,6 @@ public class PipSizeSpecHandlerTest extends ShellTestCase {
// set up the mock context for spec handler specifically
when(mContext.getResources()).thenReturn(mResources);
- mPipSizeSpecHandler = new PipSizeSpecHandler(mContext);
-
- // no overridden min edge size by default
- mPipSizeSpecHandler.setOverrideMinSize(null);
-
DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = DISPLAY_EDGE_SIZE;
displayInfo.logicalHeight = DISPLAY_EDGE_SIZE;
@@ -161,7 +157,14 @@ public class PipSizeSpecHandlerTest extends ShellTestCase {
// this is done to avoid unnecessary mocking while allowing for custom display dimensions
DisplayLayout displayLayout = new DisplayLayout(displayInfo, getContext().getResources(),
false, false);
- mPipSizeSpecHandler.setDisplayLayout(displayLayout);
+ mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
+ mPipDisplayLayoutState.setDisplayLayout(displayLayout);
+
+ setUpStaticSystemPropertiesSession();
+ mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState);
+
+ // no overridden min edge size by default
+ mPipSizeSpecHandler.setOverrideMinSize(null);
}
@After
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index 5c4863ff752f..d36060fd165f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -35,6 +35,7 @@ import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
@@ -92,6 +93,7 @@ public class PipTouchHandlerTest extends ShellTestCase {
private PipMotionHelper mMotionHelper;
private PipResizeGestureHandler mPipResizeGestureHandler;
private PipSizeSpecHandler mPipSizeSpecHandler;
+ private PipDisplayLayoutState mPipDisplayLayoutState;
private DisplayLayout mDisplayLayout;
private Rect mInsetBounds;
@@ -105,8 +107,9 @@ public class PipTouchHandlerTest extends ShellTestCase {
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mPipSizeSpecHandler = new PipSizeSpecHandler(mContext);
- mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler);
+ mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
+ mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState);
+ mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler, mPipDisplayLayoutState);
mPipSnapAlgorithm = new PipSnapAlgorithm();
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, mPipSnapAlgorithm,
new PipKeepClearAlgorithmInterface() {}, mPipSizeSpecHandler);
@@ -124,8 +127,7 @@ public class PipTouchHandlerTest extends ShellTestCase {
mPipTouchHandler.setPipResizeGestureHandler(mPipResizeGestureHandler);
mDisplayLayout = new DisplayLayout(mContext, mContext.getDisplay());
- mPipBoundsState.setDisplayLayout(mDisplayLayout);
- mPipSizeSpecHandler.setDisplayLayout(mDisplayLayout);
+ mPipDisplayLayoutState.setDisplayLayout(mDisplayLayout);
mInsetBounds = new Rect(mPipBoundsState.getDisplayBounds().left + INSET,
mPipBoundsState.getDisplayBounds().top + INSET,
mPipBoundsState.getDisplayBounds().right - INSET,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
index 30096cb99a64..f9b772345b14 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
@@ -26,6 +26,7 @@ import static org.junit.Assert.assertEquals;
import android.view.Gravity;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
@@ -47,6 +48,7 @@ public class TvPipGravityTest extends ShellTestCase {
private TvPipBoundsState mTvPipBoundsState;
private TvPipBoundsAlgorithm mTvPipBoundsAlgorithm;
private PipSizeSpecHandler mPipSizeSpecHandler;
+ private PipDisplayLayoutState mPipDisplayLayoutState;
@Before
public void setUp() {
@@ -54,8 +56,10 @@ public class TvPipGravityTest extends ShellTestCase {
return;
}
MockitoAnnotations.initMocks(this);
- mPipSizeSpecHandler = new PipSizeSpecHandler(mContext);
- mTvPipBoundsState = new TvPipBoundsState(mContext, mPipSizeSpecHandler);
+ mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
+ mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState);
+ mTvPipBoundsState = new TvPipBoundsState(mContext, mPipSizeSpecHandler,
+ mPipDisplayLayoutState);
mTvPipBoundsAlgorithm = new TvPipBoundsAlgorithm(mContext, mTvPipBoundsState,
mMockPipSnapAlgorithm, mPipSizeSpecHandler);
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 3901dabcaec8..df78d92a90c8 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
@@ -35,6 +35,7 @@ import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_P
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -249,7 +250,7 @@ public class SplitTransitionTests extends ShellTestCase {
@Test
@UiThreadTest
- public void testEnterRecents() {
+ public void testEnterRecentsAndCommit() {
enterSplit();
ActivityManager.RunningTaskInfo homeTask = new TestRunningTaskInfoBuilder()
@@ -258,27 +259,65 @@ public class SplitTransitionTests extends ShellTestCase {
.build();
// Create a request to bring home forward
- TransitionRequestInfo request = new TransitionRequestInfo(TRANSIT_TO_FRONT, homeTask, null);
+ TransitionRequestInfo request = new TransitionRequestInfo(TRANSIT_TO_FRONT, homeTask,
+ mock(RemoteTransition.class));
IBinder transition = mock(IBinder.class);
WindowContainerTransaction result = mStageCoordinator.handleRequest(transition, request);
-
- assertTrue(result.isEmpty());
+ // Don't handle recents opening
+ assertNull(result);
// make sure we haven't made any local changes yet (need to wait until transition is ready)
assertTrue(mStageCoordinator.isSplitScreenVisible());
- // simulate the transition
- TransitionInfo info = new TransitionInfoBuilder(TRANSIT_TO_FRONT, 0)
- .addChange(TRANSIT_TO_FRONT, homeTask)
- .addChange(TRANSIT_TO_BACK, mMainChild)
- .addChange(TRANSIT_TO_BACK, mSideChild)
+ // simulate the start of recents transition
+ mMainStage.onTaskVanished(mMainChild);
+ mSideStage.onTaskVanished(mSideChild);
+ mStageCoordinator.onRecentsInSplitAnimationStart(mock(SurfaceControl.Transaction.class));
+ assertTrue(mStageCoordinator.isSplitScreenVisible());
+
+ // Make sure it cleans-up if recents doesn't restore
+ WindowContainerTransaction commitWCT = new WindowContainerTransaction();
+ mStageCoordinator.onRecentsInSplitAnimationFinish(commitWCT,
+ mock(SurfaceControl.Transaction.class));
+ assertFalse(mStageCoordinator.isSplitScreenVisible());
+ }
+
+ @Test
+ @UiThreadTest
+ public void testEnterRecentsAndRestore() {
+ enterSplit();
+
+ ActivityManager.RunningTaskInfo homeTask = new TestRunningTaskInfoBuilder()
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .setActivityType(ACTIVITY_TYPE_HOME)
.build();
+
+ // Create a request to bring home forward
+ TransitionRequestInfo request = new TransitionRequestInfo(TRANSIT_TO_FRONT, homeTask,
+ mock(RemoteTransition.class));
+ IBinder transition = mock(IBinder.class);
+ WindowContainerTransaction result = mStageCoordinator.handleRequest(transition, request);
+ // Don't handle recents opening
+ assertNull(result);
+
+ // make sure we haven't made any local changes yet (need to wait until transition is ready)
+ assertTrue(mStageCoordinator.isSplitScreenVisible());
+
+ // simulate the start of recents transition
mMainStage.onTaskVanished(mMainChild);
mSideStage.onTaskVanished(mSideChild);
- mStageCoordinator.startAnimation(transition, info,
- mock(SurfaceControl.Transaction.class),
- mock(SurfaceControl.Transaction.class),
- mock(Transitions.TransitionFinishCallback.class));
+ mStageCoordinator.onRecentsInSplitAnimationStart(mock(SurfaceControl.Transaction.class));
+ assertTrue(mStageCoordinator.isSplitScreenVisible());
+
+ // Make sure we remain in split after recents restores.
+ WindowContainerTransaction restoreWCT = new WindowContainerTransaction();
+ restoreWCT.reorder(mMainChild.token, true /* toTop */);
+ restoreWCT.reorder(mSideChild.token, true /* toTop */);
+ // simulate the restoreWCT being applied:
+ mMainStage.onTaskAppeared(mMainChild, mock(SurfaceControl.class));
+ mSideStage.onTaskAppeared(mSideChild, mock(SurfaceControl.class));
+ mStageCoordinator.onRecentsInSplitAnimationFinish(restoreWCT,
+ mock(SurfaceControl.Transaction.class));
assertTrue(mStageCoordinator.isSplitScreenVisible());
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index e63bbeb05575..44f1f0147b52 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -62,6 +62,7 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
+import android.util.ArraySet;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.WindowManager;
@@ -99,6 +100,7 @@ import org.junit.runner.RunWith;
import org.mockito.InOrder;
import java.util.ArrayList;
+import java.util.function.Function;
/**
* Tests for the shell transitions.
@@ -591,6 +593,68 @@ public class ShellTransitionTests extends ShellTestCase {
}
@Test
+ public void testInterleavedMerging() {
+ Transitions transitions = createTestTransitions();
+ transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+ Function<Boolean, IBinder> startATransition = (doMerge) -> {
+ IBinder token = new Binder();
+ if (doMerge) {
+ mDefaultHandler.setShouldMerge(token);
+ }
+ transitions.requestStartTransition(token,
+ new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
+ TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
+ transitions.onTransitionReady(token, info, mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class));
+ return token;
+ };
+
+ IBinder transitToken1 = startATransition.apply(false);
+ // merge first one
+ IBinder transitToken2 = startATransition.apply(true);
+ assertEquals(1, mDefaultHandler.activeCount());
+ assertEquals(1, mDefaultHandler.mergeCount());
+
+ // don't merge next one
+ IBinder transitToken3 = startATransition.apply(false);
+ // make sure nothing happened (since it wasn't merged)
+ assertEquals(1, mDefaultHandler.activeCount());
+ assertEquals(1, mDefaultHandler.mergeCount());
+
+ // make a mergable
+ IBinder transitToken4 = startATransition.apply(true);
+ // make sure nothing happened since there is a non-mergable pending.
+ assertEquals(1, mDefaultHandler.activeCount());
+ assertEquals(1, mDefaultHandler.mergeCount());
+
+ // Queue up another mergable
+ IBinder transitToken5 = startATransition.apply(true);
+
+ // Queue up a non-mergable
+ IBinder transitToken6 = startATransition.apply(false);
+
+ // Our active now looks like: [playing, merged]
+ // and ready queue: [non-mergable, mergable, mergable, non-mergable]
+ // finish the playing one
+ mDefaultHandler.finishOne();
+ mMainExecutor.flushAll();
+ // Now we should have the non-mergable playing now with 2 merged:
+ // active: [playing, merged, merged] queue: [non-mergable]
+ assertEquals(1, mDefaultHandler.activeCount());
+ assertEquals(2, mDefaultHandler.mergeCount());
+
+ mDefaultHandler.finishOne();
+ mMainExecutor.flushAll();
+ assertEquals(1, mDefaultHandler.activeCount());
+ assertEquals(0, mDefaultHandler.mergeCount());
+
+ mDefaultHandler.finishOne();
+ mMainExecutor.flushAll();
+ }
+
+ @Test
public void testTransitionOrderMatchesCore() {
Transitions transitions = createTestTransitions();
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
@@ -1016,6 +1080,7 @@ public class ShellTransitionTests extends ShellTestCase {
ArrayList<Transitions.TransitionFinishCallback> mFinishes = new ArrayList<>();
final ArrayList<IBinder> mMerged = new ArrayList<>();
boolean mSimulateMerge = false;
+ final ArraySet<IBinder> mShouldMerge = new ArraySet<>();
@Override
public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@@ -1030,7 +1095,7 @@ public class ShellTransitionTests extends ShellTestCase {
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
- if (!mSimulateMerge) return;
+ if (!(mSimulateMerge || mShouldMerge.contains(transition))) return;
mMerged.add(transition);
finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
}
@@ -1046,12 +1111,23 @@ public class ShellTransitionTests extends ShellTestCase {
mSimulateMerge = sim;
}
+ void setShouldMerge(IBinder toMerge) {
+ mShouldMerge.add(toMerge);
+ }
+
void finishAll() {
final ArrayList<Transitions.TransitionFinishCallback> finishes = mFinishes;
mFinishes = new ArrayList<>();
for (int i = finishes.size() - 1; i >= 0; --i) {
finishes.get(i).onTransitionFinished(null /* wct */, null /* wctCB */);
}
+ mShouldMerge.clear();
+ }
+
+ void finishOne() {
+ Transitions.TransitionFinishCallback fin = mFinishes.remove(0);
+ mMerged.clear();
+ fin.onTransitionFinished(null /* wct */, null /* wctCB */);
}
int activeCount() {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
index 8f66f4e7e47b..94c064bda763 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
@@ -5,13 +5,16 @@ import android.app.WindowConfiguration
import android.graphics.Rect
import android.os.IBinder
import android.testing.AndroidTestingRunner
+import android.view.Display
import android.window.WindowContainerToken
+import android.window.WindowContainerTransaction
import android.window.WindowContainerTransaction.Change.CHANGE_DRAG_RESIZING
import androidx.test.filters.SmallTest
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_BOTTOM
import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_RIGHT
import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_TOP
import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_UNDEFINED
@@ -19,10 +22,11 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.any
import org.mockito.Mockito.argThat
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
/**
@@ -51,6 +55,8 @@ class TaskPositionerTest : ShellTestCase() {
private lateinit var mockDisplayController: DisplayController
@Mock
private lateinit var mockDisplayLayout: DisplayLayout
+ @Mock
+ private lateinit var mockDisplay: Display
private lateinit var taskPositioner: TaskPositioner
@@ -68,6 +74,9 @@ class TaskPositionerTest : ShellTestCase() {
`when`(taskToken.asBinder()).thenReturn(taskBinder)
`when`(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
`when`(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
+ `when`(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS)
+ }
mockWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply {
taskId = TASK_ID
@@ -78,6 +87,8 @@ class TaskPositionerTest : ShellTestCase() {
displayId = DISPLAY_ID
configuration.windowConfiguration.bounds = STARTING_BOUNDS
}
+ mockWindowDecoration.mDisplay = mockDisplay
+ `when`(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
}
@Test
@@ -451,6 +462,72 @@ class TaskPositionerTest : ShellTestCase() {
})
}
+ fun testDragResize_toDisallowedBounds_freezesAtLimit() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM, // Resize right-bottom corner
+ STARTING_BOUNDS.right.toFloat(),
+ STARTING_BOUNDS.bottom.toFloat()
+ )
+
+ // Resize the task by 10px to the right and bottom, a valid destination
+ val newBounds = Rect(
+ STARTING_BOUNDS.left,
+ STARTING_BOUNDS.top,
+ STARTING_BOUNDS.right + 10,
+ STARTING_BOUNDS.bottom + 10)
+ taskPositioner.onDragPositioningMove(
+ newBounds.right.toFloat(),
+ newBounds.bottom.toFloat()
+ )
+
+ // Resize the task by another 10px to the right (allowed) and to just in the disallowed
+ // area of the Y coordinate.
+ val newBounds2 = Rect(
+ newBounds.left,
+ newBounds.top,
+ newBounds.right + 10,
+ DISALLOWED_RESIZE_AREA.top
+ )
+ taskPositioner.onDragPositioningMove(
+ newBounds2.right.toFloat(),
+ newBounds2.bottom.toFloat()
+ )
+
+ taskPositioner.onDragPositioningEnd(newBounds2.right.toFloat(), newBounds2.bottom.toFloat())
+
+ // The first resize falls in the allowed area, verify there's a change for it.
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder && change.ofBounds(newBounds)
+ }
+ })
+ // The second resize falls in the disallowed area, verify there's no change for it.
+ verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder && change.ofBounds(newBounds2)
+ }
+ })
+ // Instead, there should be a change for its allowed portion (the X movement) with the Y
+ // staying frozen in the last valid resize position.
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder && change.ofBounds(
+ Rect(
+ newBounds2.left,
+ newBounds2.top,
+ newBounds2.right,
+ newBounds.bottom // Stayed at the first resize destination.
+ )
+ )
+ }
+ })
+ }
+
+ private fun WindowContainerTransaction.Change.ofBounds(bounds: Rect): Boolean {
+ return ((windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0) &&
+ bounds == configuration.windowConfiguration.bounds
+ }
+
companion object {
private const val TASK_ID = 5
private const val MIN_WIDTH = 10
@@ -458,6 +535,19 @@ class TaskPositionerTest : ShellTestCase() {
private const val DENSITY_DPI = 20
private const val DEFAULT_MIN = 40
private const val DISPLAY_ID = 1
+ private const val NAVBAR_HEIGHT = 50
+ private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600)
private val STARTING_BOUNDS = Rect(0, 0, 100, 100)
+ private val DISALLOWED_RESIZE_AREA = Rect(
+ DISPLAY_BOUNDS.left,
+ DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT,
+ DISPLAY_BOUNDS.right,
+ DISPLAY_BOUNDS.bottom)
+ private val STABLE_BOUNDS = Rect(
+ DISPLAY_BOUNDS.left,
+ DISPLAY_BOUNDS.top,
+ DISPLAY_BOUNDS.right,
+ DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT
+ )
}
}
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index b0896daee2a1..9df6822b4867 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -91,6 +91,8 @@ bool Properties::isHighEndGfx = true;
bool Properties::isLowRam = false;
bool Properties::isSystemOrPersistent = false;
+float Properties::maxHdrHeadroomOn8bit = 5.f; // TODO: Refine this number
+
StretchEffectBehavior Properties::stretchEffectBehavior = StretchEffectBehavior::ShaderHWUI;
DrawingEnabled Properties::drawingEnabled = DrawingEnabled::NotInitialized;
@@ -150,6 +152,11 @@ bool Properties::load() {
enableWebViewOverlays = base::GetBoolProperty(PROPERTY_WEBVIEW_OVERLAYS_ENABLED, true);
+ auto hdrHeadroom = (float)atof(base::GetProperty(PROPERTY_8BIT_HDR_HEADROOM, "").c_str());
+ if (hdrHeadroom >= 1.f) {
+ maxHdrHeadroomOn8bit = std::min(hdrHeadroom, 100.f);
+ }
+
// call isDrawingEnabled to force loading of the property
isDrawingEnabled();
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index ed7175e140e4..24e206bbc3b1 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -218,6 +218,8 @@ enum DebugLevel {
#define PROPERTY_MEMORY_POLICY "debug.hwui.app_memory_policy"
+#define PROPERTY_8BIT_HDR_HEADROOM "debug.hwui.8bit_hdr_headroom"
+
///////////////////////////////////////////////////////////////////////////////
// Misc
///////////////////////////////////////////////////////////////////////////////
@@ -321,6 +323,8 @@ public:
static bool isLowRam;
static bool isSystemOrPersistent;
+ static float maxHdrHeadroomOn8bit;
+
static StretchEffectBehavior getStretchEffectBehavior() {
return stretchEffectBehavior;
}
diff --git a/libs/hwui/effects/GainmapRenderer.cpp b/libs/hwui/effects/GainmapRenderer.cpp
index 8977d3ce4da3..bfe4eaf39e21 100644
--- a/libs/hwui/effects/GainmapRenderer.cpp
+++ b/libs/hwui/effects/GainmapRenderer.cpp
@@ -23,21 +23,55 @@
#include "utils/Trace.h"
#ifdef __ANDROID__
+#include "include/core/SkColorSpace.h"
+#include "include/core/SkImage.h"
+#include "include/core/SkShader.h"
+#include "include/effects/SkRuntimeEffect.h"
+#include "include/private/SkGainmapInfo.h"
#include "renderthread/CanvasContext.h"
+#include "src/core/SkColorFilterPriv.h"
+#include "src/core/SkImageInfoPriv.h"
+#include "src/core/SkRuntimeEffectPriv.h"
#endif
namespace android::uirenderer {
using namespace renderthread;
+static float getTargetHdrSdrRatio(const SkColorSpace* destColorspace) {
+ // We should always have a known destination colorspace. If we don't we must be in some
+ // legacy mode where we're lost and also definitely not going to HDR
+ if (destColorspace == nullptr) {
+ return 1.f;
+ }
+
+ constexpr float GenericSdrWhiteNits = 203.f;
+ constexpr float maxPQLux = 10000.f;
+ constexpr float maxHLGLux = 1000.f;
+ skcms_TransferFunction destTF;
+ destColorspace->transferFn(&destTF);
+ if (skcms_TransferFunction_isPQish(&destTF)) {
+ return maxPQLux / GenericSdrWhiteNits;
+ } else if (skcms_TransferFunction_isHLGish(&destTF)) {
+ return maxHLGLux / GenericSdrWhiteNits;
+ } else {
+#ifdef __ANDROID__
+ CanvasContext* context = CanvasContext::getActiveContext();
+ return context ? context->targetSdrHdrRatio() : 1.f;
+#else
+ return 1.f;
+#endif
+ }
+}
+
void DrawGainmapBitmap(SkCanvas* c, const sk_sp<const SkImage>& image, const SkRect& src,
const SkRect& dst, const SkSamplingOptions& sampling, const SkPaint* paint,
SkCanvas::SrcRectConstraint constraint,
const sk_sp<const SkImage>& gainmapImage, const SkGainmapInfo& gainmapInfo) {
ATRACE_CALL();
#ifdef __ANDROID__
- CanvasContext* context = CanvasContext::getActiveContext();
- float targetSdrHdrRatio = context ? context->targetSdrHdrRatio() : 1.f;
+ auto destColorspace = c->imageInfo().refColorSpace();
+ float targetSdrHdrRatio = getTargetHdrSdrRatio(destColorspace.get());
if (targetSdrHdrRatio > 1.f && gainmapImage) {
SkPaint gainmapPaint = *paint;
float sX = gainmapImage->width() / (float)image->width();
@@ -48,9 +82,9 @@ void DrawGainmapBitmap(SkCanvas* c, const sk_sp<const SkImage>& image, const SkR
gainmapSrc.fRight *= sX;
gainmapSrc.fTop *= sY;
gainmapSrc.fBottom *= sY;
- auto shader = SkGainmapShader::Make(image, src, sampling, gainmapImage, gainmapSrc,
- sampling, gainmapInfo, dst, targetSdrHdrRatio,
- c->imageInfo().refColorSpace());
+ auto shader =
+ SkGainmapShader::Make(image, src, sampling, gainmapImage, gainmapSrc, sampling,
+ gainmapInfo, dst, targetSdrHdrRatio, destColorspace);
gainmapPaint.setShader(shader);
c->drawRect(dst, gainmapPaint);
} else
@@ -58,4 +92,213 @@ void DrawGainmapBitmap(SkCanvas* c, const sk_sp<const SkImage>& image, const SkR
c->drawImageRect(image.get(), src, dst, sampling, paint, constraint);
}
+#ifdef __ANDROID__
+
+static constexpr char gGainmapSKSL[] = R"SKSL(
+ uniform shader base;
+ uniform shader gainmap;
+ uniform colorFilter workingSpaceToLinearSrgb;
+ uniform half4 logRatioMin;
+ uniform half4 logRatioMax;
+ uniform half4 gainmapGamma;
+ uniform half4 epsilonSdr;
+ uniform half4 epsilonHdr;
+ uniform half W;
+ uniform int gainmapIsAlpha;
+ uniform int gainmapIsRed;
+ uniform int singleChannel;
+ uniform int noGamma;
+
+ half4 toDest(half4 working) {
+ half4 ls = workingSpaceToLinearSrgb.eval(working);
+ vec3 dest = fromLinearSrgb(ls.rgb);
+ return half4(dest.r, dest.g, dest.b, ls.a);
+ }
+
+ half4 main(float2 coord) {
+ half4 S = base.eval(coord);
+ half4 G = gainmap.eval(coord);
+ if (gainmapIsAlpha == 1) {
+ G = half4(G.a, G.a, G.a, 1.0);
+ }
+ if (gainmapIsRed == 1) {
+ G = half4(G.r, G.r, G.r, 1.0);
+ }
+ if (singleChannel == 1) {
+ half L;
+ if (noGamma == 1) {
+ L = mix(logRatioMin.r, logRatioMax.r, G.r);
+ } else {
+ L = mix(logRatioMin.r, logRatioMax.r, pow(G.r, gainmapGamma.r));
+ }
+ half3 H = (S.rgb + epsilonSdr.rgb) * exp(L * W) - epsilonHdr.rgb;
+ return toDest(half4(H.r, H.g, H.b, S.a));
+ } else {
+ half3 L;
+ if (noGamma == 1) {
+ L = mix(logRatioMin.rgb, logRatioMax.rgb, G.rgb);
+ } else {
+ L = mix(logRatioMin.rgb, logRatioMax.rgb, pow(G.rgb, gainmapGamma.rgb));
+ }
+ half3 H = (S.rgb + epsilonSdr.rgb) * exp(L * W) - epsilonHdr.rgb;
+ return toDest(half4(H.r, H.g, H.b, S.a));
+ }
+ }
+)SKSL";
+
+static sk_sp<SkRuntimeEffect> gainmap_apply_effect() {
+ static const SkRuntimeEffect* effect = []() -> SkRuntimeEffect* {
+ auto buildResult = SkRuntimeEffect::MakeForShader(SkString(gGainmapSKSL), {});
+ if (buildResult.effect) {
+ return buildResult.effect.release();
+ } else {
+ LOG_ALWAYS_FATAL("Failed to build gainmap shader: %s", buildResult.errorText.c_str());
+ }
+ }();
+ SkASSERT(effect);
+ return sk_ref_sp(effect);
+}
+
+static bool all_channels_equal(const SkColor4f& c) {
+ return c.fR == c.fG && c.fR == c.fB;
+}
+
+class DeferredGainmapShader {
+private:
+ sk_sp<SkRuntimeEffect> mShader{gainmap_apply_effect()};
+ SkRuntimeShaderBuilder mBuilder{mShader};
+ SkGainmapInfo mGainmapInfo;
+ std::mutex mUniformGuard;
+
+ void setupChildren(const sk_sp<const SkImage>& baseImage,
+ const sk_sp<const SkImage>& gainmapImage, SkTileMode tileModeX,
+ SkTileMode tileModeY, const SkSamplingOptions& samplingOptions) {
+ sk_sp<SkColorSpace> baseColorSpace =
+ baseImage->colorSpace() ? baseImage->refColorSpace() : SkColorSpace::MakeSRGB();
+
+ // Determine the color space in which the gainmap math is to be applied.
+ sk_sp<SkColorSpace> gainmapMathColorSpace = baseColorSpace->makeLinearGamma();
+
+ // Create a color filter to transform from the base image's color space to the color space
+ // in which the gainmap is to be applied.
+ auto colorXformSdrToGainmap =
+ SkColorFilterPriv::MakeColorSpaceXform(baseColorSpace, gainmapMathColorSpace);
+
+ // The base image shader will convert into the color space in which the gainmap is applied.
+ auto baseImageShader = baseImage->makeRawShader(tileModeX, tileModeY, samplingOptions)
+ ->makeWithColorFilter(colorXformSdrToGainmap);
+
+ // The gainmap image shader will ignore any color space that the gainmap has.
+ const SkMatrix gainmapRectToDstRect =
+ SkMatrix::RectToRect(SkRect::MakeWH(gainmapImage->width(), gainmapImage->height()),
+ SkRect::MakeWH(baseImage->width(), baseImage->height()));
+ auto gainmapImageShader = gainmapImage->makeRawShader(tileModeX, tileModeY, samplingOptions,
+ &gainmapRectToDstRect);
+
+ // Create a color filter to transform from the color space in which the gainmap is applied
+ // to the intermediate destination color space.
+ auto colorXformGainmapToDst = SkColorFilterPriv::MakeColorSpaceXform(
+ gainmapMathColorSpace, SkColorSpace::MakeSRGBLinear());
+
+ mBuilder.child("base") = std::move(baseImageShader);
+ mBuilder.child("gainmap") = std::move(gainmapImageShader);
+ mBuilder.child("workingSpaceToLinearSrgb") = std::move(colorXformGainmapToDst);
+ }
+
+ void setupGenericUniforms(const sk_sp<const SkImage>& gainmapImage,
+ const SkGainmapInfo& gainmapInfo) {
+ const SkColor4f logRatioMin({sk_float_log(gainmapInfo.fGainmapRatioMin.fR),
+ sk_float_log(gainmapInfo.fGainmapRatioMin.fG),
+ sk_float_log(gainmapInfo.fGainmapRatioMin.fB), 1.f});
+ const SkColor4f logRatioMax({sk_float_log(gainmapInfo.fGainmapRatioMax.fR),
+ sk_float_log(gainmapInfo.fGainmapRatioMax.fG),
+ sk_float_log(gainmapInfo.fGainmapRatioMax.fB), 1.f});
+ const int noGamma = gainmapInfo.fGainmapGamma.fR == 1.f &&
+ gainmapInfo.fGainmapGamma.fG == 1.f &&
+ gainmapInfo.fGainmapGamma.fB == 1.f;
+ const uint32_t colorTypeFlags = SkColorTypeChannelFlags(gainmapImage->colorType());
+ const int gainmapIsAlpha = colorTypeFlags == kAlpha_SkColorChannelFlag;
+ const int gainmapIsRed = colorTypeFlags == kRed_SkColorChannelFlag;
+ const int singleChannel = all_channels_equal(gainmapInfo.fGainmapGamma) &&
+ all_channels_equal(gainmapInfo.fGainmapRatioMin) &&
+ all_channels_equal(gainmapInfo.fGainmapRatioMax) &&
+ (colorTypeFlags == kGray_SkColorChannelFlag ||
+ colorTypeFlags == kAlpha_SkColorChannelFlag ||
+ colorTypeFlags == kRed_SkColorChannelFlag);
+ mBuilder.uniform("logRatioMin") = logRatioMin;
+ mBuilder.uniform("logRatioMax") = logRatioMax;
+ mBuilder.uniform("gainmapGamma") = gainmapInfo.fGainmapGamma;
+ mBuilder.uniform("epsilonSdr") = gainmapInfo.fEpsilonSdr;
+ mBuilder.uniform("epsilonHdr") = gainmapInfo.fEpsilonHdr;
+ mBuilder.uniform("noGamma") = noGamma;
+ mBuilder.uniform("singleChannel") = singleChannel;
+ mBuilder.uniform("gainmapIsAlpha") = gainmapIsAlpha;
+ mBuilder.uniform("gainmapIsRed") = gainmapIsRed;
+ }
+
+ sk_sp<const SkData> build(float targetHdrSdrRatio) {
+ sk_sp<const SkData> uniforms;
+ {
+ // If we are called concurrently from multiple threads, we need to guard the call
+ // to writableUniforms() which mutates mUniform. This is otherwise safe because
+ // writeableUniforms() will make a copy if it's not unique before mutating
+ // This can happen if a BitmapShader is used on multiple canvas', such as a
+ // software + hardware canvas, which is otherwise valid as SkShader is "immutable"
+ std::lock_guard _lock(mUniformGuard);
+ const float Wunclamped = (sk_float_log(targetHdrSdrRatio) -
+ sk_float_log(mGainmapInfo.fDisplayRatioSdr)) /
+ (sk_float_log(mGainmapInfo.fDisplayRatioHdr) -
+ sk_float_log(mGainmapInfo.fDisplayRatioSdr));
+ const float W = std::max(std::min(Wunclamped, 1.f), 0.f);
+ mBuilder.uniform("W") = W;
+ uniforms = mBuilder.uniforms();
+ }
+ return uniforms;
+ }
+
+public:
+ explicit DeferredGainmapShader(const sk_sp<const SkImage>& image,
+ const sk_sp<const SkImage>& gainmapImage,
+ const SkGainmapInfo& gainmapInfo, SkTileMode tileModeX,
+ SkTileMode tileModeY, const SkSamplingOptions& sampling) {
+ mGainmapInfo = gainmapInfo;
+ setupChildren(image, gainmapImage, tileModeX, tileModeY, sampling);
+ setupGenericUniforms(gainmapImage, gainmapInfo);
+ }
+
+ static sk_sp<SkShader> Make(const sk_sp<const SkImage>& image,
+ const sk_sp<const SkImage>& gainmapImage,
+ const SkGainmapInfo& gainmapInfo, SkTileMode tileModeX,
+ SkTileMode tileModeY, const SkSamplingOptions& sampling) {
+ auto deferredHandler = std::make_shared<DeferredGainmapShader>(
+ image, gainmapImage, gainmapInfo, tileModeX, tileModeY, sampling);
+ auto callback =
+ [deferredHandler](const SkRuntimeEffectPriv::UniformsCallbackContext& renderContext)
+ -> sk_sp<const SkData> {
+ return deferredHandler->build(getTargetHdrSdrRatio(renderContext.fDstColorSpace));
+ };
+ return SkRuntimeEffectPriv::MakeDeferredShader(deferredHandler->mShader.get(), callback,
+ deferredHandler->mBuilder.children());
+ }
+};
+
+sk_sp<SkShader> MakeGainmapShader(const sk_sp<const SkImage>& image,
+ const sk_sp<const SkImage>& gainmapImage,
+ const SkGainmapInfo& gainmapInfo, SkTileMode tileModeX,
+ SkTileMode tileModeY, const SkSamplingOptions& sampling) {
+ return DeferredGainmapShader::Make(image, gainmapImage, gainmapInfo, tileModeX, tileModeY,
+ sampling);
+}
+
+#else // __ANDROID__
+
+sk_sp<SkShader> MakeGainmapShader(const sk_sp<const SkImage>& image,
+ const sk_sp<const SkImage>& gainmapImage,
+ const SkGainmapInfo& gainmapInfo, SkTileMode tileModeX,
+ SkTileMode tileModeY, const SkSamplingOptions& sampling) {
+ return nullptr;
+}
+
+#endif // __ANDROID__
+
} // namespace android::uirenderer \ No newline at end of file
diff --git a/libs/hwui/effects/GainmapRenderer.h b/libs/hwui/effects/GainmapRenderer.h
index 7c56d94d9776..4ed2445da17e 100644
--- a/libs/hwui/effects/GainmapRenderer.h
+++ b/libs/hwui/effects/GainmapRenderer.h
@@ -30,4 +30,9 @@ void DrawGainmapBitmap(SkCanvas* c, const sk_sp<const SkImage>& image, const SkR
SkCanvas::SrcRectConstraint constraint,
const sk_sp<const SkImage>& gainmapImage, const SkGainmapInfo& gainmapInfo);
+sk_sp<SkShader> MakeGainmapShader(const sk_sp<const SkImage>& image,
+ const sk_sp<const SkImage>& gainmapImage,
+ const SkGainmapInfo& gainmapInfo, SkTileMode tileModeX,
+ SkTileMode tileModeY, const SkSamplingOptions& sampling);
+
} // namespace android::uirenderer
diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
index 8266beb4ee8c..9a06be006dca 100644
--- a/libs/hwui/hwui/ImageDecoder.cpp
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -498,7 +498,7 @@ SkCodec::Result ImageDecoder::decode(void* pixels, size_t rowBytes) {
return result;
}
-SkCodec::Result ImageDecoder::extractGainmap(Bitmap* destination) {
+SkCodec::Result ImageDecoder::extractGainmap(Bitmap* destination, bool isShared) {
ATRACE_CALL();
SkGainmapInfo gainmapInfo;
std::unique_ptr<SkStream> gainmapStream;
@@ -553,9 +553,12 @@ SkCodec::Result ImageDecoder::extractGainmap(Bitmap* destination) {
return SkCodec::kInternalError;
}
- // TODO: We don't currently parcel the gainmap, but if we should then also support
- // the shared allocator
- sk_sp<Bitmap> nativeBitmap = Bitmap::allocateHeapBitmap(&bm);
+ sk_sp<Bitmap> nativeBitmap;
+ if (isShared) {
+ nativeBitmap = Bitmap::allocateAshmemBitmap(&bm);
+ } else {
+ nativeBitmap = Bitmap::allocateHeapBitmap(&bm);
+ }
if (!nativeBitmap) {
ALOGE("OOM allocating Bitmap with dimensions %i x %i", bitmapInfo.width(),
bitmapInfo.height());
diff --git a/libs/hwui/hwui/ImageDecoder.h b/libs/hwui/hwui/ImageDecoder.h
index 97573e1e8207..b3781b52a418 100644
--- a/libs/hwui/hwui/ImageDecoder.h
+++ b/libs/hwui/hwui/ImageDecoder.h
@@ -79,7 +79,7 @@ public:
// Set whether the ImageDecoder should handle RestorePrevious frames.
void setHandleRestorePrevious(bool handle);
- SkCodec::Result extractGainmap(Bitmap* destination);
+ SkCodec::Result extractGainmap(Bitmap* destination, bool isShared);
private:
// State machine for keeping track of how to handle RestorePrevious (RP)
diff --git a/libs/hwui/jni/ImageDecoder.cpp b/libs/hwui/jni/ImageDecoder.cpp
index ad80460da5a9..db1c188e425e 100644
--- a/libs/hwui/jni/ImageDecoder.cpp
+++ b/libs/hwui/jni/ImageDecoder.cpp
@@ -354,7 +354,8 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong
// cost of RAM
if (result == SkCodec::kSuccess && !jpostProcess && !preferRamOverQuality) {
// The gainmap costs RAM to improve quality, so skip this if we're prioritizing RAM instead
- result = decoder->extractGainmap(nativeBitmap.get());
+ result = decoder->extractGainmap(nativeBitmap.get(),
+ allocator == kSharedMemory_Allocator ? true : false);
jexception = get_and_clear_exception(env);
}
diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp
index 75d45e5bd8aa..7eb79be6f55b 100644
--- a/libs/hwui/jni/Shader.cpp
+++ b/libs/hwui/jni/Shader.cpp
@@ -1,6 +1,9 @@
#undef LOG_TAG
#define LOG_TAG "ShaderJNI"
+#include <vector>
+
+#include "Gainmap.h"
#include "GraphicsJNI.h"
#include "SkBitmap.h"
#include "SkBlendMode.h"
@@ -17,10 +20,9 @@
#include "SkShader.h"
#include "SkString.h"
#include "SkTileMode.h"
+#include "effects/GainmapRenderer.h"
#include "include/effects/SkRuntimeEffect.h"
-#include <vector>
-
using namespace android::uirenderer;
/**
@@ -74,7 +76,20 @@ static jlong createBitmapShaderHelper(JNIEnv* env, jobject o, jlong matrixPtr, j
if (bitmapHandle) {
// Only pass a valid SkBitmap object to the constructor if the Bitmap exists. Otherwise,
// we'll pass an empty SkBitmap to avoid crashing/excepting for compatibility.
- image = android::bitmap::toBitmap(bitmapHandle).makeImage();
+ auto& bitmap = android::bitmap::toBitmap(bitmapHandle);
+ image = bitmap.makeImage();
+
+ if (!isDirectSampled && bitmap.hasGainmap()) {
+ sk_sp<SkShader> gainmapShader = MakeGainmapShader(
+ image, bitmap.gainmap()->bitmap->makeImage(), bitmap.gainmap()->info,
+ (SkTileMode)tileModeX, (SkTileMode)tileModeY, sampling);
+ if (gainmapShader) {
+ if (matrix) {
+ gainmapShader = gainmapShader->makeWithLocalMatrix(*matrix);
+ }
+ return reinterpret_cast<jlong>(gainmapShader.release());
+ }
+ }
}
if (!image.get()) {
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index f10b2b2f0694..dd781bb85470 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -311,7 +311,7 @@ float CanvasContext::setColorMode(ColorMode mode) {
}
switch (mColorMode) {
case ColorMode::Hdr:
- return 3.f; // TODO: Refine this number
+ return Properties::maxHdrHeadroomOn8bit;
case ColorMode::Hdr10:
return 10.f;
default:
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 7a7f1abdd268..0afd949cf5c9 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -135,7 +135,9 @@ void RenderThread::frameCallback(int64_t vsyncId, int64_t frameDeadline, int64_t
!mFrameCallbackTaskPending) {
ATRACE_NAME("queue mFrameCallbackTask");
mFrameCallbackTaskPending = true;
- nsecs_t runAt = (frameTimeNanos + mDispatchFrameDelay);
+
+ nsecs_t timeUntilDeadline = frameDeadline - frameTimeNanos;
+ nsecs_t runAt = (frameTimeNanos + (timeUntilDeadline * 0.25f));
queue().postAt(runAt, [=]() { dispatchFrameCallbacks(); });
}
}
@@ -257,7 +259,6 @@ void RenderThread::initThreadLocals() {
void RenderThread::setupFrameInterval() {
nsecs_t frameIntervalNanos = DeviceInfo::getVsyncPeriod();
mTimeLord.setFrameInterval(frameIntervalNanos);
- mDispatchFrameDelay = static_cast<nsecs_t>(frameIntervalNanos * .25f);
}
void RenderThread::requireGlContext() {
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index 0a89e5e944f8..c77cd4134d1e 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -235,7 +235,6 @@ private:
bool mFrameCallbackTaskPending;
TimeLord mTimeLord;
- nsecs_t mDispatchFrameDelay = 4_ms;
RenderState* mRenderState;
EglManager* mEglManager;
WebViewFunctorManager& mFunctorManager;
diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp
index 24cfc9d70c9b..c3984055a278 100644
--- a/libs/input/MouseCursorController.cpp
+++ b/libs/input/MouseCursorController.cpp
@@ -63,24 +63,23 @@ MouseCursorController::~MouseCursorController() {
mLocked.pointerSprite.clear();
}
-bool MouseCursorController::getBounds(float* outMinX, float* outMinY, float* outMaxX,
- float* outMaxY) const {
+std::optional<FloatRect> MouseCursorController::getBounds() const {
std::scoped_lock lock(mLock);
- return getBoundsLocked(outMinX, outMinY, outMaxX, outMaxY);
+ return getBoundsLocked();
}
-bool MouseCursorController::getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX,
- float* outMaxY) const REQUIRES(mLock) {
+std::optional<FloatRect> MouseCursorController::getBoundsLocked() const REQUIRES(mLock) {
if (!mLocked.viewport.isValid()) {
- return false;
+ return {};
}
- *outMinX = mLocked.viewport.logicalLeft;
- *outMinY = mLocked.viewport.logicalTop;
- *outMaxX = mLocked.viewport.logicalRight - 1;
- *outMaxY = mLocked.viewport.logicalBottom - 1;
- return true;
+ return FloatRect{
+ static_cast<float>(mLocked.viewport.logicalLeft),
+ static_cast<float>(mLocked.viewport.logicalTop),
+ static_cast<float>(mLocked.viewport.logicalRight - 1),
+ static_cast<float>(mLocked.viewport.logicalBottom - 1),
+ };
}
void MouseCursorController::move(float deltaX, float deltaY) {
@@ -121,31 +120,19 @@ void MouseCursorController::setPosition(float x, float y) {
}
void MouseCursorController::setPositionLocked(float x, float y) REQUIRES(mLock) {
- float minX, minY, maxX, maxY;
- if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) {
- if (x <= minX) {
- mLocked.pointerX = minX;
- } else if (x >= maxX) {
- mLocked.pointerX = maxX;
- } else {
- mLocked.pointerX = x;
- }
- if (y <= minY) {
- mLocked.pointerY = minY;
- } else if (y >= maxY) {
- mLocked.pointerY = maxY;
- } else {
- mLocked.pointerY = y;
- }
- updatePointerLocked();
- }
+ const auto bounds = getBoundsLocked();
+ if (!bounds) return;
+
+ mLocked.pointerX = std::max(bounds->left, std::min(bounds->right, x));
+ mLocked.pointerY = std::max(bounds->top, std::min(bounds->bottom, y));
+
+ updatePointerLocked();
}
-void MouseCursorController::getPosition(float* outX, float* outY) const {
+FloatPoint MouseCursorController::getPosition() const {
std::scoped_lock lock(mLock);
- *outX = mLocked.pointerX;
- *outY = mLocked.pointerY;
+ return {mLocked.pointerX, mLocked.pointerY};
}
int32_t MouseCursorController::getDisplayId() const {
@@ -235,10 +222,9 @@ void MouseCursorController::setDisplayViewport(const DisplayViewport& viewport,
// Reset cursor position to center if size or display changed.
if (oldViewport.displayId != viewport.displayId || oldDisplayWidth != newDisplayWidth ||
oldDisplayHeight != newDisplayHeight) {
- float minX, minY, maxX, maxY;
- if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) {
- mLocked.pointerX = (minX + maxX) * 0.5f;
- mLocked.pointerY = (minY + maxY) * 0.5f;
+ if (const auto bounds = getBoundsLocked(); bounds) {
+ mLocked.pointerX = (bounds->left + bounds->right) * 0.5f;
+ mLocked.pointerY = (bounds->top + bounds->bottom) * 0.5f;
// Reload icon resources for density may be changed.
loadResourcesLocked(getAdditionalMouseResources);
} else {
diff --git a/libs/input/MouseCursorController.h b/libs/input/MouseCursorController.h
index db0ab56429b2..26be2a858c4e 100644
--- a/libs/input/MouseCursorController.h
+++ b/libs/input/MouseCursorController.h
@@ -43,12 +43,12 @@ public:
MouseCursorController(PointerControllerContext& context);
~MouseCursorController();
- bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
+ std::optional<FloatRect> getBounds() const;
void move(float deltaX, float deltaY);
void setButtonState(int32_t buttonState);
int32_t getButtonState() const;
void setPosition(float x, float y);
- void getPosition(float* outX, float* outY) const;
+ FloatPoint getPosition() const;
int32_t getDisplayId() const;
void fade(PointerControllerInterface::Transition transition);
void unfade(PointerControllerInterface::Transition transition);
@@ -102,7 +102,7 @@ private:
} mLocked GUARDED_BY(mLock);
- bool getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
+ std::optional<FloatRect> getBoundsLocked() const;
void setPositionLocked(float x, float y);
void updatePointerLocked();
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index fedf58d7c6d0..544edc2a716f 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -114,16 +114,15 @@ PointerController::PointerController(const sp<PointerControllerPolicyInterface>&
PointerController::~PointerController() {
mDisplayInfoListener->onPointerControllerDestroyed();
mUnregisterWindowInfosListener(mDisplayInfoListener);
- mContext.getPolicy()->onPointerDisplayIdChanged(ADISPLAY_ID_NONE, 0, 0);
+ mContext.getPolicy()->onPointerDisplayIdChanged(ADISPLAY_ID_NONE, FloatPoint{0, 0});
}
std::mutex& PointerController::getLock() const {
return mDisplayInfoListener->mLock;
}
-bool PointerController::getBounds(float* outMinX, float* outMinY, float* outMaxX,
- float* outMaxY) const {
- return mCursorController.getBounds(outMinX, outMinY, outMaxX, outMaxY);
+std::optional<FloatRect> PointerController::getBounds() const {
+ return mCursorController.getBounds();
}
void PointerController::move(float deltaX, float deltaY) {
@@ -156,15 +155,13 @@ void PointerController::setPosition(float x, float y) {
mCursorController.setPosition(transformed.x, transformed.y);
}
-void PointerController::getPosition(float* outX, float* outY) const {
+FloatPoint PointerController::getPosition() const {
const int32_t displayId = mCursorController.getDisplayId();
- mCursorController.getPosition(outX, outY);
+ const auto p = mCursorController.getPosition();
{
std::scoped_lock lock(getLock());
const auto& transform = getTransformForDisplayLocked(displayId);
- const auto xy = transform.inverse().transform(*outX, *outY);
- *outX = xy.x;
- *outY = xy.y;
+ return FloatPoint{transform.inverse().transform(p.x, p.y)};
}
}
@@ -262,19 +259,31 @@ void PointerController::reloadPointerResources() {
}
void PointerController::setDisplayViewport(const DisplayViewport& viewport) {
- std::scoped_lock lock(getLock());
+ struct PointerDisplayChangeArgs {
+ int32_t displayId;
+ FloatPoint cursorPosition;
+ };
+ std::optional<PointerDisplayChangeArgs> pointerDisplayChanged;
- bool getAdditionalMouseResources = false;
- if (mLocked.presentation == PointerController::Presentation::POINTER ||
- mLocked.presentation == PointerController::Presentation::STYLUS_HOVER) {
- getAdditionalMouseResources = true;
- }
- mCursorController.setDisplayViewport(viewport, getAdditionalMouseResources);
- if (viewport.displayId != mLocked.pointerDisplayId) {
- float xPos, yPos;
- mCursorController.getPosition(&xPos, &yPos);
- mContext.getPolicy()->onPointerDisplayIdChanged(viewport.displayId, xPos, yPos);
- mLocked.pointerDisplayId = viewport.displayId;
+ { // acquire lock
+ std::scoped_lock lock(getLock());
+
+ bool getAdditionalMouseResources = false;
+ if (mLocked.presentation == PointerController::Presentation::POINTER ||
+ mLocked.presentation == PointerController::Presentation::STYLUS_HOVER) {
+ getAdditionalMouseResources = true;
+ }
+ mCursorController.setDisplayViewport(viewport, getAdditionalMouseResources);
+ if (viewport.displayId != mLocked.pointerDisplayId) {
+ mLocked.pointerDisplayId = viewport.displayId;
+ pointerDisplayChanged = {viewport.displayId, mCursorController.getPosition()};
+ }
+ } // release lock
+
+ if (pointerDisplayChanged) {
+ // Notify the policy without holding the pointer controller lock.
+ mContext.getPolicy()->onPointerDisplayIdChanged(pointerDisplayChanged->displayId,
+ pointerDisplayChanged->cursorPosition);
}
}
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index 48d5a5756a69..6d3557c89cc7 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -50,12 +50,12 @@ public:
~PointerController() override;
- virtual bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
+ virtual std::optional<FloatRect> getBounds() const;
virtual void move(float deltaX, float deltaY);
virtual void setButtonState(int32_t buttonState);
virtual int32_t getButtonState() const;
virtual void setPosition(float x, float y);
- virtual void getPosition(float* outX, float* outY) const;
+ virtual FloatPoint getPosition() const;
virtual int32_t getDisplayId() const;
virtual void fade(Transition transition);
virtual void unfade(Transition transition);
diff --git a/libs/input/PointerControllerContext.h b/libs/input/PointerControllerContext.h
index 96d83a5f0d15..f6f5d3bc51bd 100644
--- a/libs/input/PointerControllerContext.h
+++ b/libs/input/PointerControllerContext.h
@@ -81,7 +81,7 @@ public:
virtual PointerIconStyle getDefaultPointerIconId() = 0;
virtual PointerIconStyle getDefaultStylusIconId() = 0;
virtual PointerIconStyle getCustomPointerIconId() = 0;
- virtual void onPointerDisplayIdChanged(int32_t displayId, float xPos, float yPos) = 0;
+ virtual void onPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position) = 0;
};
/*
diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp
index c820d0007a4b..2378d42793a1 100644
--- a/libs/input/tests/PointerController_test.cpp
+++ b/libs/input/tests/PointerController_test.cpp
@@ -60,7 +60,7 @@ public:
virtual PointerIconStyle getDefaultPointerIconId() override;
virtual PointerIconStyle getDefaultStylusIconId() override;
virtual PointerIconStyle getCustomPointerIconId() override;
- virtual void onPointerDisplayIdChanged(int32_t displayId, float xPos, float yPos) override;
+ virtual void onPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position) override;
bool allResourcesAreLoaded();
bool noResourcesAreLoaded();
@@ -143,8 +143,8 @@ void MockPointerControllerPolicyInterface::loadPointerIconForType(SpriteIcon* ic
}
void MockPointerControllerPolicyInterface::onPointerDisplayIdChanged(int32_t displayId,
- float /*xPos*/,
- float /*yPos*/) {
+ const FloatPoint& /*position*/
+) {
latestPointerDisplayId = displayId;
}