diff options
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); |