summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/Notification.java74
-rw-r--r--core/java/android/hardware/camera2/marshal/impl/MarshalQueryableParcelable.java13
-rw-r--r--core/java/android/os/Parcel.java78
-rw-r--r--core/java/android/service/notification/NotificationRankingUpdate.java2
-rw-r--r--core/jni/android_os_Parcel.cpp87
-rw-r--r--core/res/res/layout/notification_2025_action_list.xml1
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_base.xml1
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_call.xml2
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_conversation.xml18
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_media.xml1
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_messaging.xml18
-rw-r--r--core/res/res/layout/notification_2025_template_expanded_base.xml2
-rw-r--r--core/res/res/layout/notification_2025_template_expanded_big_picture.xml1
-rw-r--r--core/res/res/layout/notification_2025_template_expanded_big_text.xml2
-rw-r--r--core/res/res/layout/notification_2025_template_expanded_call.xml1
-rw-r--r--core/res/res/layout/notification_2025_template_expanded_conversation.xml1
-rw-r--r--core/res/res/layout/notification_2025_template_expanded_inbox.xml1
-rw-r--r--core/res/res/layout/notification_2025_template_expanded_media.xml2
-rw-r--r--core/res/res/layout/notification_2025_template_expanded_messaging.xml1
-rw-r--r--core/res/res/layout/notification_2025_template_expanded_progress.xml2
-rw-r--r--core/res/res/values/dimens.xml5
-rw-r--r--core/tests/coretests/src/android/os/ParcelTest.java61
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopPipTransitionObserver.kt81
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt58
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt164
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt47
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopPipTransitionObserverTest.kt98
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt30
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt97
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt58
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig12
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayCoreStartable.kt67
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/OWNERS4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java41
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayCoreStartableTest.kt23
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt23
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt39
-rw-r--r--services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java3
44 files changed, 796 insertions, 491 deletions
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index feaa98f644a0..7c293cb9cb3b 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -6470,9 +6470,11 @@ public class Notification implements Parcelable
contentView.setViewVisibility(R.id.notification_material_reply_text_3, View.GONE);
contentView.setTextViewText(R.id.notification_material_reply_text_3, null);
- // This may get erased by bindSnoozeAction, or if we're showing the bubble icon
- contentView.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target,
- RemoteViews.MARGIN_BOTTOM, R.dimen.notification_content_margin);
+ if (!notificationsRedesignTemplates()) {
+ // This may get erased by bindSnoozeAction, or if we're showing the bubble icon
+ contentView.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target,
+ RemoteViews.MARGIN_BOTTOM, R.dimen.notification_content_margin);
+ }
}
private boolean bindSnoozeAction(RemoteViews contentView, StandardTemplateParams p) {
@@ -6489,7 +6491,7 @@ public class Notification implements Parcelable
final boolean snoozeEnabled = !hideSnoozeButton
&& mContext.getContentResolver() != null
&& isSnoozeSettingEnabled();
- if (snoozeEnabled) {
+ if (!notificationsRedesignTemplates() && snoozeEnabled) {
contentView.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target,
RemoteViews.MARGIN_BOTTOM, 0);
}
@@ -6569,44 +6571,18 @@ public class Notification implements Parcelable
}
boolean validRemoteInput = false;
+ // With the new design, the actions_container should always be visible to act as padding
+ // when there are no actions. We're making its child GONE instead.
+ int actionsContainerForVisibilityChange = notificationsRedesignTemplates()
+ ? R.id.actions_container_layout : R.id.actions_container;
if (numActions > 0 && !p.mHideActions) {
- contentView.setViewVisibility(R.id.actions_container, View.VISIBLE);
+ contentView.setViewVisibility(actionsContainerForVisibilityChange, View.VISIBLE);
contentView.setViewVisibility(R.id.actions, View.VISIBLE);
- contentView.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target,
- RemoteViews.MARGIN_BOTTOM, 0);
- if (notificationsRedesignTemplates()) {
- // No need for additional space under smart replies/smart actions.
- contentView.setViewLayoutMarginDimen(R.id.smart_reply_container,
- RemoteViews.MARGIN_BOTTOM, 0);
- if (emphasizedMode) {
- // Emphasized actions look similar to smart replies, so let's use the same
- // margins.
- contentView.setViewLayoutMarginDimen(R.id.actions_container,
- RemoteViews.MARGIN_TOP,
- R.dimen.notification_2025_smart_reply_container_margin);
- contentView.setViewLayoutMarginDimen(R.id.actions_container,
- RemoteViews.MARGIN_BOTTOM,
- R.dimen.notification_2025_smart_reply_container_margin);
- } else {
- contentView.setViewLayoutMarginDimen(R.id.actions_container,
- RemoteViews.MARGIN_TOP, 0);
- contentView.setViewLayoutMarginDimen(R.id.actions_container,
- RemoteViews.MARGIN_BOTTOM,
- R.dimen.notification_2025_action_list_margin_bottom);
- }
- }
+ updateMarginsForActions(contentView, emphasizedMode);
validRemoteInput = populateActionsContainer(contentView, p, nonContextualActions,
numActions, emphasizedMode);
} else {
- contentView.setViewVisibility(R.id.actions_container, View.GONE);
- if (notificationsRedesignTemplates() && !snoozeEnabled) {
- // Make sure smart replies & smart actions have enough space at the bottom
- // (if present) when there are no actions. This should be set to 0 if we're
- // showing the snooze or bubble buttons.
- contentView.setViewLayoutMarginDimen(R.id.smart_reply_container,
- RemoteViews.MARGIN_BOTTOM,
- R.dimen.notification_2025_smart_reply_container_margin);
- }
+ contentView.setViewVisibility(actionsContainerForVisibilityChange, View.GONE);
}
RemoteInputHistoryItem[] replyText = getParcelableArrayFromBundle(
@@ -6652,6 +6628,30 @@ public class Notification implements Parcelable
return contentView;
}
+ private void updateMarginsForActions(RemoteViews contentView, boolean emphasizedMode) {
+ if (notificationsRedesignTemplates()) {
+ if (emphasizedMode) {
+ // Emphasized actions look similar to smart replies, so let's use the same
+ // margins.
+ contentView.setViewLayoutMarginDimen(R.id.actions_container,
+ RemoteViews.MARGIN_TOP,
+ R.dimen.notification_2025_smart_reply_container_margin);
+ contentView.setViewLayoutMarginDimen(R.id.actions_container,
+ RemoteViews.MARGIN_BOTTOM,
+ R.dimen.notification_2025_smart_reply_container_margin);
+ } else {
+ contentView.setViewLayoutMarginDimen(R.id.actions_container,
+ RemoteViews.MARGIN_TOP, 0);
+ contentView.setViewLayoutMarginDimen(R.id.actions_container,
+ RemoteViews.MARGIN_BOTTOM,
+ R.dimen.notification_2025_action_list_margin_bottom);
+ }
+ } else {
+ contentView.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target,
+ RemoteViews.MARGIN_BOTTOM, 0);
+ }
+ }
+
private boolean populateActionsContainer(RemoteViews contentView, StandardTemplateParams p,
List<Action> nonContextualActions, int numActions, boolean emphasizedMode) {
boolean validRemoteInput = false;
diff --git a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableParcelable.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableParcelable.java
index fdde2057a1a0..de3bafb4bb56 100644
--- a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableParcelable.java
+++ b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableParcelable.java
@@ -75,7 +75,6 @@ public class MarshalQueryableParcelable<T extends Parcelable>
}
Parcel parcel = Parcel.obtain();
- byte[] parcelContents;
try {
value.writeToParcel(parcel, /*flags*/0);
@@ -85,17 +84,15 @@ public class MarshalQueryableParcelable<T extends Parcelable>
"Parcelable " + value + " must not have file descriptors");
}
- parcelContents = parcel.marshall();
+ final int position = buffer.position();
+ parcel.marshall(buffer);
+ if (buffer.position() == position) {
+ throw new AssertionError("No data marshaled for " + value);
+ }
}
finally {
parcel.recycle();
}
-
- if (parcelContents.length == 0) {
- throw new AssertionError("No data marshaled for " + value);
- }
-
- buffer.put(parcelContents);
}
@Override
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 49d3f06eb80f..6cb49b3ea166 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -52,6 +52,8 @@ import com.android.internal.util.ArrayUtils;
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
+import java.nio.BufferOverflowException;
+import java.nio.ReadOnlyBufferException;
import libcore.util.SneakyThrow;
import java.io.ByteArrayInputStream;
@@ -62,6 +64,7 @@ import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.io.Serializable;
+import java.nio.ByteBuffer;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Array;
@@ -457,8 +460,15 @@ public final class Parcel {
private static native void nativeDestroy(long nativePtr);
private static native byte[] nativeMarshall(long nativePtr);
+ private static native int nativeMarshallArray(
+ long nativePtr, byte[] data, int offset, int length);
+ private static native int nativeMarshallBuffer(
+ long nativePtr, ByteBuffer buffer, int offset, int length);
private static native void nativeUnmarshall(
long nativePtr, byte[] data, int offset, int length);
+ private static native void nativeUnmarshallBuffer(
+ long nativePtr, ByteBuffer buffer, int offset, int length);
+
private static native int nativeCompareData(long thisNativePtr, long otherNativePtr);
private static native boolean nativeCompareDataInRange(
long ptrA, int offsetA, long ptrB, int offsetB, int length);
@@ -814,12 +824,80 @@ public final class Parcel {
}
/**
+ * Writes the raw bytes of the parcel to a buffer.
+ *
+ * <p class="note">The data you retrieve here <strong>must not</strong>
+ * be placed in any kind of persistent storage (on local disk, across
+ * a network, etc). For that, you should use standard serialization
+ * or another kind of general serialization mechanism. The Parcel
+ * marshalled representation is highly optimized for local IPC, and as
+ * such does not attempt to maintain compatibility with data created
+ * in different versions of the platform.
+ *
+ * @param buffer The ByteBuffer to write the data to.
+ * @throws ReadOnlyBufferException if the buffer is read-only.
+ * @throws BufferOverflowException if the buffer is too small.
+ *
+ * @hide
+ */
+ public final void marshall(@NonNull ByteBuffer buffer) {
+ if (buffer == null) {
+ throw new NullPointerException();
+ }
+ if (buffer.isReadOnly()) {
+ throw new ReadOnlyBufferException();
+ }
+
+ final int position = buffer.position();
+ final int remaining = buffer.remaining();
+
+ int marshalledSize = 0;
+ if (buffer.isDirect()) {
+ marshalledSize = nativeMarshallBuffer(mNativePtr, buffer, position, remaining);
+ } else if (buffer.hasArray()) {
+ marshalledSize = nativeMarshallArray(
+ mNativePtr, buffer.array(), buffer.arrayOffset() + position, remaining);
+ } else {
+ throw new IllegalArgumentException();
+ }
+
+ buffer.position(position + marshalledSize);
+ }
+
+ /**
* Fills the raw bytes of this Parcel with the supplied data.
*/
public final void unmarshall(@NonNull byte[] data, int offset, int length) {
nativeUnmarshall(mNativePtr, data, offset, length);
}
+ /**
+ * Fills the raw bytes of this Parcel with data from the supplied buffer.
+ *
+ * @param buffer will read buffer.remaining() bytes from the buffer.
+ *
+ * @hide
+ */
+ public final void unmarshall(@NonNull ByteBuffer buffer) {
+ if (buffer == null) {
+ throw new NullPointerException();
+ }
+
+ final int position = buffer.position();
+ final int remaining = buffer.remaining();
+
+ if (buffer.isDirect()) {
+ nativeUnmarshallBuffer(mNativePtr, buffer, position, remaining);
+ } else if (buffer.hasArray()) {
+ nativeUnmarshall(
+ mNativePtr, buffer.array(), buffer.arrayOffset() + position, remaining);
+ } else {
+ throw new IllegalArgumentException();
+ }
+
+ buffer.position(position + remaining);
+ }
+
public final void appendFrom(Parcel parcel, int offset, int length) {
nativeAppendFrom(mNativePtr, parcel.mNativePtr, offset, length);
}
diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java
index 7660ed96d30e..815444d195c9 100644
--- a/core/java/android/service/notification/NotificationRankingUpdate.java
+++ b/core/java/android/service/notification/NotificationRankingUpdate.java
@@ -219,7 +219,7 @@ public class NotificationRankingUpdate implements Parcelable {
// Gets a read/write buffer mapping the entire shared memory region.
buffer = mRankingMapFd.mapReadWrite();
// Puts the ranking map into the shared memory region buffer.
- buffer.put(mapParcel.marshall(), 0, mapSize);
+ mapParcel.marshall(buffer);
// Protects the region from being written to, by setting it to be read-only.
mRankingMapFd.setProtect(OsConstants.PROT_READ);
// Puts the SharedMemory object in the parcel.
diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp
index dec724b6a7ff..b4c58b9b246a 100644
--- a/core/jni/android_os_Parcel.cpp
+++ b/core/jni/android_os_Parcel.cpp
@@ -558,8 +558,7 @@ static void android_os_Parcel_destroy(JNIEnv* env, jclass clazz, jlong nativePtr
delete parcel;
}
-static jbyteArray android_os_Parcel_marshall(JNIEnv* env, jclass clazz, jlong nativePtr)
-{
+static Parcel* parcel_for_marshall(JNIEnv* env, jlong nativePtr) {
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
if (parcel == NULL) {
return NULL;
@@ -577,6 +576,16 @@ static jbyteArray android_os_Parcel_marshall(JNIEnv* env, jclass clazz, jlong na
return NULL;
}
+ return parcel;
+}
+
+static jbyteArray android_os_Parcel_marshall(JNIEnv* env, jclass clazz, jlong nativePtr)
+{
+ Parcel* parcel = parcel_for_marshall(env, nativePtr);
+ if (parcel == NULL) {
+ return NULL;
+ }
+
jbyteArray ret = env->NewByteArray(parcel->dataSize());
if (ret != NULL)
@@ -592,6 +601,58 @@ static jbyteArray android_os_Parcel_marshall(JNIEnv* env, jclass clazz, jlong na
return ret;
}
+static long ensure_capacity(JNIEnv* env, Parcel* parcel, jint remaining) {
+ long dataSize = parcel->dataSize();
+ if (remaining < dataSize) {
+ jniThrowExceptionFmt(env, "java/nio/BufferOverflowException",
+ "Destination buffer remaining capacity %d is less than the Parcel data size %d.",
+ remaining, dataSize);
+ return -1;
+ }
+ return dataSize;
+}
+
+static int android_os_Parcel_marshall_array(JNIEnv* env, jclass clazz, jlong nativePtr,
+ jbyteArray data, jint offset, jint remaining)
+{
+ Parcel* parcel = parcel_for_marshall(env, nativePtr);
+ if (parcel == NULL) {
+ return 0;
+ }
+
+ long data_size = ensure_capacity(env, parcel, remaining);
+ if (data_size < 0) {
+ return 0;
+ }
+
+ jbyte* array = (jbyte*)env->GetPrimitiveArrayCritical(data, 0);
+ if (array != NULL)
+ {
+ memcpy(array + offset, parcel->data(), data_size);
+ env->ReleasePrimitiveArrayCritical(data, array, 0);
+ }
+ return data_size;
+}
+
+static int android_os_Parcel_marshall_buffer(JNIEnv* env, jclass clazz, jlong nativePtr,
+ jobject javaBuffer, jint offset, jint remaining) {
+ Parcel* parcel = parcel_for_marshall(env, nativePtr);
+ if (parcel == NULL) {
+ return 0;
+ }
+
+ long data_size = ensure_capacity(env, parcel, remaining);
+ if (data_size < 0) {
+ return 0;
+ }
+
+ jbyte* buffer = (jbyte*)env->GetDirectBufferAddress(javaBuffer);
+ if (buffer != NULL) {
+ memcpy(buffer + offset, parcel->data(), data_size);
+ }
+ return data_size;
+}
+
static void android_os_Parcel_unmarshall(JNIEnv* env, jclass clazz, jlong nativePtr,
jbyteArray data, jint offset, jint length)
{
@@ -613,6 +674,25 @@ static void android_os_Parcel_unmarshall(JNIEnv* env, jclass clazz, jlong native
}
}
+static void android_os_Parcel_unmarshall_buffer(JNIEnv* env, jclass clazz, jlong nativePtr,
+ jobject javaBuffer, jint offset, jint length)
+{
+ Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+ if (parcel == NULL || length < 0) {
+ return;
+ }
+
+ jbyte* buffer = (jbyte*)env->GetDirectBufferAddress(javaBuffer);
+ if (buffer)
+ {
+ parcel->setDataSize(length);
+ parcel->setDataPosition(0);
+
+ void* raw = parcel->writeInplace(length);
+ memcpy(raw, (buffer + offset), length);
+ }
+}
+
static jint android_os_Parcel_compareData(JNIEnv* env, jclass clazz, jlong thisNativePtr,
jlong otherNativePtr)
{
@@ -911,7 +991,10 @@ static const JNINativeMethod gParcelMethods[] = {
{"nativeDestroy", "(J)V", (void*)android_os_Parcel_destroy},
{"nativeMarshall", "(J)[B", (void*)android_os_Parcel_marshall},
+ {"nativeMarshallArray", "(J[BII)I", (void*)android_os_Parcel_marshall_array},
+ {"nativeMarshallBuffer", "(JLjava/nio/ByteBuffer;II)I", (void*)android_os_Parcel_marshall_buffer},
{"nativeUnmarshall", "(J[BII)V", (void*)android_os_Parcel_unmarshall},
+ {"nativeUnmarshallBuffer", "(JLjava/nio/ByteBuffer;II)V", (void*)android_os_Parcel_unmarshall_buffer},
{"nativeCompareData", "(JJ)I", (void*)android_os_Parcel_compareData},
{"nativeCompareDataInRange", "(JIJII)Z", (void*)android_os_Parcel_compareDataInRange},
{"nativeAppendFrom", "(JJII)V", (void*)android_os_Parcel_appendFrom},
diff --git a/core/res/res/layout/notification_2025_action_list.xml b/core/res/res/layout/notification_2025_action_list.xml
index 053aca068027..6c07ec1a7c7b 100644
--- a/core/res/res/layout/notification_2025_action_list.xml
+++ b/core/res/res/layout/notification_2025_action_list.xml
@@ -22,6 +22,7 @@
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginBottom="@dimen/notification_2025_action_list_margin_bottom"
+ android:minHeight="@dimen/notification_2025_action_list_min_height"
>
<LinearLayout
diff --git a/core/res/res/layout/notification_2025_template_collapsed_base.xml b/core/res/res/layout/notification_2025_template_collapsed_base.xml
index 63f32e3b3cd2..a8d4a1b13bac 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_base.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_base.xml
@@ -74,7 +74,6 @@
android:id="@+id/notification_headerless_view_column"
android:layout_width="0px"
android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
android:layout_weight="1"
android:layout_marginVertical="@dimen/notification_2025_reduced_margin"
android:orientation="vertical"
diff --git a/core/res/res/layout/notification_2025_template_collapsed_call.xml b/core/res/res/layout/notification_2025_template_collapsed_call.xml
index 732021c65742..c94e211f3fac 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_call.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_call.xml
@@ -78,7 +78,6 @@
android:id="@+id/notification_headerless_view_column"
android:layout_width="0px"
android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
android:layout_weight="1"
android:layout_marginBottom="@dimen/notification_2025_margin"
android:layout_marginTop="@dimen/notification_2025_margin"
@@ -174,7 +173,6 @@
</FrameLayout>
<LinearLayout
- android:id="@+id/notification_action_list_margin_target"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="-20dp"
diff --git a/core/res/res/layout/notification_2025_template_collapsed_conversation.xml b/core/res/res/layout/notification_2025_template_collapsed_conversation.xml
index 1ee7ddc8d060..6cdc84b9dea7 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_conversation.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_conversation.xml
@@ -78,17 +78,10 @@
android:clipChildren="false"
>
- <!--
- NOTE: because messaging will always have 2 lines, this LinearLayout should NOT
- have the id/notification_headerless_view_column, as that is used for modifying
- vertical margins to accommodate the single-line state that base supports
- -->
<LinearLayout
android:layout_width="0px"
android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
android:layout_weight="1"
- android:layout_marginBottom="@dimen/notification_2025_margin"
android:layout_marginTop="@dimen/notification_2025_margin"
android:layout_marginStart="@dimen/notification_2025_content_margin_start"
android:clipChildren="false"
@@ -150,7 +143,6 @@
android:layout_height="@dimen/notification_right_icon_size"
android:layout_gravity="center_vertical|end"
android:layout_marginTop="@dimen/notification_2025_margin"
- android:layout_marginBottom="@dimen/notification_2025_margin"
android:layout_marginStart="@dimen/notification_2025_right_icon_content_margin"
android:forceHasOverlappingRendering="false"
android:spacing="0dp"
@@ -196,20 +188,20 @@
</FrameLayout>
- <LinearLayout
- android:id="@+id/notification_action_list_margin_target"
+ <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="-20dp"
android:clipChildren="false"
android:orientation="vertical">
- <include layout="@layout/notification_template_smart_reply_container"
+ <include layout="@layout/notification_template_smart_reply_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/notification_2025_smart_reply_container_margin"
android:layout_marginStart="@dimen/notification_2025_content_margin_start"
android:layout_marginEnd="@dimen/notification_content_margin_end" />
- <include layout="@layout/notification_2025_action_list" />
+ <include layout="@layout/notification_2025_action_list" />
+ </LinearLayout>
+
</LinearLayout>
-</LinearLayout>
</com.android.internal.widget.ConversationLayout>
diff --git a/core/res/res/layout/notification_2025_template_collapsed_media.xml b/core/res/res/layout/notification_2025_template_collapsed_media.xml
index 629af77b3dda..75867b867069 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_media.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_media.xml
@@ -76,7 +76,6 @@
android:id="@+id/notification_headerless_view_column"
android:layout_width="0px"
android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
android:layout_weight="1"
android:layout_marginVertical="@dimen/notification_2025_reduced_margin"
android:orientation="vertical"
diff --git a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
index af660254d172..0edf9df130f0 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
@@ -89,17 +89,10 @@
android:clipChildren="false"
>
- <!--
- NOTE: because messaging will always have 2 lines, this LinearLayout should NOT
- have the id/notification_headerless_view_column, as that is used for modifying
- vertical margins to accommodate the single-line state that base supports
- -->
<LinearLayout
android:layout_width="0px"
android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
android:layout_weight="1"
- android:layout_marginBottom="@dimen/notification_2025_margin"
android:layout_marginTop="@dimen/notification_2025_margin"
android:layout_marginStart="@dimen/notification_2025_content_margin_start"
android:clipChildren="false"
@@ -161,7 +154,6 @@
android:layout_height="@dimen/notification_right_icon_size"
android:layout_gravity="center_vertical|end"
android:layout_marginTop="@dimen/notification_2025_margin"
- android:layout_marginBottom="@dimen/notification_2025_margin"
android:layout_marginStart="@dimen/notification_2025_right_icon_content_margin"
android:forceHasOverlappingRendering="false"
android:spacing="0dp"
@@ -207,20 +199,20 @@
</FrameLayout>
- <LinearLayout
- android:id="@+id/notification_action_list_margin_target"
+ <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="-20dp"
android:clipChildren="false"
android:orientation="vertical">
- <include layout="@layout/notification_template_smart_reply_container"
+ <include layout="@layout/notification_template_smart_reply_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/notification_2025_smart_reply_container_margin"
android:layout_marginStart="@dimen/notification_2025_content_margin_start"
android:layout_marginEnd="@dimen/notification_content_margin_end" />
- <include layout="@layout/notification_2025_action_list" />
+ <include layout="@layout/notification_2025_action_list" />
+ </LinearLayout>
+
</LinearLayout>
-</LinearLayout>
</com.android.internal.widget.MessagingLayout>
diff --git a/core/res/res/layout/notification_2025_template_expanded_base.xml b/core/res/res/layout/notification_2025_template_expanded_base.xml
index 76a85813b980..1fe77f6f4461 100644
--- a/core/res/res/layout/notification_2025_template_expanded_base.xml
+++ b/core/res/res/layout/notification_2025_template_expanded_base.xml
@@ -24,10 +24,8 @@
>
<LinearLayout
- android:id="@+id/notification_action_list_margin_target"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginBottom="@dimen/notification_content_margin"
android:orientation="vertical"
>
diff --git a/core/res/res/layout/notification_2025_template_expanded_big_picture.xml b/core/res/res/layout/notification_2025_template_expanded_big_picture.xml
index 999afa66c65b..6f8eb148c040 100644
--- a/core/res/res/layout/notification_2025_template_expanded_big_picture.xml
+++ b/core/res/res/layout/notification_2025_template_expanded_big_picture.xml
@@ -28,7 +28,6 @@
<include layout="@layout/notification_2025_right_icon" />
<LinearLayout
- android:id="@+id/notification_action_list_margin_target"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="top"
diff --git a/core/res/res/layout/notification_2025_template_expanded_big_text.xml b/core/res/res/layout/notification_2025_template_expanded_big_text.xml
index c9206eddbcde..3c58567804cd 100644
--- a/core/res/res/layout/notification_2025_template_expanded_big_text.xml
+++ b/core/res/res/layout/notification_2025_template_expanded_big_text.xml
@@ -26,11 +26,9 @@
<include layout="@layout/notification_2025_template_header" />
<com.android.internal.widget.RemeasuringLinearLayout
- android:id="@+id/notification_action_list_margin_target"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
- android:layout_marginBottom="@dimen/notification_content_margin"
android:clipToPadding="false"
android:orientation="vertical"
>
diff --git a/core/res/res/layout/notification_2025_template_expanded_call.xml b/core/res/res/layout/notification_2025_template_expanded_call.xml
index ec214554a30b..7d561caa08da 100644
--- a/core/res/res/layout/notification_2025_template_expanded_call.xml
+++ b/core/res/res/layout/notification_2025_template_expanded_call.xml
@@ -30,7 +30,6 @@
<include layout="@layout/notification_2025_conversation_header"/>
<com.android.internal.widget.RemeasuringLinearLayout
- android:id="@+id/notification_action_list_margin_target"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
diff --git a/core/res/res/layout/notification_2025_template_expanded_conversation.xml b/core/res/res/layout/notification_2025_template_expanded_conversation.xml
index 6ee82fa116ff..b220efc669b9 100644
--- a/core/res/res/layout/notification_2025_template_expanded_conversation.xml
+++ b/core/res/res/layout/notification_2025_template_expanded_conversation.xml
@@ -29,7 +29,6 @@
<include layout="@layout/notification_2025_conversation_header"/>
<com.android.internal.widget.RemeasuringLinearLayout
- android:id="@+id/notification_action_list_margin_target"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
diff --git a/core/res/res/layout/notification_2025_template_expanded_inbox.xml b/core/res/res/layout/notification_2025_template_expanded_inbox.xml
index 1eaef228aaca..9d54e7eddcfa 100644
--- a/core/res/res/layout/notification_2025_template_expanded_inbox.xml
+++ b/core/res/res/layout/notification_2025_template_expanded_inbox.xml
@@ -24,7 +24,6 @@
>
<include layout="@layout/notification_2025_template_header" />
<LinearLayout
- android:id="@+id/notification_action_list_margin_target"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="top"
diff --git a/core/res/res/layout/notification_2025_template_expanded_media.xml b/core/res/res/layout/notification_2025_template_expanded_media.xml
index 801e339b3a92..c354e8568aa1 100644
--- a/core/res/res/layout/notification_2025_template_expanded_media.xml
+++ b/core/res/res/layout/notification_2025_template_expanded_media.xml
@@ -53,7 +53,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/notification_2025_media_actions_margin_start"
- android:minHeight="@dimen/notification_content_margin"
+ android:minHeight="@dimen/notification_2025_margin"
>
<!-- Nesting in FrameLayout is required to ensure that the marginStart actually applies
diff --git a/core/res/res/layout/notification_2025_template_expanded_messaging.xml b/core/res/res/layout/notification_2025_template_expanded_messaging.xml
index 62059af7f056..82996dec2925 100644
--- a/core/res/res/layout/notification_2025_template_expanded_messaging.xml
+++ b/core/res/res/layout/notification_2025_template_expanded_messaging.xml
@@ -29,7 +29,6 @@
<include layout="@layout/notification_2025_template_header"/>
<com.android.internal.widget.RemeasuringLinearLayout
- android:id="@+id/notification_action_list_margin_target"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
diff --git a/core/res/res/layout/notification_2025_template_expanded_progress.xml b/core/res/res/layout/notification_2025_template_expanded_progress.xml
index cf39d8b08c4f..9a4653e73cae 100644
--- a/core/res/res/layout/notification_2025_template_expanded_progress.xml
+++ b/core/res/res/layout/notification_2025_template_expanded_progress.xml
@@ -25,10 +25,8 @@
>
<LinearLayout
- android:id="@+id/notification_action_list_margin_target"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginBottom="@dimen/notification_content_margin"
android:orientation="vertical"
>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 6e540833bc46..b5ed28f82dda 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -295,6 +295,11 @@
<!-- The margin of the notification action list at the bottom in the 2025 redesign -->
<dimen name="notification_2025_action_list_margin_bottom">6dp</dimen>
+ <!-- The minimum height of the notification action container, to act as a bottom padding for the
+ notification when there are no actions. This should always be equal to
+ notification_2025_margin - notification_2025_action_list_margin_bottom. -->
+ <dimen name="notification_2025_action_list_min_height">10dp</dimen>
+
<!-- The overall height of the emphasized notification action -->
<dimen name="notification_action_emphasized_height">48dp</dimen>
diff --git a/core/tests/coretests/src/android/os/ParcelTest.java b/core/tests/coretests/src/android/os/ParcelTest.java
index 3e6520106ab0..bb059108d4b6 100644
--- a/core/tests/coretests/src/android/os/ParcelTest.java
+++ b/core/tests/coretests/src/android/os/ParcelTest.java
@@ -29,6 +29,8 @@ import android.util.Log;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -416,4 +418,63 @@ public class ParcelTest {
int binderEndPos = pA.dataPosition();
assertTrue(pA.hasBinders(binderStartPos, binderEndPos - binderStartPos));
}
+
+ private static final byte[] TEST_DATA = new byte[] {4, 8, 15, 16, 23, 42};
+
+ // Allow for some Parcel overhead
+ private static final int TEST_DATA_LENGTH = TEST_DATA.length + 100;
+
+ @Test
+ public void testMarshall_ByteBuffer_wrapped() {
+ ByteBuffer bb = ByteBuffer.allocate(TEST_DATA_LENGTH);
+ testMarshall_ByteBuffer(bb);
+ }
+
+ @Test
+ public void testMarshall_DirectByteBuffer() {
+ ByteBuffer bb = ByteBuffer.allocateDirect(TEST_DATA_LENGTH);
+ testMarshall_ByteBuffer(bb);
+ }
+
+ private void testMarshall_ByteBuffer(ByteBuffer bb) {
+ // Ensure that Parcel respects the starting offset by not starting at 0
+ bb.position(1);
+ bb.mark();
+
+ // Parcel test data, then marshall into the ByteBuffer
+ Parcel p1 = Parcel.obtain();
+ p1.writeByteArray(TEST_DATA);
+ p1.marshall(bb);
+ p1.recycle();
+
+ assertTrue(bb.position() > 1);
+ bb.reset();
+
+ // Unmarshall test data into a new Parcel
+ Parcel p2 = Parcel.obtain();
+ bb.reset();
+ p2.unmarshall(bb);
+ assertTrue(bb.position() > 1);
+ p2.setDataPosition(0);
+ byte[] marshalled = p2.marshall();
+
+ bb.reset();
+ for (int i = 0; i < TEST_DATA.length; i++) {
+ assertEquals(bb.get(), marshalled[i]);
+ }
+
+ byte[] testDataCopy = new byte[TEST_DATA.length];
+ p2.setDataPosition(0);
+ p2.readByteArray(testDataCopy);
+ for (int i = 0; i < TEST_DATA.length; i++) {
+ assertEquals(TEST_DATA[i], testDataCopy[i]);
+ }
+
+ // Test that overflowing the buffer throws an exception
+ bb.reset();
+ // Leave certainly not enough room for the test data
+ bb.limit(bb.position() + TEST_DATA.length - 1);
+ assertThrows(BufferOverflowException.class, () -> p2.marshall(bb));
+ p2.recycle();
+ }
}
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 257e27a338be..67a4d6cf89bf 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
@@ -98,6 +98,7 @@ import com.android.wm.shell.desktopmode.DesktopModeKeyGestureHandler;
import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver;
import com.android.wm.shell.desktopmode.DesktopModeMoveToDisplayTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger;
+import com.android.wm.shell.desktopmode.DesktopPipTransitionObserver;
import com.android.wm.shell.desktopmode.DesktopTaskChangeListener;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopTasksLimiter;
@@ -780,6 +781,7 @@ public abstract class WMShellModule {
OverviewToDesktopTransitionObserver overviewToDesktopTransitionObserver,
DesksOrganizer desksOrganizer,
Optional<DesksTransitionObserver> desksTransitionObserver,
+ Optional<DesktopPipTransitionObserver> desktopPipTransitionObserver,
UserProfileContexts userProfileContexts,
DesktopModeCompatPolicy desktopModeCompatPolicy,
DragToDisplayTransitionHandler dragToDisplayTransitionHandler,
@@ -823,6 +825,7 @@ public abstract class WMShellModule {
overviewToDesktopTransitionObserver,
desksOrganizer,
desksTransitionObserver.get(),
+ desktopPipTransitionObserver,
userProfileContexts,
desktopModeCompatPolicy,
dragToDisplayTransitionHandler,
@@ -1225,6 +1228,7 @@ public abstract class WMShellModule {
Transitions transitions,
ShellTaskOrganizer shellTaskOrganizer,
Optional<DesktopMixedTransitionHandler> desktopMixedTransitionHandler,
+ Optional<DesktopPipTransitionObserver> desktopPipTransitionObserver,
Optional<BackAnimationController> backAnimationController,
DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider,
ShellInit shellInit) {
@@ -1237,6 +1241,7 @@ public abstract class WMShellModule {
transitions,
shellTaskOrganizer,
desktopMixedTransitionHandler.get(),
+ desktopPipTransitionObserver,
backAnimationController.get(),
desktopWallpaperActivityTokenProvider,
shellInit)));
@@ -1258,6 +1263,19 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
+ static Optional<DesktopPipTransitionObserver> provideDesktopPipTransitionObserver(
+ Context context
+ ) {
+ if (DesktopModeStatus.canEnterDesktopMode(context)
+ && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue()) {
+ return Optional.of(
+ new DesktopPipTransitionObserver());
+ }
+ return Optional.empty();
+ }
+
+ @WMSingleton
+ @Provides
static Optional<DesktopMixedTransitionHandler> provideDesktopMixedTransitionHandler(
Context context,
Transitions transitions,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopPipTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopPipTransitionObserver.kt
new file mode 100644
index 000000000000..efd3866e1bc4
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopPipTransitionObserver.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.desktopmode
+
+import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
+import android.os.IBinder
+import android.window.DesktopModeFlags
+import android.window.TransitionInfo
+import com.android.internal.protolog.ProtoLog
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+
+/**
+ * Observer of PiP in Desktop Mode transitions. At the moment, this is specifically tracking a PiP
+ * transition for a task that is entering PiP via the minimize button on the caption bar.
+ */
+class DesktopPipTransitionObserver {
+ private val pendingPipTransitions = mutableMapOf<IBinder, PendingPipTransition>()
+
+ /** Adds a pending PiP transition to be tracked. */
+ fun addPendingPipTransition(transition: PendingPipTransition) {
+ if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue) return
+ pendingPipTransitions[transition.token] = transition
+ }
+
+ /**
+ * Called when any transition is ready, which may include transitions not tracked by this
+ * observer.
+ */
+ fun onTransitionReady(transition: IBinder, info: TransitionInfo) {
+ if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue) return
+ val pipTransition = pendingPipTransitions.remove(transition) ?: return
+
+ logD("Desktop PiP transition ready: %s", transition)
+ for (change in info.changes) {
+ val taskInfo = change.taskInfo
+ if (taskInfo == null || taskInfo.taskId == -1) {
+ continue
+ }
+
+ if (
+ taskInfo.taskId == pipTransition.taskId &&
+ taskInfo.windowingMode == WINDOWING_MODE_PINNED
+ ) {
+ logD("Desktop PiP transition was successful")
+ pipTransition.onSuccess()
+ return
+ }
+ }
+ logD("Change with PiP task not found in Desktop PiP transition; likely failed")
+ }
+
+ /**
+ * Data tracked for a pending PiP transition.
+ *
+ * @property token the PiP transition that is started.
+ * @property taskId task id of the task entering PiP.
+ * @property onSuccess callback to be invoked if the PiP transition is successful.
+ */
+ data class PendingPipTransition(val token: IBinder, val taskId: Int, val onSuccess: () -> Unit)
+
+ private fun logD(msg: String, vararg arguments: Any?) {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
+ }
+
+ private companion object {
+ private const val TAG = "DesktopPipTransitionObserver"
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
index a4e9c52ac9d9..6cb26b54e802 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
@@ -68,7 +68,6 @@ class DesktopRepository(
* @property topTransparentFullscreenTaskId the task id of any current top transparent
* fullscreen task launched on top of the desk. Cleared when the transparent task is closed or
* sent to back. (top is at index 0).
- * @property pipTaskId the task id of PiP task entered while in Desktop Mode.
*/
private data class Desk(
val deskId: Int,
@@ -81,7 +80,6 @@ class DesktopRepository(
val freeformTasksInZOrder: ArrayList<Int> = ArrayList(),
var fullImmersiveTaskId: Int? = null,
var topTransparentFullscreenTaskId: Int? = null,
- var pipTaskId: Int? = null,
) {
fun deepCopy(): Desk =
Desk(
@@ -94,7 +92,6 @@ class DesktopRepository(
freeformTasksInZOrder = ArrayList(freeformTasksInZOrder),
fullImmersiveTaskId = fullImmersiveTaskId,
topTransparentFullscreenTaskId = topTransparentFullscreenTaskId,
- pipTaskId = pipTaskId,
)
// TODO: b/362720497 - remove when multi-desktops is enabled where instances aren't
@@ -107,7 +104,6 @@ class DesktopRepository(
freeformTasksInZOrder.clear()
fullImmersiveTaskId = null
topTransparentFullscreenTaskId = null
- pipTaskId = null
}
}
@@ -127,9 +123,6 @@ class DesktopRepository(
/* Tracks last bounds of task before toggled to immersive state. */
private val boundsBeforeFullImmersiveByTaskId = SparseArray<Rect>()
- /* Callback for when a pending PiP transition has been aborted. */
- private var onPipAbortedCallback: ((Int, Int) -> Unit)? = null
-
private var desktopGestureExclusionListener: Consumer<Region>? = null
private var desktopGestureExclusionExecutor: Executor? = null
@@ -611,57 +604,6 @@ class DesktopRepository(
}
/**
- * Set whether the given task is the Desktop-entered PiP task in this display's active desk.
- *
- * TODO: b/389960283 - add explicit [deskId] argument.
- */
- fun setTaskInPip(displayId: Int, taskId: Int, enterPip: Boolean) {
- val activeDesk =
- desktopData.getActiveDesk(displayId)
- ?: error("Expected active desk in display: $displayId")
- if (enterPip) {
- activeDesk.pipTaskId = taskId
- } else {
- activeDesk.pipTaskId =
- if (activeDesk.pipTaskId == taskId) null
- else {
- logW(
- "setTaskInPip: taskId=%d did not match saved taskId=%d",
- taskId,
- activeDesk.pipTaskId,
- )
- activeDesk.pipTaskId
- }
- }
- }
-
- /**
- * Returns whether the given task is the Desktop-entered PiP task in this display's active desk.
- *
- * TODO: b/389960283 - add explicit [deskId] argument.
- */
- fun isTaskMinimizedPipInDisplay(displayId: Int, taskId: Int): Boolean =
- desktopData.getActiveDesk(displayId)?.pipTaskId == taskId
-
- /**
- * Saves callback to handle a pending PiP transition being aborted.
- *
- * TODO: b/389960283 - add explicit [deskId] argument.
- */
- fun setOnPipAbortedCallback(callbackIfPipAborted: ((displayId: Int, pipTaskId: Int) -> Unit)?) {
- onPipAbortedCallback = callbackIfPipAborted
- }
-
- /**
- * Invokes callback to handle a pending PiP transition with the given task id being aborted.
- *
- * TODO: b/389960283 - add explicit [deskId] argument.
- */
- fun onPipAborted(displayId: Int, pipTaskId: Int) {
- onPipAbortedCallback?.invoke(displayId, pipTaskId)
- }
-
- /**
* Set whether the given task is the full-immersive task in this display's active desk.
*
* TODO: b/389960283 - consider forcing callers to use [setTaskInFullImmersiveStateInDesk] with
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 5e9cd9016d92..50f5beb4166a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -214,6 +214,7 @@ class DesktopTasksController(
private val overviewToDesktopTransitionObserver: OverviewToDesktopTransitionObserver,
private val desksOrganizer: DesksOrganizer,
private val desksTransitionObserver: DesksTransitionObserver,
+ private val desktopPipTransitionObserver: Optional<DesktopPipTransitionObserver>,
private val userProfileContexts: UserProfileContexts,
private val desktopModeCompatPolicy: DesktopModeCompatPolicy,
private val dragToDisplayTransitionHandler: DragToDisplayTransitionHandler,
@@ -793,10 +794,31 @@ class DesktopTasksController(
fun minimizeTask(taskInfo: RunningTaskInfo, minimizeReason: MinimizeReason) {
val wct = WindowContainerTransaction()
-
+ val taskId = taskInfo.taskId
+ val displayId = taskInfo.displayId
+ val deskId =
+ taskRepository.getDeskIdForTask(taskInfo.taskId)
+ ?: if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
+ logW("minimizeTask: desk not found for task: ${taskInfo.taskId}")
+ return
+ } else {
+ getDefaultDeskId(taskInfo.displayId)
+ }
+ val isLastTask =
+ if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
+ taskRepository.isOnlyVisibleNonClosingTaskInDesk(
+ taskId = taskId,
+ deskId = checkNotNull(deskId) { "Expected non-null deskId" },
+ displayId = displayId,
+ )
+ } else {
+ taskRepository.isOnlyVisibleNonClosingTask(taskId = taskId, displayId = displayId)
+ }
val isMinimizingToPip =
DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue &&
- (taskInfo.pictureInPictureParams?.isAutoEnterEnabled() ?: false)
+ desktopPipTransitionObserver.isPresent &&
+ (taskInfo.pictureInPictureParams?.isAutoEnterEnabled ?: false)
+
// If task is going to PiP, start a PiP transition instead of a minimize transition
if (isMinimizingToPip) {
val requestInfo =
@@ -810,75 +832,60 @@ class DesktopTasksController(
)
val requestRes = transitions.dispatchRequest(Binder(), requestInfo, /* skip= */ null)
wct.merge(requestRes.second, true)
- freeformTaskTransitionStarter.startPipTransition(wct)
- taskRepository.setTaskInPip(taskInfo.displayId, taskInfo.taskId, enterPip = true)
- taskRepository.setOnPipAbortedCallback { displayId, taskId ->
- minimizeTaskInner(shellTaskOrganizer.getRunningTaskInfo(taskId)!!, minimizeReason)
- taskRepository.setTaskInPip(displayId, taskId, enterPip = false)
- }
- return
- }
-
- minimizeTaskInner(taskInfo, minimizeReason)
- }
- private fun minimizeTaskInner(taskInfo: RunningTaskInfo, minimizeReason: MinimizeReason) {
- val taskId = taskInfo.taskId
- val deskId = taskRepository.getDeskIdForTask(taskInfo.taskId)
- if (deskId == null && DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
- logW("minimizeTaskInner: desk not found for task: ${taskInfo.taskId}")
- return
- }
- val displayId = taskInfo.displayId
- val wct = WindowContainerTransaction()
-
- snapEventHandler.removeTaskIfTiled(displayId, taskId)
- val willExitDesktop = willExitDesktop(taskId, displayId, forceExitDesktop = false)
- val desktopExitRunnable =
- performDesktopExitCleanUp(
- wct = wct,
- deskId = deskId,
- displayId = displayId,
- willExitDesktop = willExitDesktop,
- )
- // Notify immersive handler as it might need to exit immersive state.
- val exitResult =
- desktopImmersiveController.exitImmersiveIfApplicable(
- wct = wct,
- taskInfo = taskInfo,
- reason = DesktopImmersiveController.ExitReason.MINIMIZED,
- )
- if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
- desksOrganizer.minimizeTask(
- wct = wct,
- deskId = checkNotNull(deskId) { "Expected non-null deskId" },
- task = taskInfo,
+ desktopPipTransitionObserver.get().addPendingPipTransition(
+ DesktopPipTransitionObserver.PendingPipTransition(
+ token = freeformTaskTransitionStarter.startPipTransition(wct),
+ taskId = taskInfo.taskId,
+ onSuccess = {
+ onDesktopTaskEnteredPip(
+ taskId = taskId,
+ deskId = deskId,
+ displayId = taskInfo.displayId,
+ taskIsLastVisibleTaskBeforePip = isLastTask,
+ )
+ },
+ )
)
} else {
- wct.reorder(taskInfo.token, /* onTop= */ false)
- }
- val isLastTask =
+ snapEventHandler.removeTaskIfTiled(displayId, taskId)
+ val willExitDesktop = willExitDesktop(taskId, displayId, forceExitDesktop = false)
+ val desktopExitRunnable =
+ performDesktopExitCleanUp(
+ wct = wct,
+ deskId = deskId,
+ displayId = displayId,
+ willExitDesktop = willExitDesktop,
+ )
+ // Notify immersive handler as it might need to exit immersive state.
+ val exitResult =
+ desktopImmersiveController.exitImmersiveIfApplicable(
+ wct = wct,
+ taskInfo = taskInfo,
+ reason = DesktopImmersiveController.ExitReason.MINIMIZED,
+ )
if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
- taskRepository.isOnlyVisibleNonClosingTaskInDesk(
- taskId = taskId,
+ desksOrganizer.minimizeTask(
+ wct = wct,
deskId = checkNotNull(deskId) { "Expected non-null deskId" },
- displayId = displayId,
+ task = taskInfo,
)
} else {
- taskRepository.isOnlyVisibleNonClosingTask(taskId = taskId, displayId = displayId)
+ wct.reorder(taskInfo.token, /* onTop= */ false)
}
- val transition =
- freeformTaskTransitionStarter.startMinimizedModeTransition(wct, taskId, isLastTask)
- desktopTasksLimiter.ifPresent {
- it.addPendingMinimizeChange(
- transition = transition,
- displayId = displayId,
- taskId = taskId,
- minimizeReason = minimizeReason,
- )
+ val transition =
+ freeformTaskTransitionStarter.startMinimizedModeTransition(wct, taskId, isLastTask)
+ desktopTasksLimiter.ifPresent {
+ it.addPendingMinimizeChange(
+ transition = transition,
+ displayId = displayId,
+ taskId = taskId,
+ minimizeReason = minimizeReason,
+ )
+ }
+ exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
+ desktopExitRunnable?.invoke(transition)
}
- exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
- desktopExitRunnable?.invoke(transition)
}
/** Move a task with given `taskId` to fullscreen */
@@ -1845,7 +1852,11 @@ class DesktopTasksController(
displayId: Int,
forceExitDesktop: Boolean,
): Boolean {
- if (forceExitDesktop && DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
+ if (
+ forceExitDesktop &&
+ (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue ||
+ DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue)
+ ) {
// |forceExitDesktop| is true when the callers knows we'll exit desktop, such as when
// explicitly going fullscreen, so there's no point in checking the desktop state.
return true
@@ -1862,6 +1873,33 @@ class DesktopTasksController(
return true
}
+ /** Potentially perform Desktop cleanup after a task successfully enters PiP. */
+ @VisibleForTesting
+ fun onDesktopTaskEnteredPip(
+ taskId: Int,
+ deskId: Int,
+ displayId: Int,
+ taskIsLastVisibleTaskBeforePip: Boolean,
+ ) {
+ if (
+ !willExitDesktop(taskId, displayId, forceExitDesktop = taskIsLastVisibleTaskBeforePip)
+ ) {
+ return
+ }
+
+ val wct = WindowContainerTransaction()
+ val desktopExitRunnable =
+ performDesktopExitCleanUp(
+ wct = wct,
+ deskId = deskId,
+ displayId = displayId,
+ willExitDesktop = true,
+ )
+
+ val transition = transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null)
+ desktopExitRunnable?.invoke(transition)
+ }
+
private fun performDesktopExitCleanupIfNeeded(
taskId: Int,
deskId: Int? = null,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
index 2ec6105e5af9..df4d18f8c803 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
@@ -23,7 +23,6 @@ import android.os.IBinder
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CLOSE
import android.view.WindowManager.TRANSIT_OPEN
-import android.view.WindowManager.TRANSIT_PIP
import android.view.WindowManager.TRANSIT_TO_BACK
import android.window.DesktopExperienceFlags
import android.window.DesktopModeFlags
@@ -43,8 +42,7 @@ import com.android.wm.shell.shared.TransitionUtil.isOpeningMode
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
-import com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP
-import com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP
+import java.util.Optional
/**
* A [Transitions.TransitionObserver] that observes shell transitions and updates the
@@ -57,6 +55,7 @@ class DesktopTasksTransitionObserver(
private val transitions: Transitions,
private val shellTaskOrganizer: ShellTaskOrganizer,
private val desktopMixedTransitionHandler: DesktopMixedTransitionHandler,
+ private val desktopPipTransitionObserver: Optional<DesktopPipTransitionObserver>,
private val backAnimationController: BackAnimationController,
private val desktopWallpaperActivityTokenProvider: DesktopWallpaperActivityTokenProvider,
shellInit: ShellInit,
@@ -65,8 +64,6 @@ class DesktopTasksTransitionObserver(
data class CloseWallpaperTransition(val transition: IBinder, val displayId: Int)
private var transitionToCloseWallpaper: CloseWallpaperTransition? = null
- /* Pending PiP transition and its associated display id and task id. */
- private var pendingPipTransitionAndPipTask: Triple<IBinder, Int, Int>? = null
private var currentProfileId: Int
init {
@@ -100,33 +97,7 @@ class DesktopTasksTransitionObserver(
removeTaskIfNeeded(info)
}
removeWallpaperOnLastTaskClosingIfNeeded(transition, info)
-
- val desktopRepository = desktopUserRepositories.getProfile(currentProfileId)
- info.changes.forEach { change ->
- change.taskInfo?.let { taskInfo ->
- if (
- DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue &&
- desktopRepository.isTaskMinimizedPipInDisplay(
- taskInfo.displayId,
- taskInfo.taskId,
- )
- ) {
- when (info.type) {
- TRANSIT_PIP ->
- pendingPipTransitionAndPipTask =
- Triple(transition, taskInfo.displayId, taskInfo.taskId)
-
- TRANSIT_EXIT_PIP,
- TRANSIT_REMOVE_PIP ->
- desktopRepository.setTaskInPip(
- taskInfo.displayId,
- taskInfo.taskId,
- enterPip = false,
- )
- }
- }
- }
- }
+ desktopPipTransitionObserver.ifPresent { it.onTransitionReady(transition, info) }
}
private fun removeTaskIfNeeded(info: TransitionInfo) {
@@ -301,18 +272,6 @@ class DesktopTasksTransitionObserver(
}
}
transitionToCloseWallpaper = null
- } else if (pendingPipTransitionAndPipTask?.first == transition) {
- val desktopRepository = desktopUserRepositories.getProfile(currentProfileId)
- if (aborted) {
- pendingPipTransitionAndPipTask?.let {
- desktopRepository.onPipAborted(
- /*displayId=*/ it.second,
- /* taskId=*/ it.third,
- )
- }
- }
- desktopRepository.setOnPipAbortedCallback(null)
- pendingPipTransitionAndPipTask = null
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopPipTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopPipTransitionObserverTest.kt
new file mode 100644
index 000000000000..ef394d81cc57
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopPipTransitionObserverTest.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
+import android.os.Binder
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import android.testing.AndroidTestingRunner
+import android.view.WindowManager.TRANSIT_PIP
+import android.window.TransitionInfo
+import androidx.test.filters.SmallTest
+import com.android.window.flags.Flags
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestRunningTaskInfoBuilder
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+
+/**
+ * Tests for [DesktopPipTransitionObserver].
+ *
+ * Build/Install/Run: atest WMShellUnitTests:DesktopPipTransitionObserverTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DesktopPipTransitionObserverTest : ShellTestCase() {
+
+ @JvmField @Rule val setFlagsRule = SetFlagsRule()
+
+ private lateinit var observer: DesktopPipTransitionObserver
+
+ private val transition = Binder()
+ private var onSuccessInvokedCount = 0
+
+ @Before
+ fun setUp() {
+ observer = DesktopPipTransitionObserver()
+
+ onSuccessInvokedCount = 0
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
+ fun onTransitionReady_taskInPinnedWindowingMode_onSuccessInvoked() {
+ val taskId = 1
+ val pipTransition = createPendingPipTransition(taskId)
+ val successfulChange = createChange(taskId, WINDOWING_MODE_PINNED)
+ observer.addPendingPipTransition(pipTransition)
+
+ observer.onTransitionReady(
+ transition = transition,
+ info = TransitionInfo(
+ TRANSIT_PIP, /* flags= */
+ 0
+ ).apply { addChange(successfulChange) },
+ )
+
+ assertThat(onSuccessInvokedCount).isEqualTo(1)
+ }
+
+ private fun createPendingPipTransition(
+ taskId: Int
+ ): DesktopPipTransitionObserver.PendingPipTransition {
+ return DesktopPipTransitionObserver.PendingPipTransition(
+ token = transition,
+ taskId = taskId,
+ onSuccess = { onSuccessInvokedCount += 1 },
+ )
+ }
+
+ private fun createChange(taskId: Int, windowingMode: Int): TransitionInfo.Change {
+ return TransitionInfo.Change(mock(), mock()).apply {
+ taskInfo =
+ TestRunningTaskInfoBuilder()
+ .setTaskId(taskId)
+ .setWindowingMode(windowingMode)
+ .build()
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
index c455205f6411..a10aeca95bce 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
@@ -1237,36 +1237,6 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() {
}
@Test
- fun setTaskInPip_savedAsMinimizedPipInDisplay() {
- assertThat(repo.isTaskMinimizedPipInDisplay(DEFAULT_DESKTOP_ID, taskId = 1)).isFalse()
-
- repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = true)
-
- assertThat(repo.isTaskMinimizedPipInDisplay(DEFAULT_DESKTOP_ID, taskId = 1)).isTrue()
- }
-
- @Test
- fun removeTaskInPip_removedAsMinimizedPipInDisplay() {
- repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = true)
- assertThat(repo.isTaskMinimizedPipInDisplay(DEFAULT_DESKTOP_ID, taskId = 1)).isTrue()
-
- repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = false)
-
- assertThat(repo.isTaskMinimizedPipInDisplay(DEFAULT_DESKTOP_ID, taskId = 1)).isFalse()
- }
-
- @Test
- fun setTaskInPip_multipleDisplays_bothAreInPip() {
- repo.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
- repo.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
- repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = true)
- repo.setTaskInPip(SECOND_DISPLAY, taskId = 2, enterPip = true)
-
- assertThat(repo.isTaskMinimizedPipInDisplay(DEFAULT_DESKTOP_ID, taskId = 1)).isTrue()
- assertThat(repo.isTaskMinimizedPipInDisplay(SECOND_DISPLAY, taskId = 2)).isTrue()
- }
-
- @Test
@DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun addTask_deskDoesNotExists_createsDesk() {
repo.addTask(displayId = 999, taskId = 6, isVisible = true)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 8d8140c47c1a..7efcd4fc3c8f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -265,6 +265,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Mock private lateinit var desksOrganizer: DesksOrganizer
@Mock private lateinit var userProfileContexts: UserProfileContexts
@Mock private lateinit var desksTransitionsObserver: DesksTransitionObserver
+ @Mock private lateinit var desktopPipTransitionObserver: DesktopPipTransitionObserver
@Mock private lateinit var packageManager: PackageManager
@Mock private lateinit var mockDisplayContext: Context
@Mock private lateinit var dragToDisplayTransitionHandler: DragToDisplayTransitionHandler
@@ -393,6 +394,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(wallpaperToken)
whenever(userProfileContexts[anyInt()]).thenReturn(context)
whenever(userProfileContexts.getOrCreate(anyInt())).thenReturn(context)
+ whenever(freeformTaskTransitionStarter.startPipTransition(any())).thenReturn(Binder())
controller = createController()
controller.setSplitScreenController(splitScreenController)
@@ -457,6 +459,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
overviewToDesktopTransitionObserver,
desksOrganizer,
desksTransitionsObserver,
+ Optional.of(desktopPipTransitionObserver),
userProfileContexts,
desktopModeCompatPolicy,
dragToDisplayTransitionHandler,
@@ -3502,6 +3505,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
fun onPipTaskMinimize_autoEnterEnabled_startPipTransition() {
val task = setUpPipTask(autoEnterEnabled = true)
val handler = mock(TransitionHandler::class.java)
@@ -3516,6 +3520,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
fun onPipTaskMinimize_autoEnterDisabled_startMinimizeTransition() {
val task = setUpPipTask(autoEnterEnabled = false)
whenever(
@@ -3535,6 +3540,90 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP,
+ )
+ fun onDesktopTaskEnteredPip_pipIsLastTask_removesWallpaper() {
+ val task = setUpPipTask(autoEnterEnabled = true)
+
+ controller.onDesktopTaskEnteredPip(
+ taskId = task.taskId,
+ deskId = DEFAULT_DISPLAY,
+ displayId = task.displayId,
+ taskIsLastVisibleTaskBeforePip = true,
+ )
+
+ // Wallpaper is moved to the back
+ val wct = getLatestTransition()
+ wct.assertReorder(wallpaperToken, /* toTop= */ false)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun onDesktopTaskEnteredPip_pipIsLastTask_deactivatesDesk() {
+ val deskId = DEFAULT_DISPLAY
+ val task = setUpPipTask(autoEnterEnabled = true, deskId = deskId)
+ val transition = Binder()
+ whenever(transitions.startTransition(any(), any(), anyOrNull())).thenReturn(transition)
+
+ controller.onDesktopTaskEnteredPip(
+ taskId = task.taskId,
+ deskId = deskId,
+ displayId = task.displayId,
+ taskIsLastVisibleTaskBeforePip = true,
+ )
+
+ verify(desksOrganizer).deactivateDesk(any(), eq(deskId))
+ verify(desksTransitionsObserver)
+ .addPendingTransition(DeskTransition.DeactivateDesk(transition, deskId))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
+ fun onDesktopTaskEnteredPip_pipIsLastTask_launchesHome() {
+ val task = setUpPipTask(autoEnterEnabled = true)
+
+ controller.onDesktopTaskEnteredPip(
+ taskId = task.taskId,
+ deskId = DEFAULT_DISPLAY,
+ displayId = task.displayId,
+ taskIsLastVisibleTaskBeforePip = true,
+ )
+
+ val wct = getLatestTransition()
+ wct.assertPendingIntent(launchHomeIntent(DEFAULT_DISPLAY))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
+ fun onDesktopTaskEnteredPip_pipIsNotLastTask_doesntExitDesktopMode() {
+ val task = setUpPipTask(autoEnterEnabled = true)
+ val deskId = DEFAULT_DISPLAY
+ setUpFreeformTask(deskId = deskId) // launch another freeform task
+ val transition = Binder()
+ whenever(transitions.startTransition(any(), any(), anyOrNull())).thenReturn(transition)
+
+ controller.onDesktopTaskEnteredPip(
+ taskId = task.taskId,
+ deskId = deskId,
+ displayId = task.displayId,
+ taskIsLastVisibleTaskBeforePip = false,
+ )
+
+ // No transition to exit Desktop mode is started
+ verifyWCTNotExecuted()
+ verify(desktopModeEnterExitTransitionListener, never())
+ .onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION)
+ verify(desksOrganizer, never()).deactivateDesk(any(), eq(deskId))
+ verify(desksTransitionsObserver, never())
+ .addPendingTransition(DeskTransition.DeactivateDesk(transition, deskId))
+ }
+
+ @Test
fun onDesktopWindowMinimize_singleActiveTask_noWallpaperActivityToken_doesntRemoveWallpaper() {
val task = setUpFreeformTask(active = true)
val transition = Binder()
@@ -7656,8 +7745,12 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
return task
}
- private fun setUpPipTask(autoEnterEnabled: Boolean): RunningTaskInfo =
- setUpFreeformTask().apply {
+ private fun setUpPipTask(
+ autoEnterEnabled: Boolean,
+ displayId: Int = DEFAULT_DISPLAY,
+ deskId: Int = DEFAULT_DISPLAY,
+ ): RunningTaskInfo =
+ setUpFreeformTask(displayId = displayId, deskId = deskId).apply {
pictureInPictureParams =
PictureInPictureParams.Builder().setAutoEnterEnabled(autoEnterEnabled).build()
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
index ac9a509ac6cb..5ef1ace7873d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
@@ -22,7 +22,6 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.content.ComponentName
import android.content.Context
import android.content.Intent
-import android.os.Binder
import android.os.IBinder
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
@@ -30,7 +29,6 @@ import android.view.Display.DEFAULT_DISPLAY
import android.view.WindowManager
import android.view.WindowManager.TRANSIT_CLOSE
import android.view.WindowManager.TRANSIT_OPEN
-import android.view.WindowManager.TRANSIT_PIP
import android.view.WindowManager.TRANSIT_TO_BACK
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.IWindowContainerToken
@@ -41,7 +39,6 @@ import android.window.WindowContainerTransaction
import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER
import com.android.modules.utils.testing.ExtendedMockitoRule
import com.android.window.flags.Flags
-import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP
import com.android.wm.shell.MockToken
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.back.BackAnimationController
@@ -51,10 +48,9 @@ import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpape
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
-import com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP
-import com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
+import java.util.Optional
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -90,6 +86,7 @@ class DesktopTasksTransitionObserverTest {
private val userRepositories = mock<DesktopUserRepositories>()
private val taskRepository = mock<DesktopRepository>()
private val mixedHandler = mock<DesktopMixedTransitionHandler>()
+ private val pipTransitionObserver = mock<DesktopPipTransitionObserver>()
private val backAnimationController = mock<BackAnimationController>()
private val desktopWallpaperActivityTokenProvider =
mock<DesktopWallpaperActivityTokenProvider>()
@@ -114,6 +111,7 @@ class DesktopTasksTransitionObserverTest {
transitions,
shellTaskOrganizer,
mixedHandler,
+ Optional.of(pipTransitionObserver),
backAnimationController,
desktopWallpaperActivityTokenProvider,
shellInit,
@@ -393,56 +391,6 @@ class DesktopTasksTransitionObserverTest {
verify(desktopWallpaperActivityTokenProvider).removeToken(wallpaperTask.displayId)
}
- @Test
- @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
- fun pendingPipTransitionAborted_taskRepositoryOnPipAbortedInvoked() {
- val task = createTaskInfo(1, WINDOWING_MODE_FREEFORM)
- val pipTransition = Binder()
- whenever(taskRepository.isTaskMinimizedPipInDisplay(any(), any())).thenReturn(true)
-
- transitionObserver.onTransitionReady(
- transition = pipTransition,
- info = createOpenChangeTransition(task, type = TRANSIT_PIP),
- startTransaction = mock(),
- finishTransaction = mock(),
- )
- transitionObserver.onTransitionFinished(transition = pipTransition, aborted = true)
-
- verify(taskRepository).onPipAborted(task.displayId, task.taskId)
- }
-
- @Test
- @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
- fun exitPipTransition_taskRepositoryClearTaskInPip() {
- val task = createTaskInfo(1, WINDOWING_MODE_FREEFORM)
- whenever(taskRepository.isTaskMinimizedPipInDisplay(any(), any())).thenReturn(true)
-
- transitionObserver.onTransitionReady(
- transition = mock(),
- info = createOpenChangeTransition(task, type = TRANSIT_EXIT_PIP),
- startTransaction = mock(),
- finishTransaction = mock(),
- )
-
- verify(taskRepository).setTaskInPip(task.displayId, task.taskId, enterPip = false)
- }
-
- @Test
- @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
- fun removePipTransition_taskRepositoryClearTaskInPip() {
- val task = createTaskInfo(1, WINDOWING_MODE_FREEFORM)
- whenever(taskRepository.isTaskMinimizedPipInDisplay(any(), any())).thenReturn(true)
-
- transitionObserver.onTransitionReady(
- transition = mock(),
- info = createOpenChangeTransition(task, type = TRANSIT_REMOVE_PIP),
- startTransaction = mock(),
- finishTransaction = mock(),
- )
-
- verify(taskRepository).setTaskInPip(task.displayId, task.taskId, enterPip = false)
- }
-
private fun createBackNavigationTransition(
task: RunningTaskInfo?,
type: Int = TRANSIT_TO_BACK,
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index ab18612355f0..4693377654f8 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -213,18 +213,6 @@ flag {
}
flag {
- name: "notification_undo_guts_on_config_changed"
- namespace: "systemui"
- description: "Fixes a bug where a theme or font change while notification guts were open"
- " (e.g. the snooze options or notification info) would show an empty notification by"
- " closing the guts and undoing changes."
- bug: "379267630"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "pss_app_selector_recents_split_screen"
namespace: "systemui"
description: "Allows recent apps selected for partial screenshare to be launched in split screen mode"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java
index af67a04d2f2a..2d4063b2f667 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java
@@ -27,7 +27,6 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import android.platform.test.annotations.EnableFlags;
import android.provider.Settings;
import android.testing.TestableResources;
import android.view.View;
@@ -39,7 +38,6 @@ import androidx.test.annotation.UiThreadTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.AnimatorTestRule;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
@@ -93,7 +91,6 @@ public class NotificationSnoozeTest extends SysuiTestCase {
}
@Test
- @EnableFlags(Flags.FLAG_NOTIFICATION_UNDO_GUTS_ON_CONFIG_CHANGED)
public void closeControls_withoutSave_performsUndo() {
ArrayList<SnoozeOption> options = mUnderTest.getDefaultSnoozeOptions();
mUnderTest.mSelectedOption = options.getFirst();
diff --git a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayCoreStartable.kt
index bc15bbb5e57d..263ef09ea767 100644
--- a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayCoreStartable.kt
@@ -20,6 +20,8 @@ import android.content.Context
import android.hardware.devicestate.DeviceStateManager
import android.hardware.devicestate.feature.flags.Flags
import androidx.annotation.VisibleForTesting
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -28,8 +30,11 @@ import com.android.systemui.statusbar.phone.SystemUIDialog
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
/**
* Provides a {@link com.android.systemui.statusbar.phone.SystemUIDialog} to be shown on the inner
@@ -46,6 +51,7 @@ internal constructor(
private val rearDisplayStateInteractor: RearDisplayStateInteractor,
private val rearDisplayInnerDialogDelegateFactory: RearDisplayInnerDialogDelegate.Factory,
@Application private val scope: CoroutineScope,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
) : CoreStartable, AutoCloseable {
companion object {
@@ -53,6 +59,16 @@ internal constructor(
}
@VisibleForTesting var stateChangeListener: Job? = null
+ private val keyguardVisible = MutableStateFlow(false)
+ private val keyguardVisibleFlow = keyguardVisible.asStateFlow()
+
+ @VisibleForTesting
+ val keyguardCallback =
+ object : KeyguardUpdateMonitorCallback() {
+ override fun onKeyguardVisibilityChanged(visible: Boolean) {
+ keyguardVisible.value = visible
+ }
+ }
override fun close() {
stateChangeListener?.cancel()
@@ -62,28 +78,39 @@ internal constructor(
if (Flags.deviceStateRdmV2()) {
var dialog: SystemUIDialog? = null
+ keyguardUpdateMonitor.registerCallback(keyguardCallback)
+
stateChangeListener =
- rearDisplayStateInteractor.state
- .map {
- when (it) {
- is RearDisplayStateInteractor.State.Enabled -> {
- val rearDisplayContext =
- context.createDisplayContext(it.innerDisplay)
- val delegate =
- rearDisplayInnerDialogDelegateFactory.create(
- rearDisplayContext,
- deviceStateManager::cancelStateRequest,
- )
- dialog = delegate.createDialog().apply { show() }
- }
+ scope.launch {
+ combine(rearDisplayStateInteractor.state, keyguardVisibleFlow) {
+ rearDisplayState,
+ keyguardVisible ->
+ Pair(rearDisplayState, keyguardVisible)
+ }
+ .collectLatest { (rearDisplayState, keyguardVisible) ->
+ when (rearDisplayState) {
+ is RearDisplayStateInteractor.State.Enabled -> {
+ if (!keyguardVisible) {
+ val rearDisplayContext =
+ context.createDisplayContext(
+ rearDisplayState.innerDisplay
+ )
+ val delegate =
+ rearDisplayInnerDialogDelegateFactory.create(
+ rearDisplayContext,
+ deviceStateManager::cancelStateRequest,
+ )
+ dialog = delegate.createDialog().apply { show() }
+ }
+ }
- is RearDisplayStateInteractor.State.Disabled -> {
- dialog?.dismiss()
- dialog = null
+ is RearDisplayStateInteractor.State.Disabled -> {
+ dialog?.dismiss()
+ dialog = null
+ }
}
}
- }
- .launchIn(scope)
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS b/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS
index 6cebcd98a0ba..6d3c12d139db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS
@@ -18,10 +18,10 @@ per-file *Keyguard* = file:../keyguard/OWNERS
per-file *Notification* = file:notification/OWNERS
# Files that control blur effects on shade
per-file *NotificationShadeDepth* = set noparent
-per-file *NotificationShadeDepth* = shanh@google.com, rahulbanerjee@google.com
+per-file *NotificationShadeDepth* = shanh@google.com, rahulbanerjee@google.com, tracyzhou@google.com
per-file *NotificationShadeDepth* = file:../keyguard/OWNERS
per-file *Blur* = set noparent
-per-file *Blur* = shanh@google.com, rahulbanerjee@google.com
+per-file *Blur* = shanh@google.com, rahulbanerjee@google.com, tracyzhou@google.com
# Not setting noparent here, since *Mode* matches many other classes (e.g., *ViewModel*)
per-file *Mode* = file:notification/OWNERS
per-file *SmartReply* = set noparent
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
index a0a86710b4ba..f43767d3effb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
@@ -22,7 +22,6 @@ import com.android.internal.widget.MessagingGroup
import com.android.internal.widget.MessagingMessage
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
-import com.android.systemui.Flags
import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener
@@ -147,9 +146,7 @@ internal constructor(
traceSection("updateNotifOnUiModeChanged") {
mPipeline?.allNotifs?.forEach { entry ->
entry.row?.onUiModeChanged()
- if (Flags.notificationUndoGutsOnConfigChanged()) {
- mGutsManager.closeAndUndoGuts()
- }
+ mGutsManager.closeAndUndoGuts()
}
}
}
@@ -158,16 +155,7 @@ internal constructor(
colorUpdateLogger.logEvent("VCC.updateNotificationsOnDensityOrFontScaleChanged()")
mPipeline?.allNotifs?.forEach { entry ->
entry.onDensityOrFontScaleChanged()
- if (Flags.notificationUndoGutsOnConfigChanged()) {
- mGutsManager.closeAndUndoGuts()
- } else {
- // This property actually gets reset when the guts are re-inflated, so we're never
- // actually calling onDensityOrFontScaleChanged below.
- val exposedGuts = entry.areGutsExposed()
- if (exposedGuts) {
- mGutsManager.onDensityOrFontScaleChanged(entry)
- }
- }
+ mGutsManager.closeAndUndoGuts()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 8262f4cdc16b..e9993ae31514 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -1593,10 +1593,13 @@ public class NotificationContentView extends FrameLayout implements Notification
return;
}
ImageView bubbleButton = layout.findViewById(com.android.internal.R.id.bubble_button);
- View actionContainer = layout.findViewById(com.android.internal.R.id.actions_container);
- ViewGroup actionListMarginTarget = layout.findViewById(
- com.android.internal.R.id.notification_action_list_margin_target);
- if (bubbleButton == null || actionContainer == null) {
+ // With the new design, the actions_container should always be visible to act as padding
+ // when there are no actions. We're making its child visible/invisible instead.
+ View actionsContainerForVisibilityChange = layout.findViewById(
+ notificationsRedesignTemplates()
+ ? com.android.internal.R.id.actions_container_layout
+ : com.android.internal.R.id.actions_container);
+ if (bubbleButton == null || actionsContainerForVisibilityChange == null) {
return;
}
@@ -1614,17 +1617,14 @@ public class NotificationContentView extends FrameLayout implements Notification
bubbleButton.setImageDrawable(d);
bubbleButton.setOnClickListener(mContainingNotification.getBubbleClickListener());
bubbleButton.setVisibility(VISIBLE);
- actionContainer.setVisibility(VISIBLE);
- // Set notification_action_list_margin_target's bottom margin to 0 when showing bubble
- if (actionListMarginTarget != null) {
- removeBottomMargin(actionListMarginTarget);
- }
- if (notificationsRedesignTemplates()) {
- // Similar treatment for smart reply margin
- LinearLayout smartReplyContainer = layout.findViewById(
- com.android.internal.R.id.smart_reply_container);
- if (smartReplyContainer != null) {
- removeBottomMargin(smartReplyContainer);
+ actionsContainerForVisibilityChange.setVisibility(VISIBLE);
+ if (!notificationsRedesignTemplates()) {
+ // Set notification_action_list_margin_target's bottom margin to 0 when showing
+ // bubble
+ ViewGroup actionListMarginTarget = layout.findViewById(
+ com.android.internal.R.id.notification_action_list_margin_target);
+ if (actionListMarginTarget != null) {
+ removeBottomMargin(actionListMarginTarget);
}
}
} else {
@@ -1665,8 +1665,13 @@ public class NotificationContentView extends FrameLayout implements Notification
return;
}
ImageView snoozeButton = layout.findViewById(com.android.internal.R.id.snooze_button);
- View actionContainer = layout.findViewById(com.android.internal.R.id.actions_container);
- if (snoozeButton == null || actionContainer == null) {
+ // With the new design, the actions_container should always be visible to act as padding
+ // when there are no actions. We're making its child visible/invisible instead.
+ View actionsContainerForVisibilityChange = layout.findViewById(
+ notificationsRedesignTemplates()
+ ? com.android.internal.R.id.actions_container_layout
+ : com.android.internal.R.id.actions_container);
+ if (snoozeButton == null || actionsContainerForVisibilityChange == null) {
return;
}
// Notification.Builder can 'disable' the snooze button to prevent it from being shown here
@@ -1692,7 +1697,7 @@ public class NotificationContentView extends FrameLayout implements Notification
snoozeButton.setOnClickListener(
mContainingNotification.getSnoozeClickListener(snoozeMenuItem));
snoozeButton.setVisibility(VISIBLE);
- actionContainer.setVisibility(VISIBLE);
+ actionsContainerForVisibilityChange.setVisibility(VISIBLE);
}
private void applySmartReplyView() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index caa1d28cc914..cdb78d99538b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -47,7 +47,6 @@ import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.statusbar.IStatusBarService;
import com.android.settingslib.notification.ConversationIconFactory;
import com.android.systemui.CoreStartable;
-import com.android.systemui.Flags;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
@@ -235,15 +234,6 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta
}
}
- public void onDensityOrFontScaleChanged(NotificationEntry entry) {
- if (!Flags.notificationUndoGutsOnConfigChanged()) {
- Log.wtf(TAG, "onDensityOrFontScaleChanged should not be called if"
- + " notificationUndoGutsOnConfigChanged is off");
- }
- setExposedGuts(entry.getGuts());
- bindGuts(entry.getRow());
- }
-
/**
* Sends an intent to open the notification settings for a particular package and optional
* channel.
@@ -295,11 +285,6 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta
mNotificationActivityStarter.startNotificationGutsIntent(intent, uid, row);
}
- private boolean bindGuts(final ExpandableNotificationRow row) {
- row.ensureGutsInflated();
- return bindGuts(row, mGutsMenuItem);
- }
-
@VisibleForTesting
protected boolean bindGuts(final ExpandableNotificationRow row,
NotificationMenuRowPlugin.MenuItem item) {
@@ -611,6 +596,7 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta
return mNotificationGutsExposed;
}
+ @VisibleForTesting
public void setExposedGuts(NotificationGuts guts) {
mNotificationGutsExposed = guts;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
index 83897f5bc3a7..cec0ae696b26 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
@@ -51,7 +51,6 @@ import com.android.app.animation.Interpolators;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.Flags;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
import com.android.systemui.res.R;
@@ -477,7 +476,7 @@ public class NotificationSnooze extends LinearLayout
@Override
public boolean handleCloseControls(boolean save, boolean force) {
- if (Flags.notificationUndoGutsOnConfigChanged() && !save) {
+ if (!save) {
// Undo changes and let the guts handle closing the view
mSelectedOption = null;
showSnoozeOptions(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayCoreStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayCoreStartableTest.kt
index cf54df8565d3..997cf417fe10 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayCoreStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayCoreStartableTest.kt
@@ -22,6 +22,7 @@ import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.view.Display
import androidx.test.filters.SmallTest
+import com.android.keyguard.keyguardUpdateMonitor
import com.android.systemui.SysuiTestCase
import com.android.systemui.deviceStateManager
import com.android.systemui.display.domain.interactor.RearDisplayStateInteractor
@@ -37,6 +38,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import org.junit.Before
import org.junit.Test
+import org.mockito.Mockito.times
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
@@ -59,6 +61,7 @@ class RearDisplayCoreStartableTest : SysuiTestCase() {
fakeRearDisplayStateInteractor,
kosmos.rearDisplayInnerDialogDelegateFactory,
kosmos.testScope,
+ kosmos.keyguardUpdateMonitor,
)
@Before
@@ -96,6 +99,26 @@ class RearDisplayCoreStartableTest : SysuiTestCase() {
}
}
+ @Test
+ @EnableFlags(FLAG_DEVICE_STATE_RDM_V2)
+ fun testDialogResumesAfterKeyguardGone() =
+ kosmos.runTest {
+ impl.use {
+ it.start()
+ fakeRearDisplayStateInteractor.emitRearDisplay()
+
+ verify(mockDialog).show()
+
+ it.keyguardCallback.onKeyguardVisibilityChanged(true)
+ // Do not need to check that the dialog is dismissed, since SystemUIDialog
+ // implementation handles that. We just toggle keyguard here so that the flow
+ // emits.
+
+ it.keyguardCallback.onKeyguardVisibilityChanged(false)
+ verify(mockDialog, times(2)).show()
+ }
+ }
+
private class FakeRearDisplayStateInteractor(private val kosmos: Kosmos) :
RearDisplayStateInteractor {
private val stateFlow = MutableSharedFlow<RearDisplayStateInteractor.State>()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt
index 3937d3d46d68..ff17a362eb32 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt
@@ -20,7 +20,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
-import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener
@@ -97,7 +96,6 @@ class ViewConfigCoordinatorTest : SysuiTestCase() {
fun themeChangePropagatesToEntry() {
configurationListener.onThemeChanged()
verify(entry).onDensityOrFontScaleChanged()
- checkGutsExposedCalled()
verifyNoMoreInteractions(entry, row)
}
@@ -105,7 +103,6 @@ class ViewConfigCoordinatorTest : SysuiTestCase() {
fun densityChangePropagatesToEntry() {
configurationListener.onDensityOrFontScaleChanged()
verify(entry).onDensityOrFontScaleChanged()
- checkGutsExposedCalled()
verifyNoMoreInteractions(entry, row)
}
@@ -129,7 +126,6 @@ class ViewConfigCoordinatorTest : SysuiTestCase() {
verify(entry).row
verify(row).onUiModeChanged()
verify(entry).onDensityOrFontScaleChanged()
- checkGutsExposedCalled()
verifyNoMoreInteractions(entry, row)
clearInvocations(entry, row)
@@ -160,7 +156,6 @@ class ViewConfigCoordinatorTest : SysuiTestCase() {
verify(entry).row
verify(row).onUiModeChanged()
verify(entry).onDensityOrFontScaleChanged()
- checkGutsExposedCalled()
verifyNoMoreInteractions(entry, row)
clearInvocations(entry, row)
@@ -196,14 +191,7 @@ class ViewConfigCoordinatorTest : SysuiTestCase() {
verify(entry).row
verify(row).onUiModeChanged()
verify(entry).onDensityOrFontScaleChanged()
- checkGutsExposedCalled()
verifyNoMoreInteractions(entry, row)
clearInvocations(entry, row)
}
-
- private fun checkGutsExposedCalled() {
- if (!Flags.notificationUndoGutsOnConfigChanged()) {
- verify(entry).areGutsExposed()
- }
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
index c874bc6056c6..5d7b3edc457b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.row
import android.annotation.DimenRes
import android.content.res.Resources
import android.os.UserHandle
+import android.platform.test.annotations.DisableFlags
import android.service.notification.StatusBarNotification
import android.testing.TestableLooper
import android.testing.ViewUtils
@@ -88,14 +89,11 @@ class NotificationContentViewTest : SysuiTestCase() {
spy(
when (NotificationBundleUi.isEnabled) {
true -> {
- ExpandableNotificationRow(
- mContext,
- /* attrs= */ null,
- UserHandle.CURRENT
- ).apply {
- entry = mockEntry
- entryAdapter = mockEntryAdapter
- }
+ ExpandableNotificationRow(mContext, /* attrs= */ null, UserHandle.CURRENT)
+ .apply {
+ entry = mockEntry
+ entryAdapter = mockEntryAdapter
+ }
}
false -> {
ExpandableNotificationRow(mContext, /* attrs= */ null, mockEntry).apply {
@@ -402,6 +400,7 @@ class NotificationContentViewTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(android.app.Flags.FLAG_NOTIFICATIONS_REDESIGN_TEMPLATES)
fun setExpandedChild_notShowBubbleButton_marginTargetBottomMarginShouldNotChange() {
// Given: bottom margin of actionListMarginTarget is notificationContentMargin
// Bubble button should not be shown for the given NotificationEntry
@@ -429,6 +428,7 @@ class NotificationContentViewTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(android.app.Flags.FLAG_NOTIFICATIONS_REDESIGN_TEMPLATES)
fun setExpandedChild_showBubbleButton_marginTargetBottomMarginShouldChangeToZero() {
// Given: bottom margin of actionListMarginTarget is notificationContentMargin
// Bubble button should be shown for the given NotificationEntry
@@ -458,6 +458,7 @@ class NotificationContentViewTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(android.app.Flags.FLAG_NOTIFICATIONS_REDESIGN_TEMPLATES)
fun onNotificationUpdated_notShowBubbleButton_marginTargetBottomMarginShouldNotChange() {
// Given: bottom margin of actionListMarginTarget is notificationContentMargin
val mockNotificationEntry = createMockNotificationEntry()
@@ -486,6 +487,7 @@ class NotificationContentViewTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(android.app.Flags.FLAG_NOTIFICATIONS_REDESIGN_TEMPLATES)
fun onNotificationUpdated_showBubbleButton_marginTargetBottomMarginShouldChangeToZero() {
// Given: bottom margin of actionListMarginTarget is notificationContentMargin
val mockNotificationEntry = createMockNotificationEntry()
@@ -514,7 +516,7 @@ class NotificationContentViewTest : SysuiTestCase() {
// Given: controller says bubbles are enabled for the user
view.setBubblesEnabledForUser(true)
- // Then: bottom margin of actionListMarginTarget should not change, still be 20
+ // Then: bottom margin of actionListMarginTarget should be changed to 0
assertEquals(0, getMarginBottom(actionListMarginTarget))
}
@@ -628,8 +630,7 @@ class NotificationContentViewTest : SysuiTestCase() {
whenever(sbnMock.user).thenReturn(userMock)
}
- private fun createMockNotificationEntryAdapter() =
- mock<EntryAdapter>()
+ private fun createMockNotificationEntryAdapter() = mock<EntryAdapter>()
private fun createLinearLayoutWithBottomMargin(bottomMargin: Int): LinearLayout {
val outerLayout = LinearLayout(mContext)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
index df3b1372614f..10de86644015 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
@@ -300,45 +300,6 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
}
@Test
- fun testChangeDensityOrFontScale() {
- val guts = spy(NotificationGuts(mContext))
- whenever(guts.post(any())).thenAnswer { invocation: InvocationOnMock ->
- handler.post((invocation.arguments[0] as Runnable))
- null
- }
-
- // Test doesn't support animation since the guts view is not attached.
- doNothing()
- .whenever(guts)
- .openControls(any<Int>(), any<Int>(), any<Boolean>(), any<Runnable>())
- val realRow = createTestNotificationRow()
- val menuItem = createTestMenuItem(realRow)
- val row = spy(realRow)
- whenever(row!!.windowToken).thenReturn(Binder())
- whenever(row.guts).thenReturn(guts)
- doNothing().whenever(row).ensureGutsInflated()
- val realEntry = realRow!!.entry
- val entry = spy(realEntry)
- whenever(entry.row).thenReturn(row)
- whenever(entry.getGuts()).thenReturn(guts)
- Assert.assertTrue(gutsManager.openGutsInternal(row, 0, 0, menuItem))
- executor.runAllReady()
- verify(guts).openControls(any<Int>(), any<Int>(), any<Boolean>(), any<Runnable>())
-
- // called once by mGutsManager.bindGuts() in mGutsManager.openGuts()
- verify(row).setGutsView(any())
- row.onDensityOrFontScaleChanged()
- gutsManager.onDensityOrFontScaleChanged(entry)
- executor.runAllReady()
- gutsManager.closeAndSaveGuts(false, false, false, 0, 0, false)
- verify(guts)
- .closeControls(any<Boolean>(), any<Boolean>(), any<Int>(), any<Int>(), any<Boolean>())
-
- // called again by mGutsManager.bindGuts(), in mGutsManager.onDensityOrFontScaleChanged()
- verify(row, times(2)).setGutsView(any())
- }
-
- @Test
fun testAppOpsSettingsIntent_camera() {
val ops = ArraySet<Int>()
ops.add(AppOpsManager.OP_CAMERA)
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
index de78271acddc..69e856de401a 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
@@ -325,7 +325,8 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider,
}
if (newState != INVALID_DEVICE_STATE_IDENTIFIER && newState != mLastReportedState) {
- if (Trace.isTagEnabled(TRACE_TAG_SYSTEM_SERVER)) {
+ if (mLastHingeAngleSensorEvent != null
+ && Trace.isTagEnabled(TRACE_TAG_SYSTEM_SERVER)) {
Trace.instant(TRACE_TAG_SYSTEM_SERVER,
"[Device state changed] Last hinge sensor event timestamp: "
+ mLastHingeAngleSensorEvent.timestamp);