summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
Diffstat (limited to 'libs')
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java116
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java22
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java10
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java106
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java45
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java97
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java64
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java57
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java42
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java32
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/util/DataProducer.java23
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java27
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java82
-rw-r--r--libs/WindowManager/Shell/res/drawable/pip_custom_close_bg.xml24
-rw-r--r--libs/WindowManager/Shell/res/layout/pip_menu_action.xml8
-rw-r--r--libs/WindowManager/Shell/res/layout/split_decor.xml5
-rw-r--r--libs/WindowManager/Shell/res/values-es/strings.xml6
-rw-r--r--libs/WindowManager/Shell/res/values/colors.xml5
-rw-r--r--libs/WindowManager/Shell/res/values/config.xml4
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt48
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java63
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java43
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerView.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuActionView.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java60
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java28
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java3
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt14
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java61
-rw-r--r--libs/hwui/utils/Color.cpp10
48 files changed, 987 insertions, 295 deletions
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
index fdcb7be597d5..a20e7179daf1 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
@@ -22,7 +22,6 @@ import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_UNKNOWN;
import static androidx.window.common.CommonFoldingFeature.parseListFromString;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.content.Context;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback;
@@ -30,22 +29,25 @@ import android.text.TextUtils;
import android.util.Log;
import android.util.SparseIntArray;
+import androidx.window.util.AcceptOnceConsumer;
import androidx.window.util.BaseDataProducer;
-import androidx.window.util.DataProducer;
import com.android.internal.R;
+import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import java.util.Optional;
import java.util.Set;
+import java.util.function.Consumer;
/**
- * An implementation of {@link androidx.window.util.DataProducer} that returns the device's posture
- * by mapping the state returned from {@link DeviceStateManager} to values provided in the resources
- * config at {@link R.array#config_device_state_postures}.
+ * An implementation of {@link androidx.window.util.BaseDataProducer} that returns
+ * the device's posture by mapping the state returned from {@link DeviceStateManager} to
+ * values provided in the resources' config at {@link R.array#config_device_state_postures}.
*/
-public final class DeviceStateManagerFoldingFeatureProducer extends
- BaseDataProducer<List<CommonFoldingFeature>> {
+public final class DeviceStateManagerFoldingFeatureProducer
+ extends BaseDataProducer<List<CommonFoldingFeature>> {
private static final String TAG =
DeviceStateManagerFoldingFeatureProducer.class.getSimpleName();
private static final boolean DEBUG = false;
@@ -54,15 +56,11 @@ public final class DeviceStateManagerFoldingFeatureProducer extends
private int mCurrentDeviceState = INVALID_DEVICE_STATE;
- private final DeviceStateCallback mDeviceStateCallback = (state) -> {
- mCurrentDeviceState = state;
- notifyDataChanged();
- };
@NonNull
- private final DataProducer<String> mRawFoldSupplier;
+ private final BaseDataProducer<String> mRawFoldSupplier;
public DeviceStateManagerFoldingFeatureProducer(@NonNull Context context,
- @NonNull DataProducer<String> rawFoldSupplier) {
+ @NonNull BaseDataProducer<String> rawFoldSupplier) {
mRawFoldSupplier = rawFoldSupplier;
String[] deviceStatePosturePairs = context.getResources()
.getStringArray(R.array.config_device_state_postures);
@@ -70,7 +68,8 @@ public final class DeviceStateManagerFoldingFeatureProducer extends
String[] deviceStatePostureMapping = deviceStatePosturePair.split(":");
if (deviceStatePostureMapping.length != 2) {
if (DEBUG) {
- Log.e(TAG, "Malformed device state posture pair: " + deviceStatePosturePair);
+ Log.e(TAG, "Malformed device state posture pair: "
+ + deviceStatePosturePair);
}
continue;
}
@@ -82,7 +81,8 @@ public final class DeviceStateManagerFoldingFeatureProducer extends
posture = Integer.parseInt(deviceStatePostureMapping[1]);
} catch (NumberFormatException e) {
if (DEBUG) {
- Log.e(TAG, "Failed to parse device state or posture: " + deviceStatePosturePair,
+ Log.e(TAG, "Failed to parse device state or posture: "
+ + deviceStatePosturePair,
e);
}
continue;
@@ -92,32 +92,92 @@ public final class DeviceStateManagerFoldingFeatureProducer extends
}
if (mDeviceStateToPostureMap.size() > 0) {
- context.getSystemService(DeviceStateManager.class)
- .registerCallback(context.getMainExecutor(), mDeviceStateCallback);
+ DeviceStateCallback deviceStateCallback = (state) -> {
+ mCurrentDeviceState = state;
+ mRawFoldSupplier.getData(this::notifyFoldingFeatureChange);
+ };
+ Objects.requireNonNull(context.getSystemService(DeviceStateManager.class))
+ .registerCallback(context.getMainExecutor(), deviceStateCallback);
}
}
- @Override
- @Nullable
- public Optional<List<CommonFoldingFeature>> getData() {
- final int globalHingeState = globalHingeState();
- Optional<String> displayFeaturesString = mRawFoldSupplier.getData();
- if (displayFeaturesString.isEmpty() || TextUtils.isEmpty(displayFeaturesString.get())) {
- return Optional.empty();
+ /**
+ * Add a callback to mCallbacks if there is no device state. This callback will be run
+ * once a device state is set. Otherwise,run the callback immediately.
+ */
+ private void runCallbackWhenValidState(@NonNull Consumer<List<CommonFoldingFeature>> callback,
+ String displayFeaturesString) {
+ if (isCurrentStateValid()) {
+ callback.accept(calculateFoldingFeature(displayFeaturesString));
+ } else {
+ // This callback will be added to mCallbacks and removed once it runs once.
+ AcceptOnceConsumer<List<CommonFoldingFeature>> singleRunCallback =
+ new AcceptOnceConsumer<>(this, callback);
+ addDataChangedCallback(singleRunCallback);
}
- return Optional.of(parseListFromString(displayFeaturesString.get(), globalHingeState));
+ }
+
+ /**
+ * Checks to find {@link DeviceStateManagerFoldingFeatureProducer#mCurrentDeviceState} in the
+ * {@link DeviceStateManagerFoldingFeatureProducer#mDeviceStateToPostureMap} which was
+ * initialized in the constructor of {@link DeviceStateManagerFoldingFeatureProducer}.
+ * Returns a boolean value of whether the device state is valid.
+ */
+ private boolean isCurrentStateValid() {
+ // If the device state is not found in the map, indexOfKey returns a negative number.
+ return mDeviceStateToPostureMap.indexOfKey(mCurrentDeviceState) >= 0;
}
@Override
- protected void onListenersChanged(Set<Runnable> callbacks) {
+ protected void onListenersChanged(
+ @NonNull Set<Consumer<List<CommonFoldingFeature>>> callbacks) {
super.onListenersChanged(callbacks);
if (callbacks.isEmpty()) {
- mRawFoldSupplier.removeDataChangedCallback(this::notifyDataChanged);
+ mCurrentDeviceState = INVALID_DEVICE_STATE;
+ mRawFoldSupplier.removeDataChangedCallback(this::notifyFoldingFeatureChange);
} else {
- mRawFoldSupplier.addDataChangedCallback(this::notifyDataChanged);
+ mRawFoldSupplier.addDataChangedCallback(this::notifyFoldingFeatureChange);
}
}
+ @NonNull
+ @Override
+ public Optional<List<CommonFoldingFeature>> getCurrentData() {
+ Optional<String> displayFeaturesString = mRawFoldSupplier.getCurrentData();
+ if (!isCurrentStateValid()) {
+ return Optional.empty();
+ } else {
+ return displayFeaturesString.map(this::calculateFoldingFeature);
+ }
+ }
+
+ /**
+ * Adds the data to the storeFeaturesConsumer when the data is ready.
+ * @param storeFeaturesConsumer a consumer to collect the data when it is first available.
+ */
+ public void getData(Consumer<List<CommonFoldingFeature>> storeFeaturesConsumer) {
+ mRawFoldSupplier.getData((String displayFeaturesString) -> {
+ if (TextUtils.isEmpty(displayFeaturesString)) {
+ storeFeaturesConsumer.accept(new ArrayList<>());
+ } else {
+ runCallbackWhenValidState(storeFeaturesConsumer, displayFeaturesString);
+ }
+ });
+ }
+
+ private void notifyFoldingFeatureChange(String displayFeaturesString) {
+ if (TextUtils.isEmpty(displayFeaturesString)) {
+ notifyDataChanged(new ArrayList<>());
+ } else {
+ notifyDataChanged(calculateFoldingFeature(displayFeaturesString));
+ }
+ }
+
+ private List<CommonFoldingFeature> calculateFoldingFeature(String displayFeaturesString) {
+ final int globalHingeState = globalHingeState();
+ return parseListFromString(displayFeaturesString, globalHingeState);
+ }
+
private int globalHingeState() {
return mDeviceStateToPostureMap.get(mCurrentDeviceState, COMMON_STATE_UNKNOWN);
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java
index 69ad1badce60..7906342d445d 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java
@@ -32,6 +32,7 @@ import com.android.internal.R;
import java.util.Optional;
import java.util.Set;
+import java.util.function.Consumer;
/**
* Implementation of {@link androidx.window.util.DataProducer} that produces a
@@ -40,7 +41,7 @@ import java.util.Set;
* settings where the {@link String} property is saved with the key
* {@link RawFoldingFeatureProducer#DISPLAY_FEATURES}. If this value is null or empty then the
* value in {@link android.content.res.Resources} is used. If both are empty then
- * {@link RawFoldingFeatureProducer#getData()} returns an empty object.
+ * {@link RawFoldingFeatureProducer#getData} returns an empty object.
* {@link RawFoldingFeatureProducer} listens to changes in the setting so that it can override
* the system {@link CommonFoldingFeature} data.
*/
@@ -63,12 +64,13 @@ public final class RawFoldingFeatureProducer extends BaseDataProducer<String> {
@Override
@NonNull
- public Optional<String> getData() {
+ public void getData(Consumer<String> dataConsumer) {
String displayFeaturesString = getFeatureString();
if (displayFeaturesString == null) {
- return Optional.empty();
+ dataConsumer.accept("");
+ } else {
+ dataConsumer.accept(displayFeaturesString);
}
- return Optional.of(displayFeaturesString);
}
/**
@@ -84,7 +86,7 @@ public final class RawFoldingFeatureProducer extends BaseDataProducer<String> {
}
@Override
- protected void onListenersChanged(Set<Runnable> callbacks) {
+ protected void onListenersChanged(Set<Consumer<String>> callbacks) {
if (callbacks.isEmpty()) {
unregisterObserversIfNeeded();
} else {
@@ -92,6 +94,12 @@ public final class RawFoldingFeatureProducer extends BaseDataProducer<String> {
}
}
+ @NonNull
+ @Override
+ public Optional<String> getCurrentData() {
+ return Optional.of(getFeatureString());
+ }
+
/**
* Registers settings observers, if needed. When settings observers are registered for this
* producer callbacks for changes in data will be triggered.
@@ -125,8 +133,8 @@ public final class RawFoldingFeatureProducer extends BaseDataProducer<String> {
@Override
public void onChange(boolean selfChange, Uri uri) {
if (mDisplayFeaturesUri.equals(uri)) {
- notifyDataChanged();
+ notifyDataChanged(getFeatureString());
}
}
}
-}
+} \ No newline at end of file
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index f4e91bae54ee..e50b9a1cd469 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -70,6 +70,8 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo);
void onTaskFragmentParentInfoChanged(@NonNull IBinder fragmentToken,
@NonNull Configuration parentConfig);
+ void onActivityReparentToTask(int taskId, @NonNull Intent activityIntent,
+ @NonNull IBinder activityToken);
}
/**
@@ -300,4 +302,12 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
mCallback.onTaskFragmentParentInfoChanged(fragmentToken, parentConfig);
}
}
+
+ @Override
+ public void onActivityReparentToTask(int taskId, @NonNull Intent activityIntent,
+ @NonNull IBinder activityToken) {
+ if (mCallback != null) {
+ mCallback.onActivityReparentToTask(taskId, activityIntent, activityToken);
+ }
+ }
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index e20cef2bec4e..9f33cbcbcbd5 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -40,6 +40,7 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.util.ArraySet;
+import android.util.Log;
import android.util.SparseArray;
import android.window.TaskFragmentInfo;
import android.window.WindowContainerTransaction;
@@ -59,6 +60,7 @@ import java.util.function.Consumer;
*/
public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmentCallback,
ActivityEmbeddingComponent {
+ private static final String TAG = "SplitController";
@VisibleForTesting
final SplitPresenter mPresenter;
@@ -220,6 +222,19 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
}
+ @Override
+ public void onActivityReparentToTask(int taskId, @NonNull Intent activityIntent,
+ @NonNull IBinder activityToken) {
+ // If the activity belongs to the current app process, we treat it as a new activity launch.
+ final Activity activity = ActivityThread.currentActivityThread().getActivity(activityToken);
+ if (activity != null) {
+ onActivityCreated(activity);
+ updateCallbackIfNecessary();
+ return;
+ }
+ // TODO: handle for activity in other process.
+ }
+
/** Called on receiving {@link #onTaskFragmentVanished(TaskFragmentInfo)} for cleanup. */
private void cleanupTaskFragment(@NonNull IBinder taskFragmentToken) {
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
@@ -229,8 +244,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
if (taskContainer.isEmpty()) {
// Cleanup the TaskContainer if it becomes empty.
- mPresenter.stopOverrideSplitAnimation(taskContainer.mTaskId);
- mTaskContainers.remove(taskContainer.mTaskId);
+ mPresenter.stopOverrideSplitAnimation(taskContainer.getTaskId());
+ mTaskContainers.remove(taskContainer.getTaskId());
}
return;
}
@@ -241,13 +256,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
if (taskContainer == null) {
return;
}
- final boolean wasInPip = isInPictureInPicture(taskContainer.mConfiguration);
+ final boolean wasInPip = isInPictureInPicture(taskContainer.getConfiguration());
final boolean isInPIp = isInPictureInPicture(config);
- taskContainer.mConfiguration = config;
+ taskContainer.setConfiguration(config);
// We need to check the animation override when enter/exit PIP or has bounds changed.
boolean shouldUpdateAnimationOverride = wasInPip != isInPIp;
- if (onTaskBoundsMayChange(taskContainer, config.windowConfiguration.getBounds())
+ if (taskContainer.setTaskBounds(config.windowConfiguration.getBounds())
&& !isInPIp) {
// We don't care the bounds change when it has already entered PIP.
shouldUpdateAnimationOverride = true;
@@ -257,16 +272,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
}
- /** Returns {@code true} if the bounds is changed. */
- private boolean onTaskBoundsMayChange(@NonNull TaskContainer taskContainer,
- @NonNull Rect taskBounds) {
- if (!taskBounds.isEmpty() && !taskContainer.mTaskBounds.equals(taskBounds)) {
- taskContainer.mTaskBounds.set(taskBounds);
- return true;
- }
- return false;
- }
-
/**
* Updates if we should override transition animation. We only want to override if the Task
* bounds is large enough for at least one split rule.
@@ -279,15 +284,15 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// We only want to override if it supports split.
if (supportSplit(taskContainer)) {
- mPresenter.startOverrideSplitAnimation(taskContainer.mTaskId);
+ mPresenter.startOverrideSplitAnimation(taskContainer.getTaskId());
} else {
- mPresenter.stopOverrideSplitAnimation(taskContainer.mTaskId);
+ mPresenter.stopOverrideSplitAnimation(taskContainer.getTaskId());
}
}
private boolean supportSplit(@NonNull TaskContainer taskContainer) {
// No split inside PIP.
- if (isInPictureInPicture(taskContainer.mConfiguration)) {
+ if (isInPictureInPicture(taskContainer.getConfiguration())) {
return false;
}
// Check if the parent container bounds can support any split rule.
@@ -295,7 +300,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
if (!(rule instanceof SplitRule)) {
continue;
}
- if (mPresenter.shouldShowSideBySide(taskContainer.mTaskBounds, (SplitRule) rule)) {
+ if (mPresenter.shouldShowSideBySide(taskContainer.getTaskBounds(), (SplitRule) rule)) {
return true;
}
}
@@ -425,21 +430,36 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return null;
}
+ TaskFragmentContainer newContainer(@NonNull Activity activity, int taskId) {
+ return newContainer(activity, activity, taskId);
+ }
+
/**
* Creates and registers a new organized container with an optional activity that will be
* re-parented to it in a WCT.
+ *
+ * @param activity the activity that will be reparented to the TaskFragment.
+ * @param activityInTask activity in the same Task so that we can get the Task bounds if
+ * needed.
+ * @param taskId parent Task of the new TaskFragment.
*/
- TaskFragmentContainer newContainer(@Nullable Activity activity, int taskId) {
+ TaskFragmentContainer newContainer(@Nullable Activity activity,
+ @NonNull Activity activityInTask, int taskId) {
+ if (activityInTask == null) {
+ throw new IllegalArgumentException("activityInTask must not be null,");
+ }
final TaskFragmentContainer container = new TaskFragmentContainer(activity, taskId);
if (!mTaskContainers.contains(taskId)) {
mTaskContainers.put(taskId, new TaskContainer(taskId));
}
final TaskContainer taskContainer = mTaskContainers.get(taskId);
taskContainer.mContainers.add(container);
- if (activity != null && !taskContainer.isTaskBoundsInitialized()
- && onTaskBoundsMayChange(taskContainer,
- SplitPresenter.getTaskBoundsFromActivity(activity))) {
- // Initial check before any TaskFragment has appeared.
+ if (!taskContainer.isTaskBoundsInitialized()) {
+ // Get the initial bounds before the TaskFragment has appeared.
+ final Rect taskBounds = SplitPresenter.getTaskBoundsFromActivity(activityInTask);
+ if (!taskContainer.setTaskBounds(taskBounds)) {
+ Log.w(TAG, "Can't find bounds from activity=" + activityInTask);
+ }
updateAnimationOverride(taskContainer);
}
return container;
@@ -887,6 +907,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return null;
}
+ @Nullable
+ TaskContainer getTaskContainer(int taskId) {
+ return mTaskContainers.get(taskId);
+ }
+
/**
* Returns {@code true} if an Activity with the provided component name should always be
* expanded to occupy full task bounds. Such activity must not be put in a split.
@@ -1211,37 +1236,4 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return configuration != null
&& configuration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_PINNED;
}
-
- /** Represents TaskFragments and split pairs below a Task. */
- @VisibleForTesting
- static class TaskContainer {
- /** The unique task id. */
- final int mTaskId;
- /** Active TaskFragments in this Task. */
- final List<TaskFragmentContainer> mContainers = new ArrayList<>();
- /** Active split pairs in this Task. */
- final List<SplitContainer> mSplitContainers = new ArrayList<>();
- /**
- * TaskFragments that the organizer has requested to be closed. They should be removed when
- * the organizer receives {@link #onTaskFragmentVanished(TaskFragmentInfo)} event for them.
- */
- final Set<IBinder> mFinishedContainer = new ArraySet<>();
- /** Available window bounds of this Task. */
- final Rect mTaskBounds = new Rect();
- /** Configuration of the Task. */
- @Nullable
- Configuration mConfiguration;
-
- TaskContainer(int taskId) {
- mTaskId = taskId;
- }
-
- boolean isEmpty() {
- return mContainers.isEmpty() && mFinishedContainer.isEmpty();
- }
-
- boolean isTaskBoundsInitialized() {
- return !mTaskBounds.isEmpty();
- }
- }
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 1b49585ed7dc..716a087203d3 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -19,9 +19,9 @@ package androidx.window.extensions.embedding;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import android.app.Activity;
+import android.app.WindowConfiguration;
import android.content.Context;
import android.content.Intent;
-import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
@@ -111,8 +111,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
primaryActivity, primaryRectBounds, null);
// Create new empty task fragment
- final TaskFragmentContainer secondaryContainer = mController.newContainer(null,
- primaryContainer.getTaskId());
+ final TaskFragmentContainer secondaryContainer = mController.newContainer(
+ null /* activity */, primaryActivity, primaryContainer.getTaskId());
final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds,
rule, isLtr(primaryActivity, rule));
createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(),
@@ -168,8 +168,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
* Creates a new expanded container.
*/
TaskFragmentContainer createNewExpandedContainer(@NonNull Activity launchingActivity) {
- final TaskFragmentContainer newContainer = mController.newContainer(null,
- launchingActivity.getTaskId());
+ final TaskFragmentContainer newContainer = mController.newContainer(null /* activity */,
+ launchingActivity, launchingActivity.getTaskId());
final WindowContainerTransaction wct = new WindowContainerTransaction();
createTaskFragment(wct, newContainer.getTaskFragmentToken(),
@@ -236,8 +236,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
launchingActivity.getTaskId());
}
- TaskFragmentContainer secondaryContainer = mController.newContainer(null,
- primaryContainer.getTaskId());
+ TaskFragmentContainer secondaryContainer = mController.newContainer(null /* activity */,
+ launchingActivity, primaryContainer.getTaskId());
final WindowContainerTransaction wct = new WindowContainerTransaction();
mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer,
rule);
@@ -398,20 +398,12 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
@NonNull
Rect getParentContainerBounds(@NonNull TaskFragmentContainer container) {
- final Configuration parentConfig = mFragmentParentConfigs.get(
- container.getTaskFragmentToken());
- if (parentConfig != null) {
- return parentConfig.windowConfiguration.getBounds();
+ final int taskId = container.getTaskId();
+ final TaskContainer taskContainer = mController.getTaskContainer(taskId);
+ if (taskContainer == null) {
+ throw new IllegalStateException("Can't find TaskContainer taskId=" + taskId);
}
-
- // If there is no parent yet - then assuming that activities are running in full task bounds
- final Activity topActivity = container.getTopNonFinishingActivity();
- final Rect bounds = topActivity != null ? getParentContainerBounds(topActivity) : null;
-
- if (bounds == null) {
- throw new IllegalStateException("Unknown parent bounds");
- }
- return bounds;
+ return taskContainer.getTaskBounds();
}
@NonNull
@@ -419,22 +411,19 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
final TaskFragmentContainer container = mController.getContainerWithActivity(
activity.getActivityToken());
if (container != null) {
- final Configuration parentConfig = mFragmentParentConfigs.get(
- container.getTaskFragmentToken());
- if (parentConfig != null) {
- return parentConfig.windowConfiguration.getBounds();
- }
+ return getParentContainerBounds(container);
}
-
return getTaskBoundsFromActivity(activity);
}
@NonNull
static Rect getTaskBoundsFromActivity(@NonNull Activity activity) {
+ final WindowConfiguration windowConfiguration =
+ activity.getResources().getConfiguration().windowConfiguration;
if (!activity.isInMultiWindowMode()) {
// In fullscreen mode the max bounds should correspond to the task bounds.
- return activity.getResources().getConfiguration().windowConfiguration.getMaxBounds();
+ return windowConfiguration.getMaxBounds();
}
- return activity.getResources().getConfiguration().windowConfiguration.getBounds();
+ return windowConfiguration.getBounds();
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
new file mode 100644
index 000000000000..be793018d969
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.extensions.embedding;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.util.ArraySet;
+import android.window.TaskFragmentInfo;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/** Represents TaskFragments and split pairs below a Task. */
+class TaskContainer {
+
+ /** The unique task id. */
+ private final int mTaskId;
+
+ /** Available window bounds of this Task. */
+ private final Rect mTaskBounds = new Rect();
+
+ /** Configuration of the Task. */
+ @Nullable
+ private Configuration mConfiguration;
+
+ /** Active TaskFragments in this Task. */
+ final List<TaskFragmentContainer> mContainers = new ArrayList<>();
+
+ /** Active split pairs in this Task. */
+ final List<SplitContainer> mSplitContainers = new ArrayList<>();
+
+ /**
+ * TaskFragments that the organizer has requested to be closed. They should be removed when
+ * the organizer receives {@link SplitController#onTaskFragmentVanished(TaskFragmentInfo)} event
+ * for them.
+ */
+ final Set<IBinder> mFinishedContainer = new ArraySet<>();
+
+ TaskContainer(int taskId) {
+ mTaskId = taskId;
+ }
+
+ int getTaskId() {
+ return mTaskId;
+ }
+
+ @NonNull
+ Rect getTaskBounds() {
+ return mTaskBounds;
+ }
+
+ /** Returns {@code true} if the bounds is changed. */
+ boolean setTaskBounds(@NonNull Rect taskBounds) {
+ if (!taskBounds.isEmpty() && !mTaskBounds.equals(taskBounds)) {
+ mTaskBounds.set(taskBounds);
+ return true;
+ }
+ return false;
+ }
+
+ /** Whether the Task bounds has been initialized. */
+ boolean isTaskBoundsInitialized() {
+ return !mTaskBounds.isEmpty();
+ }
+
+ @Nullable
+ Configuration getConfiguration() {
+ return mConfiguration;
+ }
+
+ void setConfiguration(@Nullable Configuration configuration) {
+ mConfiguration = configuration;
+ }
+
+ /** Whether there is any {@link TaskFragmentContainer} below this Task. */
+ boolean isEmpty() {
+ return mContainers.isEmpty() && mFinishedContainer.isEmpty();
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index a6f638822d10..792176b6d029 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -43,7 +43,6 @@ import androidx.window.util.DataProducer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
-import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
@@ -63,7 +62,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
private final DataProducer<List<CommonFoldingFeature>> mFoldingFeatureProducer;
- public WindowLayoutComponentImpl(Context context) {
+ public WindowLayoutComponentImpl(@NonNull Context context) {
((Application) context.getApplicationContext())
.registerActivityLifecycleCallbacks(new NotifyOnConfigurationChanged());
RawFoldingFeatureProducer foldingFeatureProducer = new RawFoldingFeatureProducer(context);
@@ -81,7 +80,6 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
public void addWindowLayoutInfoListener(@NonNull Activity activity,
@NonNull Consumer<WindowLayoutInfo> consumer) {
mWindowLayoutChangeListeners.put(activity, consumer);
- onDisplayFeaturesChanged();
}
/**
@@ -89,18 +87,8 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
*
* @param consumer no longer interested in receiving updates to {@link WindowLayoutInfo}
*/
- public void removeWindowLayoutInfoListener(
- @NonNull Consumer<WindowLayoutInfo> consumer) {
+ public void removeWindowLayoutInfoListener(@NonNull Consumer<WindowLayoutInfo> consumer) {
mWindowLayoutChangeListeners.values().remove(consumer);
- onDisplayFeaturesChanged();
- }
-
- void updateWindowLayout(@NonNull Activity activity,
- @NonNull WindowLayoutInfo newLayout) {
- Consumer<WindowLayoutInfo> consumer = mWindowLayoutChangeListeners.get(activity);
- if (consumer != null) {
- consumer.accept(newLayout);
- }
}
@NonNull
@@ -108,7 +96,6 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
return mWindowLayoutChangeListeners.keySet();
}
- @NonNull
private boolean isListeningForLayoutChanges(IBinder token) {
for (Activity activity: getActivitiesListeningForLayoutChanges()) {
if (token.equals(activity.getWindow().getAttributes().token)) {
@@ -125,12 +112,12 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
/**
* A convenience method to translate from the common feature state to the extensions feature
* state. More specifically, translates from {@link CommonFoldingFeature.State} to
- * {@link FoldingFeature.STATE_FLAT} or {@link FoldingFeature.STATE_HALF_OPENED}. If it is not
+ * {@link FoldingFeature#STATE_FLAT} or {@link FoldingFeature#STATE_HALF_OPENED}. If it is not
* possible to translate, then we will return a {@code null} value.
*
* @param state if it matches a value in {@link CommonFoldingFeature.State}, {@code null}
- * otherwise. @return a {@link FoldingFeature.STATE_FLAT} or
- * {@link FoldingFeature.STATE_HALF_OPENED} if the given state matches a value in
+ * otherwise. @return a {@link FoldingFeature#STATE_FLAT} or
+ * {@link FoldingFeature#STATE_HALF_OPENED} if the given state matches a value in
* {@link CommonFoldingFeature.State} and {@code null} otherwise.
*/
@Nullable
@@ -144,17 +131,24 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
}
}
- private void onDisplayFeaturesChanged() {
+ private void onDisplayFeaturesChanged(List<CommonFoldingFeature> storedFeatures) {
for (Activity activity : getActivitiesListeningForLayoutChanges()) {
- WindowLayoutInfo newLayout = getWindowLayoutInfo(activity);
- updateWindowLayout(activity, newLayout);
+ // Get the WindowLayoutInfo from the activity and pass the value to the layoutConsumer.
+ Consumer<WindowLayoutInfo> layoutConsumer = mWindowLayoutChangeListeners.get(activity);
+ WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(activity, storedFeatures);
+ layoutConsumer.accept(newWindowLayout);
}
}
- @NonNull
- private WindowLayoutInfo getWindowLayoutInfo(@NonNull Activity activity) {
- List<DisplayFeature> displayFeatures = getDisplayFeatures(activity);
- return new WindowLayoutInfo(displayFeatures);
+ /**
+ * Translates the {@link DisplayFeature} into a {@link WindowLayoutInfo} when a
+ * valid state is found.
+ * @param activity a proxy for the {@link android.view.Window} that contains the
+ */
+ private WindowLayoutInfo getWindowLayoutInfo(
+ @NonNull Activity activity, List<CommonFoldingFeature> storedFeatures) {
+ List<DisplayFeature> displayFeatureList = getDisplayFeatures(activity, storedFeatures);
+ return new WindowLayoutInfo(displayFeatureList);
}
/**
@@ -172,26 +166,21 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
*
* @param activity a proxy for the {@link android.view.Window} that contains the
* {@link DisplayFeature}.
- * @return a {@link List} of valid {@link DisplayFeature} that
* are within the {@link android.view.Window} of the {@link Activity}
*/
- private List<DisplayFeature> getDisplayFeatures(@NonNull Activity activity) {
+ private List<DisplayFeature> getDisplayFeatures(
+ @NonNull Activity activity, List<CommonFoldingFeature> storedFeatures) {
List<DisplayFeature> features = new ArrayList<>();
int displayId = activity.getDisplay().getDisplayId();
if (displayId != DEFAULT_DISPLAY) {
Log.w(TAG, "This sample doesn't support display features on secondary displays");
return features;
- }
-
- if (activity.isInMultiWindowMode()) {
+ } else if (activity.isInMultiWindowMode()) {
// It is recommended not to report any display features in multi-window mode, since it
// won't be possible to synchronize the display feature positions with window movement.
return features;
- }
-
- Optional<List<CommonFoldingFeature>> storedFeatures = mFoldingFeatureProducer.getData();
- if (storedFeatures.isPresent()) {
- for (CommonFoldingFeature baseFeature : storedFeatures.get()) {
+ } else {
+ for (CommonFoldingFeature baseFeature : storedFeatures) {
Integer state = convertToExtensionState(baseFeature.getState());
if (state == null) {
continue;
@@ -205,8 +194,8 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
features.add(new FoldingFeature(featureRect, baseFeature.getType(), state));
}
}
+ return features;
}
- return features;
}
/**
@@ -233,7 +222,8 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
private void onDisplayFeaturesChangedIfListening(Activity activity) {
IBinder token = activity.getWindow().getAttributes().token;
if (token == null || isListeningForLayoutChanges(token)) {
- onDisplayFeaturesChanged();
+ mFoldingFeatureProducer.getData(
+ WindowLayoutComponentImpl.this::onDisplayFeaturesChanged);
}
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
index 970f0a2af632..5bfb0ebdcaa8 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
@@ -28,41 +28,42 @@ import android.content.Context;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
-import android.util.Log;
import androidx.annotation.NonNull;
import androidx.window.common.CommonFoldingFeature;
import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
import androidx.window.common.EmptyLifecycleCallbacksAdapter;
import androidx.window.common.RawFoldingFeatureProducer;
-import androidx.window.util.DataProducer;
+import androidx.window.util.BaseDataProducer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.Optional;
/**
* Reference implementation of androidx.window.sidecar OEM interface for use with
* WindowManager Jetpack.
*/
class SampleSidecarImpl extends StubSidecar {
- private static final String TAG = "SampleSidecar";
-
- private final DataProducer<List<CommonFoldingFeature>> mFoldingFeatureProducer;
-
+ private List<CommonFoldingFeature> mStoredFeatures = new ArrayList<>();
SampleSidecarImpl(Context context) {
((Application) context.getApplicationContext())
.registerActivityLifecycleCallbacks(new NotifyOnConfigurationChanged());
- DataProducer<String> settingsFeatureProducer = new RawFoldingFeatureProducer(context);
- mFoldingFeatureProducer = new DeviceStateManagerFoldingFeatureProducer(context,
- settingsFeatureProducer);
+ BaseDataProducer<String> settingsFeatureProducer = new RawFoldingFeatureProducer(context);
+ BaseDataProducer<List<CommonFoldingFeature>> foldingFeatureProducer =
+ new DeviceStateManagerFoldingFeatureProducer(context,
+ settingsFeatureProducer);
- mFoldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
+ foldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
}
- private void onDisplayFeaturesChanged() {
+ private void setStoredFeatures(List<CommonFoldingFeature> storedFeatures) {
+ mStoredFeatures = storedFeatures;
+ }
+
+ private void onDisplayFeaturesChanged(List<CommonFoldingFeature> storedFeatures) {
+ setStoredFeatures(storedFeatures);
updateDeviceState(getDeviceState());
for (IBinder windowToken : getWindowsListeningForLayoutChanges()) {
SidecarWindowLayoutInfo newLayout = getWindowLayoutInfo(windowToken);
@@ -79,16 +80,16 @@ class SampleSidecarImpl extends StubSidecar {
}
private int deviceStateFromFeature() {
- List<CommonFoldingFeature> storedFeatures = mFoldingFeatureProducer.getData()
- .orElse(Collections.emptyList());
- for (int i = 0; i < storedFeatures.size(); i++) {
- CommonFoldingFeature feature = storedFeatures.get(i);
+ for (int i = 0; i < mStoredFeatures.size(); i++) {
+ CommonFoldingFeature feature = mStoredFeatures.get(i);
final int state = feature.getState();
switch (state) {
case CommonFoldingFeature.COMMON_STATE_FLAT:
return SidecarDeviceState.POSTURE_OPENED;
case CommonFoldingFeature.COMMON_STATE_HALF_OPENED:
return SidecarDeviceState.POSTURE_HALF_OPENED;
+ case CommonFoldingFeature.COMMON_STATE_UNKNOWN:
+ return SidecarDeviceState.POSTURE_UNKNOWN;
}
}
return SidecarDeviceState.POSTURE_UNKNOWN;
@@ -109,7 +110,6 @@ class SampleSidecarImpl extends StubSidecar {
private List<SidecarDisplayFeature> getDisplayFeatures(@NonNull Activity activity) {
int displayId = activity.getDisplay().getDisplayId();
if (displayId != DEFAULT_DISPLAY) {
- Log.w(TAG, "This sample doesn't support display features on secondary displays");
return Collections.emptyList();
}
@@ -119,18 +119,15 @@ class SampleSidecarImpl extends StubSidecar {
return Collections.emptyList();
}
- Optional<List<CommonFoldingFeature>> storedFeatures = mFoldingFeatureProducer.getData();
List<SidecarDisplayFeature> features = new ArrayList<>();
- if (storedFeatures.isPresent()) {
- for (CommonFoldingFeature baseFeature : storedFeatures.get()) {
- SidecarDisplayFeature feature = new SidecarDisplayFeature();
- Rect featureRect = baseFeature.getRect();
- rotateRectToDisplayRotation(displayId, featureRect);
- transformToWindowSpaceRect(activity, featureRect);
- feature.setRect(featureRect);
- feature.setType(baseFeature.getType());
- features.add(feature);
- }
+ for (CommonFoldingFeature baseFeature : mStoredFeatures) {
+ SidecarDisplayFeature feature = new SidecarDisplayFeature();
+ Rect featureRect = baseFeature.getRect();
+ rotateRectToDisplayRotation(displayId, featureRect);
+ transformToWindowSpaceRect(activity, featureRect);
+ feature.setRect(featureRect);
+ feature.setType(baseFeature.getType());
+ features.add(feature);
}
return Collections.unmodifiableList(features);
}
@@ -138,7 +135,7 @@ class SampleSidecarImpl extends StubSidecar {
@Override
protected void onListenersChanged() {
if (hasListeners()) {
- onDisplayFeaturesChanged();
+ onDisplayFeaturesChanged(mStoredFeatures);
}
}
@@ -158,7 +155,7 @@ class SampleSidecarImpl extends StubSidecar {
private void onDisplayFeaturesChangedForActivity(@NonNull Activity activity) {
IBinder token = activity.getWindow().getAttributes().token;
if (token == null || mWindowLayoutChangeListenerTokens.contains(token)) {
- onDisplayFeaturesChanged();
+ onDisplayFeaturesChanged(mStoredFeatures);
}
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java
new file mode 100644
index 000000000000..7624b693ac43
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.util;
+
+import android.annotation.NonNull;
+
+import java.util.function.Consumer;
+
+/**
+ * A base class that works with {@link BaseDataProducer} to add/remove a consumer that should
+ * only be used once when {@link BaseDataProducer#notifyDataChanged} is called.
+ * @param <T> The type of data this producer returns through {@link DataProducer#getData}.
+ */
+public class AcceptOnceConsumer<T> implements Consumer<T> {
+ private final Consumer<T> mCallback;
+ private final DataProducer<T> mProducer;
+
+ public AcceptOnceConsumer(@NonNull DataProducer<T> producer, @NonNull Consumer<T> callback) {
+ mProducer = producer;
+ mCallback = callback;
+ }
+
+ @Override
+ public void accept(@NonNull T t) {
+ mCallback.accept(t);
+ mProducer.removeDataChangedCallback(this);
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
index 930db3b701b7..0da44ac36a6e 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
@@ -19,38 +19,48 @@ package androidx.window.util;
import androidx.annotation.NonNull;
import java.util.LinkedHashSet;
+import java.util.Optional;
import java.util.Set;
+import java.util.function.Consumer;
/**
* Base class that provides the implementation for the callback mechanism of the
* {@link DataProducer} API.
*
- * @param <T> The type of data this producer returns through {@link #getData()}.
+ * @param <T> The type of data this producer returns through {@link DataProducer#getData}.
*/
public abstract class BaseDataProducer<T> implements DataProducer<T> {
- private final Set<Runnable> mCallbacks = new LinkedHashSet<>();
+ private final Set<Consumer<T>> mCallbacks = new LinkedHashSet<>();
@Override
- public final void addDataChangedCallback(@NonNull Runnable callback) {
+ public final void addDataChangedCallback(@NonNull Consumer<T> callback) {
mCallbacks.add(callback);
+ Optional<T> currentData = getCurrentData();
+ currentData.ifPresent(callback);
onListenersChanged(mCallbacks);
}
@Override
- public final void removeDataChangedCallback(@NonNull Runnable callback) {
+ public final void removeDataChangedCallback(@NonNull Consumer<T> callback) {
mCallbacks.remove(callback);
onListenersChanged(mCallbacks);
}
- protected void onListenersChanged(Set<Runnable> callbacks) {}
+ protected void onListenersChanged(Set<Consumer<T>> callbacks) {}
/**
- * Called to notify all registered callbacks that the data provided by {@link #getData()} has
- * changed.
+ * @return the current data if available and {@code Optional.empty()} otherwise.
*/
- protected void notifyDataChanged() {
- for (Runnable callback : mCallbacks) {
- callback.run();
+ @NonNull
+ public abstract Optional<T> getCurrentData();
+
+ /**
+ * Called to notify all registered consumers that the data provided
+ * by {@link DataProducer#getData} has changed.
+ */
+ protected void notifyDataChanged(T value) {
+ for (Consumer<T> callback : mCallbacks) {
+ callback.accept(value);
}
}
-}
+} \ No newline at end of file
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/DataProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/DataProducer.java
index d4d1a23b756b..ec301dc34aaa 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/util/DataProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/DataProducer.java
@@ -18,26 +18,27 @@ package androidx.window.util;
import android.annotation.NonNull;
-import java.util.Optional;
+import java.util.function.Consumer;
/**
- * Produces data through {@link #getData()} and provides a mechanism for receiving a callback when
- * the data managed by the produces has changed.
+ * Produces data through {@link DataProducer#getData} and provides a mechanism for receiving
+ * a callback when the data managed by the produces has changed.
*
- * @param <T> The type of data this producer returns through {@link #getData()}.
+ * @param <T> The type of data this producer returns through {@link DataProducer#getData}.
*/
public interface DataProducer<T> {
/**
- * Returns the data currently stored in the provider, or {@link Optional#empty()} if the
- * provider has no data.
+ * Emits the first available data at that point in time.
+ * @param dataConsumer a {@link Consumer} that will receive one value.
*/
- Optional<T> getData();
+ void getData(@NonNull Consumer<T> dataConsumer);
/**
- * Adds a callback to be notified when the data returned from {@link #getData()} has changed.
+ * Adds a callback to be notified when the data returned
+ * from {@link DataProducer#getData} has changed.
*/
- void addDataChangedCallback(@NonNull Runnable callback);
+ void addDataChangedCallback(@NonNull Consumer<T> callback);
- /** Removes a callback previously added with {@link #addDataChangedCallback(Runnable)}. */
- void removeDataChangedCallback(@NonNull Runnable callback);
+ /** Removes a callback previously added with {@link #addDataChangedCallback(Consumer)}. */
+ void removeDataChangedCallback(@NonNull Consumer<T> callback);
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index 72519dc6da5f..e0fda58fd664 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -21,6 +21,9 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -29,12 +32,12 @@ import static org.mockito.Mockito.never;
import android.app.Activity;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.window.TaskFragmentInfo;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.window.extensions.embedding.SplitController.TaskContainer;
import org.junit.Before;
import org.junit.Test;
@@ -53,6 +56,7 @@ import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class)
public class SplitControllerTest {
private static final int TASK_ID = 10;
+ private static final Rect TASK_BOUNDS = new Rect(0, 0, 600, 1200);
@Mock
private Activity mActivity;
@@ -70,8 +74,11 @@ public class SplitControllerTest {
mSplitPresenter = mSplitController.mPresenter;
spyOn(mSplitController);
spyOn(mSplitPresenter);
+ final Configuration activityConfig = new Configuration();
+ activityConfig.windowConfiguration.setBounds(TASK_BOUNDS);
+ activityConfig.windowConfiguration.setMaxBounds(TASK_BOUNDS);
doReturn(mActivityResources).when(mActivity).getResources();
- doReturn(new Configuration()).when(mActivityResources).getConfiguration();
+ doReturn(activityConfig).when(mActivityResources).getConfiguration();
}
@Test
@@ -117,4 +124,20 @@ public class SplitControllerTest {
verify(mSplitController).removeContainer(tf);
verify(mActivity, never()).finish();
}
+
+ @Test
+ public void testNewContainer() {
+ // Must pass in a valid activity.
+ assertThrows(IllegalArgumentException.class, () ->
+ mSplitController.newContainer(null /* activity */, TASK_ID));
+ assertThrows(IllegalArgumentException.class, () ->
+ mSplitController.newContainer(mActivity, null /* launchingActivity */, TASK_ID));
+
+ final TaskFragmentContainer tf = mSplitController.newContainer(null, mActivity, TASK_ID);
+ final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID);
+
+ assertNotNull(tf);
+ assertNotNull(taskContainer);
+ assertEquals(TASK_BOUNDS, taskContainer.getTaskBounds());
+ }
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
new file mode 100644
index 000000000000..9fb08dffbab8
--- /dev/null
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.extensions.embedding;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test class for {@link TaskContainer}.
+ *
+ * Build/Install/Run:
+ * atest WMJetpackUnitTests:TaskContainerTest
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TaskContainerTest {
+ private static final int TASK_ID = 10;
+ private static final Rect TASK_BOUNDS = new Rect(0, 0, 600, 1200);
+
+ @Test
+ public void testIsTaskBoundsInitialized() {
+ final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+
+ assertFalse(taskContainer.isTaskBoundsInitialized());
+
+ taskContainer.setTaskBounds(TASK_BOUNDS);
+
+ assertTrue(taskContainer.isTaskBoundsInitialized());
+ }
+
+ @Test
+ public void testSetTaskBounds() {
+ final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+
+ assertFalse(taskContainer.setTaskBounds(new Rect()));
+
+ assertTrue(taskContainer.setTaskBounds(TASK_BOUNDS));
+
+ assertFalse(taskContainer.setTaskBounds(TASK_BOUNDS));
+ }
+
+ @Test
+ public void testIsEmpty() {
+ final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+
+ assertTrue(taskContainer.isEmpty());
+
+ final TaskFragmentContainer tf = new TaskFragmentContainer(null, TASK_ID);
+ taskContainer.mContainers.add(tf);
+
+ assertFalse(taskContainer.isEmpty());
+
+ taskContainer.mFinishedContainer.add(tf.getTaskFragmentToken());
+ taskContainer.mContainers.clear();
+
+ assertFalse(taskContainer.isEmpty());
+ }
+}
diff --git a/libs/WindowManager/Shell/res/drawable/pip_custom_close_bg.xml b/libs/WindowManager/Shell/res/drawable/pip_custom_close_bg.xml
new file mode 100644
index 000000000000..39c3fe6f6106
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/pip_custom_close_bg.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="oval">
+ <solid
+ android:color="@color/pip_custom_close_bg" />
+ <size
+ android:width="@dimen/pip_custom_close_bg_size"
+ android:height="@dimen/pip_custom_close_bg_size" />
+</shape>
diff --git a/libs/WindowManager/Shell/res/layout/pip_menu_action.xml b/libs/WindowManager/Shell/res/layout/pip_menu_action.xml
index a733b31d9fb0..b51dd6a00815 100644
--- a/libs/WindowManager/Shell/res/layout/pip_menu_action.xml
+++ b/libs/WindowManager/Shell/res/layout/pip_menu_action.xml
@@ -22,6 +22,14 @@
android:forceHasOverlappingRendering="false">
<ImageView
+ android:id="@+id/custom_close_bg"
+ android:layout_width="@dimen/pip_custom_close_bg_size"
+ android:layout_height="@dimen/pip_custom_close_bg_size"
+ android:layout_gravity="center"
+ android:src="@drawable/pip_custom_close_bg"
+ android:visibility="gone"/>
+
+ <ImageView
android:id="@+id/image"
android:layout_width="@dimen/pip_action_inner_size"
android:layout_height="@dimen/pip_action_inner_size"
diff --git a/libs/WindowManager/Shell/res/layout/split_decor.xml b/libs/WindowManager/Shell/res/layout/split_decor.xml
index 9ffa5e8aa179..dfb90affe7f6 100644
--- a/libs/WindowManager/Shell/res/layout/split_decor.xml
+++ b/libs/WindowManager/Shell/res/layout/split_decor.xml
@@ -20,9 +20,10 @@
android:layout_width="match_parent">
<ImageView android:id="@+id/split_resizing_icon"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
+ android:layout_height="@*android:dimen/starting_surface_icon_size"
+ android:layout_width="@*android:dimen/starting_surface_icon_size"
android:layout_gravity="center"
+ android:scaleType="fitCenter"
android:padding="0dp"
android:visibility="gone"
android:background="@null"/>
diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml
index 974960708190..6f38ecae674d 100644
--- a/libs/WindowManager/Shell/res/values-es/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es/strings.xml
@@ -46,10 +46,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Superior 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Superior 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Pantalla inferior completa"</string>
- <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Usar Modo una mano"</string>
+ <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Usar modo Una mano"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Para salir, desliza el dedo hacia arriba desde la parte inferior de la pantalla o toca cualquier zona que haya encima de la aplicación"</string>
- <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Iniciar Modo una mano"</string>
- <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Salir del Modo una mano"</string>
+ <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Iniciar modo Una mano"</string>
+ <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Salir del modo Una mano"</string>
<string name="bubbles_settings_button_description" msgid="1301286017420516912">"Ajustes de las burbujas de <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"Menú adicional"</string>
<string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"Volver a añadir a la pila"</string>
diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml
index 4606d24d1716..6e750a3d5e34 100644
--- a/libs/WindowManager/Shell/res/values/colors.xml
+++ b/libs/WindowManager/Shell/res/values/colors.xml
@@ -30,6 +30,9 @@
<color name="bubbles_dark">@color/GM2_grey_800</color>
<color name="bubbles_icon_tint">@color/GM2_grey_700</color>
+ <!-- PiP -->
+ <color name="pip_custom_close_bg">#D93025</color>
+
<!-- Compat controls UI -->
<color name="compat_controls_background">@android:color/system_neutral1_800</color>
<color name="compat_controls_text">@android:color/system_neutral1_50</color>
@@ -47,4 +50,4 @@
<color name="splash_screen_bg_light">#FFFFFF</color>
<color name="splash_screen_bg_dark">#000000</color>
<color name="splash_window_background_default">@color/splash_screen_bg_light</color>
-</resources> \ No newline at end of file
+</resources>
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index d416c060c86c..8ba41ab60c87 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -46,6 +46,10 @@
<!-- Show PiP enter split icon, which allows apps to directly enter splitscreen from PiP. -->
<bool name="config_pipEnableEnterSplitButton">false</bool>
+ <!-- Time (duration in milliseconds) that the shell waits for an app to close the PiP by itself
+ if a custom action is present before closing it. -->
+ <integer name="config_pipForceCloseDelay">1000</integer>
+
<!-- Animation duration when using long press on recents to dock -->
<integer name="long_press_dock_anim_duration">250</integer>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index a2f9e884b37d..c21381d1486a 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -78,6 +78,9 @@
WindowConfiguration#PINNED_WINDOWING_MODE_ELEVATION_IN_DIP -->
<dimen name="pip_shadow_radius">5dp</dimen>
+ <!-- The width and height of the background for custom action in PiP menu. -->
+ <dimen name="pip_custom_close_bg_size">32dp</dimen>
+
<dimen name="dismiss_target_x_size">24dp</dimen>
<dimen name="floating_dismiss_bottom_margin">50dp</dimen>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt
index 4b7950e9090a..255e4d2c0d44 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt
@@ -19,6 +19,7 @@ package com.android.wm.shell.animation
import android.util.ArrayMap
import android.util.Log
import android.view.View
+import androidx.dynamicanimation.animation.AnimationHandler
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.FlingAnimation
import androidx.dynamicanimation.animation.FloatPropertyCompat
@@ -123,6 +124,12 @@ class PhysicsAnimator<T> private constructor (target: T) {
private var defaultFling: FlingConfig = globalDefaultFling
/**
+ * AnimationHandler to use if it need custom AnimationHandler, if this is null, it will use
+ * the default AnimationHandler in the DynamicAnimation.
+ */
+ private var customAnimationHandler: AnimationHandler? = null
+
+ /**
* Internal listeners that respond to DynamicAnimations updating and ending, and dispatch to
* the listeners provided via [addUpdateListener] and [addEndListener]. This allows us to add
* just one permanent update and end listener to the DynamicAnimations.
@@ -446,6 +453,14 @@ class PhysicsAnimator<T> private constructor (target: T) {
this.defaultFling = defaultFling
}
+ /**
+ * Set the custom AnimationHandler for all aniatmion in this animator. Set this with null for
+ * restoring to default AnimationHandler.
+ */
+ fun setCustomAnimationHandler(handler: AnimationHandler) {
+ this.customAnimationHandler = handler
+ }
+
/** Starts the animations! */
fun start() {
startAction()
@@ -495,10 +510,13 @@ class PhysicsAnimator<T> private constructor (target: T) {
// springs) on this property before flinging.
cancel(animatedProperty)
+ // Apply the custom animation handler if it not null
+ val flingAnim = getFlingAnimation(animatedProperty, target)
+ flingAnim.animationHandler =
+ customAnimationHandler ?: flingAnim.animationHandler
+
// Apply the configuration and start the animation.
- getFlingAnimation(animatedProperty, target)
- .also { flingConfig.applyToAnimation(it) }
- .start()
+ flingAnim.also { flingConfig.applyToAnimation(it) }.start()
}
}
@@ -510,6 +528,21 @@ class PhysicsAnimator<T> private constructor (target: T) {
if (flingConfig == null) {
// Apply the configuration and start the animation.
val springAnim = getSpringAnimation(animatedProperty, target)
+
+ // If customAnimationHander is exist and has not been set to the animation,
+ // it should set here.
+ if (customAnimationHandler != null &&
+ springAnim.animationHandler != customAnimationHandler) {
+ // Cancel the animation before set animation handler
+ if (springAnim.isRunning) {
+ cancel(animatedProperty)
+ }
+ // Apply the custom animation handler if it not null
+ springAnim.animationHandler =
+ customAnimationHandler ?: springAnim.animationHandler
+ }
+
+ // Apply the configuration and start the animation.
springConfig.applyToAnimation(springAnim)
animationStartActions.add(springAnim::start)
} else {
@@ -564,10 +597,13 @@ class PhysicsAnimator<T> private constructor (target: T) {
}
}
+ // Apply the custom animation handler if it not null
+ val springAnim = getSpringAnimation(animatedProperty, target)
+ springAnim.animationHandler =
+ customAnimationHandler ?: springAnim.animationHandler
+
// Apply the configuration and start the spring animation.
- getSpringAnimation(animatedProperty, target)
- .also { springConfig.applyToAnimation(it) }
- .start()
+ springAnim.also { springConfig.applyToAnimation(it) }.start()
}
}
})
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 5ef24133ca11..806c395bf395 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
@@ -96,6 +96,7 @@ import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
import com.android.wm.shell.pip.PinnedStackListenerForwarder;
@@ -214,6 +215,8 @@ public class BubbleController {
/** One handed mode controller to register transition listener. */
private Optional<OneHandedController> mOneHandedOptional;
+ /** Drag and drop controller to register listener for onDragStarted. */
+ private DragAndDropController mDragAndDropController;
/**
* Creates an instance of the BubbleController.
@@ -230,6 +233,7 @@ public class BubbleController {
ShellTaskOrganizer organizer,
DisplayController displayController,
Optional<OneHandedController> oneHandedOptional,
+ DragAndDropController dragAndDropController,
ShellExecutor mainExecutor,
Handler mainHandler,
TaskViewTransitions taskViewTransitions,
@@ -241,8 +245,8 @@ public class BubbleController {
new BubbleDataRepository(context, launcherApps, mainExecutor),
statusBarService, windowManager, windowManagerShellWrapper, launcherApps,
logger, taskStackListener, organizer, positioner, displayController,
- oneHandedOptional, mainExecutor, mainHandler, taskViewTransitions,
- syncQueue);
+ oneHandedOptional, dragAndDropController, mainExecutor, mainHandler,
+ taskViewTransitions, syncQueue);
}
/**
@@ -264,6 +268,7 @@ public class BubbleController {
BubblePositioner positioner,
DisplayController displayController,
Optional<OneHandedController> oneHandedOptional,
+ DragAndDropController dragAndDropController,
ShellExecutor mainExecutor,
Handler mainHandler,
TaskViewTransitions taskViewTransitions,
@@ -293,6 +298,7 @@ public class BubbleController {
mDisplayController = displayController;
mTaskViewTransitions = taskViewTransitions;
mOneHandedOptional = oneHandedOptional;
+ mDragAndDropController = dragAndDropController;
mSyncQueue = syncQueue;
}
@@ -433,6 +439,7 @@ public class BubbleController {
});
mOneHandedOptional.ifPresent(this::registerOneHandedState);
+ mDragAndDropController.addListener(this::collapseStack);
}
@VisibleForTesting
@@ -884,7 +891,6 @@ public class BubbleController {
return mBubbleData.isExpanded();
}
- @VisibleForTesting
public void collapseStack() {
mBubbleData.setExpanded(false /* expanded */);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
index 79d795ee613f..fedb9983a65e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
@@ -423,8 +423,9 @@ public class DisplayLayout {
}
final DisplayCutout.CutoutPathParserInfo info = cutout.getCutoutPathParserInfo();
final DisplayCutout.CutoutPathParserInfo newInfo = new DisplayCutout.CutoutPathParserInfo(
- info.getDisplayWidth(), info.getDisplayHeight(), info.getDensity(),
- info.getCutoutSpec(), rotation, info.getScale());
+ info.getDisplayWidth(), info.getDisplayHeight(), info.getStableDisplayWidth(),
+ info.getStableDisplayHeight(), info.getDensity(), info.getCutoutSpec(), rotation,
+ info.getScale(), info.getPhysicalPixelDisplaySizeRatio());
return computeSafeInsets(
DisplayCutout.constructDisplayCutout(newBounds, waterfallInsets, newInfo),
rotated ? displayHeight : displayWidth,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java
new file mode 100644
index 000000000000..fd3aa05cfc06
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.text.TextUtils;
+import android.view.View;
+
+import com.android.internal.jank.InteractionJankMonitor;
+
+/** Utils class for simplfy InteractionJank trancing call */
+public class InteractionJankMonitorUtils {
+
+ /**
+ * Begin a trace session.
+ *
+ * @param cujType the specific {@link InteractionJankMonitor.CujType}.
+ * @param view the view to trace
+ * @param tag the tag to distinguish different flow of same type CUJ.
+ */
+ public static void beginTracing(@InteractionJankMonitor.CujType int cujType,
+ @NonNull View view, @Nullable String tag) {
+ final InteractionJankMonitor.Configuration.Builder builder =
+ InteractionJankMonitor.Configuration.Builder.withView(cujType, view);
+ if (!TextUtils.isEmpty(tag)) {
+ builder.setTag(tag);
+ }
+ InteractionJankMonitor.getInstance().begin(builder);
+ }
+
+ /**
+ * End a trace session.
+ *
+ * @param cujType the specific {@link InteractionJankMonitor.CujType}.
+ */
+ public static void endTracing(@InteractionJankMonitor.CujType int cujType) {
+ InteractionJankMonitor.getInstance().end(cujType);
+ }
+
+ /**
+ * Cancel the trace session.
+ *
+ * @param cujType the specific {@link InteractionJankMonitor.CujType}.
+ */
+ public static void cancelTracing(@InteractionJankMonitor.CujType int cujType) {
+ InteractionJankMonitor.getInstance().cancel(cujType);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
index e270edb800bd..d5875c03ccd2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
@@ -221,7 +221,8 @@ public class SystemWindows {
}
final Display display = mDisplayController.getDisplay(mDisplayId);
SurfaceControlViewHost viewRoot =
- new SurfaceControlViewHost(view.getContext(), display, wwm);
+ new SurfaceControlViewHost(
+ view.getContext(), display, wwm, true /* useSfChoreographer */);
attrs.flags |= FLAG_HARDWARE_ACCELERATED;
viewRoot.setView(view, attrs);
mViewRoots.put(view, viewRoot);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index ec81d230fae7..0b8e631068fc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -55,6 +55,7 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.policy.DividerSnapAlgorithm;
import com.android.internal.policy.DockedDividerUtils;
import com.android.wm.shell.R;
@@ -62,6 +63,7 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.InteractionJankMonitorUtils;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
import java.io.PrintWriter;
@@ -434,6 +436,8 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
mSplitLayoutHandler.onLayoutSizeChanged(this);
return;
}
+ InteractionJankMonitorUtils.beginTracing(InteractionJankMonitor.CUJ_SPLIT_SCREEN_RESIZE,
+ mSplitWindowManager.getDividerView(), "Divider fling");
ValueAnimator animator = ValueAnimator
.ofInt(from, to)
.setDuration(250);
@@ -446,6 +450,8 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
if (flingFinishedCallback != null) {
flingFinishedCallback.run();
}
+ InteractionJankMonitorUtils.endTracing(
+ InteractionJankMonitor.CUJ_SPLIT_SCREEN_RESIZE);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
index 833d9d50701c..864b9a7528b0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
@@ -37,6 +37,7 @@ import android.view.LayoutInflater;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
import android.view.SurfaceSession;
+import android.view.View;
import android.view.WindowManager;
import android.view.WindowlessWindowManager;
@@ -170,6 +171,10 @@ public final class SplitWindowManager extends WindowlessWindowManager {
mDividerView.setInteractive(interactive);
}
+ View getDividerView() {
+ return mDividerView;
+ }
+
/**
* Gets {@link SurfaceControl} of the surface holding divider view. @return {@code null} if not
* feasible.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java
index 5a94fb65f174..8f9636c0bb30 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java
@@ -19,6 +19,7 @@ package com.android.wm.shell.dagger;
import static android.os.Process.THREAD_PRIORITY_DISPLAY;
import static android.os.Process.THREAD_PRIORITY_TOP_APP_BOOST;
+import android.animation.AnimationHandler;
import android.content.Context;
import android.os.Build;
import android.os.Handler;
@@ -28,9 +29,11 @@ import android.os.Trace;
import androidx.annotation.Nullable;
+import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.wm.shell.R;
import com.android.wm.shell.common.HandlerExecutor;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.annotations.ChoreographerSfVsync;
import com.android.wm.shell.common.annotations.ExternalMainThread;
import com.android.wm.shell.common.annotations.ShellAnimationThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
@@ -168,4 +171,28 @@ public abstract class WMShellConcurrencyModule {
shellSplashscreenThread.start();
return new HandlerExecutor(shellSplashscreenThread.getThreadHandler());
}
+
+ /**
+ * Provide a Shell main-thread AnimationHandler. The AnimationHandler can be set on
+ * {@link android.animation.ValueAnimator}s and will ensure that the animation will run on
+ * the Shell main-thread with the SF vsync.
+ */
+ @WMSingleton
+ @Provides
+ @ChoreographerSfVsync
+ public static AnimationHandler provideShellMainExecutorSfVsyncAnimationHandler(
+ @ShellMainThread ShellExecutor mainExecutor) {
+ try {
+ AnimationHandler handler = new AnimationHandler();
+ mainExecutor.executeBlocking(() -> {
+ // This is called on the animation thread since it calls
+ // Choreographer.getSfInstance() which returns a thread-local Choreographer instance
+ // that uses the SF vsync
+ handler.setProvider(new SfVsyncFrameCallbackProvider());
+ });
+ return handler;
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Failed to initialize SfVsync animation handler in 1s", e);
+ }
+ }
}
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 4fa5a1029e28..96e5f60c952b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.dagger;
+import android.animation.AnimationHandler;
import android.content.Context;
import android.content.pm.LauncherApps;
import android.os.Handler;
@@ -41,7 +42,9 @@ import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.annotations.ChoreographerSfVsync;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.freeform.FreeformTaskListener;
import com.android.wm.shell.fullscreen.FullscreenUnfoldController;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
@@ -110,6 +113,7 @@ public class WMShellModule {
ShellTaskOrganizer organizer,
DisplayController displayController,
@DynamicOverride Optional<OneHandedController> oneHandedOptional,
+ DragAndDropController dragAndDropController,
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler,
TaskViewTransitions taskViewTransitions,
@@ -118,7 +122,7 @@ public class WMShellModule {
floatingContentCoordinator, statusBarService, windowManager,
windowManagerShellWrapper, launcherApps, taskStackListener,
uiEventLogger, organizer, displayController, oneHandedOptional,
- mainExecutor, mainHandler, taskViewTransitions, syncQueue);
+ dragAndDropController, mainExecutor, mainHandler, taskViewTransitions, syncQueue);
}
//
@@ -182,10 +186,11 @@ public class WMShellModule {
DisplayImeController displayImeController, TransactionPool transactionPool,
ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue,
TaskStackListenerImpl taskStackListener, Transitions transitions,
- @ShellMainThread ShellExecutor mainExecutor) {
+ @ShellMainThread ShellExecutor mainExecutor,
+ @ChoreographerSfVsync AnimationHandler sfVsyncAnimationHandler) {
return new LegacySplitScreenController(context, displayController, systemWindows,
displayImeController, transactionPool, shellTaskOrganizer, syncQueue,
- taskStackListener, transitions, mainExecutor);
+ taskStackListener, transitions, mainExecutor, sfVsyncAnimationHandler);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index 11ecc9197be7..95de2dc61a43 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -35,9 +35,6 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMA
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
import android.content.ClipDescription;
import android.content.Context;
import android.content.res.Configuration;
@@ -52,17 +49,19 @@ import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
+import androidx.annotation.VisibleForTesting;
+
import com.android.internal.logging.InstanceId;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.R;
-import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
+import java.util.ArrayList;
import java.util.Optional;
/**
@@ -80,10 +79,19 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
private SplitScreenController mSplitScreen;
private ShellExecutor mMainExecutor;
private DragAndDropImpl mImpl;
+ private ArrayList<DragAndDropListener> mListeners = new ArrayList<>();
private final SparseArray<PerDisplay> mDisplayDropTargets = new SparseArray<>();
private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+ /**
+ * Listener called during drag events, currently just onDragStarted.
+ */
+ public interface DragAndDropListener {
+ /** Called when a drag has started. */
+ void onDragStarted();
+ }
+
public DragAndDropController(Context context, DisplayController displayController,
UiEventLogger uiEventLogger, IconProvider iconProvider, ShellExecutor mainExecutor) {
mContext = context;
@@ -103,6 +111,22 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
mDisplayController.addDisplayWindowListener(this);
}
+ /** Adds a listener to be notified of drag and drop events. */
+ public void addListener(DragAndDropListener listener) {
+ mListeners.add(listener);
+ }
+
+ /** Removes a drag and drop listener. */
+ public void removeListener(DragAndDropListener listener) {
+ mListeners.remove(listener);
+ }
+
+ private void notifyListeners() {
+ for (int i = 0; i < mListeners.size(); i++) {
+ mListeners.get(i).onDragStarted();
+ }
+ }
+
@Override
public void onDisplayAdded(int displayId) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Display added: %d", displayId);
@@ -137,13 +161,19 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
try {
wm.addView(rootView, layoutParams);
- mDisplayDropTargets.put(displayId,
- new PerDisplay(displayId, context, wm, rootView, dragLayout));
+ addDisplayDropTarget(displayId, context, wm, rootView, dragLayout);
} catch (WindowManager.InvalidDisplayException e) {
Slog.w(TAG, "Unable to add view for display id: " + displayId);
}
}
+ @VisibleForTesting
+ void addDisplayDropTarget(int displayId, Context context, WindowManager wm,
+ FrameLayout rootView, DragLayout dragLayout) {
+ mDisplayDropTargets.put(displayId,
+ new PerDisplay(displayId, context, wm, rootView, dragLayout));
+ }
+
@Override
public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Display changed: %d", displayId);
@@ -206,6 +236,7 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
pd.dragLayout.prepare(mDisplayController.getDisplayLayout(displayId),
event.getClipData(), loggerSessionId);
setDropTargetWindowVisibility(pd, View.VISIBLE);
+ notifyListeners();
break;
case ACTION_DRAG_ENTERED:
pd.dragLayout.show();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
index 38870bcb3383..0cea36ed48c8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
@@ -28,6 +28,7 @@ import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.FloatProperty;
+import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
@@ -110,10 +111,12 @@ public class DropZoneView extends FrameLayout {
mColorDrawable = new ColorDrawable();
setBackgroundDrawable(mColorDrawable);
+ final int iconSize = context.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.starting_surface_icon_size);
mSplashScreenView = new ImageView(context);
- mSplashScreenView.setScaleType(ImageView.ScaleType.CENTER);
- addView(mSplashScreenView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT));
+ mSplashScreenView.setScaleType(ImageView.ScaleType.FIT_CENTER);
+ addView(mSplashScreenView,
+ new FrameLayout.LayoutParams(iconSize, iconSize, Gravity.CENTER));
mSplashScreenView.setAlpha(0f);
mMarginView = new MarginView(context);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerView.java
index 9754a0369b67..73be2835d2cd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerView.java
@@ -25,6 +25,7 @@ import static com.android.wm.shell.animation.Interpolators.SLOWDOWN_INTERPOLATOR
import static com.android.wm.shell.common.split.DividerView.TOUCH_ANIMATION_DURATION;
import static com.android.wm.shell.common.split.DividerView.TOUCH_RELEASE_ANIMATION_DURATION;
+import android.animation.AnimationHandler;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
@@ -140,6 +141,7 @@ public class DividerView extends FrameLayout implements OnTouchListener,
private DividerImeController mImeController;
private DividerCallbacks mCallback;
+ private AnimationHandler mSfVsyncAnimationHandler;
private ValueAnimator mCurrentAnimator;
private boolean mEntranceAnimationRunning;
private boolean mExitAnimationRunning;
@@ -260,6 +262,10 @@ public class DividerView extends FrameLayout implements OnTouchListener,
mDefaultDisplay = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
}
+ public void setAnimationHandler(AnimationHandler sfVsyncAnimationHandler) {
+ mSfVsyncAnimationHandler = sfVsyncAnimationHandler;
+ }
+
@Override
protected void onFinishInflate() {
super.onFinishInflate();
@@ -651,6 +657,7 @@ public class DividerView extends FrameLayout implements OnTouchListener,
}
});
mCurrentAnimator = anim;
+ mCurrentAnimator.setAnimationHandler(mSfVsyncAnimationHandler);
return anim;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java
index 8b6679273bea..67e487de0993 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java
@@ -25,6 +25,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.view.Display.DEFAULT_DISPLAY;
+import android.animation.AnimationHandler;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityTaskManager;
@@ -81,6 +82,7 @@ public class LegacySplitScreenController implements DisplayController.OnDisplays
private final DividerState mDividerState = new DividerState();
private final ForcedResizableInfoActivityController mForcedResizableController;
private final ShellExecutor mMainExecutor;
+ private final AnimationHandler mSfVsyncAnimationHandler;
private final LegacySplitScreenTaskListener mSplits;
private final SystemWindows mSystemWindows;
final TransactionPool mTransactionPool;
@@ -116,12 +118,13 @@ public class LegacySplitScreenController implements DisplayController.OnDisplays
DisplayImeController imeController, TransactionPool transactionPool,
ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue,
TaskStackListenerImpl taskStackListener, Transitions transitions,
- ShellExecutor mainExecutor) {
+ ShellExecutor mainExecutor, AnimationHandler sfVsyncAnimationHandler) {
mContext = context;
mDisplayController = displayController;
mSystemWindows = systemWindows;
mImeController = imeController;
mMainExecutor = mainExecutor;
+ mSfVsyncAnimationHandler = sfVsyncAnimationHandler;
mForcedResizableController = new ForcedResizableInfoActivityController(context, this,
mainExecutor);
mTransactionPool = transactionPool;
@@ -311,6 +314,7 @@ public class LegacySplitScreenController implements DisplayController.OnDisplays
Context dctx = mDisplayController.getDisplayContext(mContext.getDisplayId());
mView = (DividerView)
LayoutInflater.from(dctx).inflate(R.layout.docked_stack_divider, null);
+ mView.setAnimationHandler(mSfVsyncAnimationHandler);
DisplayLayout displayLayout = mDisplayController.getDisplayLayout(mContext.getDisplayId());
mView.injectDependencies(this, mWindowManager, mDividerState, mForcedResizableController,
mSplits, mSplitLayout, mImePositionProcessor, mWindowManagerProxy);
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 95bb65c5873e..d357655882ff 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
@@ -30,6 +30,7 @@ import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Rect;
+import android.view.Choreographer;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
@@ -270,15 +271,14 @@ public class PipAnimationController {
mEndValue = endValue;
addListener(this);
addUpdateListener(this);
- mSurfaceControlTransactionFactory =
- new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
+ mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
mTransitionDirection = TRANSITION_DIRECTION_NONE;
}
@Override
public void onAnimationStart(Animator animation) {
mCurrentValue = mStartValue;
- onStartTransaction(mLeash, mSurfaceControlTransactionFactory.getTransaction());
+ onStartTransaction(mLeash, newSurfaceControlTransaction());
if (mPipAnimationCallback != null) {
mPipAnimationCallback.onPipAnimationStart(mTaskInfo, this);
}
@@ -286,16 +286,14 @@ public class PipAnimationController {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
- applySurfaceControlTransaction(mLeash,
- mSurfaceControlTransactionFactory.getTransaction(),
+ applySurfaceControlTransaction(mLeash, newSurfaceControlTransaction(),
animation.getAnimatedFraction());
}
@Override
public void onAnimationEnd(Animator animation) {
mCurrentValue = mEndValue;
- final SurfaceControl.Transaction tx =
- mSurfaceControlTransactionFactory.getTransaction();
+ final SurfaceControl.Transaction tx = newSurfaceControlTransaction();
onEndTransaction(mLeash, tx, mTransitionDirection);
if (mPipAnimationCallback != null) {
mPipAnimationCallback.onPipAnimationEnd(mTaskInfo, tx, this);
@@ -342,8 +340,7 @@ public class PipAnimationController {
}
PipTransitionAnimator<T> setUseContentOverlay(Context context) {
- final SurfaceControl.Transaction tx =
- mSurfaceControlTransactionFactory.getTransaction();
+ final SurfaceControl.Transaction tx = newSurfaceControlTransaction();
if (mContentOverlay != null) {
// remove existing content overlay if there is any.
tx.remove(mContentOverlay);
@@ -418,7 +415,7 @@ public class PipAnimationController {
void setDestinationBounds(Rect destinationBounds) {
mDestinationBounds.set(destinationBounds);
if (mAnimationType == ANIM_TYPE_ALPHA) {
- onStartTransaction(mLeash, mSurfaceControlTransactionFactory.getTransaction());
+ onStartTransaction(mLeash, newSurfaceControlTransaction());
}
}
@@ -453,6 +450,16 @@ public class PipAnimationController {
mEndValue = endValue;
}
+ /**
+ * @return {@link SurfaceControl.Transaction} instance with vsync-id.
+ */
+ protected SurfaceControl.Transaction newSurfaceControlTransaction() {
+ final SurfaceControl.Transaction tx =
+ mSurfaceControlTransactionFactory.getTransaction();
+ tx.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());
+ return tx;
+ }
+
@VisibleForTesting
public void setSurfaceControlTransactionFactory(
PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
index b349010be1fe..c6e48f53681c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
@@ -20,7 +20,6 @@ import android.content.Context;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
-import android.view.Choreographer;
import android.view.SurfaceControl;
import com.android.wm.shell.R;
@@ -225,18 +224,4 @@ public class PipSurfaceTransactionHelper {
public interface SurfaceControlTransactionFactory {
SurfaceControl.Transaction getTransaction();
}
-
- /**
- * Implementation of {@link SurfaceControlTransactionFactory} that returns
- * {@link SurfaceControl.Transaction} with VsyncId being set.
- */
- public static class VsyncSurfaceControlTransactionFactory
- implements SurfaceControlTransactionFactory {
- @Override
- public SurfaceControl.Transaction getTransaction() {
- final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
- tx.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
- return tx;
- }
- }
}
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 664f98857cc7..d6b914138c38 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
@@ -280,8 +280,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mSurfaceTransactionHelper = surfaceTransactionHelper;
mPipAnimationController = pipAnimationController;
mPipUiEventLoggerLogger = pipUiEventLogger;
- mSurfaceControlTransactionFactory =
- new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
+ mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
mSplitScreenOptional = splitScreenOptional;
mTaskOrganizer = shellTaskOrganizer;
mMainExecutor = mainExecutor;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java
index 9c23a32a7d2b..513ebba59258 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java
@@ -110,7 +110,10 @@ public class PipUiEventLogger {
PICTURE_IN_PICTURE_STASH_RIGHT(711),
@UiEvent(doc = "User taps on the settings button in PiP menu")
- PICTURE_IN_PICTURE_SHOW_SETTINGS(933);
+ PICTURE_IN_PICTURE_SHOW_SETTINGS(933),
+
+ @UiEvent(doc = "Closes PiP with app-provided close action")
+ PICTURE_IN_PICTURE_CUSTOM_CLOSE(1058);
private final int mId;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index f73b81e9d3f3..bbec4eccce3c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -121,6 +121,7 @@ public class PhonePipMenuController implements PipMenuController {
private final Optional<SplitScreenController> mSplitScreenController;
private final PipUiEventLogger mPipUiEventLogger;
private ParceledListSlice<RemoteAction> mAppActions;
+ private RemoteAction mCloseAction;
private ParceledListSlice<RemoteAction> mMediaActions;
private SyncRtSurfaceTransactionApplier mApplier;
private int mMenuState;
@@ -171,7 +172,7 @@ public class PhonePipMenuController implements PipMenuController {
detachPipMenuView();
}
- private void attachPipMenuView() {
+ void attachPipMenuView() {
// In case detach was not called (e.g. PIP unexpectedly closed)
if (mPipMenuView != null) {
detachPipMenuView();
@@ -459,6 +460,7 @@ public class PhonePipMenuController implements PipMenuController {
public void setAppActions(ParceledListSlice<RemoteAction> appActions,
RemoteAction closeAction) {
mAppActions = appActions;
+ mCloseAction = closeAction;
updateMenuActions();
}
@@ -490,9 +492,8 @@ public class PhonePipMenuController implements PipMenuController {
private void updateMenuActions() {
if (mPipMenuView != null) {
final ParceledListSlice<RemoteAction> menuActions = resolveMenuActions();
- if (menuActions != null) {
- mPipMenuView.setActions(mPipBoundsState.getBounds(), menuActions.getList());
- }
+ mPipMenuView.setActions(mPipBoundsState.getBounds(),
+ menuActions == null ? null : menuActions.getList(), mCloseAction);
}
}
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 175a2445f28d..272331b7cd3f 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
@@ -521,6 +521,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
};
if (mPipTaskOrganizer.isInPip() && saveRestoreSnapFraction) {
+ mMenuController.attachPipMenuView();
// Calculate the snap fraction of the current stack along the old movement bounds
final PipSnapAlgorithm pipSnapAlgorithm = mPipBoundsAlgorithm.getSnapAlgorithm();
final Rect postChangeStackBounds = new Rect(mPipBoundsState.getBounds());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
index 11633a91e8c6..a0e22011b5d0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
@@ -220,10 +220,16 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen
return;
}
+ final SurfaceControl targetViewLeash =
+ mTargetViewContainer.getViewRootImpl().getSurfaceControl();
+ if (!targetViewLeash.isValid()) {
+ // The surface of mTargetViewContainer is somehow not ready, bail early
+ return;
+ }
+
// Put the dismiss target behind the task
SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- t.setRelativeLayer(mTargetViewContainer.getViewRootImpl().getSurfaceControl(),
- mTaskLeash, -1);
+ t.setRelativeLayer(targetViewLeash, mTaskLeash, -1);
t.apply();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
index 5ddb534b6829..0f3ff36601fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
@@ -146,10 +146,11 @@ public class PipInputConsumer {
"%s: Failed to create input consumer, %s", TAG, e);
}
mMainExecutor.execute(() -> {
- // Choreographer.getInstance() must be called on the thread that the input event
+ // Choreographer.getSfInstance() must be called on the thread that the input event
// receiver should be receiving events
+ // TODO(b/222697646): remove getSfInstance usage and use vsyncId for transactions
mInputEventReceiver = new InputEventReceiver(inputChannel,
- Looper.myLooper(), Choreographer.getInstance());
+ Looper.myLooper(), Choreographer.getSfInstance());
if (mRegistrationListener != null) {
mRegistrationListener.onRegistrationChanged(true /* isRegistered */);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuActionView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuActionView.java
index f11ae422e837..7f84500e8406 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuActionView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuActionView.java
@@ -19,6 +19,7 @@ package com.android.wm.shell.pip.phone;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
+import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
@@ -30,6 +31,7 @@ import com.android.wm.shell.R;
*/
public class PipMenuActionView extends FrameLayout {
private ImageView mImageView;
+ private View mCustomCloseBackground;
public PipMenuActionView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -39,10 +41,16 @@ public class PipMenuActionView extends FrameLayout {
protected void onFinishInflate() {
super.onFinishInflate();
mImageView = findViewById(R.id.image);
+ mCustomCloseBackground = findViewById(R.id.custom_close_bg);
}
/** pass through to internal {@link #mImageView} */
public void setImageDrawable(Drawable drawable) {
mImageView.setImageDrawable(drawable);
}
+
+ /** pass through to internal {@link #mCustomCloseBackground} */
+ public void setCustomCloseBackgroundVisibility(@View.Visibility int visibility) {
+ mCustomCloseBackground.setVisibility(visibility);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
index c0fa8c0a8898..6390c8984dac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
@@ -33,8 +33,10 @@ import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManager;
-import android.app.PendingIntent.CanceledException;
+import android.app.PendingIntent;
import android.app.RemoteAction;
import android.app.WindowConfiguration;
import android.content.ComponentName;
@@ -72,6 +74,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import java.util.Optional;
/**
@@ -113,6 +116,7 @@ public class PipMenuView extends FrameLayout {
private boolean mFocusedTaskAllowSplitScreen;
private final List<RemoteAction> mActions = new ArrayList<>();
+ private RemoteAction mCloseAction;
private AccessibilityManager mAccessibilityManager;
private Drawable mBackgroundDrawable;
@@ -151,6 +155,9 @@ public class PipMenuView extends FrameLayout {
protected View mTopEndContainer;
protected PipMenuIconsAlgorithm mPipMenuIconsAlgorithm;
+ // How long the shell will wait for the app to close the PiP if a custom action is set.
+ private final int mPipForceCloseDelay;
+
public PipMenuView(Context context, PhonePipMenuController controller,
ShellExecutor mainExecutor, Handler mainHandler,
Optional<SplitScreenController> splitScreenController,
@@ -166,6 +173,9 @@ public class PipMenuView extends FrameLayout {
mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
inflate(context, R.layout.pip_menu, this);
+ mPipForceCloseDelay = context.getResources().getInteger(
+ R.integer.config_pipForceCloseDelay);
+
mBackgroundDrawable = mContext.getDrawable(R.drawable.pip_menu_background);
mBackgroundDrawable.setAlpha(0);
mViewRoot = findViewById(R.id.background);
@@ -437,9 +447,13 @@ public class PipMenuView extends FrameLayout {
return new Size(width, height);
}
- void setActions(Rect stackBounds, List<RemoteAction> actions) {
+ void setActions(Rect stackBounds, @Nullable List<RemoteAction> actions,
+ @Nullable RemoteAction closeAction) {
mActions.clear();
- mActions.addAll(actions);
+ if (actions != null && !actions.isEmpty()) {
+ mActions.addAll(actions);
+ }
+ mCloseAction = closeAction;
if (mMenuState == MENU_STATE_FULL) {
updateActionViews(mMenuState, stackBounds);
}
@@ -492,6 +506,8 @@ public class PipMenuView extends FrameLayout {
final RemoteAction action = mActions.get(i);
final PipMenuActionView actionView =
(PipMenuActionView) mActionsGroup.getChildAt(i);
+ final boolean isCloseAction = mCloseAction != null && Objects.equals(
+ mCloseAction.getActionIntent(), action.getActionIntent());
// TODO: Check if the action drawable has changed before we reload it
action.getIcon().loadDrawableAsync(mContext, d -> {
@@ -500,16 +516,12 @@ public class PipMenuView extends FrameLayout {
actionView.setImageDrawable(d);
}
}, mMainHandler);
+ actionView.setCustomCloseBackgroundVisibility(
+ isCloseAction ? View.VISIBLE : View.GONE);
actionView.setContentDescription(action.getContentDescription());
if (action.isEnabled()) {
- actionView.setOnClickListener(v -> {
- try {
- action.getActionIntent().send();
- } catch (CanceledException e) {
- ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: Failed to send action, %s", TAG, e);
- }
- });
+ actionView.setOnClickListener(
+ v -> onActionViewClicked(action.getActionIntent(), isCloseAction));
}
actionView.setEnabled(action.isEnabled());
actionView.setAlpha(action.isEnabled() ? 1f : DISABLED_ACTION_ALPHA);
@@ -559,6 +571,32 @@ public class PipMenuView extends FrameLayout {
}
}
+ /**
+ * Execute the {@link PendingIntent} attached to the {@link PipMenuActionView}.
+ * If the given {@link PendingIntent} matches {@link #mCloseAction}, we need to make sure
+ * the PiP is removed after a certain timeout in case the app does not respond in a
+ * timely manner.
+ */
+ private void onActionViewClicked(@NonNull PendingIntent intent, boolean isCloseAction) {
+ try {
+ intent.send();
+ } catch (PendingIntent.CanceledException e) {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Failed to send action, %s", TAG, e);
+ }
+ if (isCloseAction) {
+ mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_CUSTOM_CLOSE);
+ mAllowTouches = false;
+ mMainExecutor.executeDelayed(() -> {
+ hideMenu();
+ // TODO: it's unsafe to call onPipDismiss with a delay here since
+ // we may have a different PiP by the time this runnable is executed.
+ mController.onPipDismiss();
+ mAllowTouches = true;
+ }, mPipForceCloseDelay);
+ }
+ }
+
private void enterSplit() {
// Do not notify menu visibility when hiding the menu, the controller will do this when it
// handles the message
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index 7028f9a25fbe..fa0f0925a08a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -33,6 +33,11 @@ import android.content.Context;
import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Debug;
+import android.os.Looper;
+import android.view.Choreographer;
+
+import androidx.dynamicanimation.animation.AnimationHandler;
+import androidx.dynamicanimation.animation.AnimationHandler.FrameCallbackScheduler;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
@@ -84,6 +89,26 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
/** Coordinator instance for resolving conflicts with other floating content. */
private FloatingContentCoordinator mFloatingContentCoordinator;
+ private ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal =
+ ThreadLocal.withInitial(() -> {
+ final Looper initialLooper = Looper.myLooper();
+ final FrameCallbackScheduler scheduler = new FrameCallbackScheduler() {
+ @Override
+ public void postFrameCallback(@androidx.annotation.NonNull Runnable runnable) {
+ // TODO(b/222697646): remove getSfInstance usage and use vsyncId for
+ // transactions
+ Choreographer.getSfInstance().postFrameCallback(t -> runnable.run());
+ }
+
+ @Override
+ public boolean isCurrentThread() {
+ return Looper.myLooper() == initialLooper;
+ }
+ };
+ AnimationHandler handler = new AnimationHandler(scheduler);
+ return handler;
+ });
+
/**
* PhysicsAnimator instance for animating {@link PipBoundsState#getMotionBoundsState()}
* using physics animations.
@@ -186,8 +211,11 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
}
public void init() {
+ // Note: Needs to get the shell main thread sf vsync animation handler
mTemporaryBoundsPhysicsAnimator = PhysicsAnimator.getInstance(
mPipBoundsState.getMotionBoundsState().getBoundsInMotion());
+ mTemporaryBoundsPhysicsAnimator.setCustomAnimationHandler(
+ mSfAnimationHandlerThreadLocal.get());
}
@NonNull
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
index 89d85e4b292d..abf1a9500e6d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
@@ -625,7 +625,8 @@ public class PipResizeGestureHandler {
class PipResizeInputEventReceiver extends BatchedInputEventReceiver {
PipResizeInputEventReceiver(InputChannel channel, Looper looper) {
- super(channel, looper, Choreographer.getInstance());
+ // TODO(b/222697646): remove getSfInstance usage and use vsyncId for transactions
+ super(channel, looper, Choreographer.getSfInstance());
}
public void onInputEvent(InputEvent event) {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
index 8da6224d990c..37e9344348d9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
@@ -25,6 +25,8 @@ import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -81,9 +83,19 @@ class ExitPipViaIntentTest(testSpec: FlickerTestParameter) : ExitPipToAppTransit
override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
/** {@inheritDoc} */
+ @FlakyTest(bugId = 197726610)
+ @Test
+ override fun pipLayerExpands() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ super.pipLayerExpands()
+ }
+
@Presubmit
@Test
- override fun pipLayerExpands() = super.pipLayerExpands()
+ fun pipLayerExpands_ShellTransit() {
+ Assume.assumeTrue(isShellTransitionsEnabled)
+ super.pipLayerExpands()
+ }
companion object {
/**
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
index 9f745208d3ed..aaeebef03d0f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
@@ -16,15 +16,28 @@
package com.android.wm.shell.draganddrop;
+import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.DragEvent.ACTION_DRAG_STARTED;
+
import static org.junit.Assert.assertFalse;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import android.content.ClipData;
+import android.content.ClipDescription;
import android.content.Context;
+import android.content.Intent;
import android.os.RemoteException;
import android.view.Display;
import android.view.DragEvent;
import android.view.View;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -33,6 +46,7 @@ import com.android.internal.logging.UiEventLogger;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.splitscreen.SplitScreenController;
import org.junit.Before;
import org.junit.Test;
@@ -40,6 +54,8 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Optional;
+
/**
* Tests for the drag and drop controller.
*/
@@ -56,6 +72,9 @@ public class DragAndDropControllerTest {
@Mock
private UiEventLogger mUiEventLogger;
+ @Mock
+ private DragAndDropController.DragAndDropListener mDragAndDropListener;
+
private DragAndDropController mController;
@Before
@@ -63,6 +82,7 @@ public class DragAndDropControllerTest {
MockitoAnnotations.initMocks(this);
mController = new DragAndDropController(mContext, mDisplayController, mUiEventLogger,
mock(IconProvider.class), mock(ShellExecutor.class));
+ mController.initialize(Optional.of(mock(SplitScreenController.class)));
}
@Test
@@ -77,4 +97,45 @@ public class DragAndDropControllerTest {
mController.onDisplayAdded(nonDefaultDisplayId);
assertFalse(mController.onDrag(dragLayout, mock(DragEvent.class)));
}
+
+ @Test
+ public void testListenerOnDragStarted() {
+ final View dragLayout = mock(View.class);
+ final Display display = mock(Display.class);
+ doReturn(display).when(dragLayout).getDisplay();
+ doReturn(DEFAULT_DISPLAY).when(display).getDisplayId();
+
+ final ClipData clipData = createClipData();
+ final DragEvent event = mock(DragEvent.class);
+ doReturn(ACTION_DRAG_STARTED).when(event).getAction();
+ doReturn(clipData).when(event).getClipData();
+ doReturn(clipData.getDescription()).when(event).getClipDescription();
+
+ mController.addListener(mDragAndDropListener);
+
+ // Ensure there's a target so that onDrag will execute
+ mController.addDisplayDropTarget(0, mContext, mock(WindowManager.class),
+ mock(FrameLayout.class), mock(DragLayout.class));
+
+ // Verify the listener is called on a valid drag action.
+ mController.onDrag(dragLayout, event);
+ verify(mDragAndDropListener, times(1)).onDragStarted();
+
+ // Verify the listener isn't called after removal.
+ reset(mDragAndDropListener);
+ mController.removeListener(mDragAndDropListener);
+ mController.onDrag(dragLayout, event);
+ verify(mDragAndDropListener, never()).onDragStarted();
+ }
+
+ private ClipData createClipData() {
+ ClipDescription clipDescription = new ClipDescription(MIMETYPE_APPLICATION_SHORTCUT,
+ new String[] { MIMETYPE_APPLICATION_SHORTCUT });
+ Intent i = new Intent();
+ i.putExtra(Intent.EXTRA_PACKAGE_NAME, "pkg");
+ i.putExtra(Intent.EXTRA_SHORTCUT_ID, "shortcutId");
+ i.putExtra(Intent.EXTRA_USER, android.os.Process.myUserHandle());
+ ClipData.Item item = new ClipData.Item(i);
+ return new ClipData(clipDescription, item);
+ }
}
diff --git a/libs/hwui/utils/Color.cpp b/libs/hwui/utils/Color.cpp
index 28f547703dde..3afb419f9b8b 100644
--- a/libs/hwui/utils/Color.cpp
+++ b/libs/hwui/utils/Color.cpp
@@ -399,10 +399,12 @@ skcms_TransferFunction GetPQSkTransferFunction(float sdr_white_level) {
// Skia skcms' default HLG maps encoded [0, 1] to linear [1, 12] in order to follow ARIB
// but LinearEffect expects a decoded [0, 1] range instead to follow Rec 2100.
std::optional<skcms_TransferFunction> GetHLGScaleTransferFunction() {
- std::optional<skcms_TransferFunction> hlgFn = {};
- skcms_TransferFunction_makeScaledHLGish(&hlgFn.value(), 1.f / 12.f, 2.f, 2.f, 1.f / 0.17883277f,
- 0.28466892f, 0.55991073f);
- return hlgFn;
+ skcms_TransferFunction hlgFn;
+ if (skcms_TransferFunction_makeScaledHLGish(&hlgFn, 1.f / 12.f, 2.f, 2.f, 1.f / 0.17883277f,
+ 0.28466892f, 0.55991073f)) {
+ return std::make_optional<skcms_TransferFunction>(hlgFn);
+ }
+ return {};
}
} // namespace uirenderer