summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--TEST_MAPPING6
-rw-r--r--core/api/system-current.txt1
-rw-r--r--core/java/android/app/ContextImpl.java4
-rw-r--r--core/java/android/app/supervision/SupervisionManager.java2
-rw-r--r--core/java/android/hardware/camera2/marshal/impl/MarshalQueryableParcelable.java13
-rw-r--r--core/java/android/hardware/display/DisplayManager.java5
-rw-r--r--core/java/android/os/Parcel.java78
-rw-r--r--core/java/android/permission/PermissionManager.java6
-rw-r--r--core/java/android/provider/Settings.java3
-rw-r--r--core/java/android/service/notification/NotificationRankingUpdate.java2
-rw-r--r--core/java/android/service/notification/TEST_MAPPING5
-rw-r--r--core/java/android/view/Display.java14
-rw-r--r--core/java/android/view/DisplayInfo.java3
-rw-r--r--core/java/android/window/DesktopExperienceFlags.java1
-rw-r--r--core/java/android/window/flags/lse_desktop_experience.aconfig7
-rw-r--r--core/jni/android_os_Parcel.cpp87
-rw-r--r--core/proto/android/os/incident.proto7
-rw-r--r--core/proto/android/server/Android.bp28
-rw-r--r--core/proto/android/server/bluetooth_manager_service.proto49
-rw-r--r--core/tests/coretests/src/android/os/ParcelTest.java61
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java2
-rw-r--r--media/java/android/media/MediaRouter.java12
-rw-r--r--packages/Shell/src/com/android/shell/BugreportProgressService.java24
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt45
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt21
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt64
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/modifiers/AnimatedBackground.kt159
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/modifiers/FadingBackground.kt113
-rw-r--r--packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt40
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt6
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ActionIntentCreatorTest.kt12
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/DefaultIntentCreatorTest.java16
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt21
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.kt28
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfoTest.java1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt47
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java16
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt41
-rw-r--r--packages/SystemUI/res/layout/notification_2025_info.xml37
-rw-r--r--packages/SystemUI/res/layout/notification_info.xml15
-rw-r--r--packages/SystemUI/res/layout/partial_conversation_info.xml15
-rw-r--r--packages/SystemUI/res/layout/promoted_notification_info.xml15
-rw-r--r--packages/SystemUI/res/values/strings.xml2
-rw-r--r--packages/SystemUI/src/com/android/systemui/SwipeHelper.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ActionIntentCreator.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/DefaultIntentCreator.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt42
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt394
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepoDumpHelper.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/dump/DumpableFromToString.kt (renamed from packages/SystemUI/src/com/android/systemui/display/data/DisplayEvent.kt)17
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java62
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModel.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayCoreStartable.kt67
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/OWNERS4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt142
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java29
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java60
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java59
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManager.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticRowListener.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt41
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java29
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java28
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModelTest.kt22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayCoreStartableTest.kt23
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java49
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt12
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt11
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt3
-rw-r--r--ravenwood/TEST_MAPPING10
-rw-r--r--services/accessibility/java/com/android/server/accessibility/HearingDevicePhoneCallNotificationController.java3
-rw-r--r--services/core/java/com/android/server/TelephonyRegistry.java4
-rw-r--r--services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java16
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceInfo.java17
-rw-r--r--services/core/java/com/android/server/display/DisplayGroup.java12
-rw-r--r--services/core/java/com/android/server/display/LocalDisplayAdapter.java17
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplay.java6
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplayMapper.java15
-rw-r--r--services/core/java/com/android/server/display/OverlayDisplayAdapter.java37
-rw-r--r--services/core/java/com/android/server/display/WifiDisplayAdapter.java6
-rw-r--r--services/core/java/com/android/server/notification/TEST_MAPPING5
-rw-r--r--services/core/java/com/android/server/power/Notifier.java52
-rw-r--r--services/core/java/com/android/server/power/PowerManagerService.java11
-rw-r--r--services/core/java/com/android/server/power/WakeLockLog.java42
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperCropper.java10
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java46
-rw-r--r--services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java3
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java98
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java80
-rw-r--r--services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java69
-rw-r--r--services/tests/powerservicetests/src/com/android/server/power/WakeLockLogTest.java85
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/HearingDevicePhoneCallNotificationControllerTest.java52
-rw-r--r--services/tests/uiservicestests/Android.bp83
-rw-r--r--services/tests/uiservicestests/AndroidTest.xml1
-rw-r--r--services/tests/uiservicestests/notification-tests.xml30
-rw-r--r--services/tests/uiservicestests/notification-zen-tests.xml30
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java77
126 files changed, 2219 insertions, 1344 deletions
diff --git a/TEST_MAPPING b/TEST_MAPPING
index e469f167d32f..ce0da7e21071 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -25,6 +25,12 @@
"name": "FrameworksUiServicesTests"
},
{
+ "name": "FrameworksUiServicesNotificationTests"
+ },
+ {
+ "name": "FrameworksUiServicesZenTests"
+ },
+ {
"name": "FrameworksInputMethodSystemServerTests_server_inputmethod"
},
{
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 32b170a6286b..cd7e434d30d9 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2941,6 +2941,7 @@ package android.app.smartspace.uitemplatedata {
package android.app.supervision {
@FlaggedApi("android.app.supervision.flags.supervision_manager_apis") public class SupervisionManager {
+ method @FlaggedApi("android.app.supervision.flags.supervision_manager_apis") @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS}) public android.content.Intent createConfirmSupervisionCredentialsIntent();
method @FlaggedApi("android.app.supervision.flags.supervision_manager_apis") @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isSupervisionEnabled();
}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 7e5c0fbe1ee1..99a2763650cd 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2473,11 +2473,9 @@ class ContextImpl extends Context {
@Override
public int getPermissionRequestState(String permission) {
Objects.requireNonNull(permission, "Permission name can't be null");
- int deviceId = PermissionManager.resolveDeviceIdForPermissionCheck(this, getDeviceId(),
- permission);
PermissionManager permissionManager = getSystemService(PermissionManager.class);
return permissionManager.getPermissionRequestState(getOpPackageName(), permission,
- deviceId);
+ getDeviceId());
}
@Override
diff --git a/core/java/android/app/supervision/SupervisionManager.java b/core/java/android/app/supervision/SupervisionManager.java
index 8217ca1953ab..172ed2358a5d 100644
--- a/core/java/android/app/supervision/SupervisionManager.java
+++ b/core/java/android/app/supervision/SupervisionManager.java
@@ -97,6 +97,8 @@ public class SupervisionManager {
*
* @hide
*/
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_SUPERVISION_MANAGER_APIS)
@RequiresPermission(anyOf = {MANAGE_USERS, QUERY_USERS})
@Nullable
public Intent createConfirmSupervisionCredentialsIntent() {
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/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 92a56fc575e3..8216130e3731 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -433,8 +433,9 @@ public final class DisplayManager {
public static final int VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL = 1 << 8;
/**
- * Virtual display flag: Indicates that the display should support system decorations. Virtual
- * displays without this flag shouldn't show home, navigation bar or wallpaper.
+ * Virtual display flag: Indicates that the display supports and should always show system
+ * decorations. Virtual displays without this flag shouldn't show home, navigation bar or
+ * wallpaper.
* <p>This flag doesn't work without {@link #VIRTUAL_DISPLAY_FLAG_TRUSTED}</p>
*
* @see #createVirtualDisplay
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/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 561a2c96e6a7..0433c76fbbf4 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -1907,8 +1907,9 @@ public final class PermissionManager {
@Context.PermissionRequestState
public int getPermissionRequestState(@NonNull String packageName, @NonNull String permission,
int deviceId) {
+ int actualDeviceId = resolveDeviceIdForPermissionCheck(mContext, deviceId, permission);
return sPermissionRequestStateCache.query(
- new PermissionRequestStateQuery(packageName, permission, deviceId));
+ new PermissionRequestStateQuery(packageName, permission, actualDeviceId));
}
/**
@@ -2035,7 +2036,8 @@ public final class PermissionManager {
*/
public int checkPackageNamePermission(String permName, String pkgName,
int deviceId, @UserIdInt int userId) {
- String persistentDeviceId = getPersistentDeviceId(deviceId);
+ int actualDeviceId = resolveDeviceIdForPermissionCheck(mContext, deviceId, permName);
+ String persistentDeviceId = getPersistentDeviceId(actualDeviceId);
return sPackageNamePermissionCache.query(
new PackageNamePermissionQuery(permName, pkgName, persistentDeviceId, userId));
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index d342494e32e6..85f38c984f5c 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -15522,7 +15522,8 @@ public final class Settings {
* <ul>
* <li><pre>secure</pre>: creates a secure display</li>
* <li><pre>own_content_only</pre>: only shows this display's own content</li>
- * <li><pre>should_show_system_decorations</pre>: supports system decorations</li>
+ * <li><pre>should_show_system_decorations</pre>: always shows system decorations</li>
+ * <li><pre>fixed_content_mode</pre>: does not allow the content mode switch</li>
* </ul>
* </p><p>
* Example:
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/java/android/service/notification/TEST_MAPPING b/core/java/android/service/notification/TEST_MAPPING
index dc7129cde5e5..ea7ee4addbe6 100644
--- a/core/java/android/service/notification/TEST_MAPPING
+++ b/core/java/android/service/notification/TEST_MAPPING
@@ -4,7 +4,10 @@
"name": "CtsNotificationTestCases_notification"
},
{
- "name": "FrameworksUiServicesTests_notification"
+ "name": "FrameworksUiServicesNotificationTests"
+ },
+ {
+ "name": "FrameworksUiServicesZenTests"
}
],
"postsubmit": [
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 3f45e29f2d43..5c816543e191 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -286,7 +286,7 @@ public final class Display {
/**
* Display flag: Indicates that the display should show system decorations.
* <p>
- * This flag identifies secondary displays that should show system decorations, such as
+ * This flag identifies secondary displays that should always show system decorations, such as
* navigation bar, home activity or wallpaper.
* </p>
* <p>Note that this flag doesn't work without {@link #FLAG_TRUSTED}</p>
@@ -401,6 +401,18 @@ public final class Display {
public static final int FLAG_ROTATES_WITH_CONTENT = 1 << 14;
/**
+ * Display flag: Indicates that the display is allowed to switch the content mode between
+ * projected/extended and mirroring. This allows the display to dynamically add or remove the
+ * home and system decorations.
+ *
+ * Note that this flag shouldn't be enabled with {@link #FLAG_PRIVATE} or
+ * {@link #FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS} at the same time; otherwise it will be ignored.
+ *
+ * @hide
+ */
+ public static final int FLAG_ALLOWS_CONTENT_MODE_SWITCH = 1 << 15;
+
+ /**
* Display flag: Indicates that the contents of the display should not be scaled
* to fit the physical screen dimensions. Used for development only to emulate
* devices with smaller physicals screens while preserving density.
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index d880072aa404..bf000d5fa39a 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -1125,6 +1125,9 @@ public final class DisplayInfo implements Parcelable {
if ((flags & Display.FLAG_REAR) != 0) {
result.append(", FLAG_REAR_DISPLAY");
}
+ if ((flags & Display.FLAG_ALLOWS_CONTENT_MODE_SWITCH) != 0) {
+ result.append(", FLAG_ALLOWS_CONTENT_MODE_SWITCH");
+ }
return result.toString();
}
}
diff --git a/core/java/android/window/DesktopExperienceFlags.java b/core/java/android/window/DesktopExperienceFlags.java
index e3041c00e9fc..50b8bd22350c 100644
--- a/core/java/android/window/DesktopExperienceFlags.java
+++ b/core/java/android/window/DesktopExperienceFlags.java
@@ -61,6 +61,7 @@ public enum DesktopExperienceFlags {
ENABLE_DISPLAY_RECONNECT_INTERACTION(Flags::enableDisplayReconnectInteraction, false),
ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING(Flags::enableDisplayWindowingModeSwitching, true),
ENABLE_DRAG_TO_MAXIMIZE(Flags::enableDragToMaximize, true),
+ ENABLE_KEYBOARD_SHORTCUTS_TO_SWITCH_DESKS(Flags::keyboardShortcutsToSwitchDesks, false),
ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT(Flags::enableMoveToNextDisplayShortcut, true),
ENABLE_MULTIPLE_DESKTOPS_BACKEND(Flags::enableMultipleDesktopsBackend, false),
ENABLE_MULTIPLE_DESKTOPS_FRONTEND(Flags::enableMultipleDesktopsFrontend, false),
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 9781e7046038..53db2b1178cf 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -602,6 +602,13 @@ flag {
}
flag {
+ name: "keyboard_shortcuts_to_switch_desks"
+ namespace: "lse_desktop_experience"
+ description: "Enable switching the active desk with keyboard shortcuts"
+ bug: "389957556"
+}
+
+flag {
name: "enable_connected_displays_dnd"
namespace: "lse_desktop_experience"
description: "Enable drag-and-drop between connected displays."
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/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index 59e01bfa0c4b..9df351fa39c4 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -35,7 +35,6 @@ import "frameworks/base/core/proto/android/os/system_properties.proto";
import "frameworks/base/core/proto/android/providers/settings.proto";
import "frameworks/base/core/proto/android/server/activitymanagerservice.proto";
import "frameworks/base/core/proto/android/server/alarm/alarmmanagerservice.proto";
-import "frameworks/base/core/proto/android/server/bluetooth_manager_service.proto";
import "frameworks/base/core/proto/android/server/fingerprint.proto";
import "frameworks/base/core/proto/android/server/jobscheduler.proto";
import "frameworks/base/core/proto/android/server/location/context_hub.proto";
@@ -483,10 +482,8 @@ message IncidentProto {
(section).args = "connmetrics --proto"
];
- optional com.android.server.BluetoothManagerServiceDumpProto bluetooth_manager = 3050 [
- (section).type = SECTION_DUMPSYS,
- (section).args = "bluetooth_manager --proto"
- ];
+ // Deprecated BluetoothManagerServiceDumpProto
+ reserved 3050;
optional com.android.server.location.ContextHubServiceProto context_hub = 3051 [
(section).type = SECTION_DUMPSYS,
diff --git a/core/proto/android/server/Android.bp b/core/proto/android/server/Android.bp
deleted file mode 100644
index 362daa73ff14..000000000000
--- a/core/proto/android/server/Android.bp
+++ /dev/null
@@ -1,28 +0,0 @@
-//
-// Copyright (C) 2021 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-package {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-filegroup {
- name: "srcs_bluetooth_manager_service_proto",
- srcs: [
- "bluetooth_manager_service.proto",
- ],
- visibility: ["//packages/modules/Bluetooth:__subpackages__"],
-}
-
diff --git a/core/proto/android/server/bluetooth_manager_service.proto b/core/proto/android/server/bluetooth_manager_service.proto
deleted file mode 100644
index c33f66a9aeca..000000000000
--- a/core/proto/android/server/bluetooth_manager_service.proto
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-syntax = "proto2";
-package com.android.server;
-
-import "frameworks/base/core/proto/android/privacy.proto";
-import "frameworks/proto_logging/stats/enums/bluetooth/enums.proto";
-
-option java_multiple_files = true;
-
-message BluetoothManagerServiceDumpProto {
- option (.android.msg_privacy).dest = DEST_AUTOMATIC;
-
- message ActiveLog {
- option (.android.msg_privacy).dest = DEST_AUTOMATIC;
- optional int64 timestamp_ms = 1;
- optional bool enable = 2;
- optional string package_name = 3;
- optional .android.bluetooth.EnableDisableReasonEnum reason = 4;
- }
-
- optional bool enabled = 1;
- optional int32 state = 2;
- optional string state_name = 3;
- optional string address = 4 [(.android.privacy).dest = DEST_EXPLICIT];
- optional string name = 5 [(.android.privacy).dest = DEST_EXPLICIT];
- optional int64 last_enabled_time_ms = 6;
- optional int64 curr_timestamp_ms = 7;
- repeated ActiveLog active_logs = 8;
- optional int32 num_crashes = 9;
- optional bool crash_log_maxed = 10;
- repeated int64 crash_timestamps_ms = 11;
- optional int32 num_ble_apps = 12;
- repeated string ble_app_package_names = 13;
-} \ No newline at end of file
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/windowdecor/tiling/DesktopTilingWindowDecoration.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
index 9c55f0ecda93..7c5f34f979cd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
@@ -152,6 +152,8 @@ class DesktopTilingWindowDecoration(
endBounds = destinationBounds,
callback,
)
+ } else {
+ callback.invoke()
}
}
return isTiled
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 05750a54f566..b139c000a1a9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -889,8 +889,8 @@ public class BackAnimationControllerTest extends ShellTestCase {
*/
private void doStartEvents(int startX, int moveX) {
doMotionEvent(MotionEvent.ACTION_DOWN, startX);
- mController.onThresholdCrossed();
doMotionEvent(MotionEvent.ACTION_MOVE, moveX);
+ mController.onThresholdCrossed();
}
private void simulateRemoteAnimationStart() throws RemoteException {
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index b57476f4341f..6e0821f9f89b 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -183,7 +183,17 @@ public class MediaRouter {
appContext.registerReceiver(new VolumeChangeReceiver(),
new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION));
- mDisplayService.registerDisplayListener(this, mHandler);
+ if (com.android.server.display.feature.flags.Flags
+ .displayListenerPerformanceImprovements()
+ && com.android.server.display.feature.flags.Flags
+ .delayImplicitRrRegistrationUntilRrAccessed()) {
+ mDisplayService.registerDisplayListener(this, mHandler,
+ DisplayManager.EVENT_TYPE_DISPLAY_ADDED
+ | DisplayManager.EVENT_TYPE_DISPLAY_CHANGED
+ | DisplayManager.EVENT_TYPE_DISPLAY_REMOVED);
+ } else {
+ mDisplayService.registerDisplayListener(this, mHandler);
+ }
AudioRoutesInfo newAudioRoutes = null;
try {
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index fb0678fedb56..5bba99f84d43 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -255,11 +255,11 @@ public class BugreportProgressService extends Service {
/** Always keep remote bugreport files created in the last day. */
private static final long REMOTE_MIN_KEEP_AGE = DateUtils.DAY_IN_MILLIS;
- /** Minimum delay for sending last update notification */
- private static final int DELAY_NOTIFICATION_MS = 250;
-
private final Object mLock = new Object();
+/** Minimum delay between percentage points before sending an update notification */
+ private static final int MIN_NOTIFICATION_GAP = 10;
+
/** Managed bugreport info (keyed by id) */
@GuardedBy("mLock")
private final SparseArray<BugreportInfo> mBugreportInfos = new SparseArray<>();
@@ -1460,17 +1460,6 @@ public class BugreportProgressService extends Service {
* Sends a notification indicating the bugreport has finished so use can share it.
*/
private void sendBugreportNotification(BugreportInfo info, boolean takingScreenshot) {
-
- final long lastUpdate = System.currentTimeMillis() - info.lastUpdate.longValue();
- if (lastUpdate < DELAY_NOTIFICATION_MS) {
- Log.d(TAG, "Delaying final notification for "
- + (DELAY_NOTIFICATION_MS - lastUpdate) + " ms ");
- mMainThreadHandler.postDelayed(() -> {
- sendBugreportNotification(info, takingScreenshot);
- }, DELAY_NOTIFICATION_MS - lastUpdate);
- return;
- }
-
// Since adding the details can take a while, do it before notifying user.
addDetailsToZipFile(info);
@@ -1523,7 +1512,7 @@ public class BugreportProgressService extends Service {
builder.setSubText(info.getName());
}
- Log.v(TAG, "Sending 'Share' notification for ID " + info.id + ": " + title);
+ Log.d(TAG, "Sending 'Share' notification for ID " + info.id + ": " + title);
NotificationManager.from(mContext).notify(info.id, builder.build());
}
@@ -2753,6 +2742,11 @@ public class BugreportProgressService extends Service {
if (progress > CAPPED_PROGRESS) {
progress = CAPPED_PROGRESS;
}
+
+ if ((progress - info.lastProgress.intValue()) < MIN_NOTIFICATION_GAP) {
+ return;
+ }
+
if (DEBUG) {
if (progress != info.progress.intValue()) {
Log.v(TAG, "Updating progress for name " + info.getName() + "(id: " + info.id
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
index a352b1eebb81..82e5f5bb6dc8 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
@@ -21,7 +21,6 @@ import android.view.View
import android.view.ViewGroup
import android.view.ViewGroupOverlay
import androidx.compose.foundation.BorderStroke
-import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
@@ -62,6 +61,7 @@ import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.drawscope.scale
import androidx.compose.ui.graphics.drawscope.translate
+import androidx.compose.ui.graphics.isSpecified
import androidx.compose.ui.graphics.layer.GraphicsLayer
import androidx.compose.ui.graphics.layer.drawLayer
import androidx.compose.ui.graphics.rememberGraphicsLayer
@@ -82,6 +82,7 @@ import androidx.lifecycle.setViewTreeLifecycleOwner
import androidx.lifecycle.setViewTreeViewModelStoreOwner
import androidx.savedstate.findViewTreeSavedStateRegistryOwner
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
+import com.android.compose.modifiers.animatedBackground
import com.android.compose.modifiers.thenIf
import com.android.compose.ui.graphics.FullScreenComposeViewInOverlay
import com.android.systemui.animation.ComposableControllerFactory
@@ -291,7 +292,7 @@ fun Expandable(
.updateExpandableSize()
.then(minInteractiveSizeModifier)
.then(clickModifier(controller, onClick, interactionSource))
- .background(color, shape)
+ .animatedBackground(color, shape = shape)
.border(controller)
.onGloballyPositioned { controller.boundsInComposeViewRoot = it.boundsInRoot() }
) {
@@ -307,19 +308,27 @@ private fun WrappedContent(
contentColor: Color,
content: @Composable (Expandable) -> Unit,
) {
- CompositionLocalProvider(LocalContentColor provides contentColor) {
- // We make sure that the content itself (wrapped by the background) is at least 40.dp, which
- // is the same as the M3 buttons. This applies even if onClick is null, to make it easier to
- // write expandables that are sometimes clickable and sometimes not. There shouldn't be any
- // Expandable smaller than 40dp because if the expandable is not clickable directly, then
- // something in its content should be (and with a size >= 40dp).
- val minSize = 40.dp
- Box(
- Modifier.defaultMinSize(minWidth = minSize, minHeight = minSize),
- contentAlignment = Alignment.Center,
- ) {
- content(expandable)
+ val minSizeContent =
+ @Composable {
+ // We make sure that the content itself (wrapped by the background) is at least 40.dp,
+ // which is the same as the M3 buttons. This applies even if onClick is null, to make it
+ // easier to write expandables that are sometimes clickable and sometimes not. There
+ // shouldn't be any Expandable smaller than 40dp because if the expandable is not
+ // clickable directly, then something in its content should be (and with a size >=
+ // 40dp).
+ val minSize = 40.dp
+ Box(
+ Modifier.defaultMinSize(minWidth = minSize, minHeight = minSize),
+ contentAlignment = Alignment.Center,
+ ) {
+ content(expandable)
+ }
}
+
+ if (contentColor.isSpecified) {
+ CompositionLocalProvider(LocalContentColor provides contentColor, content = minSizeContent)
+ } else {
+ minSizeContent()
}
}
@@ -345,7 +354,7 @@ private fun Modifier.expandable(
.thenIf(drawContent) {
Modifier.border(controller)
.then(clickModifier(controller, onClick, interactionSource))
- .background(controller.color, controller.shape)
+ .animatedBackground(controller.color, shape = controller.shape)
}
.onPlaced { controller.boundsInComposeViewRoot = it.boundsInRoot() }
.drawWithContent {
@@ -422,7 +431,7 @@ private class DrawExpandableInOverlayNode(
// Background.
this@draw.drawBackground(
state,
- controller.color,
+ controller.color(),
controller.borderStroke,
size = Size(state.width.toFloat(), state.height.toFloat()),
)
@@ -469,7 +478,7 @@ private fun clickModifier(
/** Draw [content] in [overlay] while respecting its screen position given by [animatorState]. */
@Composable
private fun AnimatedContentInOverlay(
- color: Color,
+ color: () -> Color,
sizeInOriginalLayout: Size,
overlay: ViewGroupOverlay,
controller: ExpandableControllerImpl,
@@ -523,7 +532,7 @@ private fun AnimatedContentInOverlay(
return@drawWithContent
}
- drawBackground(animatorState, color, controller.borderStroke)
+ drawBackground(animatorState, color(), controller.borderStroke)
drawContent()
},
// We center the content in the expanding container.
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
index 72da175e26cf..d7d5a48e2b79 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
@@ -26,7 +26,6 @@ import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.Stable
-import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -80,6 +79,24 @@ fun rememberExpandableController(
borderStroke: BorderStroke? = null,
transitionControllerFactory: ComposableControllerFactory? = null,
): ExpandableController {
+ return rememberExpandableController(
+ color = { color },
+ shape = shape,
+ contentColor = contentColor,
+ borderStroke = borderStroke,
+ transitionControllerFactory = transitionControllerFactory,
+ )
+}
+
+/** Create an [ExpandableController] to control an [Expandable]. */
+@Composable
+fun rememberExpandableController(
+ color: () -> Color,
+ shape: Shape,
+ contentColor: Color = Color.Unspecified,
+ borderStroke: BorderStroke? = null,
+ transitionControllerFactory: ComposableControllerFactory? = null,
+): ExpandableController {
val composeViewRoot = LocalView.current
val density = LocalDensity.current
val layoutDirection = LocalLayoutDirection.current
@@ -125,7 +142,7 @@ fun rememberExpandableController(
}
internal class ExpandableControllerImpl(
- internal val color: Color,
+ internal val color: () -> Color,
internal val contentColor: Color,
internal val shape: Shape,
internal val borderStroke: BorderStroke?,
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt
index 959f28f2d99e..80cbbea7659f 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt
@@ -95,10 +95,20 @@ interface NestedDraggable {
* nested scrollable.
*
* This is called whenever a nested scrollable does not consume some scroll amount. If this
- * returns `true`, then [onDragStarted] will be called and this draggable will have priority and
+ * returns `true`, then [onDragStarted] will be called, this draggable will have priority and
* consume all future events during preScroll until the nested scroll is finished.
*/
- fun shouldConsumeNestedScroll(sign: Float): Boolean
+ fun shouldConsumeNestedPostScroll(sign: Float): Boolean = true
+
+ /**
+ * Whether this draggable should consume any scroll amount with the given [sign] *before* it can
+ * be consumed by a nested scrollable.
+ *
+ * This is called before a nested scrollable is able to consume that scroll amount. If this
+ * returns `true`, then [onDragStarted] will be called, this draggable will have priority and
+ * consume all future scroll events during preScroll until the nested scroll is finished.
+ */
+ fun shouldConsumeNestedPreScroll(sign: Float): Boolean = false
interface Controller {
/**
@@ -540,6 +550,14 @@ private class NestedDraggableNode(
}
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+ val sign = available.toFloat().sign
+ maybeCreateNewController(
+ sign = sign,
+ condition = {
+ source == NestedScrollSource.UserInput &&
+ draggable.shouldConsumeNestedPreScroll(sign)
+ },
+ )
val controller = nestedScrollController ?: return Offset.Zero
return scrollWithOverscroll(controller, available)
}
@@ -560,28 +578,34 @@ private class NestedDraggableNode(
}
val sign = offset.sign
+ maybeCreateNewController(
+ sign,
+ condition = { draggable.shouldConsumeNestedPostScroll(sign) },
+ )
+ val controller = nestedScrollController ?: return Offset.Zero
+ return scrollWithOverscroll(controller, available)
+ }
+
+ private fun maybeCreateNewController(sign: Float, condition: () -> Boolean) {
if (
- nestedDragsEnabled &&
- nestedScrollController == null &&
- // TODO(b/388231324): Remove this.
- !lastEventWasScrollWheel &&
- draggable.shouldConsumeNestedScroll(sign) &&
- lastFirstDown != null
+ !nestedDragsEnabled ||
+ nestedScrollController != null ||
+ lastEventWasScrollWheel ||
+ lastFirstDown == null ||
+ !condition()
) {
- val startedPosition = checkNotNull(lastFirstDown)
-
- // TODO(b/382665591): Ensure that there is at least one pointer down.
- val pointersDownCount = pointersDown.size.coerceAtLeast(1)
- val pointerType = pointersDown.entries.firstOrNull()?.value
- nestedScrollController =
- NestedScrollController(
- overscrollEffect,
- draggable.onDragStarted(startedPosition, sign, pointersDownCount, pointerType),
- )
+ return
}
- val controller = nestedScrollController ?: return Offset.Zero
- return scrollWithOverscroll(controller, available)
+ // TODO(b/382665591): Ensure that there is at least one pointer down.
+ val pointersDownCount = pointersDown.size.coerceAtLeast(1)
+ val pointerType = pointersDown.entries.firstOrNull()?.value
+ val startedPosition = checkNotNull(lastFirstDown)
+ nestedScrollController =
+ NestedScrollController(
+ overscrollEffect,
+ draggable.onDragStarted(startedPosition, sign, pointersDownCount, pointerType),
+ )
}
private fun scrollWithOverscroll(controller: NestedScrollController, offset: Offset): Offset {
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/modifiers/AnimatedBackground.kt b/packages/SystemUI/compose/core/src/com/android/compose/modifiers/AnimatedBackground.kt
new file mode 100644
index 000000000000..5b1f0a7c6eb6
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/modifiers/AnimatedBackground.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.modifiers
+
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Outline
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.drawOutline
+import androidx.compose.ui.graphics.drawscope.ContentDrawScope
+import androidx.compose.ui.node.DrawModifierNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.ObserverModifierNode
+import androidx.compose.ui.node.invalidateDraw
+import androidx.compose.ui.node.observeReads
+import androidx.compose.ui.platform.InspectorInfo
+import androidx.compose.ui.platform.debugInspectorInfo
+import androidx.compose.ui.unit.LayoutDirection
+
+/**
+ * Draws a background in a given [shape] and with a [color] or [alpha] that can be animated.
+ *
+ * @param color color to paint background with
+ * @param alpha alpha of the background
+ * @param shape desired shape of the background
+ */
+fun Modifier.animatedBackground(
+ color: () -> Color,
+ alpha: () -> Float = DefaultAlpha,
+ shape: Shape = RectangleShape,
+) =
+ this.then(
+ BackgroundElement(
+ color = color,
+ alpha = alpha,
+ shape = shape,
+ inspectorInfo =
+ debugInspectorInfo {
+ name = "background"
+ value = color
+ properties["color"] = color
+ properties["alpha"] = alpha
+ properties["shape"] = shape
+ },
+ )
+ )
+
+private val DefaultAlpha = { 1f }
+
+private class BackgroundElement(
+ private val color: () -> Color,
+ private val alpha: () -> Float,
+ private val shape: Shape,
+ private val inspectorInfo: InspectorInfo.() -> Unit,
+) : ModifierNodeElement<BackgroundNode>() {
+ override fun create(): BackgroundNode {
+ return BackgroundNode(color, alpha, shape)
+ }
+
+ override fun update(node: BackgroundNode) {
+ node.color = color
+ node.alpha = alpha
+ node.shape = shape
+ }
+
+ override fun InspectorInfo.inspectableProperties() {
+ inspectorInfo()
+ }
+
+ override fun hashCode(): Int {
+ var result = color.hashCode()
+ result = 31 * result + alpha.hashCode()
+ result = 31 * result + shape.hashCode()
+ return result
+ }
+
+ override fun equals(other: Any?): Boolean {
+ val otherModifier = other as? BackgroundElement ?: return false
+ return color == otherModifier.color &&
+ alpha == otherModifier.alpha &&
+ shape == otherModifier.shape
+ }
+}
+
+private class BackgroundNode(var color: () -> Color, var alpha: () -> Float, var shape: Shape) :
+ DrawModifierNode, Modifier.Node(), ObserverModifierNode {
+
+ // Naively cache outline calculation if input parameters are the same, we manually observe
+ // reads inside shape#createOutline separately
+ private var lastSize: Size = Size.Unspecified
+ private var lastLayoutDirection: LayoutDirection? = null
+ private var lastOutline: Outline? = null
+ private var lastShape: Shape? = null
+ private var tmpOutline: Outline? = null
+
+ override fun ContentDrawScope.draw() {
+ if (shape === RectangleShape) {
+ // shortcut to avoid Outline calculation and allocation
+ drawRect()
+ } else {
+ drawOutline()
+ }
+ drawContent()
+ }
+
+ override fun onObservedReadsChanged() {
+ // Reset cached properties
+ lastSize = Size.Unspecified
+ lastLayoutDirection = null
+ lastOutline = null
+ lastShape = null
+ // Invalidate draw so we build the cache again - this is needed because observeReads within
+ // the draw scope obscures the state reads from the draw scope's observer
+ invalidateDraw()
+ }
+
+ private fun ContentDrawScope.drawRect() {
+ drawRect(color = color(), alpha = alpha())
+ }
+
+ private fun ContentDrawScope.drawOutline() {
+ val outline = getOutline()
+ drawOutline(outline, color = color(), alpha = alpha())
+ }
+
+ private fun ContentDrawScope.getOutline(): Outline {
+ val outline: Outline?
+ if (size == lastSize && layoutDirection == lastLayoutDirection && lastShape == shape) {
+ outline = lastOutline!!
+ } else {
+ // Manually observe reads so we can directly invalidate the outline when it changes
+ // Use tmpOutline to avoid creating an object reference to local var outline
+ observeReads { tmpOutline = shape.createOutline(size, layoutDirection, this) }
+ outline = tmpOutline
+ tmpOutline = null
+ }
+ lastOutline = outline
+ lastSize = size
+ lastLayoutDirection = layoutDirection
+ lastShape = shape
+ return outline!!
+ }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/modifiers/FadingBackground.kt b/packages/SystemUI/compose/core/src/com/android/compose/modifiers/FadingBackground.kt
deleted file mode 100644
index 794b7a4a3d30..000000000000
--- a/packages/SystemUI/compose/core/src/com/android/compose/modifiers/FadingBackground.kt
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compose.modifiers
-
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.DrawModifier
-import androidx.compose.ui.geometry.Size
-import androidx.compose.ui.graphics.Brush
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.Outline
-import androidx.compose.ui.graphics.RectangleShape
-import androidx.compose.ui.graphics.Shape
-import androidx.compose.ui.graphics.SolidColor
-import androidx.compose.ui.graphics.drawOutline
-import androidx.compose.ui.graphics.drawscope.ContentDrawScope
-import androidx.compose.ui.platform.InspectorInfo
-import androidx.compose.ui.platform.InspectorValueInfo
-import androidx.compose.ui.platform.debugInspectorInfo
-import androidx.compose.ui.unit.LayoutDirection
-
-/**
- * Draws a fading [shape] with a solid [color] and [alpha] behind the content.
- *
- * @param color color to paint background with
- * @param alpha alpha of the background
- * @param shape desired shape of the background
- */
-fun Modifier.fadingBackground(color: Color, alpha: () -> Float, shape: Shape = RectangleShape) =
- this.then(
- FadingBackground(
- brush = SolidColor(color),
- alpha = alpha,
- shape = shape,
- inspectorInfo =
- debugInspectorInfo {
- name = "background"
- value = color
- properties["color"] = color
- properties["alpha"] = alpha
- properties["shape"] = shape
- },
- )
- )
-
-private class FadingBackground
-constructor(
- private val brush: Brush,
- private val shape: Shape,
- private val alpha: () -> Float,
- inspectorInfo: InspectorInfo.() -> Unit,
-) : DrawModifier, InspectorValueInfo(inspectorInfo) {
- // naive cache outline calculation if size is the same
- private var lastSize: Size? = null
- private var lastLayoutDirection: LayoutDirection? = null
- private var lastOutline: Outline? = null
-
- override fun ContentDrawScope.draw() {
- if (shape === RectangleShape) {
- // shortcut to avoid Outline calculation and allocation
- drawRect()
- } else {
- drawOutline()
- }
- drawContent()
- }
-
- private fun ContentDrawScope.drawRect() {
- drawRect(brush, alpha = alpha())
- }
-
- private fun ContentDrawScope.drawOutline() {
- val outline =
- if (size == lastSize && layoutDirection == lastLayoutDirection) {
- lastOutline!!
- } else {
- shape.createOutline(size, layoutDirection, this)
- }
- drawOutline(outline, brush = brush, alpha = alpha())
- lastOutline = outline
- lastSize = size
- lastLayoutDirection = layoutDirection
- }
-
- override fun hashCode(): Int {
- var result = brush.hashCode()
- result = 31 * result + alpha.hashCode()
- result = 31 * result + shape.hashCode()
- return result
- }
-
- override fun equals(other: Any?): Boolean {
- val otherModifier = other as? FadingBackground ?: return false
- return brush == otherModifier.brush &&
- alpha == otherModifier.alpha &&
- shape == otherModifier.shape
- }
-
- override fun toString(): String = "FadingBackground(brush=$brush, alpha = $alpha, shape=$shape)"
-}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt
index d8e46ad34c37..a03d769c9c36 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt
@@ -971,6 +971,35 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw
assertThat(availableToEffectPostFling).isWithin(1f).of(100f)
}
+ @Test
+ fun consumeNestedPreScroll() {
+ var consumeNestedPreScroll by mutableStateOf(false)
+ val draggable = TestDraggable(shouldConsumeNestedPreScroll = { consumeNestedPreScroll })
+
+ val touchSlop =
+ rule.setContentWithTouchSlop {
+ Box(
+ Modifier.fillMaxSize()
+ .nestedDraggable(draggable, orientation)
+ // Always consume everything so that the only way to start the drag is to
+ // intercept preScroll events.
+ .scrollable(rememberScrollableState { it }, orientation)
+ )
+ }
+
+ rule.onRoot().performTouchInput {
+ down(center)
+ moveBy((touchSlop + 1f).toOffset())
+ }
+
+ assertThat(draggable.onDragStartedCalled).isFalse()
+
+ consumeNestedPreScroll = true
+ rule.onRoot().performTouchInput { moveBy(1f.toOffset()) }
+
+ assertThat(draggable.onDragStartedCalled).isTrue()
+ }
+
private fun ComposeContentTestRule.setContentWithTouchSlop(
content: @Composable () -> Unit
): Float {
@@ -996,7 +1025,8 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw
{ velocity, _ ->
velocity
},
- private val shouldConsumeNestedScroll: (Float) -> Boolean = { true },
+ private val shouldConsumeNestedPostScroll: (Float) -> Boolean = { true },
+ private val shouldConsumeNestedPreScroll: (Float) -> Boolean = { false },
) : NestedDraggable {
var shouldStartDrag = true
var onDragStartedCalled = false
@@ -1042,8 +1072,12 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw
}
}
- override fun shouldConsumeNestedScroll(sign: Float): Boolean {
- return shouldConsumeNestedScroll.invoke(sign)
+ override fun shouldConsumeNestedPostScroll(sign: Float): Boolean {
+ return shouldConsumeNestedPostScroll.invoke(sign)
+ }
+
+ override fun shouldConsumeNestedPreScroll(sign: Float): Boolean {
+ return shouldConsumeNestedPreScroll.invoke(sign)
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
index 216f0a74e1c7..7782705d4c61 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
@@ -73,7 +73,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.compose.animation.Expandable
import com.android.compose.animation.scene.ContentScope
-import com.android.compose.modifiers.fadingBackground
+import com.android.compose.modifiers.animatedBackground
import com.android.compose.theme.colorAttr
import com.android.systemui.Flags.notificationShadeBlur
import com.android.systemui.animation.Expandable
@@ -172,8 +172,8 @@ fun FooterActions(
val backgroundTopRadius = dimensionResource(R.dimen.qs_corner_radius)
val backgroundModifier =
remember(backgroundColor, backgroundAlphaValue, backgroundTopRadius) {
- Modifier.fadingBackground(
- backgroundColor,
+ Modifier.animatedBackground(
+ { backgroundColor },
backgroundAlphaValue,
RoundedCornerShape(topStart = backgroundTopRadius, topEnd = backgroundTopRadius),
)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index 1360611ed814..024ca22069ae 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -68,7 +68,7 @@ internal class DraggableHandler(
return layoutImpl.swipeDetector.detectSwipe(change)
}
- override fun shouldConsumeNestedScroll(sign: Float): Boolean {
+ override fun shouldConsumeNestedPostScroll(sign: Float): Boolean {
return this.enabled()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ActionIntentCreatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ActionIntentCreatorTest.kt
index 239e02640908..652a2ff21e9b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ActionIntentCreatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ActionIntentCreatorTest.kt
@@ -25,6 +25,10 @@ import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.systemui.SysuiTestCase
import com.android.systemui.res.R
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
@@ -33,7 +37,11 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
class ActionIntentCreatorTest : SysuiTestCase() {
- val creator = ActionIntentCreator()
+ private val scheduler = TestCoroutineScheduler()
+ private val mainDispatcher = UnconfinedTestDispatcher(scheduler)
+ private val testScope = TestScope(mainDispatcher)
+
+ val creator = ActionIntentCreator(testScope.backgroundScope)
@Test
fun test_getTextEditorIntent() {
@@ -65,7 +73,7 @@ class ActionIntentCreatorTest : SysuiTestCase() {
}
@Test
- fun test_getImageEditIntent() {
+ fun test_getImageEditIntent() = runTest {
context.getOrCreateTestableResources().addOverride(R.string.config_screenshotEditor, "")
val fakeUri = Uri.parse("content://foo")
var intent = creator.getImageEditIntent(fakeUri, context)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/DefaultIntentCreatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/DefaultIntentCreatorTest.java
index 126b3fa9e7ca..10c5c52d21a7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/DefaultIntentCreatorTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/DefaultIntentCreatorTest.java
@@ -34,6 +34,8 @@ import com.android.systemui.res.R;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.concurrent.atomic.AtomicReference;
+
@SmallTest
@RunWith(AndroidJUnit4.class)
public class DefaultIntentCreatorTest extends SysuiTestCase {
@@ -73,12 +75,16 @@ public class DefaultIntentCreatorTest extends SysuiTestCase {
}
@Test
- public void test_getImageEditIntent() {
+ public void test_getImageEditIntentAsync() {
getContext().getOrCreateTestableResources().addOverride(R.string.config_screenshotEditor,
"");
Uri fakeUri = Uri.parse("content://foo");
- Intent intent = mIntentCreator.getImageEditIntent(fakeUri, getContext());
+ final AtomicReference<Intent> intentHolder = new AtomicReference<>(null);
+ mIntentCreator.getImageEditIntentAsync(fakeUri, getContext(), output -> {
+ intentHolder.set(output);
+ });
+ Intent intent = intentHolder.get();
assertEquals(Intent.ACTION_EDIT, intent.getAction());
assertEquals("image/*", intent.getType());
assertEquals(null, intent.getComponent());
@@ -90,8 +96,10 @@ public class DefaultIntentCreatorTest extends SysuiTestCase {
"com.android.remotecopy.RemoteCopyActivity");
getContext().getOrCreateTestableResources().addOverride(R.string.config_screenshotEditor,
fakeComponent.flattenToString());
- intent = mIntentCreator.getImageEditIntent(fakeUri, getContext());
- assertEquals(fakeComponent, intent.getComponent());
+ mIntentCreator.getImageEditIntentAsync(fakeUri, getContext(), output -> {
+ intentHolder.set(output);
+ });
+ assertEquals(fakeComponent, intentHolder.get().getComponent());
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
index 197b0eea05cb..859137507bbf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
@@ -26,6 +26,7 @@ import android.view.Display.TYPE_INTERNAL
import android.view.IWindowManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.app.displaylib.DisplayRepository.PendingDisplay
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.FlowValue
import com.android.systemui.coroutines.collectLastValue
@@ -71,14 +72,20 @@ class DisplayRepositoryTest : SysuiTestCase() {
// that the initial state (soon after construction) contains the expected ones set in every
// test.
private val displayRepository: DisplayRepositoryImpl by lazy {
- DisplayRepositoryImpl(
+ // TODO b/401305290 - move this to kosmos
+ val displayRepositoryFromLib =
+ com.android.app.displaylib.DisplayRepositoryImpl(
displayManager,
- commandQueue,
- windowManager,
testHandler,
- TestScope(UnconfinedTestDispatcher()),
+ testScope.backgroundScope,
UnconfinedTestDispatcher(),
)
+ DisplayRepositoryImpl(
+ commandQueue,
+ windowManager,
+ testScope.backgroundScope,
+ displayRepositoryFromLib,
+ )
.also {
verify(displayManager, never()).registerDisplayListener(any(), any())
// It needs to be called, just once, for the initial value.
@@ -403,7 +410,7 @@ class DisplayRepositoryTest : SysuiTestCase() {
val pendingDisplay by lastPendingDisplay()
sendOnDisplayConnected(1, TYPE_EXTERNAL)
- val initialPendingDisplay: DisplayRepository.PendingDisplay? = pendingDisplay
+ val initialPendingDisplay: PendingDisplay? = pendingDisplay
assertThat(pendingDisplay).isNotNull()
sendOnDisplayChanged(1)
@@ -416,7 +423,7 @@ class DisplayRepositoryTest : SysuiTestCase() {
val pendingDisplay by lastPendingDisplay()
sendOnDisplayConnected(1, TYPE_EXTERNAL)
- val initialPendingDisplay: DisplayRepository.PendingDisplay? = pendingDisplay
+ val initialPendingDisplay: PendingDisplay? = pendingDisplay
assertThat(pendingDisplay).isNotNull()
sendOnDisplayConnected(2, TYPE_EXTERNAL)
@@ -648,7 +655,7 @@ class DisplayRepositoryTest : SysuiTestCase() {
return flowValue
}
- private fun TestScope.lastPendingDisplay(): FlowValue<DisplayRepository.PendingDisplay?> {
+ private fun TestScope.lastPendingDisplay(): FlowValue<PendingDisplay?> {
val flowValue = collectLastValue(displayRepository.pendingDisplay)
captureAddedRemovedListener()
verify(displayManager)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt
index e41d46ce90af..6c7783ae44e1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt
@@ -31,7 +31,7 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyString
-import org.mockito.kotlin.eq
+import org.mockito.kotlin.any
import org.mockito.kotlin.verify
@RunWith(AndroidJUnit4::class)
@@ -105,7 +105,7 @@ class PerDisplayInstanceRepositoryImplTest : SysuiTestCase() {
@Test
fun start_registersDumpable() {
- verify(kosmos.dumpManager).registerNormalDumpable(anyString(), eq(underTest))
+ verify(kosmos.dumpManager).registerNormalDumpable(anyString(), any())
}
private fun createDisplay(displayId: Int): Display =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt
index 4a954b391f5f..387c62d76083 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt
@@ -512,6 +512,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase(
.setImportance(NotificationManager.IMPORTANCE_HIGH)
.build()
+ whenever(row.canViewBeDismissed()).thenReturn(true)
whenever(highPriorityProvider.isHighPriority(entry)).thenReturn(true)
val statusBarNotification = entry.sbn
gutsManager.initializeNotificationInfo(row, notificationInfoView)
@@ -534,6 +535,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase(
any<UiEventLogger>(),
/* isDeviceProvisioned = */ eq(false),
/* isNonblockable = */ eq(false),
+ /* isDismissable = */ eq(true),
/* wasShownHighPriority = */ eq(true),
eq(assistantFeedbackController),
eq(metricsLogger),
@@ -549,6 +551,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase(
NotificationEntryHelper.modifyRanking(row.entry)
.setUserSentiment(NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE)
.build()
+ whenever(row.canViewBeDismissed()).thenReturn(true)
val statusBarNotification = row.entry.sbn
val entry = row.entry
@@ -574,6 +577,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase(
any<UiEventLogger>(),
/* isDeviceProvisioned = */ eq(true),
/* isNonblockable = */ eq(false),
+ /* isDismissable = */ eq(true),
/* wasShownHighPriority = */ eq(false),
eq(assistantFeedbackController),
eq(metricsLogger),
@@ -589,6 +593,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase(
NotificationEntryHelper.modifyRanking(row.entry)
.setUserSentiment(NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE)
.build()
+ whenever(row.canViewBeDismissed()).thenReturn(true)
val statusBarNotification = row.entry.sbn
val entry = row.entry
@@ -612,6 +617,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase(
any<UiEventLogger>(),
/* isDeviceProvisioned = */ eq(false),
/* isNonblockable = */ eq(false),
+ /* isDismissable = */ eq(true),
/* wasShownHighPriority = */ eq(false),
eq(assistantFeedbackController),
eq(metricsLogger),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.kt
index 5f817de58b40..b5f3269903b8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.kt
@@ -49,6 +49,7 @@ import android.view.View.GONE
import android.view.View.VISIBLE
import android.widget.ImageView
import android.widget.TextView
+import androidx.core.view.isVisible
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
@@ -865,6 +866,31 @@ class NotificationInfoTest : SysuiTestCase() {
assertThat(underTest.findViewById<View>(R.id.feedback).visibility).isEqualTo(GONE)
}
+ @Test
+ @Throws(RemoteException::class)
+ fun testDismissListenerBound() {
+ val latch = CountDownLatch(1)
+ bindNotification(onCloseClick = { _: View? -> latch.countDown() })
+
+ val dismissView = underTest.findViewById<View>(R.id.inline_dismiss)
+ assertThat(dismissView.isVisible).isTrue()
+ dismissView.performClick()
+
+ // Verify that listener was triggered.
+ assertThat(latch.count).isEqualTo(0)
+ }
+
+ @Test
+ @Throws(RemoteException::class)
+ fun testDismissHiddenWhenUndismissable() {
+
+ entry.sbn.notification.flags =
+ entry.sbn.notification.flags or android.app.Notification.FLAG_NO_DISMISS
+ bindNotification(isDismissable = false)
+ val dismissView = underTest.findViewById<View>(R.id.inline_dismiss)
+ assertThat(dismissView.isVisible).isFalse()
+ }
+
private fun bindNotification(
pm: PackageManager = this.mockPackageManager,
iNotificationManager: INotificationManager = this.mockINotificationManager,
@@ -883,6 +909,7 @@ class NotificationInfoTest : SysuiTestCase() {
uiEventLogger: UiEventLogger = this.uiEventLogger,
isDeviceProvisioned: Boolean = true,
isNonblockable: Boolean = false,
+ isDismissable: Boolean = true,
wasShownHighPriority: Boolean = true,
assistantFeedbackController: AssistantFeedbackController = this.assistantFeedbackController,
metricsLogger: MetricsLogger = kosmos.metricsLogger,
@@ -905,6 +932,7 @@ class NotificationInfoTest : SysuiTestCase() {
uiEventLogger,
isDeviceProvisioned,
isNonblockable,
+ isDismissable,
wasShownHighPriority,
assistantFeedbackController,
metricsLogger,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
index af52c31b1c53..9fdfca14a5b2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
@@ -28,7 +28,6 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.platform.test.annotations.DisableFlags;
import android.provider.Settings;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
@@ -39,7 +38,6 @@ import android.view.ViewGroup;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.systemui.Flags;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -415,7 +413,6 @@ public class NotificationMenuRowTest extends LeakCheckedTest {
assertTrue("when alpha is .5, menu is visible", row.isMenuVisible());
}
- @DisableFlags(Flags.FLAG_MAGNETIC_NOTIFICATION_SWIPES)
@Test
public void testOnTouchMove() {
NotificationMenuRow row = Mockito.spy(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfoTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfoTest.java
index 388729743f9e..209dfb2d2ed6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfoTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfoTest.java
@@ -152,6 +152,7 @@ public class PromotedNotificationInfoTest extends SysuiTestCase {
true,
false,
true,
+ true,
mAssistantFeedbackController,
mMetricsLogger,
null);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt
index 6c6ba933c03a..ccc8be7de038 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImplTest.kt
@@ -130,9 +130,7 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() {
kosmos.testScope.runTest {
// GIVEN a threshold of 100 px
val threshold = 100f
- underTest.onDensityChange(
- threshold / MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP
- )
+ underTest.setSwipeThresholdPx(threshold)
// GIVEN that targets are set and the rows are being pulled
setTargets()
@@ -152,9 +150,7 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() {
kosmos.testScope.runTest {
// GIVEN a threshold of 100 px
val threshold = 100f
- underTest.onDensityChange(
- threshold / MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP
- )
+ underTest.setSwipeThresholdPx(threshold)
// GIVEN that targets are set and the rows are being pulled
canRowBeDismissed = false
@@ -176,9 +172,7 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() {
kosmos.testScope.runTest {
// GIVEN a threshold of 100 px
val threshold = 100f
- underTest.onDensityChange(
- threshold / MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP
- )
+ underTest.setSwipeThresholdPx(threshold)
// GIVEN that targets are set and the rows are being pulled
setTargets()
@@ -198,9 +192,7 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() {
kosmos.testScope.runTest {
// GIVEN a threshold of 100 px
val threshold = 100f
- underTest.onDensityChange(
- threshold / MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP
- )
+ underTest.setSwipeThresholdPx(threshold)
// GIVEN that targets are set and the rows are being pulled
canRowBeDismissed = false
@@ -302,29 +294,6 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() {
assertThat(underTest.isSwipedViewRoundableSet).isFalse()
}
- @Test
- fun isMagneticRowDismissible_isDismissibleWhenDetached() =
- kosmos.testScope.runTest {
- setDetachedState()
-
- val isDismissible = underTest.isMagneticRowSwipeDetached(swipedRow)
- assertThat(isDismissible).isTrue()
- }
-
- @Test
- fun setMagneticRowTranslation_whenDetached_belowAttachThreshold_reattaches() =
- kosmos.testScope.runTest {
- // GIVEN that the swiped view has been detached
- setDetachedState()
-
- // WHEN setting a new translation above the attach threshold
- val translation = 50f
- underTest.setMagneticRowTranslation(swipedRow, translation)
-
- // THEN the swiped view reattaches magnetically and the state becomes PULLING
- assertThat(underTest.currentState).isEqualTo(State.PULLING)
- }
-
@After
fun tearDown() {
// We reset the manager so that all MagneticRowListener can cancel all animations
@@ -333,9 +302,7 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() {
private fun setDetachedState() {
val threshold = 100f
- underTest.onDensityChange(
- threshold / MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP
- )
+ underTest.setSwipeThresholdPx(threshold)
// Set the pulling state
setTargets()
@@ -360,8 +327,8 @@ class MagneticNotificationRowManagerImplTest : SysuiTestCase() {
private fun MagneticRowListener.asTestableListener(rowIndex: Int): MagneticRowListener {
val delegate = this
return object : MagneticRowListener {
- override fun setMagneticTranslation(translation: Float, trackEagerly: Boolean) {
- delegate.setMagneticTranslation(translation, trackEagerly)
+ override fun setMagneticTranslation(translation: Float) {
+ delegate.setMagneticTranslation(translation)
}
override fun triggerMagneticForce(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
index de48f4018989..789701f5e4b0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
@@ -49,7 +49,6 @@ import android.view.ViewConfiguration;
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.classifier.FalsingManagerFake;
import com.android.systemui.flags.FakeFeatureFlags;
@@ -363,7 +362,6 @@ public class NotificationSwipeHelperTest extends SysuiTestCase {
verify(mSwipeHelper, times(1)).isFalseGesture();
}
- @DisableFlags(Flags.FLAG_MAGNETIC_NOTIFICATION_SWIPES)
@Test
public void testIsDismissGesture_farEnough() {
doReturn(false).when(mSwipeHelper).isFalseGesture();
@@ -376,20 +374,6 @@ public class NotificationSwipeHelperTest extends SysuiTestCase {
verify(mSwipeHelper, times(1)).isFalseGesture();
}
- @EnableFlags(Flags.FLAG_MAGNETIC_NOTIFICATION_SWIPES)
- @Test
- public void testIsDismissGesture_magneticSwipeIsDismissible() {
- doReturn(false).when(mSwipeHelper).isFalseGesture();
- doReturn(false).when(mSwipeHelper).swipedFarEnough();
- doReturn(false).when(mSwipeHelper).swipedFastEnough();
- doReturn(true).when(mCallback).isMagneticViewDetached(any());
- when(mCallback.canChildBeDismissedInDirection(any(), anyBoolean())).thenReturn(true);
- when(mEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_UP);
-
- assertTrue("Should be a dismissal", mSwipeHelper.isDismissGesture(mEvent));
- verify(mSwipeHelper, times(1)).isFalseGesture();
- }
-
@Test
public void testIsDismissGesture_notFarOrFastEnough() {
doReturn(false).when(mSwipeHelper).isFalseGesture();
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
index 91b3896332f5..39cf02dbc772 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
@@ -54,9 +54,7 @@ class FakeHomeStatusBarViewModel(
MutableStateFlow(OngoingActivityChipModel.Inactive())
override val ongoingActivityChips =
- MutableStateFlow(
- ChipsVisibilityModel(MultipleOngoingActivityChipsModel(), areChipsAllowed = false)
- )
+ ChipsVisibilityModel(MultipleOngoingActivityChipsModel(), areChipsAllowed = false)
override val ongoingActivityChipsLegacy =
MutableStateFlow(MultipleOngoingActivityChipsModelLegacy())
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
index 2da692b4cb45..20cf3ae6b8dd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
@@ -49,6 +49,7 @@ import com.android.systemui.kosmos.runCurrent
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.lifecycle.activateIn
import com.android.systemui.log.assertLogsWtf
import com.android.systemui.mediaprojection.data.model.MediaProjectionState
import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository
@@ -107,7 +108,8 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class HomeStatusBarViewModelImplTest : SysuiTestCase() {
private val kosmos = testKosmos().useUnconfinedTestDispatcher()
- private val Kosmos.underTest by Kosmos.Fixture { kosmos.homeStatusBarViewModel }
+ private val Kosmos.underTest by
+ Kosmos.Fixture { kosmos.homeStatusBarViewModel.also { it.activateIn(kosmos.testScope) } }
@Before
fun setUp() {
@@ -891,32 +893,26 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@EnableChipsModernization
fun ongoingActivityChips_statusBarHidden_noSecureCamera_noHun_notAllowed() =
kosmos.runTest {
- val latest by collectLastValue(underTest.ongoingActivityChips)
-
// home status bar not allowed
kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(false, taskInfo = null)
- assertThat(latest!!.areChipsAllowed).isFalse()
+ assertThat(underTest.ongoingActivityChips.areChipsAllowed).isFalse()
}
@Test
@EnableChipsModernization
fun ongoingActivityChips_statusBarNotHidden_noSecureCamera_noHun_isAllowed() =
kosmos.runTest {
- val latest by collectLastValue(underTest.ongoingActivityChips)
-
transitionKeyguardToGone()
- assertThat(latest!!.areChipsAllowed).isTrue()
+ assertThat(underTest.ongoingActivityChips.areChipsAllowed).isTrue()
}
@Test
@EnableChipsModernization
fun ongoingActivityChips_statusBarNotHidden_secureCamera_noHun_notAllowed() =
kosmos.runTest {
- val latest by collectLastValue(underTest.ongoingActivityChips)
-
fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.OCCLUDED,
@@ -924,7 +920,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
)
kosmos.keyguardInteractor.onCameraLaunchDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
- assertThat(latest!!.areChipsAllowed).isFalse()
+ assertThat(underTest.ongoingActivityChips.areChipsAllowed).isFalse()
}
@Test
@@ -932,8 +928,6 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@EnableChipsModernization
fun ongoingActivityChips_statusBarNotHidden_noSecureCamera_hunBySystem_noHunFlagOff_notAllowed() =
kosmos.runTest {
- val latest by collectLastValue(underTest.ongoingActivityChips)
-
transitionKeyguardToGone()
headsUpNotificationRepository.setNotifications(
@@ -943,7 +937,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
)
)
- assertThat(latest!!.areChipsAllowed).isFalse()
+ assertThat(underTest.ongoingActivityChips.areChipsAllowed).isFalse()
}
@Test
@@ -951,8 +945,6 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@EnableChipsModernization
fun ongoingActivityChips_statusBarNotHidden_noSecureCamera_hunByUser_noHunFlagOff_isAllowed() =
kosmos.runTest {
- val latest by collectLastValue(underTest.ongoingActivityChips)
-
transitionKeyguardToGone()
headsUpNotificationRepository.setNotifications(
@@ -962,16 +954,14 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
)
)
- assertThat(latest!!.areChipsAllowed).isTrue()
+ assertThat(underTest.ongoingActivityChips.areChipsAllowed).isTrue()
}
@Test
@EnableFlags(StatusBarNoHunBehavior.FLAG_NAME)
@EnableChipsModernization
- fun ongoingActivityChips_tatusBarNotHidden_noSecureCamera_hunBySystem_noHunFlagOn_isAllowed() =
+ fun ongoingActivityChips_statusBarNotHidden_noSecureCamera_hunBySystem_noHunFlagOn_isAllowed() =
kosmos.runTest {
- val latest by collectLastValue(underTest.ongoingActivityChips)
-
transitionKeyguardToGone()
headsUpNotificationRepository.setNotifications(
@@ -981,7 +971,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
)
)
- assertThat(latest!!.areChipsAllowed).isTrue()
+ assertThat(underTest.ongoingActivityChips.areChipsAllowed).isTrue()
}
@Test
@@ -989,8 +979,6 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@EnableChipsModernization
fun ongoingActivityChips_statusBarNotHidden_noSecureCamera_hunByUser_noHunFlagOn_isAllowed() =
kosmos.runTest {
- val latest by collectLastValue(underTest.ongoingActivityChips)
-
transitionKeyguardToGone()
headsUpNotificationRepository.setNotifications(
@@ -1000,7 +988,7 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
)
)
- assertThat(latest!!.areChipsAllowed).isTrue()
+ assertThat(underTest.ongoingActivityChips.areChipsAllowed).isTrue()
}
@Test
@@ -1008,17 +996,16 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() {
@EnableChipsModernization
fun ongoingActivityChips_followsChipsViewModel() =
kosmos.runTest {
- val latest by collectLastValue(underTest.ongoingActivityChips)
transitionKeyguardToGone()
screenRecordRepository.screenRecordState.value = ScreenRecordModel.Recording
- assertIsScreenRecordChip(latest!!.chips.active[0])
+ assertIsScreenRecordChip(underTest.ongoingActivityChips.chips.active[0])
addOngoingCallState(key = "call")
- assertIsScreenRecordChip(latest!!.chips.active[0])
- assertIsCallChip(latest!!.chips.active[1], "call", context)
+ assertIsScreenRecordChip(underTest.ongoingActivityChips.chips.active[0])
+ assertIsCallChip(underTest.ongoingActivityChips.chips.active[1], "call", context)
}
@Test
diff --git a/packages/SystemUI/res/layout/notification_2025_info.xml b/packages/SystemUI/res/layout/notification_2025_info.xml
index 7b6916652924..fa852a2b8e85 100644
--- a/packages/SystemUI/res/layout/notification_2025_info.xml
+++ b/packages/SystemUI/res/layout/notification_2025_info.xml
@@ -18,6 +18,7 @@
<!-- extends LinearLayout -->
<com.android.systemui.statusbar.notification.row.NotificationInfo
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:id="@+id/notification_guts"
android:layout_width="match_parent"
@@ -324,18 +325,34 @@
</com.android.systemui.statusbar.notification.row.ButtonLinearLayout>
</LinearLayout>
-
- <LinearLayout
+ <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/bottom_buttons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@*android:dimen/notification_2025_margin"
android:minHeight="@dimen/notification_2025_guts_button_size"
- android:gravity="center_vertical"
- >
+ android:gravity="center_vertical">
+
+ <TextView
+ android:id="@+id/inline_dismiss"
+ android:text="@string/notification_inline_dismiss"
+ android:paddingEnd="@dimen/notification_importance_button_padding"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingTop="8dp"
+ android:paddingBottom="@*android:dimen/notification_2025_margin"
+ app:layout_constraintStart_toStartOf="parent"
+ android:gravity="center"
+ android:minWidth="@dimen/notification_2025_min_tap_target_size"
+ android:minHeight="@dimen/notification_2025_min_tap_target_size"
+ android:maxWidth="200dp"
+ style="@style/TextAppearance.NotificationInfo.Button"
+ android:textSize="@*android:dimen/notification_2025_action_text_size"
+ />
<TextView
android:id="@+id/turn_off_notifications"
android:text="@string/inline_turn_off_notifications"
+ android:paddingStart="@dimen/notification_importance_button_padding"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="32dp"
@@ -345,6 +362,8 @@
android:minWidth="@dimen/notification_2025_min_tap_target_size"
android:minHeight="@dimen/notification_2025_min_tap_target_size"
android:maxWidth="200dp"
+ app:layout_constraintStart_toEndOf="@id/inline_dismiss"
+ app:layout_constraintBaseline_toBaselineOf="@id/inline_dismiss"
style="@style/TextAppearance.NotificationInfo.Button"
android:textSize="@*android:dimen/notification_2025_action_text_size"/>
<TextView
@@ -354,12 +373,18 @@
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingBottom="@*android:dimen/notification_2025_margin"
- android:gravity="center"
+ android:gravity="end"
+ app:layout_constraintEnd_toEndOf="parent"
android:minWidth="@dimen/notification_2025_min_tap_target_size"
android:minHeight="@dimen/notification_2025_min_tap_target_size"
android:maxWidth="125dp"
style="@style/TextAppearance.NotificationInfo.Button"
android:textSize="@*android:dimen/notification_2025_action_text_size"/>
- </LinearLayout>
+ <androidx.constraintlayout.helper.widget.Flow
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ app:constraint_referenced_ids="inline_dismiss,turn_off_notifications,done"
+ app:flow_wrapMode="chain"/>
+ </androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</com.android.systemui.statusbar.notification.row.NotificationInfo>
diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml
index 089ceaee6ce3..d4bd142d9089 100644
--- a/packages/SystemUI/res/layout/notification_info.xml
+++ b/packages/SystemUI/res/layout/notification_info.xml
@@ -341,15 +341,28 @@ asked for it -->
android:paddingEnd="4dp"
>
<TextView
+ android:id="@+id/inline_dismiss"
+ android:text="@string/notification_inline_dismiss"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ android:gravity="start|center_vertical"
+ android:minWidth="@dimen/notification_importance_toggle_size"
+ android:minHeight="@dimen/notification_importance_toggle_size"
+ android:maxWidth="200dp"
+ android:paddingEnd="@dimen/notification_importance_button_padding"
+ style="@style/TextAppearance.NotificationInfo.Button"/>
+ <TextView
android:id="@+id/turn_off_notifications"
android:text="@string/inline_turn_off_notifications"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_alignParentStart="true"
+ android:layout_toEndOf="@id/inline_dismiss"
android:gravity="start|center_vertical"
android:minWidth="@dimen/notification_importance_toggle_size"
android:minHeight="@dimen/notification_importance_toggle_size"
android:maxWidth="200dp"
+ android:paddingStart="@dimen/notification_importance_button_padding"
style="@style/TextAppearance.NotificationInfo.Button"/>
<TextView
android:id="@+id/done"
diff --git a/packages/SystemUI/res/layout/partial_conversation_info.xml b/packages/SystemUI/res/layout/partial_conversation_info.xml
index 4850b35833e5..d1755eff6dab 100644
--- a/packages/SystemUI/res/layout/partial_conversation_info.xml
+++ b/packages/SystemUI/res/layout/partial_conversation_info.xml
@@ -143,15 +143,28 @@
android:paddingEnd="4dp"
>
<TextView
+ android:id="@+id/inline_dismiss"
+ android:text="@string/notification_inline_dismiss"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ android:gravity="start|center_vertical"
+ android:minWidth="@dimen/notification_importance_toggle_size"
+ android:minHeight="@dimen/notification_importance_toggle_size"
+ android:maxWidth="200dp"
+ android:paddingEnd="@dimen/notification_importance_button_padding"
+ style="@style/TextAppearance.NotificationInfo.Button"/>
+ <TextView
android:id="@+id/turn_off_notifications"
android:text="@string/inline_turn_off_notifications"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_alignParentStart="true"
+ android:layout_toEndOf="@id/inline_dismiss"
android:gravity="start|center_vertical"
android:minWidth="@dimen/notification_importance_toggle_size"
android:minHeight="@dimen/notification_importance_toggle_size"
android:maxWidth="200dp"
+ android:paddingStart="@dimen/notification_importance_button_padding"
style="@style/TextAppearance.NotificationInfo.Button"/>
<TextView
android:id="@+id/done"
diff --git a/packages/SystemUI/res/layout/promoted_notification_info.xml b/packages/SystemUI/res/layout/promoted_notification_info.xml
index 2e0a0ca1185c..3982a6638666 100644
--- a/packages/SystemUI/res/layout/promoted_notification_info.xml
+++ b/packages/SystemUI/res/layout/promoted_notification_info.xml
@@ -373,15 +373,28 @@ asked for it -->
android:paddingEnd="4dp"
>
<TextView
+ android:id="@+id/inline_dismiss"
+ android:text="@string/notification_inline_dismiss"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ android:gravity="start|center_vertical"
+ android:minWidth="@dimen/notification_importance_toggle_size"
+ android:minHeight="@dimen/notification_importance_toggle_size"
+ android:maxWidth="200dp"
+ android:paddingEnd="@dimen/notification_importance_button_padding"
+ style="@style/TextAppearance.NotificationInfo.Button"/>
+ <TextView
android:id="@+id/turn_off_notifications"
android:text="@string/inline_turn_off_notifications"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_alignParentStart="true"
+ android:layout_toEndOf="@id/inline_dismiss"
android:gravity="start|center_vertical"
android:minWidth="@dimen/notification_importance_toggle_size"
android:minHeight="@dimen/notification_importance_toggle_size"
android:maxWidth="200dp"
+ android:paddingStart="@dimen/notification_importance_button_padding"
style="@style/TextAppearance.NotificationInfo.Button"/>
<TextView
android:id="@+id/done"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index cd94a265aa80..fefc222f283f 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2056,7 +2056,7 @@
<string name="inline_ok_button">Apply</string>
<!-- Notification inline controls: button to show block screen [CHAR_LIMIT=35] -->
- <string name="inline_turn_off_notifications">Turn off notifications</string>
+ <string name="inline_turn_off_notifications">Turn off</string>
<!-- [CHAR LIMIT=100] Notification Importance title -->
<string name="notification_silence_title">Silent</string>
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index e3afe2e190e9..c78f75a334fd 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -778,26 +778,18 @@ public class SwipeHelper implements Gefingerpoken, Dumpable {
protected boolean swipedFarEnough() {
float translation = getTranslation(mTouchedView);
- return Math.abs(translation) > SWIPED_FAR_ENOUGH_SIZE_FRACTION * getSize(mTouchedView);
+ return Math.abs(translation) > SWIPED_FAR_ENOUGH_SIZE_FRACTION * getSize(
+ mTouchedView);
}
public boolean isDismissGesture(MotionEvent ev) {
float translation = getTranslation(mTouchedView);
return ev.getActionMasked() == MotionEvent.ACTION_UP
&& !mFalsingManager.isUnlockingDisabled()
- && !isFalseGesture() && isSwipeDismissible()
+ && !isFalseGesture() && (swipedFastEnough() || swipedFarEnough())
&& mCallback.canChildBeDismissedInDirection(mTouchedView, translation > 0);
}
- /** Can the swipe gesture on the touched view be considered as a dismiss intention */
- public boolean isSwipeDismissible() {
- if (magneticNotificationSwipes()) {
- return mCallback.isMagneticViewDetached(mTouchedView) || swipedFastEnough();
- } else {
- return swipedFastEnough() || swipedFarEnough();
- }
- }
-
/** Returns true if the gesture should be rejected. */
public boolean isFalseGesture() {
boolean falsingDetected = mCallback.isAntiFalsingNeeded();
@@ -978,13 +970,6 @@ public class SwipeHelper implements Gefingerpoken, Dumpable {
void onMagneticInteractionEnd(View view, float velocity);
/**
- * Determine if a view managed by magnetic interactions is magnetically detached
- * @param view The magnetic view
- * @return if the view is detached according to its magnetic state.
- */
- boolean isMagneticViewDetached(View view);
-
- /**
* Called when the child is long pressed and available to start drag and drop.
*
* @param v the view that was long pressed.
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ActionIntentCreator.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ActionIntentCreator.kt
index df6c1b18e3e9..8cebe04d4e01 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ActionIntentCreator.kt
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ActionIntentCreator.kt
@@ -24,11 +24,17 @@ import android.content.Intent
import android.net.Uri
import android.text.TextUtils
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.res.R
+import java.util.function.Consumer
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
@SysUISingleton
-class ActionIntentCreator @Inject constructor() : IntentCreator {
+class ActionIntentCreator
+@Inject
+constructor(@Application private val applicationScope: CoroutineScope) : IntentCreator {
override fun getTextEditorIntent(context: Context?) =
Intent(context, EditTextActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
@@ -65,7 +71,7 @@ class ActionIntentCreator @Inject constructor() : IntentCreator {
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
- override fun getImageEditIntent(uri: Uri?, context: Context): Intent {
+ suspend fun getImageEditIntent(uri: Uri?, context: Context): Intent {
val editorPackage = context.getString(R.string.config_screenshotEditor)
return Intent(Intent.ACTION_EDIT).apply {
if (!TextUtils.isEmpty(editorPackage)) {
@@ -78,6 +84,14 @@ class ActionIntentCreator @Inject constructor() : IntentCreator {
}
}
+ override fun getImageEditIntentAsync(
+ uri: Uri?,
+ context: Context,
+ outputConsumer: Consumer<Intent>,
+ ) {
+ applicationScope.launch { outputConsumer.accept(getImageEditIntent(uri, context)) }
+ }
+
override fun getRemoteCopyIntent(clipData: ClipData?, context: Context): Intent {
val remoteCopyPackage = context.getString(R.string.config_remoteCopyPackage)
return Intent(REMOTE_COPY_ACTION).apply {
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index 314b6e7f5a28..984d2478eb72 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -558,8 +558,10 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
private void editImage(Uri uri) {
mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_EDIT_TAPPED);
- mContext.startActivity(mIntentCreator.getImageEditIntent(uri, mContext));
- animateOut();
+ mIntentCreator.getImageEditIntentAsync(uri, mContext, intent -> {
+ mContext.startActivity(intent);
+ animateOut();
+ });
}
private void editText() {
@@ -747,8 +749,10 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
mIntentCreator.getTextEditorIntent(mContext));
break;
case IMAGE:
- finishWithSharedTransition(CLIPBOARD_OVERLAY_EDIT_TAPPED,
- mIntentCreator.getImageEditIntent(mClipboardModel.getUri(), mContext));
+ mIntentCreator.getImageEditIntentAsync(mClipboardModel.getUri(), mContext,
+ intent -> {
+ finishWithSharedTransition(CLIPBOARD_OVERLAY_EDIT_TAPPED, intent);
+ });
break;
default:
Log.w(TAG, "Got preview tapped callback for non-editable type "
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DefaultIntentCreator.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DefaultIntentCreator.java
index 4b24536ad28f..e9a9cbf106fc 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DefaultIntentCreator.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DefaultIntentCreator.java
@@ -27,6 +27,8 @@ import android.text.TextUtils;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.res.R;
+import java.util.function.Consumer;
+
import javax.inject.Inject;
@SysUISingleton
@@ -73,7 +75,7 @@ public class DefaultIntentCreator implements IntentCreator {
return chooserIntent;
}
- public Intent getImageEditIntent(Uri uri, Context context) {
+ public void getImageEditIntentAsync(Uri uri, Context context, Consumer<Intent> outputConsumer) {
String editorPackage = context.getString(R.string.config_screenshotEditor);
Intent editIntent = new Intent(Intent.ACTION_EDIT);
if (!TextUtils.isEmpty(editorPackage)) {
@@ -83,7 +85,7 @@ public class DefaultIntentCreator implements IntentCreator {
editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
editIntent.putExtra(EXTRA_EDIT_SOURCE, EDIT_SOURCE_CLIPBOARD);
- return editIntent;
+ outputConsumer.accept(editIntent);
}
public Intent getRemoteCopyIntent(ClipData clipData, Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java
index c8a6b05f090b..283596f9f309 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java
@@ -21,9 +21,11 @@ import android.content.Context;
import android.content.Intent;
import android.net.Uri;
+import java.util.function.Consumer;
+
public interface IntentCreator {
Intent getTextEditorIntent(Context context);
Intent getShareIntent(ClipData clipData, Context context);
- Intent getImageEditIntent(Uri uri, Context context);
+ void getImageEditIntentAsync(Uri uri, Context context, Consumer<Intent> outputConsumer);
Intent getRemoteCopyIntent(ClipData clipData, Context context);
}
diff --git a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
index 9b181be93b61..f3316958f01d 100644
--- a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
@@ -16,8 +16,13 @@
package com.android.systemui.display
+import android.hardware.display.DisplayManager
+import android.os.Handler
+import com.android.app.displaylib.DisplayLibComponent
+import com.android.app.displaylib.createDisplayLibComponent
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DeviceStateRepository
import com.android.systemui.display.data.repository.DeviceStateRepositoryImpl
import com.android.systemui.display.data.repository.DisplayRepository
@@ -28,6 +33,8 @@ import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepos
import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepositoryImpl
import com.android.systemui.display.data.repository.FocusedDisplayRepository
import com.android.systemui.display.data.repository.FocusedDisplayRepositoryImpl
+import com.android.systemui.display.data.repository.PerDisplayRepoDumpHelper
+import com.android.systemui.display.data.repository.PerDisplayRepository
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractorImpl
import com.android.systemui.display.domain.interactor.DisplayWindowPropertiesInteractorModule
@@ -40,9 +47,11 @@ import dagger.Module
import dagger.Provides
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
/** Module binding display related classes. */
-@Module(includes = [DisplayWindowPropertiesInteractorModule::class])
+@Module(includes = [DisplayWindowPropertiesInteractorModule::class, DisplayLibModule::class])
interface DisplayModule {
@Binds
fun bindConnectedDisplayInteractor(
@@ -73,6 +82,9 @@ interface DisplayModule {
impl: DisplayWindowPropertiesRepositoryImpl
): DisplayWindowPropertiesRepository
+ @Binds
+ fun dumpRegistrationLambda(helper: PerDisplayRepoDumpHelper): PerDisplayRepository.InitCallback
+
companion object {
@Provides
@SysUISingleton
@@ -103,3 +115,31 @@ interface DisplayModule {
}
}
}
+
+/** Module to bind the DisplayRepository from displaylib to the systemui dagger graph. */
+@Module
+object DisplayLibModule {
+ @Provides
+ @SysUISingleton
+ fun displayLibComponent(
+ displayManager: DisplayManager,
+ @Background backgroundHandler: Handler,
+ @Background bgApplicationScope: CoroutineScope,
+ @Background backgroundCoroutineDispatcher: CoroutineDispatcher,
+ ): DisplayLibComponent {
+ return createDisplayLibComponent(
+ displayManager,
+ backgroundHandler,
+ bgApplicationScope,
+ backgroundCoroutineDispatcher,
+ )
+ }
+
+ @Provides
+ @SysUISingleton
+ fun providesDisplayRepositoryFromLib(
+ displayLibComponent: DisplayLibComponent
+ ): com.android.app.displaylib.DisplayRepository {
+ return displayLibComponent.displayRepository
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
index 101e8cc23f17..051fe7e5517c 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
@@ -17,106 +17,30 @@
package com.android.systemui.display.data.repository
import android.annotation.SuppressLint
-import android.hardware.display.DisplayManager
-import android.hardware.display.DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED
-import android.hardware.display.DisplayManager.DisplayListener
-import android.hardware.display.DisplayManager.EVENT_TYPE_DISPLAY_ADDED
-import android.hardware.display.DisplayManager.EVENT_TYPE_DISPLAY_CHANGED
-import android.hardware.display.DisplayManager.EVENT_TYPE_DISPLAY_REMOVED
-import android.os.Handler
-import android.util.Log
-import android.view.Display
import android.view.IWindowManager
import com.android.app.displaylib.DisplayRepository as DisplayRepositoryFromLib
-import com.android.app.tracing.FlowTracing.traceEach
-import com.android.app.tracing.traceSection
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.display.data.DisplayEvent
import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.util.Compile
-import com.android.systemui.util.kotlin.pairwiseBy
-import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.filterIsInstance
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.scan
import kotlinx.coroutines.flow.stateIn
/** Repository for providing access to display related information and events. */
interface DisplayRepository : DisplayRepositoryFromLib {
- /** Display change event indicating a change to the given displayId has occurred. */
- val displayChangeEvent: Flow<Int>
-
- /** Display addition event indicating a new display has been added. */
- val displayAdditionEvent: Flow<Display?>
-
- /** Display removal event indicating a display has been removed. */
- val displayRemovalEvent: Flow<Int>
/** A [StateFlow] that maintains a set of display IDs that should have system decorations. */
val displayIdsWithSystemDecorations: StateFlow<Set<Int>>
-
- /**
- * Provides the current set of display ids.
- *
- * Note that it is preferred to use this instead of [displays] if only the
- * [Display.getDisplayId] is needed.
- */
- val displayIds: StateFlow<Set<Int>>
-
- /**
- * Pending display id that can be enabled/disabled.
- *
- * When `null`, it means there is no pending display waiting to be enabled.
- */
- val pendingDisplay: Flow<PendingDisplay?>
-
- /** Whether the default display is currently off. */
- val defaultDisplayOff: Flow<Boolean>
-
- /**
- * Given a display ID int, return the corresponding Display object, or null if none exist.
- *
- * This method is guaranteed to not result in any binder call.
- */
- fun getDisplay(displayId: Int): Display? =
- displays.value.firstOrNull { it.displayId == displayId }
-
- /** Represents a connected display that has not been enabled yet. */
- interface PendingDisplay {
- /** Id of the pending display. */
- val id: Int
-
- /** Enables the display, making it available to the system. */
- suspend fun enable()
-
- /**
- * Ignores the pending display. When called, this specific display id doesn't appear as
- * pending anymore until the display is disconnected and reconnected again.
- */
- suspend fun ignore()
-
- /** Disables the display, making it unavailable to the system. */
- suspend fun disable()
- }
}
@SysUISingleton
@@ -124,310 +48,11 @@ interface DisplayRepository : DisplayRepositoryFromLib {
class DisplayRepositoryImpl
@Inject
constructor(
- private val displayManager: DisplayManager,
private val commandQueue: CommandQueue,
private val windowManager: IWindowManager,
- @Background backgroundHandler: Handler,
@Background bgApplicationScope: CoroutineScope,
- @Background backgroundCoroutineDispatcher: CoroutineDispatcher,
-) : DisplayRepository {
- private val allDisplayEvents: Flow<DisplayEvent> =
- conflatedCallbackFlow {
- val callback =
- object : DisplayListener {
- override fun onDisplayAdded(displayId: Int) {
- trySend(DisplayEvent.Added(displayId))
- }
-
- override fun onDisplayRemoved(displayId: Int) {
- trySend(DisplayEvent.Removed(displayId))
- }
-
- override fun onDisplayChanged(displayId: Int) {
- trySend(DisplayEvent.Changed(displayId))
- }
- }
- displayManager.registerDisplayListener(
- callback,
- backgroundHandler,
- EVENT_TYPE_DISPLAY_ADDED or
- EVENT_TYPE_DISPLAY_CHANGED or
- EVENT_TYPE_DISPLAY_REMOVED,
- )
- awaitClose { displayManager.unregisterDisplayListener(callback) }
- }
- .onStart { emit(DisplayEvent.Changed(Display.DEFAULT_DISPLAY)) }
- .debugLog("allDisplayEvents")
- .flowOn(backgroundCoroutineDispatcher)
-
- override val displayChangeEvent: Flow<Int> =
- allDisplayEvents.filterIsInstance<DisplayEvent.Changed>().map { event -> event.displayId }
-
- override val displayRemovalEvent: Flow<Int> =
- allDisplayEvents.filterIsInstance<DisplayEvent.Removed>().map { it.displayId }
-
- // This is necessary because there might be multiple displays, and we could
- // have missed events for those added before this process or flow started.
- // Note it causes a binder call from the main thread (it's traced).
- private val initialDisplays: Set<Display> =
- traceSection("$TAG#initialDisplays") { displayManager.displays?.toSet() ?: emptySet() }
- private val initialDisplayIds = initialDisplays.map { display -> display.displayId }.toSet()
-
- /** Propagate to the listeners only enabled displays */
- private val enabledDisplayIds: StateFlow<Set<Int>> =
- allDisplayEvents
- .scan(initial = initialDisplayIds) { previousIds: Set<Int>, event: DisplayEvent ->
- val id = event.displayId
- when (event) {
- is DisplayEvent.Removed -> previousIds - id
- is DisplayEvent.Added,
- is DisplayEvent.Changed -> previousIds + id
- }
- }
- .distinctUntilChanged()
- .debugLog("enabledDisplayIds")
- .stateIn(bgApplicationScope, SharingStarted.WhileSubscribed(), initialDisplayIds)
-
- private val defaultDisplay by lazy {
- getDisplayFromDisplayManager(Display.DEFAULT_DISPLAY)
- ?: error("Unable to get default display.")
- }
-
- /**
- * Represents displays that went though the [DisplayListener.onDisplayAdded] callback.
- *
- * Those are commonly the ones provided by [DisplayManager.getDisplays] by default.
- */
- private val enabledDisplays: StateFlow<Set<Display>> =
- enabledDisplayIds
- .mapElementsLazily { displayId -> getDisplayFromDisplayManager(displayId) }
- .onEach {
- if (it.isEmpty()) Log.wtf(TAG, "No enabled displays. This should never happen.")
- }
- .flowOn(backgroundCoroutineDispatcher)
- .debugLog("enabledDisplays")
- .stateIn(
- bgApplicationScope,
- started = SharingStarted.WhileSubscribed(),
- // This triggers a single binder call on the UI thread per process. The
- // alternative would be to use sharedFlows, but they are prohibited due to
- // performance concerns.
- // Ultimately, this is a trade-off between a one-time UI thread binder call and
- // the constant overhead of sharedFlows.
- initialValue = initialDisplays,
- )
-
- /**
- * Represents displays that went though the [DisplayListener.onDisplayAdded] callback.
- *
- * Those are commonly the ones provided by [DisplayManager.getDisplays] by default.
- */
- override val displays: StateFlow<Set<Display>> = enabledDisplays
-
- override val displayIds: StateFlow<Set<Int>> = enabledDisplayIds
-
- /**
- * Implementation that maps from [displays], instead of [allDisplayEvents] for 2 reasons:
- * 1. Guarantee that it emits __after__ [displays] emitted. This way it is guaranteed that
- * calling [getDisplay] for the newly added display will be non-null.
- * 2. Reuse the existing instance of [Display] without a new call to [DisplayManager].
- */
- override val displayAdditionEvent: Flow<Display?> =
- displays
- .pairwiseBy { previousDisplays, currentDisplays -> currentDisplays - previousDisplays }
- .flatMapLatest { it.asFlow() }
-
- val _ignoredDisplayIds = MutableStateFlow<Set<Int>>(emptySet())
- private val ignoredDisplayIds: Flow<Set<Int>> = _ignoredDisplayIds.debugLog("ignoredDisplayIds")
-
- private fun getInitialConnectedDisplays(): Set<Int> =
- traceSection("$TAG#getInitialConnectedDisplays") {
- displayManager
- .getDisplays(DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)
- .map { it.displayId }
- .toSet()
- .also {
- if (DEBUG) {
- Log.d(TAG, "getInitialConnectedDisplays: $it")
- }
- }
- }
-
- /* keeps connected displays until they are disconnected. */
- private val connectedDisplayIds: StateFlow<Set<Int>> =
- conflatedCallbackFlow {
- val connectedIds = getInitialConnectedDisplays().toMutableSet()
- val callback =
- object : DisplayConnectionListener {
- override fun onDisplayConnected(id: Int) {
- if (DEBUG) {
- Log.d(TAG, "display with id=$id connected.")
- }
- connectedIds += id
- _ignoredDisplayIds.value -= id
- trySend(connectedIds.toSet())
- }
-
- override fun onDisplayDisconnected(id: Int) {
- connectedIds -= id
- if (DEBUG) {
- Log.d(TAG, "display with id=$id disconnected.")
- }
- _ignoredDisplayIds.value -= id
- trySend(connectedIds.toSet())
- }
- }
- trySend(connectedIds.toSet())
- displayManager.registerDisplayListener(
- callback,
- backgroundHandler,
- /* eventFlags */ 0,
- DisplayManager.PRIVATE_EVENT_TYPE_DISPLAY_CONNECTION_CHANGED,
- )
- awaitClose { displayManager.unregisterDisplayListener(callback) }
- }
- .distinctUntilChanged()
- .debugLog("connectedDisplayIds")
- .stateIn(
- bgApplicationScope,
- started = SharingStarted.WhileSubscribed(),
- // The initial value is set to empty, but connected displays are gathered as soon as
- // the flow starts being collected. This is to ensure the call to get displays (an
- // IPC) happens in the background instead of when this object
- // is instantiated.
- initialValue = emptySet(),
- )
-
- private val connectedExternalDisplayIds: Flow<Set<Int>> =
- connectedDisplayIds
- .map { connectedDisplayIds ->
- traceSection("$TAG#filteringExternalDisplays") {
- connectedDisplayIds
- .filter { id -> getDisplayType(id) == Display.TYPE_EXTERNAL }
- .toSet()
- }
- }
- .flowOn(backgroundCoroutineDispatcher)
- .debugLog("connectedExternalDisplayIds")
-
- private fun getDisplayType(displayId: Int): Int? =
- traceSection("$TAG#getDisplayType") { displayManager.getDisplay(displayId)?.type }
-
- private fun getDisplayFromDisplayManager(displayId: Int): Display? =
- traceSection("$TAG#getDisplay") { displayManager.getDisplay(displayId) }
-
- /**
- * Pending displays are the ones connected, but not enabled and not ignored.
- *
- * A connected display is ignored after the user makes the decision to use it or not. For now,
- * the initial decision from the user is final and not reversible.
- */
- private val pendingDisplayIds: Flow<Set<Int>> =
- combine(enabledDisplayIds, connectedExternalDisplayIds, ignoredDisplayIds) {
- enabledDisplaysIds,
- connectedExternalDisplayIds,
- ignoredDisplayIds ->
- if (DEBUG) {
- Log.d(
- TAG,
- "combining enabled=$enabledDisplaysIds, " +
- "connectedExternalDisplayIds=$connectedExternalDisplayIds, " +
- "ignored=$ignoredDisplayIds",
- )
- }
- connectedExternalDisplayIds - enabledDisplaysIds - ignoredDisplayIds
- }
- .debugLog("allPendingDisplayIds")
-
- /** Which display id should be enabled among the pending ones. */
- private val pendingDisplayId: Flow<Int?> =
- pendingDisplayIds.map { it.maxOrNull() }.distinctUntilChanged().debugLog("pendingDisplayId")
-
- override val pendingDisplay: Flow<DisplayRepository.PendingDisplay?> =
- pendingDisplayId
- .map { displayId ->
- val id = displayId ?: return@map null
- object : DisplayRepository.PendingDisplay {
- override val id = id
-
- override suspend fun enable() {
- traceSection("DisplayRepository#enable($id)") {
- if (DEBUG) {
- Log.d(TAG, "Enabling display with id=$id")
- }
- displayManager.enableConnectedDisplay(id)
- }
- // After the display has been enabled, it is automatically ignored.
- ignore()
- }
-
- override suspend fun ignore() {
- traceSection("DisplayRepository#ignore($id)") {
- _ignoredDisplayIds.value += id
- }
- }
-
- override suspend fun disable() {
- ignore()
- traceSection("DisplayRepository#disable($id)") {
- if (DEBUG) {
- Log.d(TAG, "Disabling display with id=$id")
- }
- displayManager.disableConnectedDisplay(id)
- }
- }
- }
- }
- .debugLog("pendingDisplay")
-
- override val defaultDisplayOff: Flow<Boolean> =
- displayChangeEvent
- .filter { it == Display.DEFAULT_DISPLAY }
- .map { defaultDisplay.state == Display.STATE_OFF }
- .distinctUntilChanged()
-
- private fun <T> Flow<T>.debugLog(flowName: String): Flow<T> {
- return if (DEBUG) {
- traceEach(flowName, logcat = true, traceEmissionCount = true)
- } else {
- this
- }
- }
-
- /**
- * Maps a set of T to a set of V, minimizing the number of `createValue` calls taking into
- * account the diff between each root flow emission.
- *
- * This is needed to minimize the number of [getDisplayFromDisplayManager] in this class. Note
- * that if the [createValue] returns a null element, it will not be added in the output set.
- */
- private fun <T, V> Flow<Set<T>>.mapElementsLazily(createValue: (T) -> V?): Flow<Set<V>> {
- data class State<T, V>(
- val previousSet: Set<T>,
- // Caches T values from the previousSet that were already converted to V
- val valueMap: Map<T, V>,
- val resultSet: Set<V>,
- )
-
- val emptyInitialState = State(emptySet<T>(), emptyMap(), emptySet<V>())
- return this.scan(emptyInitialState) { state, currentSet ->
- if (currentSet == state.previousSet) {
- state
- } else {
- val removed = state.previousSet - currentSet
- val added = currentSet - state.previousSet
- val newMap = state.valueMap.toMutableMap()
-
- added.forEach { key -> createValue(key)?.let { newMap[key] = it } }
- removed.forEach { key -> newMap.remove(key) }
-
- val resultSet = newMap.values.toSet()
- State(currentSet, newMap, resultSet)
- }
- }
- .filter { it != emptyInitialState }
- .map { it.resultSet }
- }
+ private val displayRepositoryFromLib: com.android.app.displaylib.DisplayRepository,
+) : DisplayRepositoryFromLib by displayRepositoryFromLib, DisplayRepository {
private val decorationEvents: Flow<Event> = callbackFlow {
val callback =
@@ -481,20 +106,5 @@ constructor(
private companion object {
const val TAG = "DisplayRepository"
- val DEBUG = Log.isLoggable(TAG, Log.DEBUG) || Compile.IS_DEBUG
}
}
-
-/** Used to provide default implementations for all methods. */
-private interface DisplayConnectionListener : DisplayListener {
-
- override fun onDisplayConnected(id: Int) {}
-
- override fun onDisplayDisconnected(id: Int) {}
-
- override fun onDisplayAdded(id: Int) {}
-
- override fun onDisplayRemoved(id: Int) {}
-
- override fun onDisplayChanged(id: Int) {}
-}
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepoDumpHelper.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepoDumpHelper.kt
new file mode 100644
index 000000000000..212d55612935
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepoDumpHelper.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.systemui.display.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.dump.DumpableFromToString
+import javax.inject.Inject
+
+/** Helper class to register PerDisplayRepository in the dump manager in SystemUI. */
+@SysUISingleton
+class PerDisplayRepoDumpHelper @Inject constructor(private val dumpManager: DumpManager) :
+ PerDisplayRepository.InitCallback {
+ /**
+ * Registers PerDisplayRepository in the dump manager.
+ *
+ * The repository will be identified by the given debug name.
+ */
+ override fun onInit(debugName: String, instance: Any) {
+ dumpManager.registerNormalDumpable(
+ "PerDisplayRepository-$debugName",
+ DumpableFromToString(instance),
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt
index d1d013542fbf..7e00c60dc43a 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt
@@ -20,13 +20,10 @@ import android.util.Log
import android.view.Display
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.app.tracing.traceSection
-import com.android.systemui.Dumpable
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.dump.DumpManager
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
-import java.io.PrintWriter
import java.util.concurrent.ConcurrentHashMap
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.collectLatest
@@ -88,6 +85,20 @@ interface PerDisplayRepository<T> {
/** Debug name for this repository, mainly for tracing and logging. */
val debugName: String
+
+ /**
+ * Callback to run when a given repository is initialized.
+ *
+ * This allows the caller to perform custom logic when the repository is ready to be used, e.g.
+ * register to dumpManager.
+ *
+ * Note that the instance is *leaked* outside of this class, so it should only be done when
+ * repository is meant to live as long as the caller. In systemUI this is ok because the
+ * repository lives as long as the process itself.
+ */
+ interface InitCallback {
+ fun onInit(debugName: String, instance: Any)
+ }
}
/**
@@ -110,8 +121,8 @@ constructor(
@Assisted private val instanceProvider: PerDisplayInstanceProvider<T>,
@Background private val backgroundApplicationScope: CoroutineScope,
private val displayRepository: DisplayRepository,
- private val dumpManager: DumpManager,
-) : PerDisplayRepository<T>, Dumpable {
+ private val initCallback: PerDisplayRepository.InitCallback,
+) : PerDisplayRepository<T> {
private val perDisplayInstances = ConcurrentHashMap<Int, T?>()
@@ -120,7 +131,7 @@ constructor(
}
private suspend fun start() {
- dumpManager.registerNormalDumpable("PerDisplayRepository-${debugName}", this)
+ initCallback.onInit(debugName, this)
displayRepository.displayIds.collectLatest { displayIds ->
val toRemove = perDisplayInstances.keys - displayIds
toRemove.forEach { displayId ->
@@ -169,8 +180,9 @@ constructor(
private const val TAG = "PerDisplayInstanceRepo"
}
- override fun dump(pw: PrintWriter, args: Array<out String>) {
- pw.println(perDisplayInstances)
+ override fun toString(): String {
+ return "PerDisplayInstanceRepositoryImpl(" +
+ "debugName='$debugName', instances=$perDisplayInstances)"
}
}
@@ -193,6 +205,7 @@ class DefaultDisplayOnlyInstanceRepositoryImpl<T>(
private val lazyDefaultDisplayInstance by lazy {
instanceProvider.createInstance(Display.DEFAULT_DISPLAY)
}
+
override fun get(displayId: Int): T? = lazyDefaultDisplayInstance
}
diff --git a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
index 15a3cbdb8072..84f103e8f730 100644
--- a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
@@ -18,6 +18,7 @@ package com.android.systemui.display.domain.interactor
import android.companion.virtual.VirtualDeviceManager
import android.view.Display
+import com.android.app.displaylib.DisplayRepository as DisplayRepositoryFromLib
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DeviceStateRepository
@@ -138,7 +139,8 @@ constructor(
.distinctUntilChanged()
.flowOn(backgroundCoroutineDispatcher)
- private fun DisplayRepository.PendingDisplay.toInteractorPendingDisplay(): PendingDisplay =
+ private fun DisplayRepositoryFromLib.PendingDisplay.toInteractorPendingDisplay():
+ PendingDisplay =
object : PendingDisplay {
override suspend fun enable() = this@toInteractorPendingDisplay.enable()
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/DisplayEvent.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpableFromToString.kt
index 626a68f6a59d..438931a1962d 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/DisplayEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpableFromToString.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * 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.
@@ -14,11 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.display.data
+package com.android.systemui.dump
-sealed interface DisplayEvent {
- val displayId: Int
- data class Added(override val displayId: Int) : DisplayEvent
- data class Removed(override val displayId: Int) : DisplayEvent
- data class Changed(override val displayId: Int) : DisplayEvent
+import com.android.systemui.Dumpable
+import java.io.PrintWriter
+
+/** Dumpable implementation that just calls toString() on the instance. */
+class DumpableFromToString<T>(private val instance: T) : Dumpable {
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("$instance")
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index cf5c3402792e..f2a10cc43fd9 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -147,14 +147,14 @@ import com.android.systemui.util.RingerModeTracker;
import com.android.systemui.util.settings.GlobalSettings;
import com.android.systemui.util.settings.SecureSettings;
+import dagger.Lazy;
+
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import javax.inject.Inject;
-import dagger.Lazy;
-
/**
* Helper to show the global actions dialog. Each item is an {@link Action} that may show depending
* on whether the keyguard is showing, and whether the device is provisioned.
@@ -270,6 +270,16 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
private final UserLogoutInteractor mLogoutInteractor;
private final GlobalActionsInteractor mInteractor;
private final Lazy<DisplayWindowPropertiesRepository> mDisplayWindowPropertiesRepositoryLazy;
+ private final Handler mHandler;
+
+ private final UserTracker.Callback mOnUserSwitched = new UserTracker.Callback() {
+ @Override
+ public void onBeforeUserSwitching(int newUser) {
+ // Dismiss the dialog as soon as we start switching. This will schedule a message
+ // in a handler so it will be pretty quick.
+ dismissDialog();
+ }
+ };
@VisibleForTesting
public enum GlobalActionsEvent implements UiEventLogger.UiEventEnum {
@@ -425,6 +435,29 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
mInteractor = interactor;
mDisplayWindowPropertiesRepositoryLazy = displayWindowPropertiesRepository;
+ mHandler = new Handler(mMainHandler.getLooper()) {
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MESSAGE_DISMISS:
+ if (mDialog != null) {
+ if (SYSTEM_DIALOG_REASON_DREAM.equals(msg.obj)) {
+ // Hide instantly.
+ mDialog.hide();
+ mDialog.dismiss();
+ } else {
+ mDialog.dismiss();
+ }
+ mDialog = null;
+ }
+ break;
+ case MESSAGE_REFRESH:
+ refreshSilentMode();
+ mAdapter.notifyDataSetChanged();
+ break;
+ }
+ }
+ };
+
// receive broadcasts
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
@@ -537,6 +570,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
expandable != null ? expandable.dialogTransitionController(
new DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
INTERACTION_JANK_TAG)) : null;
+ mUserTracker.addCallback(mOnUserSwitched, mBackgroundExecutor);
if (controller != null) {
mDialogTransitionAnimator.show(mDialog, controller);
} else {
@@ -1404,6 +1438,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
mWindowManagerFuncs.onGlobalActionsHidden();
mLifecycle.setCurrentState(Lifecycle.State.CREATED);
mInteractor.onDismissed();
+ mUserTracker.removeCallback(mOnUserSwitched);
}
/**
@@ -2228,29 +2263,6 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
mDialogPressDelay = 0; // ms
}
- private Handler mHandler = new Handler() {
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MESSAGE_DISMISS:
- if (mDialog != null) {
- if (SYSTEM_DIALOG_REASON_DREAM.equals(msg.obj)) {
- // Hide instantly.
- mDialog.hide();
- mDialog.dismiss();
- } else {
- mDialog.dismiss();
- }
- mDialog = null;
- }
- break;
- case MESSAGE_REFRESH:
- refreshSilentMode();
- mAdapter.notifyDataSetChanged();
- break;
- }
- }
- };
-
private void onAirplaneModeChanged() {
// Let the service state callbacks handle the state.
if (mHasTelephony || mAirplaneModeOn == null) return;
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt
index cedf661d8ee3..5b65531cdd55 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt
@@ -127,7 +127,6 @@ open class SeekBarObserver(private val holder: MediaViewHolder) :
}
holder.seekBar.setMax(data.duration)
- val totalTimeDescription = data.durationDescription
if (data.scrubbing) {
holder.scrubbingTotalTimeView.text = formatTimeLabel(data.duration)
}
@@ -147,17 +146,9 @@ open class SeekBarObserver(private val holder: MediaViewHolder) :
}
}
- val elapsedTimeDescription = data.elapsedTimeDescription
if (data.scrubbing) {
holder.scrubbingElapsedTimeView.text = formatTimeLabel(it)
}
-
- holder.seekBar.contentDescription =
- holder.seekBar.context.getString(
- R.string.controls_media_seekbar_description,
- elapsedTimeDescription,
- totalTimeDescription,
- )
}
}
@@ -166,6 +157,18 @@ open class SeekBarObserver(private val holder: MediaViewHolder) :
return DateUtils.formatElapsedTime(milliseconds / DateUtils.SECOND_IN_MILLIS)
}
+ fun updateContentDescription(
+ elapsedTimeDescription: CharSequence,
+ durationDescription: CharSequence,
+ ) {
+ holder.seekBar.contentDescription =
+ holder.seekBar.context.getString(
+ R.string.controls_media_seekbar_description,
+ elapsedTimeDescription,
+ durationDescription,
+ )
+ }
+
@VisibleForTesting
open fun buildResetAnimator(targetTime: Int): Animator {
val animator =
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
index 006eb203a669..f69985ee5364 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
@@ -224,6 +224,8 @@ public class MediaControlPanel {
this::setIsScrubbing;
private final SeekBarViewModel.EnabledChangeListener mEnabledChangeListener =
this::setIsSeekBarEnabled;
+ private final SeekBarViewModel.ContentDescriptionListener mContentDescriptionListener =
+ this::setSeekbarContentDescription;
private final BroadcastDialogController mBroadcastDialogController;
private boolean mIsCurrentBroadcastedApp = false;
@@ -327,6 +329,7 @@ public class MediaControlPanel {
}
mSeekBarViewModel.removeScrubbingChangeListener(mScrubbingChangeListener);
mSeekBarViewModel.removeEnabledChangeListener(mEnabledChangeListener);
+ mSeekBarViewModel.removeContentDescriptionListener(mContentDescriptionListener);
mSeekBarViewModel.onDestroy();
mMediaViewController.onDestroy();
}
@@ -395,6 +398,10 @@ public class MediaControlPanel {
});
}
+ private void setSeekbarContentDescription(CharSequence elapsedTime, CharSequence duration) {
+ mSeekBarObserver.updateContentDescription(elapsedTime, duration);
+ }
+
/**
* Reloads animator duration scale.
*/
@@ -424,6 +431,7 @@ public class MediaControlPanel {
mSeekBarViewModel.attachTouchHandlers(vh.getSeekBar());
mSeekBarViewModel.setScrubbingChangeListener(mScrubbingChangeListener);
mSeekBarViewModel.setEnabledChangeListener(mEnabledChangeListener);
+ mSeekBarViewModel.setContentDescriptionListener(mContentDescriptionListener);
mMediaViewController.attach(player);
vh.getPlayer().setOnLongClickListener(v -> {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
index dba190022c8b..e87d5de56177 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
@@ -229,6 +229,20 @@ constructor(
}
}
+ private val seekbarDescriptionListener =
+ object : SeekBarViewModel.ContentDescriptionListener {
+ override fun onContentDescriptionChanged(
+ elapsedTimeDescription: CharSequence,
+ durationDescription: CharSequence,
+ ) {
+ if (!SceneContainerFlag.isEnabled) return
+ seekBarObserver.updateContentDescription(
+ elapsedTimeDescription,
+ durationDescription,
+ )
+ }
+ }
+
/**
* Sets the listening state of the player.
*
@@ -350,6 +364,7 @@ constructor(
}
seekBarViewModel.removeScrubbingChangeListener(scrubbingChangeListener)
seekBarViewModel.removeEnabledChangeListener(enabledChangeListener)
+ seekBarViewModel.removeContentDescriptionListener(seekbarDescriptionListener)
seekBarViewModel.onDestroy()
}
mediaHostStatesManager.removeController(this)
@@ -653,6 +668,7 @@ constructor(
seekBarViewModel.attachTouchHandlers(mediaViewHolder.seekBar)
seekBarViewModel.setScrubbingChangeListener(scrubbingChangeListener)
seekBarViewModel.setEnabledChangeListener(enabledChangeListener)
+ seekBarViewModel.setContentDescriptionListener(seekbarDescriptionListener)
val mediaCard = mediaViewHolder.player
attach(mediaViewHolder.player)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModel.kt
index 8744c5c9a838..78a8cf8e9432 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModel.kt
@@ -104,19 +104,20 @@ constructor(
)
set(value) {
val enabledChanged = value.enabled != field.enabled
+ field = value
if (enabledChanged) {
enabledChangeListener?.onEnabledChanged(value.enabled)
}
+ _progress.postValue(value)
+
bgExecutor.execute {
val durationDescription = formatTimeContentDescription(value.duration)
val elapsedDescription =
value.elapsedTime?.let { formatTimeContentDescription(it) } ?: ""
- field =
- value.copy(
- durationDescription = durationDescription,
- elapsedTimeDescription = elapsedDescription,
- )
- _progress.postValue(field)
+ contentDescriptionListener?.onContentDescriptionChanged(
+ elapsedDescription,
+ durationDescription,
+ )
}
}
@@ -175,6 +176,7 @@ constructor(
private var scrubbingChangeListener: ScrubbingChangeListener? = null
private var enabledChangeListener: EnabledChangeListener? = null
+ private var contentDescriptionListener: ContentDescriptionListener? = null
/** Set to true when the user is touching the seek bar to change the position. */
private var scrubbing = false
@@ -394,6 +396,16 @@ constructor(
}
}
+ fun setContentDescriptionListener(listener: ContentDescriptionListener) {
+ contentDescriptionListener = listener
+ }
+
+ fun removeContentDescriptionListener(listener: ContentDescriptionListener) {
+ if (listener == contentDescriptionListener) {
+ contentDescriptionListener = null
+ }
+ }
+
/** returns a pair of whether seekbar is enabled and the duration of media. */
private fun getEnabledStateAndDuration(metadata: MediaMetadata?): Pair<Boolean, Int> {
val duration = metadata?.getLong(MediaMetadata.METADATA_KEY_DURATION)?.toInt() ?: 0
@@ -468,6 +480,13 @@ constructor(
fun onEnabledChanged(enabled: Boolean)
}
+ interface ContentDescriptionListener {
+ fun onContentDescriptionChanged(
+ elapsedTimeDescription: CharSequence,
+ durationDescription: CharSequence,
+ )
+ }
+
private class SeekBarChangeListener(
val viewModel: SeekBarViewModel,
val falsingManager: FalsingManager,
@@ -639,7 +658,5 @@ constructor(
val duration: Int,
/** whether seekBar is listening to progress updates */
val listening: Boolean,
- val elapsedTimeDescription: CharSequence = "",
- val durationDescription: CharSequence = "",
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
index 22971a9eb703..a7ebb2289814 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
@@ -88,7 +88,11 @@ constructor(
LaunchedEffect(listening, pagerState) {
snapshotFlow { listening() }
.collect {
- if (!listening()) {
+ // Whenever we go from not listening to listening, we should be in the first
+ // page. If we did this when going from listening to not listening, opening
+ // edit mode in second page will cause it to go to first page during the
+ // transition.
+ if (listening()) {
pagerState.scrollToPage(0)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
index 3c00d5069bc7..6bafd432669a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
@@ -76,6 +76,7 @@ import androidx.compose.ui.util.trace
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.compose.animation.Expandable
import com.android.compose.animation.bounceable
+import com.android.compose.animation.rememberExpandableController
import com.android.compose.modifiers.thenIf
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.Flags
@@ -265,8 +266,7 @@ private fun TileExpandable(
content: @Composable (Expandable) -> Unit,
) {
Expandable(
- color = color(),
- shape = shape,
+ controller = rememberExpandableController(color = color, shape = shape),
modifier = modifier.clip(shape).verticalSquish(squishiness),
useModifierBasedImplementation = true,
) {
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/BundleEntryAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt
index 26c302bf6409..b1a26af336d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt
@@ -113,6 +113,10 @@ class BundleEntryAdapter(val entry: BundleEntry) : EntryAdapter {
return false
}
+ override fun isPromotedOngoing(): Boolean {
+ return false
+ }
+
override fun isFullScreenCapable(): Boolean {
return false
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java
index 3118ce56ac69..4299825bd5e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java
@@ -132,6 +132,11 @@ public interface EntryAdapter {
boolean isAmbient();
+ /**
+ * Returns whether this row represents promoted ongoing notification.
+ */
+ boolean isPromotedOngoing();
+
default boolean isFullScreenCapable() {
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt
index 1168c647c26a..345b6aae9673 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt
@@ -137,6 +137,10 @@ class NotificationEntryAdapter(
return entry.ranking.isAmbient
}
+ override fun isPromotedOngoing(): Boolean {
+ return entry.isPromotedOngoing
+ }
+
override fun isFullScreenCapable(): Boolean {
return entry.sbn.notification.fullScreenIntent != null
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
index 8021d8f58ccc..a552ca554fd5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
@@ -234,21 +234,21 @@ private class ShadeNode(val controller: NodeController) {
fun getChildCount(): Int = controller.getChildCount()
fun addChildAt(child: ShadeNode, index: Int) {
- traceSection("ShadeNode#addChildAt") {
+ traceSection({ "ShadeNode#${controller::class.simpleName}#addChildAt" }) {
controller.addChildAt(child.controller, index)
child.controller.onViewAdded()
}
}
fun moveChildTo(child: ShadeNode, index: Int) {
- traceSection("ShadeNode#moveChildTo") {
+ traceSection({ "ShadeNode#${controller::class.simpleName}#moveChildTo" }) {
controller.moveChildTo(child.controller, index)
child.controller.onViewMoved()
}
}
fun removeChild(child: ShadeNode, isTransfer: Boolean) {
- traceSection("ShadeNode#removeChild") {
+ traceSection({ "ShadeNode#${controller::class.simpleName}#removeChild" }) {
controller.removeChild(child.controller, isTransfer)
child.controller.onViewRemoved()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
index 2a01a14f56aa..777392df67cc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
@@ -19,19 +19,26 @@ package com.android.systemui.statusbar.notification.promoted
import android.app.Flags
import android.app.Flags.notificationsRedesignTemplates
import android.app.Notification
+import android.content.Context
import android.graphics.PorterDuff
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.View.GONE
+import android.view.View.MeasureSpec.AT_MOST
+import android.view.View.MeasureSpec.EXACTLY
+import android.view.View.MeasureSpec.UNSPECIFIED
+import android.view.View.MeasureSpec.makeMeasureSpec
import android.view.View.VISIBLE
import android.view.ViewGroup.MarginLayoutParams
import android.view.ViewStub
import android.widget.Chronometer
import android.widget.DateTimeView
+import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.TextView
+import androidx.annotation.DimenRes
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
@@ -42,7 +49,9 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.key
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.view.isVisible
@@ -88,22 +97,12 @@ fun AODPromotedNotification(
}
key(content.identity) {
- val sidePaddings = dimensionResource(systemuiR.dimen.notification_side_paddings)
- val sidePaddingValues = PaddingValues(horizontal = sidePaddings, vertical = 0.dp)
-
- val borderStroke = BorderStroke(1.dp, SecondaryText.brush)
-
- val borderRadius = dimensionResource(systemuiR.dimen.notification_corner_radius)
- val borderShape = RoundedCornerShape(borderRadius)
-
- Box(modifier = modifier.padding(sidePaddingValues)) {
- AODPromotedNotificationView(
- layoutResource = layoutResource,
- content = content,
- audiblyAlertedIconVisible = audiblyAlertedIconVisible,
- modifier = Modifier.border(borderStroke, borderShape),
- )
- }
+ AODPromotedNotificationView(
+ layoutResource = layoutResource,
+ content = content,
+ audiblyAlertedIconVisible = audiblyAlertedIconVisible,
+ modifier = modifier,
+ )
}
}
@@ -114,27 +113,91 @@ fun AODPromotedNotificationView(
audiblyAlertedIconVisible: Boolean,
modifier: Modifier = Modifier,
) {
- AndroidView(
- factory = { context ->
- val view =
- traceSection("$TAG.inflate") {
- LayoutInflater.from(context).inflate(layoutResource, /* root= */ null)
- }
-
- val updater =
- traceSection("$TAG.findViews") { AODPromotedNotificationViewUpdater(view) }
-
- view.setTag(viewUpdaterTagId, updater)
-
- view
- },
- update = { view ->
- val updater = view.getTag(viewUpdaterTagId) as AODPromotedNotificationViewUpdater
-
- traceSection("$TAG.update") { updater.update(content, audiblyAlertedIconVisible) }
- },
- modifier = modifier,
- )
+ val sidePaddings = dimensionResource(systemuiR.dimen.notification_side_paddings)
+ val sidePaddingValues = PaddingValues(horizontal = sidePaddings, vertical = 0.dp)
+
+ val boxModifier = modifier.padding(sidePaddingValues)
+
+ val borderStroke = BorderStroke(1.dp, SecondaryText.brush)
+
+ val borderRadius = dimensionResource(systemuiR.dimen.notification_corner_radius)
+ val borderShape = RoundedCornerShape(borderRadius)
+
+ val maxHeight =
+ with(LocalDensity.current) {
+ scaledFontHeight(systemuiR.dimen.notification_max_height_for_promoted_ongoing)
+ .toPx()
+ }
+ .toInt()
+
+ val viewModifier = Modifier.border(borderStroke, borderShape)
+
+ Box(modifier = boxModifier) {
+ AndroidView(
+ factory = { context ->
+ val notif =
+ traceSection("$TAG.inflate") {
+ LayoutInflater.from(context).inflate(layoutResource, /* root= */ null)
+ }
+ val updater =
+ traceSection("$TAG.findViews") { AODPromotedNotificationViewUpdater(notif) }
+
+ val frame = FrameLayoutWithMaxHeight(maxHeight, context)
+ frame.addView(notif)
+ frame.setTag(viewUpdaterTagId, updater)
+
+ frame
+ },
+ update = { frame ->
+ val updater = frame.getTag(viewUpdaterTagId) as AODPromotedNotificationViewUpdater
+
+ traceSection("$TAG.update") { updater.update(content, audiblyAlertedIconVisible) }
+ frame.maxHeight = maxHeight
+ },
+ modifier = viewModifier,
+ )
+ }
+}
+
+private class FrameLayoutWithMaxHeight(maxHeight: Int, context: Context) : FrameLayout(context) {
+ var maxHeight = maxHeight
+ set(value) {
+ if (field != value) {
+ field = value
+ requestLayout()
+ }
+ }
+
+ // This mirrors the logic in NotificationContentView.onMeasure.
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ if (childCount < 1) {
+ return
+ }
+
+ val child = getChildAt(0)
+ val childLayoutHeight = child.layoutParams.height
+ val childHeightSpec =
+ if (childLayoutHeight >= 0) {
+ makeMeasureSpec(maxHeight.coerceAtMost(childLayoutHeight), EXACTLY)
+ } else {
+ makeMeasureSpec(maxHeight, AT_MOST)
+ }
+ measureChildWithMargins(child, widthMeasureSpec, 0, childHeightSpec, 0)
+ val childMeasuredHeight = child.measuredHeight
+
+ val ownHeightMode = MeasureSpec.getMode(heightMeasureSpec)
+ val ownHeightSize = MeasureSpec.getSize(heightMeasureSpec)
+
+ val ownMeasuredWidth = MeasureSpec.getSize(widthMeasureSpec)
+ val ownMeasuredHeight =
+ if (ownHeightMode != UNSPECIFIED) {
+ childMeasuredHeight.coerceAtMost(ownHeightSize)
+ } else {
+ childMeasuredHeight
+ }
+
+ setMeasuredDimension(ownMeasuredWidth, ownMeasuredHeight)
+ }
}
private val PromotedNotificationContentModel.layoutResource: Int?
@@ -521,6 +584,11 @@ private enum class AodPromotedNotificationColor(val colorInt: Int) {
val brush = SolidColor(androidx.compose.ui.graphics.Color(colorInt))
}
+@Composable
+private fun scaledFontHeight(@DimenRes dimenId: Int): Dp {
+ return dimensionResource(dimenId) * LocalDensity.current.fontScale.coerceAtLeast(1f)
+}
+
private val viewUpdaterTagId = systemuiR.id.aod_promoted_notification_view_updater_tag
private const val TAG = "AODPromotedNotification"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 689222608abe..e76867373139 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -40,6 +40,7 @@ import android.view.animation.Interpolator;
import com.android.app.animation.Interpolators;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.jank.InteractionJankMonitor.Configuration;
+import com.android.systemui.Flags;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.common.shared.colors.SurfaceEffectColors;
import com.android.systemui.res.R;
@@ -101,7 +102,8 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
private ValueAnimator mBackgroundColorAnimator;
private float mAppearAnimationFraction = -1.0f;
private float mAppearAnimationTranslation;
- private int mNormalColor;
+ protected int mNormalColor;
+ protected int mOpaqueColor;
private boolean mIsBelowSpeedBump;
private long mLastActionUpTime;
@@ -130,17 +132,13 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
protected void updateColors() {
if (notificationRowTransparency()) {
- if (mIsBlurSupported) {
- mNormalColor = SurfaceEffectColors.surfaceEffect1(getContext());
- } else {
- mNormalColor = mContext.getColor(
- com.android.internal.R.color.materialColorSurfaceContainer);
- }
+ mNormalColor = SurfaceEffectColors.surfaceEffect1(getContext());
+ mOpaqueColor = mContext.getColor(
+ com.android.internal.R.color.materialColorSurfaceContainer);
} else {
mNormalColor = mContext.getColor(
com.android.internal.R.color.materialColorSurfaceContainerHigh);
}
- setBackgroundToNormalColor();
mTintedRippleColor = mContext.getColor(
R.color.notification_ripple_tinted_color);
mNormalRippleColor = mContext.getColor(
@@ -151,12 +149,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
mOverrideAmount = 0.0f;
}
- private void setBackgroundToNormalColor() {
- if (mBackgroundNormal != null) {
- mBackgroundNormal.setNormalColor(mNormalColor);
- }
- }
-
/**
* Reload background colors from resources and invalidate views.
*/
@@ -186,7 +178,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
mBackgroundNormal = findViewById(R.id.backgroundNormal);
mFakeShadow = findViewById(R.id.fake_shadow);
mShadowHidden = mFakeShadow.getVisibility() != VISIBLE;
- setBackgroundToNormalColor();
initBackground();
updateBackgroundTint();
updateOutlineAlpha();
@@ -352,7 +343,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
}
protected boolean usesTransparentBackground() {
- return mIsBlurSupported && notificationRowTransparency();
+ return false;
}
@Override
@@ -709,7 +700,11 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
if (withTint && mBgTint != NO_COLOR) {
return mBgTint;
} else {
- return mNormalColor;
+ if (Flags.notificationRowTransparency()) {
+ return usesTransparentBackground() ? mNormalColor : mOpaqueColor;
+ } else {
+ return mNormalColor;
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 8da2f768bf71..76830646587d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -82,6 +82,7 @@ import androidx.dynamicanimation.animation.SpringAnimation;
import com.android.app.animation.Interpolators;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.graphics.ColorUtils;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -979,7 +980,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
} else if (isAboveShelf() != wasAboveShelf) {
mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);
}
- updateColors();
+ updateBackgroundTint();
}
/**
@@ -1678,20 +1679,34 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Override
protected void setBackgroundTintColor(int color) {
- super.setBackgroundTintColor(color);
- NotificationContentView view = getShowingLayout();
- if (view != null) {
- view.setBackgroundTintColor(color);
- }
- if (notificationRowTransparency() && mBackgroundNormal != null) {
+ if (notificationRowTransparency()) {
+ boolean isColorized = false;
if (NotificationBundleUi.isEnabled() && mEntryAdapter != null) {
- mBackgroundNormal.setBgIsColorized(mEntryAdapter.isColorized());
+ isColorized = mEntryAdapter.isColorized();
} else {
if (mEntry != null) {
- mBackgroundNormal.setBgIsColorized(
- mEntry.getSbn().getNotification().isColorized());
+ isColorized = mEntry.getSbn().getNotification().isColorized();
}
}
+ boolean isTransparent = usesTransparentBackground();
+ if (isColorized) {
+ // For colorized notifications, use a color that matches the tint color at 90% alpha
+ // when the row is transparent.
+ color = ColorUtils.setAlphaComponent(
+ color, (int) (0xFF * (isTransparent ? 0.9f : 1)));
+ } else {
+ // For non-colorized notifications, use the semi-transparent normal color token
+ // when the row is transparent, and the opaque color token otherwise.
+ if (!isTransparent && mBgTint == NO_COLOR) {
+ color = mOpaqueColor;
+ }
+ }
+ }
+
+ super.setBackgroundTintColor(color);
+ NotificationContentView view = getShowingLayout();
+ if (view != null) {
+ view.setBackgroundTintColor(color);
}
}
@@ -3113,7 +3128,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mChildrenContainer.setOnKeyguard(onKeyguard);
}
}
- updateColors();
+ updateBackgroundTint();
}
}
@@ -3243,12 +3258,19 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
return false;
}
- final NotificationEntry entry = mEntry;
- if (entry == null) {
- return false;
+ if (NotificationBundleUi.isEnabled()) {
+ final EntryAdapter entryAdapter = mEntryAdapter;
+ if (entryAdapter == null) {
+ return false;
+ }
+ return entryAdapter.isPromotedOngoing();
+ } else {
+ final NotificationEntry entry = mEntry;
+ if (entry == null) {
+ return false;
+ }
+ return entry.isPromotedOngoing();
}
-
- return entry.isPromotedOngoing();
}
private boolean isPromotedNotificationExpanded(boolean allowOnKeyguard) {
@@ -4633,6 +4655,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Override
protected boolean usesTransparentBackground() {
- return super.usesTransparentBackground() && !mIsHeadsUp && !mOnKeyguard;
+ // Row background should be opaque when it's displayed as a heads-up notification or
+ // displayed on keyguard.
+ // TODO(b/388891313): Account for isBlurSupported when it is initialized and updated
+ // correctly.
+ return notificationRowTransparency() && !mIsHeadsUp && !mOnKeyguard;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index f36a0cf51b97..292f74a65554 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -19,8 +19,6 @@ package com.android.systemui.statusbar.notification.row;
import static com.android.systemui.Flags.notificationColorUpdateLogger;
import static com.android.systemui.Flags.physicalNotificationMovement;
-import static java.lang.Math.abs;
-
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.content.res.Configuration;
@@ -31,7 +29,6 @@ import android.util.FloatProperty;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.view.View;
-import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.FrameLayout;
@@ -113,27 +110,14 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro
protected SpringAnimation mMagneticAnimator = new SpringAnimation(
this /* object */, DynamicAnimation.TRANSLATION_X);
- private int mTouchSlop;
-
protected MagneticRowListener mMagneticRowListener = new MagneticRowListener() {
@Override
- public void setMagneticTranslation(float translation, boolean trackEagerly) {
- if (!mMagneticAnimator.isRunning()) {
- setTranslation(translation);
- return;
- }
-
- if (trackEagerly) {
- float delta = abs(getTranslation() - translation);
- if (delta > mTouchSlop) {
- mMagneticAnimator.animateToFinalPosition(translation);
- } else {
- mMagneticAnimator.cancel();
- setTranslation(translation);
- }
- } else {
+ public void setMagneticTranslation(float translation) {
+ if (mMagneticAnimator.isRunning()) {
mMagneticAnimator.animateToFinalPosition(translation);
+ } else {
+ setTranslation(translation);
}
}
@@ -199,7 +183,6 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro
private void initDimens() {
mContentShift = getResources().getDimensionPixelSize(
R.dimen.shelf_transform_content_shift);
- mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
index 4914e1073059..e4997e4f53ad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
@@ -36,9 +36,9 @@ import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.internal.graphics.ColorUtils;
import com.android.internal.util.ContrastColorUtil;
import com.android.systemui.Dumpable;
+import com.android.systemui.common.shared.colors.SurfaceEffectColors;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.shared.NotificationAddXOnHoverToDismiss;
import com.android.systemui.util.DrawableDumpKt;
@@ -52,7 +52,6 @@ import java.util.Arrays;
public class NotificationBackgroundView extends View implements Dumpable,
ExpandableNotificationRow.DismissButtonTargetVisibilityListener {
- private static final int MAX_ALPHA = 0xFF;
private final boolean mDontModifyCorners;
private Drawable mBackground;
private int mClipTopAmount;
@@ -73,8 +72,6 @@ public class NotificationBackgroundView extends View implements Dumpable,
private final ColorStateList mLightColoredStatefulColors;
private final ColorStateList mDarkColoredStatefulColors;
private int mNormalColor;
- private boolean mBgIsColorized = false;
- private boolean mForceOpaque = false;
private final int convexR = 9;
private final int concaveR = 22;
@@ -88,13 +85,15 @@ public class NotificationBackgroundView extends View implements Dumpable,
R.color.notification_state_color_light);
mDarkColoredStatefulColors = getResources().getColorStateList(
R.color.notification_state_color_dark);
+ if (notificationRowTransparency()) {
+ mNormalColor = SurfaceEffectColors.surfaceEffect1(getContext());
+ } else {
+ mNormalColor = mContext.getColor(
+ com.android.internal.R.color.materialColorSurfaceContainerHigh);
+ }
mFocusOverlayStroke = getResources().getDimension(R.dimen.notification_focus_stroke_width);
}
- public void setNormalColor(int color) {
- mNormalColor = color;
- }
-
@Override
public void onTargetVisibilityChanged(boolean targetVisible) {
if (NotificationAddXOnHoverToDismiss.isUnexpectedlyInLegacyMode()) {
@@ -140,21 +139,6 @@ public class NotificationBackgroundView extends View implements Dumpable,
}
}
- /**
- * A way to tell whether the background has been colorized.
- */
- public boolean isColorized() {
- return mBgIsColorized;
- }
-
- /**
- * A way to inform this class whether the background has been colorized.
- * We need to know this, in order to *not* override that color.
- */
- public void setBgIsColorized(boolean b) {
- mBgIsColorized = b;
- }
-
private Path calculateDismissButtonCutoutPath(Rect backgroundBounds) {
// TODO(b/365585705): Adapt to RTL after the UX design is finalized.
@@ -311,28 +295,21 @@ public class NotificationBackgroundView extends View implements Dumpable,
return ((LayerDrawable) mBackground).getDrawable(1);
}
- private void updateBaseLayerColor() {
- // BG base layer being a drawable, there isn't a method like setColor() to color it.
- // Instead, we set a color filter that essentially replaces every pixel of the drawable.
- // For non-colorized notifications, this function specifies a new color token.
- // For colorized notifications, this uses a color that matches the tint color at 90% alpha.
- int color = isColorized()
- ? ColorUtils.setAlphaComponent(mTintColor, (int) (MAX_ALPHA * 0.9f))
- : mNormalColor;
- getBaseBackgroundLayer().setColorFilter(
- new PorterDuffColorFilter(
- color,
- PorterDuff.Mode.SRC)); // SRC operator discards the drawable's color+alpha
- }
-
public void setTint(int tintColor) {
Drawable baseLayer = getBaseBackgroundLayer();
- baseLayer.mutate().setTintMode(PorterDuff.Mode.SRC_ATOP);
- baseLayer.setTint(tintColor);
- mTintColor = tintColor;
if (notificationRowTransparency()) {
- updateBaseLayerColor();
+ // BG base layer being a drawable, there isn't a method like setColor() to color it.
+ // Instead, we set a color filter that essentially replaces every pixel of the drawable.
+ baseLayer.setColorFilter(
+ new PorterDuffColorFilter(
+ tintColor,
+ // SRC operator discards the drawable's color+alpha
+ PorterDuff.Mode.SRC));
+ } else {
+ baseLayer.mutate().setTintMode(PorterDuff.Mode.SRC_ATOP);
+ baseLayer.setTint(tintColor);
}
+ mTintColor = tintColor;
setStatefulColors();
invalidate();
}
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 b3357d01ab7a..8262f4cdc16b 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
@@ -269,6 +269,7 @@ public class NotificationContentView extends FrameLayout implements Notification
mNotificationMaxHeight = maxHeight;
}
+ // This logic is mirrored in FrameLayoutWithMaxHeight.onMeasure in AODPromotedNotification.kt.
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
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 869b75ea070e..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
@@ -430,6 +430,7 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta
NotificationBundleUi.isEnabled()
? !row.getEntry().isBlockable()
: row.getIsNonblockable(),
+ row.canViewBeDismissed(),
mHighPriorityProvider.isHighPriority(row.getEntry()),
mAssistantFeedbackController,
mMetricsLogger,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
index 904911fcf545..b6f4ffce8e00 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
@@ -121,6 +121,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
private boolean mIsAutomaticChosen;
private boolean mIsSingleDefaultChannel;
private boolean mIsNonblockable;
+ private boolean mIsDismissable;
private NotificationEntry mEntry;
private StatusBarNotification mSbn;
private boolean mIsDeviceProvisioned;
@@ -161,6 +162,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
mPressedApply = true;
mGutsContainer.closeControls(v, /* save= */ true);
};
+ private OnClickListener mOnCloseClickListener;
public NotificationInfo(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -204,6 +206,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
UiEventLogger uiEventLogger,
boolean isDeviceProvisioned,
boolean isNonblockable,
+ boolean isDismissable,
boolean wasShownHighPriority,
AssistantFeedbackController assistantFeedbackController,
MetricsLogger metricsLogger,
@@ -228,11 +231,13 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
mStartingChannelImportance = mSingleNotificationChannel.getImportance();
mWasShownHighPriority = wasShownHighPriority;
mIsNonblockable = isNonblockable;
+ mIsDismissable = isDismissable;
mAppUid = mSbn.getUid();
mDelegatePkg = mSbn.getOpPkg();
mIsDeviceProvisioned = isDeviceProvisioned;
mShowAutomaticSetting = mAssistantFeedbackController.isFeedbackEnabled();
mUiEventLogger = uiEventLogger;
+ mOnCloseClickListener = onCloseClick;
mIsSystemRegisteredCall = mSbn.getNotification().isStyle(Notification.CallStyle.class)
&& mINotificationManager.isInCall(mSbn.getPackageName(), mSbn.getUid());
@@ -279,6 +284,11 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
turnOffButton.setVisibility(turnOffButton.hasOnClickListeners() && !mIsNonblockable
? VISIBLE : GONE);
+ View dismissButton = findViewById(R.id.inline_dismiss);
+ dismissButton.setOnClickListener(mOnCloseClickListener);
+ dismissButton.setVisibility(dismissButton.hasOnClickListeners() && mIsDismissable
+ ? VISIBLE : GONE);
+
View done = findViewById(R.id.done);
done.setOnClickListener(mOnDismissSettings);
done.setAccessibilityDelegate(mGutsContainer.getAccessibilityDelegate());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index 286f07b7413d..e89a76fd5a69 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -32,6 +32,7 @@ import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
+import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
import android.view.LayoutInflater;
import android.view.View;
@@ -357,9 +358,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
final float dismissThreshold = getDismissThreshold();
final boolean snappingToDismiss = delta < -dismissThreshold || delta > dismissThreshold;
if (mSnappingToDismiss != snappingToDismiss) {
- if (!Flags.magneticNotificationSwipes()) {
- getMenuView().performHapticFeedback(CLOCK_TICK);
- }
+ getMenuView().performHapticFeedback(CLOCK_TICK);
}
mSnappingToDismiss = snappingToDismiss;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java
index 39ffdf29b57c..01ee788f7fd7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java
@@ -68,6 +68,7 @@ public class PromotedNotificationInfo extends NotificationInfo {
UiEventLogger uiEventLogger,
boolean isDeviceProvisioned,
boolean isNonblockable,
+ boolean isDismissable,
boolean wasShownHighPriority,
AssistantFeedbackController assistantFeedbackController,
MetricsLogger metricsLogger, OnClickListener onCloseClick) throws RemoteException {
@@ -75,7 +76,7 @@ public class PromotedNotificationInfo extends NotificationInfo {
onUserInteractionCallback, channelEditorDialogController, packageDemotionInteractor,
pkg, notificationChannel,
entry, onSettingsClick, onAppSettingsClick, feedbackClickListener, uiEventLogger,
- isDeviceProvisioned, isNonblockable, wasShownHighPriority,
+ isDeviceProvisioned, isNonblockable, isDismissable, wasShownHighPriority,
assistantFeedbackController, metricsLogger, onCloseClick);
mNotificationManager = iNotificationManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManager.kt
index 48cff7497e3c..aa6951715755 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManager.kt
@@ -33,12 +33,12 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
interface MagneticNotificationRowManager {
/**
- * Notifies a change in the device density. The density can be used to compute the values of
- * thresholds in pixels.
+ * Set the swipe threshold in pixels. After crossing the threshold, the magnetic target detaches
+ * and the magnetic neighbors snap back.
*
- * @param[density] The device density.
+ * @param[threshold] Swipe threshold in pixels.
*/
- fun onDensityChange(density: Float)
+ fun setSwipeThresholdPx(thresholdPx: Float)
/**
* Set the magnetic and roundable targets of a magnetic swipe interaction.
@@ -87,9 +87,6 @@ interface MagneticNotificationRowManager {
*/
fun onMagneticInteractionEnd(row: ExpandableNotificationRow, velocity: Float? = null)
- /** Determine if the given [ExpandableNotificationRow] has been magnetically detached. */
- fun isMagneticRowSwipeDetached(row: ExpandableNotificationRow): Boolean
-
/* Reset any roundness that magnetic targets may have */
fun resetRoundness()
@@ -107,15 +104,12 @@ interface MagneticNotificationRowManager {
/** Detaching threshold in dp */
const val MAGNETIC_DETACH_THRESHOLD_DP = 56
- /** Re-attaching threshold in dp */
- const val MAGNETIC_ATTACH_THRESHOLD_DP = 40
-
/* An empty implementation of a manager */
@JvmStatic
val Empty: MagneticNotificationRowManager
get() =
object : MagneticNotificationRowManager {
- override fun onDensityChange(density: Float) {}
+ override fun setSwipeThresholdPx(thresholdPx: Float) {}
override fun setMagneticAndRoundableTargets(
swipingRow: ExpandableNotificationRow,
@@ -133,10 +127,6 @@ interface MagneticNotificationRowManager {
velocity: Float?,
) {}
- override fun isMagneticRowSwipeDetached(
- row: ExpandableNotificationRow
- ): Boolean = false
-
override fun resetRoundness() {}
override fun reset() {}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt
index 3f05cef6a0c6..9bd5a5bd903f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticNotificationRowManagerImpl.kt
@@ -47,7 +47,6 @@ constructor(
private set
private var magneticDetachThreshold = Float.POSITIVE_INFINITY
- private var magneticAttachThreshold = 0f
// Has the roundable target been set for the magnetic view that is being swiped.
val isSwipedViewRoundableSet: Boolean
@@ -58,18 +57,13 @@ constructor(
SpringForce().setStiffness(DETACH_STIFFNESS).setDampingRatio(DETACH_DAMPING_RATIO)
private val snapForce =
SpringForce().setStiffness(SNAP_BACK_STIFFNESS).setDampingRatio(SNAP_BACK_DAMPING_RATIO)
- private val attachForce =
- SpringForce().setStiffness(ATTACH_STIFFNESS).setDampingRatio(ATTACH_DAMPING_RATIO)
// Multiplier applied to the translation of a row while swiped
val swipedRowMultiplier =
MAGNETIC_TRANSLATION_MULTIPLIERS[MAGNETIC_TRANSLATION_MULTIPLIERS.size / 2]
- override fun onDensityChange(density: Float) {
- magneticDetachThreshold =
- density * MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP
- magneticAttachThreshold =
- density * MagneticNotificationRowManager.MAGNETIC_ATTACH_THRESHOLD_DP
+ override fun setSwipeThresholdPx(thresholdPx: Float) {
+ magneticDetachThreshold = thresholdPx
}
override fun setMagneticAndRoundableTargets(
@@ -145,7 +139,8 @@ constructor(
}
}
State.DETACHED -> {
- translateDetachedRow(translation)
+ val swiped = currentMagneticListeners.swipedListener()
+ swiped?.setMagneticTranslation(translation)
}
}
return true
@@ -237,41 +232,6 @@ constructor(
)
}
- private fun translateDetachedRow(translation: Float) {
- val targetTranslation = swipedRowMultiplier * translation
- val crossedThreshold = abs(targetTranslation) <= magneticAttachThreshold
- if (crossedThreshold) {
- attachNeighbors(translation)
- updateRoundness(translation)
- currentMagneticListeners.swipedListener()?.let { attach(it, translation) }
- currentState = State.PULLING
- } else {
- val swiped = currentMagneticListeners.swipedListener()
- swiped?.setMagneticTranslation(translation, trackEagerly = false)
- }
- }
-
- private fun attachNeighbors(translation: Float) {
- currentMagneticListeners.forEachIndexed { i, target ->
- target?.let {
- if (i != currentMagneticListeners.size / 2) {
- val multiplier = MAGNETIC_TRANSLATION_MULTIPLIERS[i]
- target.cancelMagneticAnimations()
- target.triggerMagneticForce(
- endTranslation = translation * multiplier,
- attachForce,
- )
- }
- }
- }
- }
-
- private fun attach(listener: MagneticRowListener, toPosition: Float) {
- listener.cancelMagneticAnimations()
- listener.triggerMagneticForce(toPosition, attachForce)
- msdlPlayer.playToken(MSDLToken.SWIPE_THRESHOLD_INDICATOR)
- }
-
override fun onMagneticInteractionEnd(row: ExpandableNotificationRow, velocity: Float?) {
if (row.isSwipedTarget()) {
when (currentState) {
@@ -294,9 +254,6 @@ constructor(
}
}
- override fun isMagneticRowSwipeDetached(row: ExpandableNotificationRow): Boolean =
- row.isSwipedTarget() && currentState == State.DETACHED
-
override fun resetRoundness() = notificationRoundnessManager.clear()
override fun reset() {
@@ -343,8 +300,6 @@ constructor(
private const val DETACH_DAMPING_RATIO = 0.95f
private const val SNAP_BACK_STIFFNESS = 550f
private const val SNAP_BACK_DAMPING_RATIO = 0.6f
- private const val ATTACH_STIFFNESS = 800f
- private const val ATTACH_DAMPING_RATIO = 0.95f
// Maximum value of corner roundness that gets applied during the pre-detach dragging
private const val MAX_PRE_DETACH_ROUNDNESS = 0.8f
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticRowListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticRowListener.kt
index c3b9024ad9b1..5959ef1e093b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticRowListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MagneticRowListener.kt
@@ -21,21 +21,8 @@ import androidx.dynamicanimation.animation.SpringForce
/** A listener that responds to magnetic forces applied to an [ExpandableNotificationRow] */
interface MagneticRowListener {
- /**
- * Set a translation due to a magnetic attachment.
- *
- * The request to set a View's translation may occur while a magnetic animation is running. In
- * such a case, the [trackEagerly] argument will determine if we decide to eagerly track the
- * incoming translation or not. If true, the ongoing animation will update its target position
- * and continue to animate only if the incoming translation produces a delta higher than a touch
- * slop threshold. Otherwise, the animation will be cancelled and the View translation will be
- * set directly. If [trackEagerly] is false, we respect the animation and only update the
- * animated target.
- *
- * @param[translation] Incoming gesture translation.
- * @param[trackEagerly] Whether we eagerly track the incoming translation directly or not.
- */
- fun setMagneticTranslation(translation: Float, trackEagerly: Boolean = true)
+ /** Set a translation due to a magnetic attachment. */
+ fun setMagneticTranslation(translation: Float)
/**
* Trigger the magnetic behavior when the row detaches or snaps back from its magnetic
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 2d0a1c069c5c..bb3abc1fba38 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -486,22 +486,15 @@ public class NotificationStackScrollLayoutController implements Dumpable {
}
@Override
- public boolean isMagneticViewDetached(View view) {
- if (view instanceof ExpandableNotificationRow row) {
- return mMagneticNotificationRowManager.isMagneticRowSwipeDetached(row);
- } else {
- return false;
- }
- }
-
- @Override
public float getTotalTranslationLength(View animView) {
return mView.getTotalTranslationLength(animView);
}
@Override
public void onDensityScaleChange(float density) {
- mMagneticNotificationRowManager.onDensityChange(density);
+ mMagneticNotificationRowManager.setSwipeThresholdPx(
+ density * MagneticNotificationRowManager.MAGNETIC_DETACH_THRESHOLD_DP
+ );
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
index 33b94783b24a..c5a846e1da05 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
@@ -33,7 +33,6 @@ import android.view.ViewConfiguration;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor;
-import com.android.systemui.Flags;
import com.android.systemui.SwipeHelper;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
@@ -192,11 +191,6 @@ class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeAc
@Override
public boolean handleUpEvent(MotionEvent ev, View animView, float velocity,
float translation) {
- if (Flags.magneticNotificationSwipes()
- && (mCallback.isMagneticViewDetached(animView) || swipedFastEnough())) {
- dismiss(animView, velocity);
- return true;
- }
NotificationMenuRowPlugin menuRow = getCurrentMenuRow();
if (menuRow != null) {
menuRow.onTouchEnd();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
index ba666512af5a..2f9cff46d687 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
@@ -196,9 +196,8 @@ fun StatusBarRoot(
setContent {
PlatformTheme {
- val chipsVisibilityModel by
+ val chipsVisibilityModel =
statusBarViewModel.ongoingActivityChips
- .collectAsStateWithLifecycle()
if (chipsVisibilityModel.areChipsAllowed) {
OngoingActivityChips(
chips = chipsVisibilityModel.chips,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
index c717b180575c..540babad5dd1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
@@ -21,6 +21,8 @@ import android.graphics.Rect
import android.view.Display
import android.view.View
import androidx.compose.runtime.getValue
+import com.android.app.tracing.FlowTracing.traceEach
+import com.android.app.tracing.TrackGroupUtils.trackGroup
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
@@ -130,7 +132,7 @@ interface HomeStatusBarViewModel : Activatable {
val primaryOngoingActivityChip: StateFlow<OngoingActivityChipModel>
/** All supported activity chips, whether they are currently active or not. */
- val ongoingActivityChips: StateFlow<ChipsVisibilityModel>
+ val ongoingActivityChips: ChipsVisibilityModel
/**
* The multiple ongoing activity chips that should be shown on the left-hand side of the status
@@ -386,11 +388,9 @@ constructor(
}
override val isHomeStatusBarAllowed =
- isHomeStatusBarAllowedCompat.stateIn(
- bgScope,
- SharingStarted.WhileSubscribed(),
- initialValue = false,
- )
+ isHomeStatusBarAllowedCompat
+ .traceEach(trackGroup(TRACK_GROUP, "isHomeStatusBarAllowed"), logcat = true)
+ .stateIn(bgScope, SharingStarted.WhileSubscribed(), initialValue = false)
private val shouldHomeStatusBarBeVisible =
combine(
@@ -461,24 +461,29 @@ constructor(
isHomeStatusBarAllowed && !isSecureCameraActive && !hideStartSideContentForHeadsUp
}
- override val ongoingActivityChips =
+ private val chipsVisibilityModel: Flow<ChipsVisibilityModel> =
combine(ongoingActivityChipsViewModel.chips, canShowOngoingActivityChips) { chips, canShow
->
ChipsVisibilityModel(chips, areChipsAllowed = canShow)
}
- .stateIn(
- bgScope,
- SharingStarted.WhileSubscribed(),
- initialValue =
- ChipsVisibilityModel(
- chips = MultipleOngoingActivityChipsModel(),
- areChipsAllowed = false,
- ),
- )
+ .traceEach(trackGroup(TRACK_GROUP, "chips"), logcat = true) {
+ "Chips[allowed=${it.areChipsAllowed} numChips=${it.chips.active.size}]"
+ }
+
+ override val ongoingActivityChips: ChipsVisibilityModel by
+ hydrator.hydratedStateOf(
+ traceName = "ongoingActivityChips",
+ initialValue =
+ ChipsVisibilityModel(
+ chips = MultipleOngoingActivityChipsModel(),
+ areChipsAllowed = false,
+ ),
+ source = chipsVisibilityModel,
+ )
private val hasOngoingActivityChips =
if (StatusBarChipsModernization.isEnabled) {
- ongoingActivityChips.map { it.chips.active.any { chip -> !chip.isHidden } }
+ chipsVisibilityModel.map { it.chips.active.any { chip -> !chip.isHidden } }
} else if (StatusBarNotifChips.isEnabled) {
ongoingActivityChipsLegacy.map { it.primary is OngoingActivityChipModel.Active }
} else {
@@ -607,6 +612,8 @@ constructor(
private const val COL_PREFIX_NOTIF_CONTAINER = "notifContainer"
private const val COL_PREFIX_SYSTEM_INFO = "systemInfo"
+ private const val TRACK_GROUP = "StatusBar"
+
fun tableLogBufferName(displayId: Int) = "HomeStatusBarViewModel[$displayId]"
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
index 5c893da45b8d..6a4f3da054e6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
@@ -44,6 +44,7 @@ import android.app.RemoteAction;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.Context;
+import android.content.Intent;
import android.graphics.Insets;
import android.graphics.Rect;
import android.net.Uri;
@@ -77,6 +78,7 @@ import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.util.Optional;
+import java.util.function.Consumer;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -158,6 +160,31 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
* is false are removed.[
*/
private void initController() {
+ IntentCreator fakeIntentCreator = new IntentCreator() {
+ @Override
+ public Intent getTextEditorIntent(Context context) {
+ return new Intent();
+ }
+
+ @Override
+ public Intent getShareIntent(ClipData clipData, Context context) {
+ Intent intent = Intent.createChooser(new Intent(Intent.ACTION_SEND), null);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ return intent;
+ }
+
+ @Override
+ public void getImageEditIntentAsync(Uri uri, Context context,
+ Consumer<Intent> outputConsumer) {
+ outputConsumer.accept(new Intent(Intent.ACTION_EDIT));
+ }
+
+ @Override
+ public Intent getRemoteCopyIntent(ClipData clipData, Context context) {
+ return new Intent();
+ }
+ };
+
mOverlayController = new ClipboardOverlayController(
mContext,
mClipboardOverlayView,
@@ -171,7 +198,7 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
mClipboardTransitionExecutor,
mClipboardIndicationProvider,
mUiEventLogger,
- new ActionIntentCreator());
+ fakeIntentCreator);
verify(mClipboardOverlayView).setCallbacks(mOverlayCallbacksCaptor.capture());
mCallbacks = mOverlayCallbacksCaptor.getValue();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index fb70846049da..061f7984d44b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -134,7 +134,6 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
@Mock private RingerModeTracker mRingerModeTracker;
@Mock private RingerModeLiveData mRingerModeLiveData;
@Mock private PackageManager mPackageManager;
- @Mock private Handler mHandler;
@Mock private UserContextProvider mUserContextProvider;
@Mock private VibratorHelper mVibratorHelper;
@Mock private ShadeController mShadeController;
@@ -148,6 +147,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
private TestableLooper mTestableLooper;
private KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
private GlobalActionsInteractor mInteractor;
+ private Handler mHandler;
@Before
public void setUp() throws Exception {
@@ -166,6 +166,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
mGlobalSettings = new FakeGlobalSettings();
mSecureSettings = new FakeSettings();
mInteractor = mKosmos.getGlobalActionsInteractor();
+ mHandler = new Handler(mTestableLooper.getLooper());
mGlobalActionsDialogLite = new GlobalActionsDialogLite(mContext,
mWindowManagerFuncs,
@@ -771,6 +772,31 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
mGlobalActionsDialogLite.showOrHideDialog(false, false, null, Display.DEFAULT_DISPLAY);
}
+ @Test
+ public void userSwitching_dismissDialog() {
+ String[] actions = {
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_POWER,
+ GlobalActionsDialogLite.GLOBAL_ACTION_KEY_RESTART,
+ };
+ doReturn(actions).when(mResources)
+ .getStringArray(com.android.internal.R.array.config_globalActionsList);
+
+ mGlobalActionsDialogLite.showOrHideDialog(false, true, null, Display.DEFAULT_DISPLAY);
+ mTestableLooper.processAllMessages();
+
+ assertThat(mGlobalActionsDialogLite.mDialog.isShowing()).isTrue();
+
+ ArgumentCaptor<UserTracker.Callback> captor =
+ ArgumentCaptor.forClass(UserTracker.Callback.class);
+
+ verify(mUserTracker).addCallback(captor.capture(), any());
+
+ captor.getValue().onBeforeUserSwitching(100);
+ mTestableLooper.processAllMessages();
+
+ assertThat(mGlobalActionsDialogLite.mDialog).isNull();
+ }
+
private UserInfo mockCurrentUser(int flags) {
return new UserInfo(10, "A User", flags);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModelTest.kt
index f394c805f5b7..8f1d07bac4df 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModelTest.kt
@@ -855,6 +855,20 @@ public class SeekBarViewModelTest : SysuiTestCase() {
@Test
fun contentDescriptionUpdated() {
+ var elapsedTimeDesc: CharSequence? = null
+ var durationDesc: CharSequence? = null
+ val listener =
+ object : SeekBarViewModel.ContentDescriptionListener {
+ override fun onContentDescriptionChanged(
+ elapsedTimeDescription: CharSequence,
+ durationDescription: CharSequence,
+ ) {
+ elapsedTimeDesc = elapsedTimeDescription
+ durationDesc = durationDescription
+ }
+ }
+ viewModel.setContentDescriptionListener(listener)
+
// When there is a duration and position
val duration = (1.5 * 60 * 60 * 1000).toLong()
val metadata =
@@ -875,9 +889,7 @@ public class SeekBarViewModelTest : SysuiTestCase() {
viewModel.updateController(mockController)
fakeExecutor.runNextReady()
- // Then the content description is set
- val result = viewModel.progress.value!!
-
+ // Then the content description listener gets an update
val expectedProgress =
MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.WIDE)
.formatMeasures(Measure(3, MeasureUnit.SECOND))
@@ -888,7 +900,7 @@ public class SeekBarViewModelTest : SysuiTestCase() {
Measure(30, MeasureUnit.MINUTE),
Measure(0, MeasureUnit.SECOND),
)
- assertThat(result.durationDescription).isEqualTo(expectedDuration)
- assertThat(result.elapsedTimeDescription).isEqualTo(expectedProgress)
+ assertThat(elapsedTimeDesc).isEqualTo(expectedProgress)
+ assertThat(durationDesc).isEqualTo(expectedDuration)
}
}
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/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 84f39be2eeed..00ee893e0e4d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -61,6 +61,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.internal.R;
+import com.android.internal.logging.MetricsLogger;
import com.android.internal.widget.CachingIconView;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.SysuiTestableContext;
@@ -71,12 +72,18 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips;
import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
import com.android.systemui.statusbar.notification.FeedbackIcon;
+import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.SourceType;
+import com.android.systemui.statusbar.notification.collection.EntryAdapter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryAdapter;
+import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator;
import com.android.systemui.statusbar.notification.headsup.PinnedStatus;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi;
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded;
import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener;
+import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import com.android.systemui.statusbar.notification.shared.NotificationContentAlphaOptimization;
@@ -942,9 +949,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
public void isExpanded_sensitivePromotedNotification_notExpanded() throws Exception {
// GIVEN
final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
- NotificationEntry entry = mock(NotificationEntry.class);
- when(entry.isPromotedOngoing()).thenReturn(true);
- row.setEntry(entry);
+ setRowPromotedOngoing(row);
row.setSensitive(/* sensitive= */true, /* hideSensitive= */false);
row.setHideSensitiveForIntrinsicHeight(/* hideSensitive= */true);
@@ -957,9 +962,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
public void isExpanded_promotedNotificationNotOnKeyguard_expanded() throws Exception {
// GIVEN
final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
- NotificationEntry entry = mock(NotificationEntry.class);
- when(entry.isPromotedOngoing()).thenReturn(true);
- row.setEntry(entry);
+ setRowPromotedOngoing(row);
row.setOnKeyguard(false);
// THEN
@@ -971,9 +974,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
public void isExpanded_promotedNotificationAllowOnKeyguard_expanded() throws Exception {
// GIVEN
final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
- NotificationEntry entry = mock(NotificationEntry.class);
- when(entry.isPromotedOngoing()).thenReturn(true);
- row.setEntry(entry);
+ setRowPromotedOngoing(row);
row.setOnKeyguard(true);
// THEN
@@ -986,9 +987,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
throws Exception {
// GIVEN
final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
- NotificationEntry entry = mock(NotificationEntry.class);
- when(entry.isPromotedOngoing()).thenReturn(true);
- row.setEntry(entry);
+ setRowPromotedOngoing(row);
row.setOnKeyguard(true);
row.setIgnoreLockscreenConstraints(true);
@@ -996,15 +995,31 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
assertThat(row.isExpanded()).isTrue();
}
+ private static void setRowPromotedOngoing(ExpandableNotificationRow row) {
+ final NotificationEntry entry = mock(NotificationEntry.class);
+ when(entry.isPromotedOngoing()).thenReturn(true);
+ if (NotificationBundleUi.isEnabled()) {
+ final EntryAdapter entryAdapter = new NotificationEntryAdapter(
+ mock(NotificationActivityStarter.class),
+ mock(MetricsLogger.class),
+ mock(PeopleNotificationIdentifier.class),
+ mock(NotificationIconStyleProvider.class),
+ mock(VisualStabilityCoordinator.class),
+ mock(NotificationActionClickManager.class),
+ entry);
+ row.setEntryAdapter(entryAdapter);
+ } else {
+ row.setEntry(entry);
+ }
+ }
+
@Test
@EnableFlags({PromotedNotificationUi.FLAG_NAME, PromotedNotificationUiForceExpanded.FLAG_NAME})
public void isExpanded_promotedNotificationSaveSpaceOnLockScreen_notExpanded()
throws Exception {
// GIVEN
final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
- NotificationEntry entry = mock(NotificationEntry.class);
- when(entry.isPromotedOngoing()).thenReturn(true);
- row.setEntry(entry);
+ setRowPromotedOngoing(row);
row.setOnKeyguard(true);
row.setSaveSpaceOnLockscreen(true);
@@ -1018,9 +1033,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
throws Exception {
// GIVEN
final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
- NotificationEntry entry = mock(NotificationEntry.class);
- when(entry.isPromotedOngoing()).thenReturn(true);
- row.setEntry(entry);
+ setRowPromotedOngoing(row);
row.setOnKeyguard(true);
row.setSaveSpaceOnLockscreen(false);
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 dc3652d0c550..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
@@ -391,6 +391,7 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
.setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE)
.setImportance(NotificationManager.IMPORTANCE_HIGH)
.build()
+ whenever(row.canViewBeDismissed()).thenReturn(true)
whenever(highPriorityProvider.isHighPriority(entry)).thenReturn(true)
val statusBarNotification = entry.sbn
gutsManager.initializeNotificationInfo(row, notificationInfoView)
@@ -412,7 +413,8 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
any<UiEventLogger>(),
eq(true),
eq(false),
- eq(true), /* wasShownHighPriority */
+ eq(true),
+ eq(true),
eq(assistantFeedbackController),
any<MetricsLogger>(),
any<View.OnClickListener>(),
@@ -427,6 +429,7 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
NotificationEntryHelper.modifyRanking(row.entry)
.setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE)
.build()
+ whenever(row.canViewBeDismissed()).thenReturn(true)
val statusBarNotification = row.entry.sbn
val entry = row.entry
gutsManager.initializeNotificationInfo(row, notificationInfoView)
@@ -448,7 +451,8 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
any<UiEventLogger>(),
eq(true),
eq(false),
- eq(false), /* wasShownHighPriority */
+ eq(true), /* wasShownHighPriority */
+ eq(false),
eq(assistantFeedbackController),
any<MetricsLogger>(),
any<View.OnClickListener>(),
@@ -463,6 +467,7 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
NotificationEntryHelper.modifyRanking(row.entry)
.setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE)
.build()
+ whenever(row.canViewBeDismissed()).thenReturn(true)
val statusBarNotification = row.entry.sbn
val entry = row.entry
gutsManager.initializeNotificationInfo(row, notificationInfoView)
@@ -484,7 +489,8 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
any<UiEventLogger>(),
eq(true),
eq(false),
- eq(false), /* wasShownHighPriority */
+ eq(true), /* wasShownHighPriority */
+ eq(false),
eq(assistantFeedbackController),
any<MetricsLogger>(),
any<View.OnClickListener>(),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
index 663a85330f70..338e4bec7aa2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
@@ -26,6 +26,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import com.android.app.displaylib.DisplayRepository.PendingDisplay
import org.mockito.Mockito.`when` as whenever
/** Creates a mock display. */
@@ -41,8 +42,8 @@ fun display(type: Int, flags: Int = 0, id: Int = 0, state: Int? = null): Display
}
/** Creates a mock [DisplayRepository.PendingDisplay]. */
-fun createPendingDisplay(id: Int = 0): DisplayRepository.PendingDisplay =
- mock<DisplayRepository.PendingDisplay> { whenever(this.id).thenReturn(id) }
+fun createPendingDisplay(id: Int = 0): PendingDisplay =
+ mock<PendingDisplay> { whenever(this.id).thenReturn(id) }
@SysUISingleton
/** Fake [DisplayRepository] implementation for testing. */
@@ -50,7 +51,7 @@ class FakeDisplayRepository @Inject constructor() : DisplayRepository {
private val flow = MutableStateFlow<Set<Display>>(emptySet())
private val displayIdFlow = MutableStateFlow<Set<Int>>(emptySet())
private val pendingDisplayFlow =
- MutableSharedFlow<DisplayRepository.PendingDisplay?>(replay = 1)
+ MutableSharedFlow<PendingDisplay?>(replay = 1)
private val displayAdditionEventFlow = MutableSharedFlow<Display?>(replay = 0)
private val displayRemovalEventFlow = MutableSharedFlow<Int>(replay = 0)
private val displayIdsWithSystemDecorationsFlow = MutableStateFlow<Set<Int>>(emptySet())
@@ -101,7 +102,7 @@ class FakeDisplayRepository @Inject constructor() : DisplayRepository {
suspend fun emit(value: Set<Display>) = flow.emit(value)
/** Emits [value] as [pendingDisplay] flow value. */
- suspend fun emit(value: DisplayRepository.PendingDisplay?) = pendingDisplayFlow.emit(value)
+ suspend fun emit(value: PendingDisplay?) = pendingDisplayFlow.emit(value)
override val displays: StateFlow<Set<Display>>
get() = flow
@@ -109,7 +110,7 @@ class FakeDisplayRepository @Inject constructor() : DisplayRepository {
override val displayIds: StateFlow<Set<Int>>
get() = displayIdFlow
- override val pendingDisplay: Flow<DisplayRepository.PendingDisplay?>
+ override val pendingDisplay: Flow<PendingDisplay?>
get() = pendingDisplayFlow
private val _defaultDisplayOff: MutableStateFlow<Boolean> = MutableStateFlow(false)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt
index aa23aa30b7bc..5ab3b3de49f4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt
@@ -66,6 +66,7 @@ class FakePerDisplayInstanceProviderWithTeardown :
val Kosmos.fakePerDisplayInstanceProviderWithTeardown by
Kosmos.Fixture { FakePerDisplayInstanceProviderWithTeardown() }
+val Kosmos.perDisplayDumpHelper by Kosmos.Fixture { PerDisplayRepoDumpHelper(dumpManager) }
val Kosmos.fakePerDisplayInstanceRepository by
Kosmos.Fixture {
PerDisplayInstanceRepositoryImpl(
@@ -73,6 +74,6 @@ val Kosmos.fakePerDisplayInstanceRepository by
instanceProvider = fakePerDisplayInstanceProviderWithTeardown,
testScope.backgroundScope,
displayRepository,
- dumpManager,
+ perDisplayDumpHelper,
)
}
diff --git a/ravenwood/TEST_MAPPING b/ravenwood/TEST_MAPPING
index 1148539187ac..083d2aa15316 100644
--- a/ravenwood/TEST_MAPPING
+++ b/ravenwood/TEST_MAPPING
@@ -180,9 +180,11 @@
// AUTO-GENERATED-END
],
"ravenwood-postsubmit": [
- {
- "name": "SystemUiRavenTests",
- "host": true
- }
+ // We haven't maintained SystemUiRavenTests, and as a result, it's been demoted already.
+ // Disable it until we fix the issues: b/319647875
+ // {
+ // "name": "SystemUiRavenTests",
+ // "host": true
+ // }
]
}
diff --git a/services/accessibility/java/com/android/server/accessibility/HearingDevicePhoneCallNotificationController.java b/services/accessibility/java/com/android/server/accessibility/HearingDevicePhoneCallNotificationController.java
index 805d7f820c8d..94cef418b6c8 100644
--- a/services/accessibility/java/com/android/server/accessibility/HearingDevicePhoneCallNotificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/HearingDevicePhoneCallNotificationController.java
@@ -165,6 +165,9 @@ public class HearingDevicePhoneCallNotificationController {
if (state == TelephonyManager.CALL_STATE_OFFHOOK) {
if (com.android.server.accessibility.Flags.hearingInputChangeWhenCommDevice()) {
AudioDeviceInfo commDevice = mAudioManager.getCommunicationDevice();
+ if (commDevice == null) {
+ return;
+ }
mHearingDevice = getSupportedInputHearingDeviceInfo(List.of(commDevice));
if (mHearingDevice != null) {
showNotificationIfNeeded();
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index b75b7ddf8181..9ae8ff869800 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -3871,6 +3871,10 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
*/
public void notifyCarrierRoamingNtnSignalStrengthChanged(int subId,
@NonNull NtnSignalStrength ntnSignalStrength) {
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ log("notifyCarrierRoamingNtnSignalStrengthChanged: invalid subscription id");
+ return;
+ }
if (!checkNotifyPermission("notifyCarrierRoamingNtnSignalStrengthChanged")) {
log("notifyCarrierRoamingNtnSignalStrengthChanged: caller does not have required "
+ "permissions.");
diff --git a/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java b/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java
index c53e4bdc2205..3a8d5835ccb1 100644
--- a/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java
+++ b/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java
@@ -24,6 +24,7 @@ import android.database.DatabaseErrorHandler;
import android.database.DefaultDatabaseErrorHandler;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
+import android.database.sqlite.SQLiteFullException;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteRawStatement;
import android.os.Environment;
@@ -174,11 +175,16 @@ class DiscreteOpsDbHelper extends SQLiteOpenHelper {
if (DEBUG) {
Slog.i(LOG_TAG, "DB execSQL, sql: " + sql);
}
- SQLiteDatabase db = getWritableDatabase();
- if (bindArgs == null) {
- db.execSQL(sql);
- } else {
- db.execSQL(sql, bindArgs);
+ try {
+ SQLiteDatabase db = getWritableDatabase();
+ if (bindArgs == null) {
+ db.execSQL(sql);
+ } else {
+ db.execSQL(sql, bindArgs);
+ }
+ } catch (SQLiteFullException exception) {
+ Slog.e(LOG_TAG, "Couldn't exec sql command, disk is full. Discrete ops "
+ + "db file size (bytes) : " + getDatabaseFile().length(), exception);
}
}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index 3aaf4f6fe85a..7450dffc05ab 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -122,8 +122,8 @@ final class DisplayDeviceInfo {
public static final int FLAG_MASK_DISPLAY_CUTOUT = 1 << 11;
/**
- * Flag: This flag identifies secondary displays that should show system decorations, such as
- * navigation bar, home activity or wallpaper.
+ * Flag: This flag identifies secondary displays that should always show system decorations,
+ * such as navigation bar, home activity or wallpaper.
* <p>Note that this flag doesn't work without {@link #FLAG_TRUSTED}</p>
* @hide
*/
@@ -191,6 +191,19 @@ final class DisplayDeviceInfo {
public static final int FLAG_STEAL_TOP_FOCUS_DISABLED = 1 << 19;
/**
+ * Flag: Indicates that the display is allowed to switch the content mode between
+ * projected/extended and mirroring. This allows the display to dynamically add or remove the
+ * home and system decorations.
+ *
+ * Note that this flag should not be enabled with any of {@link #FLAG_PRIVATE},
+ * {@link #FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS}, or {@link #FLAG_OWN_CONTENT_ONLY} at the
+ * same time; otherwise it will be ignored.
+ *
+ * @hide
+ */
+ public static final int FLAG_ALLOWS_CONTENT_MODE_SWITCH = 1 << 20;
+
+ /**
* Touch attachment: Display does not receive touch.
*/
public static final int TOUCH_NONE = 0;
diff --git a/services/core/java/com/android/server/display/DisplayGroup.java b/services/core/java/com/android/server/display/DisplayGroup.java
index f73b66c78fce..ce8d8a6db9d3 100644
--- a/services/core/java/com/android/server/display/DisplayGroup.java
+++ b/services/core/java/com/android/server/display/DisplayGroup.java
@@ -16,6 +16,8 @@
package com.android.server.display;
+import android.util.IndentingPrintWriter;
+
import java.util.ArrayList;
import java.util.List;
@@ -97,4 +99,14 @@ public class DisplayGroup {
}
return displayIds;
}
+
+ /** Dumps information about the DisplayGroup. */
+ void dumpLocked(IndentingPrintWriter ipw) {
+ final int numDisplays = mDisplays.size();
+ for (int i = 0; i < numDisplays; i++) {
+ LogicalDisplay logicalDisplay = mDisplays.get(i);
+ ipw.println("Display " + logicalDisplay.getDisplayIdLocked() + " "
+ + logicalDisplay.getPrimaryDisplayDeviceLocked());
+ }
+ }
}
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 7b714ad2bd9e..2cad7ed2e9e9 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -766,7 +766,7 @@ final class LocalDisplayAdapter extends DisplayAdapter {
mInfo.flags |= DisplayDeviceInfo.FLAG_ROUND;
}
} else {
- if (!res.getBoolean(R.bool.config_localDisplaysMirrorContent)) {
+ if (shouldOwnContentOnly()) {
mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY;
}
@@ -780,6 +780,15 @@ final class LocalDisplayAdapter extends DisplayAdapter {
}
}
+ if (getFeatureFlags().isDisplayContentModeManagementEnabled()) {
+ // Public display with FLAG_OWN_CONTENT_ONLY disabled is allowed to switch the
+ // content mode.
+ if (mIsFirstDisplay
+ || (!isDisplayPrivate(physicalAddress) && !shouldOwnContentOnly())) {
+ mInfo.flags |= DisplayDeviceInfo.FLAG_ALLOWS_CONTENT_MODE_SWITCH;
+ }
+ }
+
if (DisplayCutout.getMaskBuiltInDisplayCutout(res, mInfo.uniqueId)) {
mInfo.flags |= DisplayDeviceInfo.FLAG_MASK_DISPLAY_CUTOUT;
}
@@ -822,6 +831,7 @@ final class LocalDisplayAdapter extends DisplayAdapter {
R.string.display_manager_hdmi_display_name);
}
}
+
mInfo.frameRateOverrides = mFrameRateOverrides;
// The display is trusted since it is created by system.
@@ -1467,6 +1477,11 @@ final class LocalDisplayAdapter extends DisplayAdapter {
return false;
}
+ private boolean shouldOwnContentOnly() {
+ final Resources res = getOverlayContext().getResources();
+ return !res.getBoolean(R.bool.config_localDisplaysMirrorContent);
+ }
+
private boolean isDisplayStealTopFocusDisabled(DisplayAddress.Physical physicalAddress) {
if (physicalAddress == null) {
return false;
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index b2b9ef17ec8d..0e6870f7ed7d 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -489,6 +489,11 @@ final class LogicalDisplay {
if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_STEAL_TOP_FOCUS_DISABLED) != 0) {
mBaseDisplayInfo.flags |= Display.FLAG_STEAL_TOP_FOCUS_DISABLED;
}
+ // Rear display should not be allowed to use the content mode switch.
+ if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_ALLOWS_CONTENT_MODE_SWITCH) != 0
+ && mDevicePosition != Layout.Display.POSITION_REAR) {
+ mBaseDisplayInfo.flags |= Display.FLAG_ALLOWS_CONTENT_MODE_SWITCH;
+ }
Rect maskingInsets = getMaskingInsets(deviceInfo);
int maskedWidth = deviceInfo.width - maskingInsets.left - maskingInsets.right;
int maskedHeight = deviceInfo.height - maskingInsets.top - maskingInsets.bottom;
@@ -1155,6 +1160,7 @@ final class LogicalDisplay {
pw.println("mRequestedMinimalPostProcessing=" + mRequestedMinimalPostProcessing);
pw.println("mFrameRateOverrides=" + Arrays.toString(mFrameRateOverrides));
pw.println("mPendingFrameRateOverrideUids=" + mPendingFrameRateOverrideUids);
+ pw.println("mDisplayGroupId=" + mDisplayGroupId);
pw.println("mDisplayGroupName=" + mDisplayGroupName);
pw.println("mThermalBrightnessThrottlingDataId=" + mThermalBrightnessThrottlingDataId);
pw.println("mLeadDisplayId=" + mLeadDisplayId);
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index f4daf8761e9b..4a4c616b34e3 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -478,6 +478,21 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
ipw.decreaseIndent();
ipw.println();
}
+
+ final int displayGroupCount = mDisplayGroups.size();
+ ipw.println();
+ ipw.println("Display Groups: size=" + displayGroupCount);
+ for (int i = 0; i < displayGroupCount; i++) {
+ int groupId = mDisplayGroups.keyAt(i);
+ DisplayGroup displayGroup = mDisplayGroups.valueAt(i);
+ ipw.println("Group " + groupId + ":");
+ ipw.increaseIndent();
+ displayGroup.dumpLocked(ipw);
+ ipw.decreaseIndent();
+ ipw.println();
+ }
+
+
mDeviceStateToLayoutMap.dumpLocked(ipw);
}
diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
index b5a9b19bc5c5..60b7fca99e7b 100644
--- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
@@ -76,6 +76,7 @@ import java.util.regex.Pattern;
* <li><code>secure</code>: creates a secure display</li>
* <li><code>own_content_only</code>: only shows this display's own content</li>
* <li><code>should_show_system_decorations</code>: supports system decorations</li>
+ * <li><code>fixed_content_mode</code>: not allowed to switch content mode</li>
* <li><code>gravity_top_left</code>: display the overlay at the top left of the screen</li>
* <li><code>gravity_top_right</code>: display the overlay at the top right of the screen</li>
* <li><code>gravity_bottom_right</code>: display the overlay at the bottom right of the screen</li>
@@ -117,6 +118,18 @@ final class OverlayDisplayAdapter extends DisplayAdapter {
private static final String OVERLAY_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS =
"should_show_system_decorations";
+ /**
+ * When this flag is set, the overlay display is not allowed to switch content mode.
+ * Note that it is the opposite of {@link DisplayDeviceInfo#FLAG_ALLOWS_CONTENT_MODE_SWITCH},
+ * because we want overlay displays (such as those used for connected display simulation in
+ * development) to have {@link DisplayDeviceInfo#FLAG_ALLOWS_CONTENT_MODE_SWITCH} enabled by
+ * default without explicitly specifying it.
+ *
+ * @see DisplayDeviceInfo#FLAG_ALLOWS_CONTENT_MODE_SWITCH
+ */
+ private static final String OVERLAY_DISPLAY_FLAG_FIXED_CONTENT_MODE =
+ "fixed_content_mode";
+
// Gravity flags to decide where the overlay should be shown.
private static final String GRAVITY_TOP_LEFT = "gravity_top_left";
private static final String GRAVITY_BOTTOM_RIGHT = "gravity_bottom_right";
@@ -384,6 +397,17 @@ final class OverlayDisplayAdapter extends DisplayAdapter {
if (mFlags.mShouldShowSystemDecorations) {
mInfo.flags |= DisplayDeviceInfo.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
}
+ if (getFeatureFlags().isDisplayContentModeManagementEnabled()) {
+ if (!mFlags.mFixedContentMode
+ && !mFlags.mOwnContentOnly
+ && !mFlags.mShouldShowSystemDecorations) {
+ // For overlay displays, if FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS and
+ // FLAG_OWN_CONTENT_ONLY are both disabled,
+ // then FLAG_ALLOWS_CONTENT_MODE_SWITCH should be enabled by default,
+ // unless OVERLAY_DISPLAY_FLAG_FIXED_CONTENT_MODE is set.
+ mInfo.flags |= DisplayDeviceInfo.FLAG_ALLOWS_CONTENT_MODE_SWITCH;
+ }
+ }
mInfo.type = Display.TYPE_OVERLAY;
mInfo.touch = DisplayDeviceInfo.TOUCH_VIRTUAL;
mInfo.state = mState;
@@ -628,16 +652,21 @@ final class OverlayDisplayAdapter extends DisplayAdapter {
/** See {@link #OVERLAY_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS}. */
final boolean mShouldShowSystemDecorations;
+ /** See {@link #OVERLAY_DISPLAY_FLAG_FIXED_CONTENT_MODE}. */
+ final boolean mFixedContentMode;
+
final int mGravity;
OverlayFlags(
boolean secure,
boolean ownContentOnly,
boolean shouldShowSystemDecorations,
+ boolean fixedContentMode,
int gravity) {
mSecure = secure;
mOwnContentOnly = ownContentOnly;
mShouldShowSystemDecorations = shouldShowSystemDecorations;
+ mFixedContentMode = fixedContentMode;
mGravity = gravity;
}
@@ -647,12 +676,14 @@ final class OverlayDisplayAdapter extends DisplayAdapter {
false /* secure */,
false /* ownContentOnly */,
false /* shouldShowSystemDecorations */,
+ false /* fixedContentMode */,
Gravity.NO_GRAVITY);
}
boolean secure = false;
boolean ownContentOnly = false;
boolean shouldShowSystemDecorations = false;
+ boolean fixedContentMode = false;
int gravity = Gravity.NO_GRAVITY;
for (String flag: flagString.split(FLAG_SPLITTER)) {
if (OVERLAY_DISPLAY_FLAG_SECURE.equals(flag)) {
@@ -661,11 +692,14 @@ final class OverlayDisplayAdapter extends DisplayAdapter {
ownContentOnly = true;
} else if (OVERLAY_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS.equals(flag)) {
shouldShowSystemDecorations = true;
+ } else if (OVERLAY_DISPLAY_FLAG_FIXED_CONTENT_MODE.equals(flag)) {
+ fixedContentMode = true;
} else {
gravity = parseOverlayGravity(flag);
}
}
- return new OverlayFlags(secure, ownContentOnly, shouldShowSystemDecorations, gravity);
+ return new OverlayFlags(secure, ownContentOnly, shouldShowSystemDecorations,
+ fixedContentMode, gravity);
}
@Override
@@ -674,6 +708,7 @@ final class OverlayDisplayAdapter extends DisplayAdapter {
.append("secure=").append(mSecure)
.append(", ownContentOnly=").append(mOwnContentOnly)
.append(", shouldShowSystemDecorations=").append(mShouldShowSystemDecorations)
+ .append(", fixedContentMode=").append(mFixedContentMode)
.append(", gravity").append(Gravity.toString(mGravity))
.append("}")
.toString();
diff --git a/services/core/java/com/android/server/display/WifiDisplayAdapter.java b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
index 902eefa824b5..89679c728127 100644
--- a/services/core/java/com/android/server/display/WifiDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
@@ -666,6 +666,12 @@ final class WifiDisplayAdapter extends DisplayAdapter {
mInfo.setAssumedDensityForExternalDisplay(mWidth, mHeight);
// The display is trusted since it is created by system.
mInfo.flags |= DisplayDeviceInfo.FLAG_TRUSTED;
+ if (getFeatureFlags().isDisplayContentModeManagementEnabled()) {
+ // The wifi display is allowed to switch content mode since FLAG_PRIVATE,
+ // FLAG_OWN_CONTENT_ONLY, and FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS are not
+ // enabled in WifiDisplayDevice#getDisplayDeviceInfoLocked().
+ mInfo.flags |= DisplayDeviceInfo.FLAG_ALLOWS_CONTENT_MODE_SWITCH;
+ }
mInfo.displayShape =
DisplayShape.createDefaultDisplayShape(mInfo.width, mInfo.height, false);
}
diff --git a/services/core/java/com/android/server/notification/TEST_MAPPING b/services/core/java/com/android/server/notification/TEST_MAPPING
index dc7129cde5e5..ea7ee4addbe6 100644
--- a/services/core/java/com/android/server/notification/TEST_MAPPING
+++ b/services/core/java/com/android/server/notification/TEST_MAPPING
@@ -4,7 +4,10 @@
"name": "CtsNotificationTestCases_notification"
},
{
- "name": "FrameworksUiServicesTests_notification"
+ "name": "FrameworksUiServicesNotificationTests"
+ },
+ {
+ "name": "FrameworksUiServicesZenTests"
}
],
"postsubmit": [
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 102dc071c7b6..417b5c7ae62b 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -16,6 +16,7 @@
package com.android.server.power;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
@@ -36,8 +37,8 @@ import android.os.BatteryStats;
import android.os.BatteryStatsInternal;
import android.os.Bundle;
import android.os.Handler;
-import android.os.IWakeLockCallback;
import android.os.IScreenTimeoutPolicyListener;
+import android.os.IWakeLockCallback;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
@@ -67,6 +68,7 @@ import com.android.internal.app.IBatteryStats;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
import com.android.server.input.InputManagerInternal;
@@ -147,7 +149,8 @@ public class Notifier {
@Nullable private final StatusBarManagerInternal mStatusBarManagerInternal;
private final TrustManager mTrustManager;
private final Vibrator mVibrator;
- private final WakeLockLog mWakeLockLog;
+ @NonNull private final WakeLockLog mPartialWakeLockLog;
+ @NonNull private final WakeLockLog mFullWakeLockLog;
private final DisplayManagerInternal mDisplayManagerInternal;
private final NotifierHandler mHandler;
@@ -250,7 +253,9 @@ public class Notifier {
mShowWirelessChargingAnimationConfig = context.getResources().getBoolean(
com.android.internal.R.bool.config_showBuiltinWirelessChargingAnim);
- mWakeLockLog = mInjector.getWakeLockLog(context);
+ mFullWakeLockLog = mInjector.getWakeLockLog(context);
+ mPartialWakeLockLog = mInjector.getWakeLockLog(context);
+
// Initialize interactive state for battery stats.
try {
mBatteryStats.noteInteractive(true);
@@ -324,7 +329,8 @@ public class Notifier {
// Ignore
}
}
- mWakeLockLog.onWakeLockAcquired(tag, ownerUid, flags, /*eventTime=*/ -1);
+ getWakeLockLog(flags).onWakeLockAcquired(tag,
+ getUidForWakeLockLog(ownerUid, workSource), flags, /*eventTime=*/ -1);
}
mWakefulnessSessionObserver.onWakeLockAcquired(flags);
}
@@ -473,7 +479,8 @@ public class Notifier {
// Ignore
}
}
- mWakeLockLog.onWakeLockReleased(tag, ownerUid, /*eventTime=*/ -1);
+ getWakeLockLog(flags).onWakeLockReleased(tag,
+ getUidForWakeLockLog(ownerUid, workSource), /*eventTime=*/ -1);
}
mWakefulnessSessionObserver.onWakeLockReleased(flags, releaseReason);
}
@@ -960,11 +967,18 @@ public class Notifier {
* @param pw The stream to print to.
*/
public void dump(PrintWriter pw) {
- if (mWakeLockLog != null) {
- mWakeLockLog.dump(pw);
- }
+ pw.println("Notifier:");
+
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+ ipw.println("Partial Wakelock Log:");
+ mPartialWakeLockLog.dump(ipw);
+
+ ipw.println("");
+ ipw.println("Full Wakelock Log:");
+ mFullWakeLockLog.dump(ipw);
- mWakefulnessSessionObserver.dump(pw);
+ ipw.println("");
+ mWakefulnessSessionObserver.dump(ipw);
}
private void updatePendingBroadcastLocked() {
@@ -1232,7 +1246,9 @@ public class Notifier {
// Do Nothing
}
}
- mWakeLockLog.onWakeLockAcquired(tag, ownerUid, flags, currentTime);
+
+ getWakeLockLog(flags).onWakeLockAcquired(tag, getUidForWakeLockLog(ownerUid, workSource),
+ flags, currentTime);
}
@SuppressLint("AndroidFrameworkRequiresPermission")
@@ -1253,7 +1269,8 @@ public class Notifier {
// Ignore
}
}
- mWakeLockLog.onWakeLockReleased(tag, ownerUid, currentTime);
+ getWakeLockLog(flags).onWakeLockReleased(tag, getUidForWakeLockLog(ownerUid, workSource),
+ currentTime);
}
@SuppressLint("AndroidFrameworkRequiresPermission")
@@ -1419,6 +1436,15 @@ public class Notifier {
}
}
+ private @NonNull WakeLockLog getWakeLockLog(int flags) {
+ return PowerManagerService.isScreenLock(flags) ? mFullWakeLockLog : mPartialWakeLockLog;
+ }
+
+ private int getUidForWakeLockLog(int ownerUid, WorkSource workSource) {
+ int attributionUid = workSource != null ? workSource.getAttributionUid() : -1;
+ return attributionUid != -1 ? attributionUid : ownerUid;
+ }
+
private final class NotifierHandler extends Handler {
public NotifierHandler(Looper looper) {
@@ -1501,7 +1527,7 @@ public class Notifier {
/**
* Gets the WakeLockLog object
*/
- WakeLockLog getWakeLockLog(Context context);
+ @NonNull WakeLockLog getWakeLockLog(Context context);
/**
* Gets the AppOpsManager system service
@@ -1522,7 +1548,7 @@ public class Notifier {
}
@Override
- public WakeLockLog getWakeLockLog(Context context) {
+ public @NonNull WakeLockLog getWakeLockLog(Context context) {
return new WakeLockLog(context);
}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index dd454cd61b2c..3eac4b54cd2b 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -1723,9 +1723,16 @@ public final class PowerManagerService extends SystemService
}
}
- @SuppressWarnings("deprecation")
private static boolean isScreenLock(final WakeLock wakeLock) {
- switch (wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) {
+ return isScreenLock(wakeLock.mFlags);
+ }
+
+ /**
+ * Returns if a wakelock flag corresponds to a screen wake lock.
+ */
+ @SuppressWarnings("deprecation")
+ public static boolean isScreenLock(int flags) {
+ switch (flags & PowerManager.WAKE_LOCK_LEVEL_MASK) {
case PowerManager.FULL_WAKE_LOCK:
case PowerManager.SCREEN_BRIGHT_WAKE_LOCK:
case PowerManager.SCREEN_DIM_WAKE_LOCK:
diff --git a/services/core/java/com/android/server/power/WakeLockLog.java b/services/core/java/com/android/server/power/WakeLockLog.java
index eda222e71c9e..7f152d6fc9fa 100644
--- a/services/core/java/com/android/server/power/WakeLockLog.java
+++ b/services/core/java/com/android/server/power/WakeLockLog.java
@@ -81,11 +81,12 @@ final class WakeLockLog {
private static final int TYPE_ACQUIRE = 0x1;
private static final int TYPE_RELEASE = 0x2;
private static final int MAX_LOG_ENTRY_BYTE_SIZE = 9;
- private static final int LOG_SIZE = 1024 * 10;
+ private static final int LOG_SIZE = 1024 * 3;
private static final int LOG_SIZE_MIN = MAX_LOG_ENTRY_BYTE_SIZE + 1;
- private static final int TAG_DATABASE_SIZE = 128;
+ private static final int TAG_DATABASE_SIZE = 64;
private static final int TAG_DATABASE_SIZE_MAX = 128;
+ private static final int TAG_DATABASE_STARTING_SIZE = 16;
private static final int LEVEL_SCREEN_TIMEOUT_OVERRIDE_WAKE_LOCK = 0;
private static final int LEVEL_PARTIAL_WAKE_LOCK = 1;
@@ -182,7 +183,7 @@ final class WakeLockLog {
* @param pw The {@code PrintWriter} to write to.
*/
public void dump(PrintWriter pw) {
- dump(pw, false);
+ dump(pw, /* includeTagDb= */ true);
}
@VisibleForTesting
@@ -1161,15 +1162,16 @@ final class WakeLockLog {
*/
static class TagDatabase {
private final int mInvalidIndex;
- private final TagData[] mArray;
+ private final int mMaxArraySize;
+ private TagData[] mArray;
private Callback mCallback;
TagDatabase(Injector injector) {
- int size = Math.min(injector.getTagDatabaseSize(), TAG_DATABASE_SIZE_MAX);
-
- // Largest possible index used as "INVALID", hence the (size - 1) sizing.
- mArray = new TagData[size - 1];
- mInvalidIndex = size - 1;
+ // Largest possible index used as "INVALID", hence the (size - 1) sizing
+ mMaxArraySize = Math.min(injector.getTagDatabaseSize(), TAG_DATABASE_SIZE_MAX - 1);
+ int startingSize = Math.min(mMaxArraySize, injector.getTagDatabaseStartingSize());
+ mArray = new TagData[startingSize];
+ mInvalidIndex = mMaxArraySize;
}
@Override
@@ -1195,8 +1197,10 @@ final class WakeLockLog {
sb.append(", entries: ").append(entries);
sb.append(", Bytes used: ").append(byteEstimate);
if (DEBUG) {
- sb.append(", Avg tag size: ").append(tagSize / tags);
- sb.append("\n ").append(Arrays.toString(mArray));
+ sb.append(", Avg tag size: ").append(tags == 0 ? 0 : (tagSize / tags));
+ for (int i = 0; i < mArray.length; i++) {
+ sb.append("\n [").append(i).append("] ").append(mArray[i]);
+ }
}
return sb.toString();
}
@@ -1284,6 +1288,18 @@ final class WakeLockLog {
return null;
}
+ // We don't have a spot available, see if we can still increase the array size
+ if (firstAvailable == -1) {
+ if (mArray.length < mMaxArraySize) {
+ int oldSize = mArray.length;
+ int newSize = Math.min(oldSize * 2, mMaxArraySize);
+ TagData[] newArray = new TagData[newSize];
+ System.arraycopy(mArray, 0, newArray, 0, oldSize);
+ mArray = newArray;
+ firstAvailable = oldSize;
+ }
+ }
+
// If we need to remove an index, report to listeners that we are removing an index.
boolean useOldest = firstAvailable == -1;
if (useOldest && mCallback != null) {
@@ -1402,6 +1418,10 @@ final class WakeLockLog {
return TAG_DATABASE_SIZE;
}
+ public int getTagDatabaseStartingSize() {
+ return TAG_DATABASE_STARTING_SIZE;
+ }
+
public int getLogSize() {
return LOG_SIZE;
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
index 6e640d890fb8..424439df3c4b 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
@@ -856,10 +856,14 @@ public class WallpaperCropper {
BitmapFactory.decodeFile(wallpaperFile.getAbsolutePath(), options);
wallpaperImageSize.set(options.outWidth, options.outHeight);
}
+ boolean isRtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault())
+ == View.LAYOUT_DIRECTION_RTL;
+ Rect croppedImageBound = getCrop(displaySize, mDefaultDisplayInfo, wallpaperImageSize,
+ getRelativeCropHints(wallpaperData), isRtl);
- double maxDisplayToImageRatio = Math.max((double) displaySize.x / wallpaperImageSize.x,
- (double) displaySize.y / wallpaperImageSize.y);
- if (maxDisplayToImageRatio > 1.5) {
+ double maxDisplayToImageRatio = Math.max((double) displaySize.x / croppedImageBound.width(),
+ (double) displaySize.y / croppedImageBound.height());
+ if (maxDisplayToImageRatio > 1.3) {
return false;
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index afacae8d0c13..59a042981375 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -41,6 +41,7 @@ import static android.util.TypedValue.COMPLEX_UNIT_DIP;
import static android.util.TypedValue.COMPLEX_UNIT_MASK;
import static android.util.TypedValue.COMPLEX_UNIT_SHIFT;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.FLAG_ALLOWS_CONTENT_MODE_SWITCH;
import static android.view.Display.FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
import static android.view.Display.FLAG_PRIVATE;
import static android.view.Display.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
@@ -3266,41 +3267,40 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
/* inTopology= */ shouldShowContent);
}
- /**
- * Whether the display is allowed to switch the content mode between extended and mirroring.
- * If the content mode is extended, the display will start home activity and show system
- * decorations, such as wallpapaer, status bar and navigation bar.
- * If the content mode is mirroring, the display will not show home activity or system
- * decorations.
- * The content mode is switched when {@link Display#canHostTasks()} changes.
- *
- * Note that we only allow displays that are able to show system decorations to use the content
- * mode switch; however, not all displays that are able to show system decorations are allowed
- * to use the content mode switch.
- */
- boolean allowContentModeSwitch() {
- // The default display should always show system decorations.
- if (isDefaultDisplay) {
+ /**
+ * Whether the display is allowed to switch the content mode between extended and mirroring.
+ * If the content mode is extended, the display will start home activity and show system
+ * decorations, such as wallpapaer, status bar and navigation bar.
+ * If the content mode is mirroring, the display will not show home activity or system
+ * decorations.
+ * The content mode is switched when {@link Display#canHostTasks()} changes.
+ *
+ * Note that we only allow displays that are able to show system decorations to use the content
+ * mode switch; however, not all displays that are able to show system decorations are allowed
+ * to use the content mode switch.
+ */
+ boolean allowContentModeSwitch() {
+ if ((mDisplay.getFlags() & FLAG_ALLOWS_CONTENT_MODE_SWITCH) == 0) {
return false;
}
- // Private display should never show system decorations.
- if (isPrivate()) {
+ // The default display should always show system decorations.
+ if (isDefaultDisplay) {
return false;
}
- if (shouldNeverShowSystemDecorations()) {
+ // Private or untrusted display should never show system decorations.
+ if (isPrivate() || !isTrusted()) {
return false;
}
- // TODO(b/391965805): Remove this after introducing FLAG_ALLOW_CONTENT_MODE_SWITCH.
- if ((mDisplay.getFlags() & Display.FLAG_REAR) != 0) {
+ if (shouldNeverShowSystemDecorations()) {
return false;
}
- // TODO(b/391965805): Remove this after introducing FLAG_ALLOW_CONTENT_MODE_SWITCH.
- // Virtual displays cannot add or remove system decorations during their lifecycle.
- if (mDisplay.getType() == Display.TYPE_VIRTUAL) {
+ // Display with FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS enabled should always show system
+ // decorations, and should not switch the content mode.
+ if ((mDisplay.getFlags() & FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0) {
return false;
}
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);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
index f8b4113a3c04..b8a5f341eb8a 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -234,6 +234,7 @@ public class LocalDisplayAdapterTest {
doReturn(true).when(mFlags).isDisplayOffloadEnabled();
doReturn(true).when(mFlags).isEvenDimmerEnabled();
+ doReturn(true).when(mFlags).isDisplayContentModeManagementEnabled();
initDisplayOffloadSession();
}
@@ -1468,6 +1469,103 @@ public class LocalDisplayAdapterTest {
assertFalse(mDisplayOffloadSession.isActive());
}
+ @Test
+ public void testAllowsContentSwitch_firstDisplay() throws Exception {
+ // Set up a first display
+ setUpDisplay(new FakeDisplay(PORT_A));
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ // The first display should be allowed to use the content mode switch
+ DisplayDevice firstDisplayDevice = mListener.addedDisplays.get(0);
+ assertTrue((firstDisplayDevice.getDisplayDeviceInfoLocked().flags
+ & DisplayDeviceInfo.FLAG_ALLOWS_CONTENT_MODE_SWITCH) != 0);
+ }
+
+ @Test
+ public void testAllowsContentSwitch_secondaryDisplayPublicAndNotShouldShowOwnContent()
+ throws Exception {
+ // Set up a first display and a secondary display
+ setUpDisplay(new FakeDisplay(PORT_A));
+ setUpDisplay(new FakeDisplay(PORT_B));
+ updateAvailableDisplays();
+
+ // Set the secondary display to be a public display
+ doReturn(new int[0]).when(mMockedResources)
+ .getIntArray(com.android.internal.R.array.config_localPrivateDisplayPorts);
+ // Disable FLAG_OWN_CONTENT_ONLY for the secondary display
+ doReturn(true).when(mMockedResources)
+ .getBoolean(com.android.internal.R.bool.config_localDisplaysMirrorContent);
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ // This secondary display should be allowed to use the content mode switch
+ DisplayDevice secondaryDisplayDevice = mListener.addedDisplays.get(1);
+ assertTrue((secondaryDisplayDevice.getDisplayDeviceInfoLocked().flags
+ & DisplayDeviceInfo.FLAG_ALLOWS_CONTENT_MODE_SWITCH) != 0);
+ }
+
+ @Test
+ public void testAllowsContentSwitch_privateDisplay() throws Exception {
+ // Set up a first display and a secondary display
+ setUpDisplay(new FakeDisplay(PORT_A));
+ setUpDisplay(new FakeDisplay(PORT_B));
+ updateAvailableDisplays();
+
+ // Set the secondary display to be a private display
+ doReturn(new int[]{ PORT_B }).when(mMockedResources)
+ .getIntArray(com.android.internal.R.array.config_localPrivateDisplayPorts);
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ // The private display should not be allowed to use the content mode switch
+ DisplayDevice secondaryDisplayDevice = mListener.addedDisplays.get(1);
+ assertTrue((secondaryDisplayDevice.getDisplayDeviceInfoLocked().flags
+ & DisplayDeviceInfo.FLAG_ALLOWS_CONTENT_MODE_SWITCH) == 0);
+ }
+
+ @Test
+ public void testAllowsContentSwitch_ownContentOnlyDisplay() throws Exception {
+ // Set up a first display and a secondary display
+ setUpDisplay(new FakeDisplay(PORT_A));
+ setUpDisplay(new FakeDisplay(PORT_B));
+ updateAvailableDisplays();
+
+ // Enable FLAG_OWN_CONTENT_ONLY for the secondary display
+ doReturn(false).when(mMockedResources)
+ .getBoolean(com.android.internal.R.bool.config_localDisplaysMirrorContent);
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ // The secondary display with FLAG_OWN_CONTENT_ONLY enabled should not be allowed to use the
+ // content mode switch
+ DisplayDevice secondaryDisplayDevice = mListener.addedDisplays.get(1);
+ assertTrue((secondaryDisplayDevice.getDisplayDeviceInfoLocked().flags
+ & DisplayDeviceInfo.FLAG_ALLOWS_CONTENT_MODE_SWITCH) == 0);
+ }
+
+ @Test
+ public void testAllowsContentSwitch_flagShouldShowSystemDecorations() throws Exception {
+ // Set up a display
+ FakeDisplay display = new FakeDisplay(PORT_A);
+ setUpDisplay(display);
+ updateAvailableDisplays();
+
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ // Display with FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS enabled should not be allowed to use the
+ // content mode switch
+ DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+ int flags = displayDevice.getDisplayDeviceInfoLocked().flags;
+ boolean allowsContentModeSwitch =
+ ((flags & DisplayDeviceInfo.FLAG_ALLOWS_CONTENT_MODE_SWITCH) != 0);
+ boolean shouldShowSystemDecorations =
+ ((flags & DisplayDeviceInfo.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0);
+ assertFalse(allowsContentModeSwitch && shouldShowSystemDecorations);
+ }
+
private void initDisplayOffloadSession() {
when(mDisplayOffloader.startOffload()).thenReturn(true);
when(mDisplayOffloader.allowAutoBrightnessInDoze()).thenReturn(true);
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
index 241ffdc19ce4..4f74667f3ff2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
@@ -33,10 +33,8 @@ import static com.android.window.flags.Flags.FLAG_MULTI_CROP;
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
@@ -48,7 +46,6 @@ import android.platform.test.annotations.RequiresFlagsEnabled;
import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
-import android.view.Display;
import android.view.DisplayInfo;
import android.view.WindowInsets;
import android.view.WindowManager;
@@ -95,7 +92,6 @@ public class WallpaperCropperTest {
@Mock
private Resources mResources;
- private WallpaperCropper mWallpaperCropper;
private static final Point PORTRAIT_ONE = new Point(500, 800);
private static final Point PORTRAIT_TWO = new Point(400, 1000);
@@ -171,7 +167,6 @@ public class WallpaperCropperTest {
return getWallpaperTestDir(userId);
}).when(() -> WallpaperUtils.getWallpaperDir(anyInt()));
- mWallpaperCropper = new WallpaperCropper(mWallpaperDisplayHelper);
}
private File getWallpaperTestDir(int userId) {
@@ -738,13 +733,13 @@ public class WallpaperCropperTest {
DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = 2560;
displayInfo.logicalHeight = 1044;
+ setUpWithDisplays(List.of(new Point(displayInfo.logicalWidth, displayInfo.logicalHeight)));
doReturn(displayInfo).when(mWallpaperDisplayHelper).getDisplayInfo(eq(DEFAULT_DISPLAY));
WallpaperData wallpaperData = createWallpaperData(/* isStockWallpaper = */ false,
new Point(100, 100));
- assertThat(
- mWallpaperCropper.isWallpaperCompatibleForDisplay(DEFAULT_DISPLAY,
- wallpaperData)).isTrue();
+ assertThat(new WallpaperCropper(mWallpaperDisplayHelper).isWallpaperCompatibleForDisplay(
+ DEFAULT_DISPLAY, wallpaperData)).isTrue();
}
// Test isWallpaperCompatibleForDisplay always return true for the stock wallpaper.
@@ -755,49 +750,67 @@ public class WallpaperCropperTest {
DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = 2560;
displayInfo.logicalHeight = 1044;
+ setUpWithDisplays(List.of(new Point(displayInfo.logicalWidth, displayInfo.logicalHeight)));
doReturn(displayInfo).when(mWallpaperDisplayHelper).getDisplayInfo(eq(displayId));
WallpaperData wallpaperData = createWallpaperData(/* isStockWallpaper = */ true,
new Point(100, 100));
- assertThat(
- mWallpaperCropper.isWallpaperCompatibleForDisplay(displayId,
- wallpaperData)).isTrue();
+ assertThat(new WallpaperCropper(mWallpaperDisplayHelper).isWallpaperCompatibleForDisplay(
+ displayId, wallpaperData)).isTrue();
}
// Test isWallpaperCompatibleForDisplay wallpaper is suitable for the display and wallpaper
// aspect ratio meets the hard-coded aspect ratio.
@Test
- public void isWallpaperCompatibleForDisplay_wallpaperSizeSuitableForDisplayAndMeetAspectRatio_returnTrue()
+ public void isWallpaperCompatibleForDisplay_wallpaperSizeLargerThanDisplayAndMeetAspectRatio_returnTrue()
throws Exception {
final int displayId = 2;
DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = 2560;
displayInfo.logicalHeight = 1044;
+ setUpWithDisplays(List.of(new Point(displayInfo.logicalWidth, displayInfo.logicalHeight)));
doReturn(displayInfo).when(mWallpaperDisplayHelper).getDisplayInfo(eq(displayId));
WallpaperData wallpaperData = createWallpaperData(/* isStockWallpaper = */ false,
new Point(4000, 3000));
- assertThat(
- mWallpaperCropper.isWallpaperCompatibleForDisplay(displayId,
- wallpaperData)).isTrue();
+ assertThat(new WallpaperCropper(mWallpaperDisplayHelper).isWallpaperCompatibleForDisplay(
+ displayId, wallpaperData)).isTrue();
}
- // Test isWallpaperCompatibleForDisplay wallpaper is not suitable for the display and wallpaper
- // aspect ratio meets the hard-coded aspect ratio.
+ // Test isWallpaperCompatibleForDisplay wallpaper is smaller than the display but larger than
+ // the threshold and wallpaper aspect ratio meets the hard-coded aspect ratio.
@Test
- public void isWallpaperCompatibleForDisplay_wallpaperSizeNotSuitableForDisplayAndMeetAspectRatio_returnFalse()
+ public void isWallpaperCompatibleForDisplay_wallpaperSizeSmallerThanDisplayButBeyondThresholdAndMeetAspectRatio_returnTrue()
throws Exception {
final int displayId = 2;
DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = 2560;
displayInfo.logicalHeight = 1044;
+ setUpWithDisplays(List.of(new Point(displayInfo.logicalWidth, displayInfo.logicalHeight)));
doReturn(displayInfo).when(mWallpaperDisplayHelper).getDisplayInfo(eq(displayId));
WallpaperData wallpaperData = createWallpaperData(/* isStockWallpaper = */ false,
- new Point(1000, 500));
+ new Point(2000, 900));
+
+ assertThat(new WallpaperCropper(mWallpaperDisplayHelper).isWallpaperCompatibleForDisplay(
+ displayId, wallpaperData)).isTrue();
+ }
+
+ // Test isWallpaperCompatibleForDisplay wallpaper is smaller than the display but larger than
+ // the threshold and wallpaper aspect ratio meets the hard-coded aspect ratio.
+ @Test
+ public void isWallpaperCompatibleForDisplay_wallpaperSizeSmallerThanDisplayButAboveThresholdAndMeetAspectRatio_returnFalse()
+ throws Exception {
+ final int displayId = 2;
+ DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.logicalWidth = 2560;
+ displayInfo.logicalHeight = 1044;
+ setUpWithDisplays(List.of(new Point(displayInfo.logicalWidth, displayInfo.logicalHeight)));
+ doReturn(displayInfo).when(mWallpaperDisplayHelper).getDisplayInfo(eq(displayId));
+ WallpaperData wallpaperData = createWallpaperData(/* isStockWallpaper = */ false,
+ new Point(2000, 800));
- assertThat(
- mWallpaperCropper.isWallpaperCompatibleForDisplay(displayId,
- wallpaperData)).isFalse();
+ assertThat(new WallpaperCropper(mWallpaperDisplayHelper).isWallpaperCompatibleForDisplay(
+ displayId, wallpaperData)).isFalse();
}
// Test isWallpaperCompatibleForDisplay wallpaper is suitable for the display and wallpaper
@@ -809,13 +822,13 @@ public class WallpaperCropperTest {
DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = 2560;
displayInfo.logicalHeight = 1044;
+ setUpWithDisplays(List.of(new Point(displayInfo.logicalWidth, displayInfo.logicalHeight)));
doReturn(displayInfo).when(mWallpaperDisplayHelper).getDisplayInfo(eq(displayId));
WallpaperData wallpaperData = createWallpaperData(/* isStockWallpaper = */ false,
new Point(2000, 4000));
- assertThat(
- mWallpaperCropper.isWallpaperCompatibleForDisplay(displayId,
- wallpaperData)).isFalse();
+ assertThat(new WallpaperCropper(mWallpaperDisplayHelper).isWallpaperCompatibleForDisplay(
+ displayId, wallpaperData)).isFalse();
}
// Test isWallpaperCompatibleForDisplay, portrait display, wallpaper is suitable for the display
@@ -827,24 +840,13 @@ public class WallpaperCropperTest {
DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = 1044;
displayInfo.logicalHeight = 2560;
+ setUpWithDisplays(List.of(new Point(displayInfo.logicalWidth, displayInfo.logicalHeight)));
doReturn(displayInfo).when(mWallpaperDisplayHelper).getDisplayInfo(eq(displayId));
WallpaperData wallpaperData = createWallpaperData(/* isStockWallpaper = */ false,
new Point(2000, 4000));
- assertThat(
- mWallpaperCropper.isWallpaperCompatibleForDisplay(displayId,
- wallpaperData)).isTrue();
- }
-
- private void mockDisplay(int displayId, Point displayResolution) {
- final Display mockDisplay = mock(Display.class);
- when(mockDisplay.getDisplayInfo(any(DisplayInfo.class))).thenAnswer(invocation -> {
- DisplayInfo displayInfo = invocation.getArgument(0);
- displayInfo.displayId = displayId;
- displayInfo.logicalWidth = displayResolution.x;
- displayInfo.logicalHeight = displayResolution.y;
- return true;
- });
+ assertThat(new WallpaperCropper(mWallpaperDisplayHelper).isWallpaperCompatibleForDisplay(
+ displayId, wallpaperData)).isTrue();
}
private WallpaperData createWallpaperData(boolean isStockWallpaper, Point wallpaperSize)
diff --git a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
index b6f4e13cad8c..94ce72368a92 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
@@ -41,6 +41,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
+import android.annotation.NonNull;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
import android.content.Context;
@@ -52,8 +53,8 @@ import android.os.BatteryStats;
import android.os.BatteryStatsInternal;
import android.os.Handler;
import android.os.IBinder;
-import android.os.IWakeLockCallback;
import android.os.IScreenTimeoutPolicyListener;
+import android.os.IWakeLockCallback;
import android.os.Looper;
import android.os.PowerManager;
import android.os.RemoteException;
@@ -724,6 +725,7 @@ public class NotifierTest {
final int uid = 1234;
final int pid = 5678;
+
mNotifier.onWakeLockReleased(PowerManager.PARTIAL_WAKE_LOCK, "wakelockTag",
"my.package.name", uid, pid, /* workSource= */ null, /* historyTag= */ null,
exceptingCallback);
@@ -789,6 +791,55 @@ public class NotifierTest {
}
@Test
+ public void test_wakeLockLogUsesWorkSource() {
+ createNotifier();
+ clearInvocations(mWakeLockLog);
+ IWakeLockCallback exceptingCallback = new IWakeLockCallback.Stub() {
+ @Override public void onStateChanged(boolean enabled) throws RemoteException {
+ throw new RemoteException("Just testing");
+ }
+ };
+
+ final int uid = 1234;
+ final int pid = 5678;
+ WorkSource worksource = new WorkSource(1212);
+ WorkSource worksource2 = new WorkSource(3131);
+
+ mNotifier.onWakeLockAcquired(PowerManager.PARTIAL_WAKE_LOCK, "wakelockTag",
+ "my.package.name", uid, pid, worksource, /* historyTag= */ null,
+ exceptingCallback);
+ verify(mWakeLockLog).onWakeLockAcquired("wakelockTag", 1212,
+ PowerManager.PARTIAL_WAKE_LOCK, -1);
+
+ // Release the wakelock
+ mNotifier.onWakeLockReleased(PowerManager.FULL_WAKE_LOCK, "wakelockTag2",
+ "my.package.name", uid, pid, worksource2, /* historyTag= */ null,
+ exceptingCallback);
+ verify(mWakeLockLog).onWakeLockReleased("wakelockTag2", 3131, -1);
+
+ // clear the handler
+ mTestLooper.dispatchAll();
+
+ // Now test with improveWakelockLatency flag true
+ clearInvocations(mWakeLockLog);
+ when(mPowerManagerFlags.improveWakelockLatency()).thenReturn(true);
+
+ mNotifier.onWakeLockAcquired(PowerManager.PARTIAL_WAKE_LOCK, "wakelockTag",
+ "my.package.name", uid, pid, worksource, /* historyTag= */ null,
+ exceptingCallback);
+ mTestLooper.dispatchAll();
+ verify(mWakeLockLog).onWakeLockAcquired("wakelockTag", 1212,
+ PowerManager.PARTIAL_WAKE_LOCK, 1);
+
+ // Release the wakelock
+ mNotifier.onWakeLockReleased(PowerManager.FULL_WAKE_LOCK, "wakelockTag2",
+ "my.package.name", uid, pid, worksource2, /* historyTag= */ null,
+ exceptingCallback);
+ mTestLooper.dispatchAll();
+ verify(mWakeLockLog).onWakeLockReleased("wakelockTag2", 3131, 1);
+ }
+
+ @Test
public void
test_notifierProcessesWorkSourceDeepCopy_OnWakelockChanging() throws RemoteException {
when(mPowerManagerFlags.improveWakelockLatency()).thenReturn(true);
@@ -943,15 +994,23 @@ public class NotifierTest {
assertEquals(mNotifier.getWakelockMonitorTypeForLogging(PowerManager.PARTIAL_WAKE_LOCK),
PowerManager.PARTIAL_WAKE_LOCK);
assertEquals(mNotifier.getWakelockMonitorTypeForLogging(
+ PowerManager.DOZE_WAKE_LOCK), -1);
+ }
+
+ @Test
+ public void getWakelockMonitorTypeForLogging_evaluateProximityLevel() {
+ // How proximity wakelock is evaluated depends on boolean configuration. Test both.
+ when(mResourcesSpy.getBoolean(
+ com.android.internal.R.bool.config_suspendWhenScreenOffDueToProximity))
+ .thenReturn(false);
+ createNotifier();
+ assertEquals(mNotifier.getWakelockMonitorTypeForLogging(
PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK),
PowerManager.PARTIAL_WAKE_LOCK);
- assertEquals(mNotifier.getWakelockMonitorTypeForLogging(
- PowerManager.DOZE_WAKE_LOCK), -1);
when(mResourcesSpy.getBoolean(
com.android.internal.R.bool.config_suspendWhenScreenOffDueToProximity))
.thenReturn(true);
-
createNotifier();
assertEquals(mNotifier.getWakelockMonitorTypeForLogging(
PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK), -1);
@@ -1238,7 +1297,7 @@ public class NotifierTest {
}
@Override
- public WakeLockLog getWakeLockLog(Context context) {
+ public @NonNull WakeLockLog getWakeLockLog(Context context) {
return mWakeLockLog;
}
diff --git a/services/tests/powerservicetests/src/com/android/server/power/WakeLockLogTest.java b/services/tests/powerservicetests/src/com/android/server/power/WakeLockLogTest.java
index c1d7c7b4a4c2..534337ee9dbf 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/WakeLockLogTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/WakeLockLogTest.java
@@ -27,6 +27,8 @@ import android.content.pm.PackageManager;
import android.os.PowerManager;
import android.os.Process;
+import com.android.server.power.WakeLockLog.TagData;
+
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
@@ -62,8 +64,9 @@ public class WakeLockLogTest {
@Test
public void testAddTwoItems_withNoEventTimeSupplied() {
final int tagDatabaseSize = 128;
+ final int tagStartingSize = 16;
final int logSize = 20;
- TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+ TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, tagStartingSize, logSize));
WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
when(injectorSpy.currentTimeMillis()).thenReturn(1000L);
log.onWakeLockAcquired("TagPartial", 101,
@@ -93,8 +96,9 @@ public class WakeLockLogTest {
@Test
public void testAddTwoItems() {
final int tagDatabaseSize = 128;
+ final int tagStartingSize = 16;
final int logSize = 20;
- TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+ TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, tagStartingSize, logSize));
WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
log.onWakeLockAcquired("TagPartial", 101,
@@ -117,8 +121,9 @@ public class WakeLockLogTest {
@Test
public void testAddTwoItemsWithTimeReset() {
final int tagDatabaseSize = 128;
+ final int tagStartingSize = 16;
final int logSize = 20;
- TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+ TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, tagStartingSize, logSize));
WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
log.onWakeLockAcquired("TagPartial", 101, PowerManager.PARTIAL_WAKE_LOCK, 1000L);
@@ -136,9 +141,10 @@ public class WakeLockLogTest {
@Test
public void testAddTwoItemsWithTagOverwrite() {
- final int tagDatabaseSize = 2;
+ final int tagDatabaseSize = 1;
+ final int tagStartingSize = 1;
final int logSize = 20;
- TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+ TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, tagStartingSize, logSize));
WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
log.onWakeLockAcquired("TagPartial", 101, PowerManager.PARTIAL_WAKE_LOCK, 1000L);
@@ -157,8 +163,9 @@ public class WakeLockLogTest {
@Test
public void testAddFourItemsWithRingBufferOverflow() {
final int tagDatabaseSize = 6;
+ final int tagStartingSize = 2;
final int logSize = 10;
- TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+ TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, tagStartingSize, logSize));
WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
// Wake lock 1 acquired - log size = 3
@@ -206,8 +213,9 @@ public class WakeLockLogTest {
@Test
public void testAddItemWithBadTag() {
final int tagDatabaseSize = 6;
+ final int tagStartingSize = 2;
final int logSize = 10;
- TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+ TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, tagStartingSize, logSize));
WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
// Bad tag means it wont get written
@@ -224,8 +232,9 @@ public class WakeLockLogTest {
@Test
public void testAddItemWithReducedTagName() {
final int tagDatabaseSize = 6;
+ final int tagStartingSize = 2;
final int logSize = 10;
- TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+ TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, tagStartingSize, logSize));
WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
log.onWakeLockAcquired("*job*/com.one.two.3hree/.one..Last", 101,
@@ -242,9 +251,10 @@ public class WakeLockLogTest {
@Test
public void testAddAcquireAndReleaseWithRepeatTagName() {
- final int tagDatabaseSize = 6;
+ final int tagDatabaseSize = 5;
+ final int tagStartingSize = 5;
final int logSize = 10;
- TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+ TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, tagStartingSize, logSize));
WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
log.onWakeLockAcquired("HowdyTag", 101, PowerManager.PARTIAL_WAKE_LOCK, 1000L);
@@ -263,8 +273,9 @@ public class WakeLockLogTest {
@Test
public void testAddAcquireAndReleaseWithTimeTravel() {
final int tagDatabaseSize = 6;
+ final int tagStartingSize = 2;
final int logSize = 10;
- TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+ TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, tagStartingSize, logSize));
WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
log.onWakeLockAcquired("HowdyTag", 101, PowerManager.PARTIAL_WAKE_LOCK, 1100L);
@@ -283,8 +294,9 @@ public class WakeLockLogTest {
@Test
public void testAddSystemWakelock() {
final int tagDatabaseSize = 6;
+ final int tagStartingSize = 2;
final int logSize = 10;
- TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+ TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, tagStartingSize, logSize));
WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
log.onWakeLockAcquired("TagPartial", 101,
@@ -302,8 +314,9 @@ public class WakeLockLogTest {
@Test
public void testAddItemWithNoPackageName() {
final int tagDatabaseSize = 128;
+ final int tagStartingSize = 16;
final int logSize = 20;
- TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+ TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, tagStartingSize, logSize));
WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
when(mPackageManager.getPackagesForUid(101)).thenReturn(null);
@@ -322,8 +335,9 @@ public class WakeLockLogTest {
@Test
public void testAddItemWithMultiplePackageNames() {
final int tagDatabaseSize = 128;
+ final int tagStartingSize = 16;
final int logSize = 20;
- TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+ TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, tagStartingSize, logSize));
WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
when(mPackageManager.getPackagesForUid(101)).thenReturn(
@@ -344,8 +358,9 @@ public class WakeLockLogTest {
@Test
public void testAddItemsWithRepeatOwnerUid_UsesCache() {
final int tagDatabaseSize = 128;
+ final int tagStartingSize = 16;
final int logSize = 20;
- TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+ TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, tagStartingSize, logSize));
WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
log.onWakeLockAcquired("TagPartial", 101,
@@ -375,8 +390,9 @@ public class WakeLockLogTest {
@Test
public void testAddItemsWithRepeatOwnerUid_SavedAcquisitions_UsesCache() {
final int tagDatabaseSize = 128;
+ final int tagStartingSize = 16;
final int logSize = 10;
- TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize));
+ TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, tagStartingSize, logSize));
WakeLockLog log = new WakeLockLog(injectorSpy, mContext);
log.onWakeLockAcquired("TagPartial", 101,
@@ -420,6 +436,34 @@ public class WakeLockLogTest {
verify(mPackageManager, times(1)).getPackagesForUid(101);
}
+ @Test
+ public void testTagDatabaseGrowsBeyondStartingSize() {
+ final int tagDatabaseSize = 3;
+ final int tagStartingSize = 1;
+ final int logSize = 10;
+ // start with size = 1 and max size
+ TestInjector injector = new TestInjector(tagDatabaseSize, tagStartingSize, logSize);
+ WakeLockLog.TagDatabase td = new WakeLockLog.TagDatabase(injector);
+
+ // Add one
+ TagData data1 = td.findOrCreateTag("Tagname1", 1001, /* shouldCreate= */ true);
+ assertEquals(0, td.getTagIndex(data1));
+
+ // Check that it grows by adding 1 more
+ TagData data2 = td.findOrCreateTag("Tagname2", 1001, /* shouldCreate= */ true);
+ assertEquals(1, td.getTagIndex(data2));
+
+ // Lets add the last one to fill up the DB to maxSize
+ TagData data3 = td.findOrCreateTag("Tagname3", 1001, /* shouldCreate= */ true);
+ assertEquals(2, td.getTagIndex(data3));
+
+ // Adding a fourth one should replace the oldest one (Tagname1)
+ TagData data4 = td.findOrCreateTag("Tagname4", 1001, /* shouldCreate= */ true);
+ assertEquals(0, td.getTagIndex(data4));
+ assertEquals(tagDatabaseSize, td.getTagIndex(data1));
+
+ }
+
private String dumpLog(WakeLockLog log, boolean includeTagDb) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
@@ -429,10 +473,12 @@ public class WakeLockLogTest {
public static class TestInjector extends WakeLockLog.Injector {
private final int mTagDatabaseSize;
+ private final int mTagStartingSize;
private final int mLogSize;
- public TestInjector(int tagDatabaseSize, int logSize) {
+ public TestInjector(int tagDatabaseSize, int tagStartingSize, int logSize) {
mTagDatabaseSize = tagDatabaseSize;
+ mTagStartingSize = tagStartingSize;
mLogSize = logSize;
}
@@ -442,6 +488,11 @@ public class WakeLockLogTest {
}
@Override
+ public int getTagDatabaseStartingSize() {
+ return mTagStartingSize;
+ }
+
+ @Override
public int getLogSize() {
return mLogSize;
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/HearingDevicePhoneCallNotificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/HearingDevicePhoneCallNotificationControllerTest.java
index 63c572af37b2..3565244d90b3 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/HearingDevicePhoneCallNotificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/HearingDevicePhoneCallNotificationControllerTest.java
@@ -36,6 +36,8 @@ import android.content.Context;
import android.media.AudioDeviceInfo;
import android.media.AudioDevicePort;
import android.media.AudioManager;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
@@ -67,6 +69,8 @@ import java.util.concurrent.Executor;
public class HearingDevicePhoneCallNotificationControllerTest {
@Rule
public MockitoRule mockito = MockitoJUnit.rule();
+ @Rule
+ public SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private static final String TEST_ADDRESS = "55:66:77:88:99:AA";
@@ -118,6 +122,7 @@ public class HearingDevicePhoneCallNotificationControllerTest {
AudioManager.DEVICE_OUT_BLE_HEADSET);
when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn(
new AudioDeviceInfo[]{hapDeviceInfo});
+ when(mAudioManager.getCommunicationDevice()).thenReturn(hapDeviceInfo);
when(mAudioManager.getAvailableCommunicationDevices()).thenReturn(List.of(hapDeviceInfo));
mTestCallStateListener.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK);
@@ -132,6 +137,7 @@ public class HearingDevicePhoneCallNotificationControllerTest {
AudioManager.DEVICE_OUT_BLUETOOTH_A2DP);
when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn(
new AudioDeviceInfo[]{a2dpDeviceInfo});
+ when(mAudioManager.getCommunicationDevice()).thenReturn(a2dpDeviceInfo);
when(mAudioManager.getAvailableCommunicationDevices()).thenReturn(List.of(a2dpDeviceInfo));
mTestCallStateListener.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK);
@@ -146,6 +152,7 @@ public class HearingDevicePhoneCallNotificationControllerTest {
AudioManager.DEVICE_OUT_BLE_HEADSET);
when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn(
new AudioDeviceInfo[]{hapDeviceInfo});
+ when(mAudioManager.getCommunicationDevice()).thenReturn(hapDeviceInfo);
when(mAudioManager.getAvailableCommunicationDevices()).thenReturn(List.of(hapDeviceInfo));
mTestCallStateListener.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK);
@@ -155,6 +162,51 @@ public class HearingDevicePhoneCallNotificationControllerTest {
eq(SystemMessageProto.SystemMessage.NOTE_HEARING_DEVICE_INPUT_SWITCH));
}
+ @Test
+ @EnableFlags(Flags.FLAG_HEARING_INPUT_CHANGE_WHEN_COMM_DEVICE)
+ public void onCallStateChanged_nonHearingDevice_offHookThenIdle_callAddAndRemoveListener() {
+ final ArgumentCaptor<AudioManager.OnCommunicationDeviceChangedListener> listenerCaptor =
+ ArgumentCaptor.forClass(AudioManager.OnCommunicationDeviceChangedListener.class);
+ AudioDeviceInfo a2dpDeviceInfo = createAudioDeviceInfo(TEST_ADDRESS,
+ AudioManager.DEVICE_OUT_BLUETOOTH_A2DP);
+ when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn(
+ new AudioDeviceInfo[]{a2dpDeviceInfo});
+ when(mAudioManager.getCommunicationDevice()).thenReturn(a2dpDeviceInfo);
+
+ mTestCallStateListener.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK);
+ mTestCallStateListener.onCallStateChanged(TelephonyManager.CALL_STATE_IDLE);
+
+ verify(mAudioManager).addOnCommunicationDeviceChangedListener(any(Executor.class),
+ listenerCaptor.capture());
+ verify(mAudioManager).removeOnCommunicationDeviceChangedListener(
+ eq(listenerCaptor.getValue()));
+ }
+
+
+ @Test
+ @EnableFlags(Flags.FLAG_HEARING_INPUT_CHANGE_WHEN_COMM_DEVICE)
+ public void onCallStateChanged_hearingDeviceFromCommunicationDeviceChanged_showNotification() {
+ final ArgumentCaptor<AudioManager.OnCommunicationDeviceChangedListener> listenerCaptor =
+ ArgumentCaptor.forClass(AudioManager.OnCommunicationDeviceChangedListener.class);
+ AudioDeviceInfo hapDeviceInfo = createAudioDeviceInfo(TEST_ADDRESS,
+ AudioManager.DEVICE_OUT_BLE_HEADSET);
+ AudioDeviceInfo a2dpDeviceInfo = createAudioDeviceInfo(TEST_ADDRESS,
+ AudioManager.DEVICE_OUT_BLUETOOTH_A2DP);
+ when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn(
+ new AudioDeviceInfo[]{a2dpDeviceInfo});
+ when(mAudioManager.getCommunicationDevice()).thenReturn(a2dpDeviceInfo);
+
+ mTestCallStateListener.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK);
+ verify(mAudioManager).addOnCommunicationDeviceChangedListener(any(Executor.class),
+ listenerCaptor.capture());
+ when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn(
+ new AudioDeviceInfo[]{hapDeviceInfo});
+ listenerCaptor.getValue().onCommunicationDeviceChanged(hapDeviceInfo);
+
+ verify(mNotificationManager).notify(
+ eq(SystemMessageProto.SystemMessage.NOTE_HEARING_DEVICE_INPUT_SWITCH), any());
+ }
+
private AudioDeviceInfo createAudioDeviceInfo(String address, int type) {
AudioDevicePort audioDevicePort = mock(AudioDevicePort.class);
doReturn(type).when(audioDevicePort).type();
diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp
index 66d7611a29c6..d34d74d80882 100644
--- a/services/tests/uiservicestests/Android.bp
+++ b/services/tests/uiservicestests/Android.bp
@@ -11,13 +11,8 @@ package {
default_applicable_licenses: ["frameworks_base_license"],
}
-android_test {
- name: "FrameworksUiServicesTests",
-
- // Include test java files
- srcs: [
- "src/**/*.java",
- ],
+java_defaults {
+ name: "FrameworksUiServicesTests-defaults",
static_libs: [
"compatibility-device-util-axt-minus-dexmaker",
@@ -95,12 +90,72 @@ android_test {
javacflags: ["-parameters"],
}
-test_module_config {
- name: "FrameworksUiServicesTests_notification",
- base: "FrameworksUiServicesTests",
- test_suites: [
- "automotive-tests",
- "device-tests",
+// Utility files used by multiple tests
+filegroup {
+ name: "shared-srcs",
+ srcs: [
+ "src/android/app/ExampleActivity.java",
+ "src/android/app/NotificationSystemUtil.java",
+ "src/com/android/frameworks/tests/uiservices/DummyProvider.java",
+ "src/com/android/internal/logging/InstanceIdSequenceFake.java",
+ "src/com/android/server/UiServiceTestCase.java",
+ "src/com/android/server/notification/ZenChangeOrigin.java",
+ "src/com/android/server/notification/ZenModeEventLoggerFake.java",
+ ],
+ visibility: ["//visibility:private"],
+}
+
+filegroup {
+ name: "notification-srcs",
+ srcs: [
+ "src/**/Notification*.java",
+ "src/com/android/server/notification/*.java",
+ ],
+ visibility: ["//visibility:private"],
+}
+
+filegroup {
+ name: "notification-zen-srcs",
+ srcs: [
+ "src/android/app/NotificationManagerZenTest.java",
+ "src/com/android/server/notification/Zen*Test.java",
+ ],
+ visibility: ["//visibility:private"],
+}
+
+android_test {
+ name: "FrameworksUiServicesTests",
+
+ // Include test java files but not the notification & zen ones which are separated
+ srcs: [
+ "src/**/*.java",
+ ],
+
+ exclude_srcs: [
+ ":notification-srcs",
+ ":notification-zen-srcs",
+ ],
+
+ defaults: ["FrameworksUiServicesTests-defaults"],
+}
+
+android_test {
+ name: "FrameworksUiServicesNotificationTests",
+ srcs: [
+ ":notification-srcs",
+ ":shared-srcs",
+ ],
+ exclude_srcs: [":notification-zen-srcs"],
+ defaults: ["FrameworksUiServicesTests-defaults"],
+ test_config: "notification-tests.xml",
+}
+
+android_test {
+ name: "FrameworksUiServicesZenTests",
+ srcs: [
+ ":notification-zen-srcs",
+ ":shared-srcs",
],
- exclude_annotations: ["androidx.test.filters.LargeTest"],
+ defaults: ["FrameworksUiServicesTests-defaults"],
+ test_config: "notification-zen-tests.xml",
}
diff --git a/services/tests/uiservicestests/AndroidTest.xml b/services/tests/uiservicestests/AndroidTest.xml
index 11e8f090a8fa..93c8c72630f1 100644
--- a/services/tests/uiservicestests/AndroidTest.xml
+++ b/services/tests/uiservicestests/AndroidTest.xml
@@ -15,6 +15,7 @@
-->
<configuration description="Runs Frameworks UI Services Tests.">
<target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="cleanup-apks" value="true" />
<option name="test-file-name" value="FrameworksUiServicesTests.apk" />
</target_preparer>
diff --git a/services/tests/uiservicestests/notification-tests.xml b/services/tests/uiservicestests/notification-tests.xml
new file mode 100644
index 000000000000..acfd844efe26
--- /dev/null
+++ b/services/tests/uiservicestests/notification-tests.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Runs Frameworks UI Services Tests (notifications subset).">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="FrameworksUiServicesNotificationTests.apk" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="framework-base-presubmit" />
+ <option name="test-tag" value="FrameworksUiServicesNotificationTests" />
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.frameworks.tests.uiservices" />
+ <option name="runner" value="android.testing.TestableInstrumentation" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+</configuration>
diff --git a/services/tests/uiservicestests/notification-zen-tests.xml b/services/tests/uiservicestests/notification-zen-tests.xml
new file mode 100644
index 000000000000..01d8aab83d59
--- /dev/null
+++ b/services/tests/uiservicestests/notification-zen-tests.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Runs Frameworks UI Services Tests (zen mode subset).">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="FrameworksUiServicesZenTests.apk" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="framework-base-presubmit" />
+ <option name="test-tag" value="FrameworksUiServicesZenTests" />
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.frameworks.tests.uiservices" />
+ <option name="runner" value="android.testing.TestableInstrumentation" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+</configuration>
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 25fdedf32908..ed00a9e8e74b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -30,7 +30,10 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.os.Build.VERSION_CODES.P;
import static android.os.Build.VERSION_CODES.Q;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.FLAG_ALLOWS_CONTENT_MODE_SWITCH;
import static android.view.Display.FLAG_PRIVATE;
+import static android.view.Display.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
+import static android.view.Display.FLAG_TRUSTED;
import static android.view.DisplayCutout.BOUNDS_POSITION_TOP;
import static android.view.DisplayCutout.fromBoundingRect;
import static android.view.Surface.ROTATION_0;
@@ -2923,6 +2926,63 @@ public class DisplayContentTests extends WindowTestsBase {
assertFalse(dc.mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(dc));
}
+ @EnableFlags(FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT)
+ @Test
+ public void testSetShouldShowSystemDecorations_shouldShowSystemDecorationsDisplay() {
+ // Set up a non-default display with FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS enabled
+ final DisplayInfo displayInfo = new DisplayInfo(mDisplayInfo);
+ displayInfo.displayId = DEFAULT_DISPLAY + 1;
+ displayInfo.flags = FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
+ final DisplayContent dc = createNewDisplay(displayInfo);
+
+ dc.onDisplayInfoChangeApplied();
+ assertFalse(dc.mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(dc));
+ }
+
+ @EnableFlags(FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT)
+ @Test
+ public void testSetShouldShowSystemDecorations_notAllowContentModeSwitchDisplay() {
+ // Set up a non-default display without FLAG_ALLOWS_CONTENT_MODE_SWITCH enabled
+ final DisplayInfo displayInfo = new DisplayInfo(mDisplayInfo);
+ displayInfo.displayId = DEFAULT_DISPLAY + 1;
+ displayInfo.flags = FLAG_TRUSTED;
+ final DisplayContent dc = createNewDisplay(displayInfo);
+
+ dc.onDisplayInfoChangeApplied();
+ assertFalse(dc.mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(dc));
+ }
+
+ @EnableFlags(FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT)
+ @Test
+ public void testSetShouldShowSystemDecorations_untrustedDisplay() {
+ // Set up a non-default display without FLAG_TRUSTED enabled
+ final DisplayInfo displayInfo = new DisplayInfo(mDisplayInfo);
+ displayInfo.displayId = DEFAULT_DISPLAY + 1;
+ displayInfo.flags = FLAG_ALLOWS_CONTENT_MODE_SWITCH;
+ final DisplayContent dc = createNewDisplay(displayInfo);
+
+ dc.onDisplayInfoChangeApplied();
+ assertFalse(dc.mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(dc));
+ }
+
+ @EnableFlags(FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT)
+ @Test
+ public void testSetShouldShowSystemDecorations_nonDefaultNonPrivateDisplay() {
+ final DisplayInfo displayInfo = new DisplayInfo(mDisplayInfo);
+ displayInfo.displayId = DEFAULT_DISPLAY + 1;
+ displayInfo.flags = (FLAG_ALLOWS_CONTENT_MODE_SWITCH | FLAG_TRUSTED);
+ final DisplayContent dc = createNewDisplay(displayInfo);
+
+ spyOn(dc.mDisplay);
+ doReturn(false).when(dc.mDisplay).canHostTasks();
+ dc.onDisplayInfoChangeApplied();
+ assertFalse(dc.mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(dc));
+
+ doReturn(true).when(dc.mDisplay).canHostTasks();
+ dc.onDisplayInfoChangeApplied();
+ assertTrue(dc.mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(dc));
+ }
+
@EnableFlags(FLAG_ENABLE_PERSISTING_DISPLAY_SIZE_FOR_CONNECTED_DISPLAYS)
@Test
public void testForcedDensityRatioSetForExternalDisplays_persistDensityScaleFlagEnabled() {
@@ -2991,23 +3051,6 @@ public class DisplayContentTests extends WindowTestsBase {
assertEquals(320, displayContent.mBaseDisplayDensity);
}
- @EnableFlags(FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT)
- @Test
- public void testSetShouldShowSystemDecorations_nonDefaultNonPrivateDisplay() {
- final DisplayInfo displayInfo = new DisplayInfo(mDisplayInfo);
- displayInfo.displayId = DEFAULT_DISPLAY + 1;
- final DisplayContent dc = createNewDisplay(displayInfo);
-
- spyOn(dc.mDisplay);
- doReturn(false).when(dc.mDisplay).canHostTasks();
- dc.onDisplayInfoChangeApplied();
- assertFalse(dc.mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(dc));
-
- doReturn(true).when(dc.mDisplay).canHostTasks();
- dc.onDisplayInfoChangeApplied();
- assertTrue(dc.mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(dc));
- }
-
private void removeRootTaskTests(Runnable runnable) {
final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
final Task rootTask1 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,