Merge "Deprecate min/max size in PipResizeGestureHandler" into main
diff --git a/apct-tests/perftests/core/src/android/os/TracePerfTest.java b/apct-tests/perftests/core/src/android/os/TracePerfTest.java
index d905124..00e1c1f 100644
--- a/apct-tests/perftests/core/src/android/os/TracePerfTest.java
+++ b/apct-tests/perftests/core/src/android/os/TracePerfTest.java
@@ -147,7 +147,7 @@
.addField(1 /* sending_thread_name */, "foo")
.endNested()
.endProto()
- .addTerminatingFlow(5)
+ .setTerminatingFlow(5)
.emit();
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
@@ -158,7 +158,7 @@
.addField(1 /* sending_thread_name */, "foo")
.endNested()
.endProto()
- .addTerminatingFlow(5)
+ .setTerminatingFlow(5)
.emit();
}
}
diff --git a/apct-tests/perftests/healthconnect/OWNERS b/apct-tests/perftests/healthconnect/OWNERS
index acfe799..7c9e769 100644
--- a/apct-tests/perftests/healthconnect/OWNERS
+++ b/apct-tests/perftests/healthconnect/OWNERS
@@ -1,5 +1,4 @@
# Bug component: 1219472
-arkivanov@google.com
jstembridge@google.com
itsleo@google.com
diff --git a/api/OWNERS b/api/OWNERS
index f2bcf13..31ffa8c 100644
--- a/api/OWNERS
+++ b/api/OWNERS
@@ -1,4 +1,3 @@
-hansson@google.com
# Modularization team
file:platform/packages/modules/common:/OWNERS
diff --git a/cmds/idmap2/OWNERS b/cmds/idmap2/OWNERS
index 062ffd4..def9f40 100644
--- a/cmds/idmap2/OWNERS
+++ b/cmds/idmap2/OWNERS
@@ -1,4 +1,3 @@
set noparent
-toddke@google.com
patb@google.com
zyy@google.com
diff --git a/cmds/incidentd/OWNERS b/cmds/incidentd/OWNERS
index bcdcfc3..db8fa9f 100644
--- a/cmds/incidentd/OWNERS
+++ b/cmds/incidentd/OWNERS
@@ -1,4 +1,3 @@
joeo@google.com
yaochen@google.com
yanmin@google.com
-zhouwenjie@google.com
diff --git a/core/java/android/app/DreamManager.java b/core/java/android/app/DreamManager.java
index c597a9d..555006b 100644
--- a/core/java/android/app/DreamManager.java
+++ b/core/java/android/app/DreamManager.java
@@ -71,8 +71,11 @@
@TestApi
@RequiresPermission(WRITE_SECURE_SETTINGS)
public void setScreensaverEnabled(boolean enabled) {
- Settings.Secure.putIntForUser(mContext.getContentResolver(),
- Settings.Secure.SCREENSAVER_ENABLED, enabled ? 1 : 0, UserHandle.USER_CURRENT);
+ try {
+ mService.setScreensaverEnabled(enabled);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
}
/**
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 1b71e73..cc72d8f 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -9526,14 +9526,21 @@
.viewType(viewType)
.highlightExpander(isConversationLayout)
.hideProgress(true)
- .text(null)
.hideLeftIcon(isOneToOne)
.hideRightIcon(hideRightIcons || isOneToOne);
if (notificationsRedesignTemplates()) {
+ String lastMessage = !mMessages.isEmpty()
+ ? mMessages.getLast().mText.toString() : null;
+
p.title(conversationTitle)
+ // The text is not actually displayed like this (since we're using a
+ // MessagingLinearLayout instead of the regular text), but we're using it to
+ // know whether the notification will have a second line in practice.
+ .text(lastMessage)
.hideAppName(isCollapsed);
} else {
p.title(isLegacyHeaderless ? conversationTitle : null)
+ .text(null)
.headerTextSecondary(isLegacyHeaderless ? null : conversationTitle);
}
RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index e93d8bdb..3a02188 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -169,6 +169,23 @@
"name": "CtsWindowManagerBackgroundActivityTestCases"
}
],
+ // v2/sysui/suite/test-mapping-sysui-screenshot-test
+ "sysui-screenshot-test": [
+ {
+ "name": "SystemUIGoogleScreenshotTests",
+ "options": [
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "android.platform.test.annotations.Postsubmit"
+ }
+ ]
+ }
+ ],
"postsubmit": [
{
"file_patterns": ["(/|^)ActivityThreadClientTest.java"],
diff --git a/core/java/android/app/contentsuggestions/OWNERS b/core/java/android/app/contentsuggestions/OWNERS
index 5f8de77..3d21a6a 100644
--- a/core/java/android/app/contentsuggestions/OWNERS
+++ b/core/java/android/app/contentsuggestions/OWNERS
@@ -1,4 +1,3 @@
# Bug component: 643919
hackz@google.com
-volnov@google.com
diff --git a/core/java/android/app/contextualsearch/flags.aconfig b/core/java/android/app/contextualsearch/flags.aconfig
index 1de034b..1a14b20 100644
--- a/core/java/android/app/contextualsearch/flags.aconfig
+++ b/core/java/android/app/contextualsearch/flags.aconfig
@@ -23,6 +23,18 @@
bug: "371065456"
}
+
+flag {
+ name: "report_secure_surfaces_in_assist_structure"
+ namespace: "windowing_frontend"
+ description: "SurfaceView reports when the surface is using a SECURE flag."
+ bug: "390504528"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+
flag {
name: "contextual_search_prevent_self_capture"
namespace: "sysui_integrations"
diff --git a/core/java/android/app/people/OWNERS b/core/java/android/app/people/OWNERS
index 7371a88..399511a 100644
--- a/core/java/android/app/people/OWNERS
+++ b/core/java/android/app/people/OWNERS
@@ -1,4 +1,3 @@
# Bug component: 978868
-danningc@google.com
-juliacr@google.com
\ No newline at end of file
+juliacr@google.com
diff --git a/core/java/android/app/wearable/OWNERS b/core/java/android/app/wearable/OWNERS
index 497eaf0..56c8ca5 100644
--- a/core/java/android/app/wearable/OWNERS
+++ b/core/java/android/app/wearable/OWNERS
@@ -2,4 +2,3 @@
hackz@google.com
oni@google.com
tomchan@google.com
-volnov@google.com
\ No newline at end of file
diff --git a/core/java/android/content/integrity/OWNERS b/core/java/android/content/integrity/OWNERS
index 20c758a..ca65fda 100644
--- a/core/java/android/content/integrity/OWNERS
+++ b/core/java/android/content/integrity/OWNERS
@@ -1,5 +1,4 @@
# Bug component: 722021
toddke@android.com
-toddke@google.com
patb@google.com
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index e645060..fd59ea9 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1347,6 +1347,33 @@
314961188L;
/**
+ * Excludes the packages the override is applied to from the camera compatibility treatment for
+ * fixed-orientation apps, which simulates running on a portrait device, in the orientation
+ * requested by the app.
+ *
+ * <p>This treatment aims to mitigate camera issues on large screens, like stretched or sideways
+ * previews. It simulates running on a portrait device by:
+ * <ul>
+ * <li>Letterboxing the app window,
+ * <li>Cropping the camera buffer to match the app's requested orientation,
+ * <li>Setting the camera sensor orientation to portrait.
+ * <li>Setting the display rotation to match the app's requested orientation, given portrait
+ * natural orientation,
+ * <li>Refreshes the activity to trigger new camera setup, with sandboxed values.
+ * </ul>
+ *
+ * <p>By setting this override to {@code true}, it disables the camera compatibility treatment
+ * which simulates app's requested orientation.
+ *
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ public static final long OVERRIDE_CAMERA_COMPAT_DISABLE_SIMULATE_REQUESTED_ORIENTATION =
+ 398195815L; // buganizer id
+
+ /**
* This change id forces the packages it is applied to sandbox {@link android.view.View} API to
* an activity bounds for:
*
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 9e91f59..49fd634 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -5000,10 +5000,9 @@
* supports the Android XR Spatial APIs. The feature version indicates the highest version of
* the Android XR Spatial APIs supported by the device.
*
- * <p>Also see <a href="https://developer.android.com/xr">Getting started with Spatializing
- * your app</a>.
+ * <p>Also see <a href="https://developer.android.com/develop/xr">Develop with the Android XR
+ * SDK</a>.
*/
- // TODO(b/374330735): update public documentation once link content is finalized
@FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES)
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_XR_API_SPATIAL =
diff --git a/core/java/android/content/pm/parsing/OWNERS b/core/java/android/content/pm/parsing/OWNERS
index 8049d5c..445a833 100644
--- a/core/java/android/content/pm/parsing/OWNERS
+++ b/core/java/android/content/pm/parsing/OWNERS
@@ -2,4 +2,3 @@
chiuwinson@google.com
patb@google.com
-toddke@google.com
diff --git a/core/java/android/content/pm/permission/OWNERS b/core/java/android/content/pm/permission/OWNERS
index cf7e689..8ef8474 100644
--- a/core/java/android/content/pm/permission/OWNERS
+++ b/core/java/android/content/pm/permission/OWNERS
@@ -3,6 +3,5 @@
include platform/frameworks/base:/core/java/android/permission/OWNERS
toddke@android.com
-toddke@google.com
patb@google.com
diff --git a/core/java/android/hardware/serial/OWNERS b/core/java/android/hardware/serial/OWNERS
new file mode 100644
index 0000000..bc2c66a
--- /dev/null
+++ b/core/java/android/hardware/serial/OWNERS
@@ -0,0 +1,6 @@
+kkaplon@google.com
+mjel@google.com
+chominskib@google.com
+wzwonarz@google.com
+gstepniewski@google.com
+xutan@google.com
\ No newline at end of file
diff --git a/core/java/android/hardware/serial/flags/flags.aconfig b/core/java/android/hardware/serial/flags/flags.aconfig
new file mode 100644
index 0000000..d8244ba
--- /dev/null
+++ b/core/java/android/hardware/serial/flags/flags.aconfig
@@ -0,0 +1,10 @@
+package: "android.hardware.serial"
+container: "system"
+
+flag {
+ name: "enable_serial_api"
+ namespace: "serial"
+ description: "Feature flag to enable serial API"
+ bug: "369155426"
+ is_exported: true
+}
\ No newline at end of file
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 1e6469c..0ceafa0 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -1515,7 +1515,7 @@
VERSION_CODES.VANILLA_ICE_CREAM * SDK_INT_MULTIPLIER;
/**
- * The upcoming, not yet finalized, version of Android.
+ * Android 36.0.
*/
public static final int BAKLAVA = VERSION_CODES.BAKLAVA * SDK_INT_MULTIPLIER;
}
diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
index 7497234..ce1717b 100644
--- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java
+++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
@@ -92,11 +92,8 @@
// queue for async messages when inserting a message at the tail.
private int mAsyncMessageCount;
- /**
- * @hide
- */
private final AtomicLong mMessageCount = new AtomicLong();
- private final Thread mThread;
+ private final String mThreadName;
private final long mTid;
/**
@@ -133,7 +130,7 @@
mUseConcurrent = sIsProcessAllowedToUseConcurrent;
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
- mThread = Thread.currentThread();
+ mThreadName = Thread.currentThread().getName();
mTid = Process.myTid();
}
@@ -223,10 +220,10 @@
traceMessageCount();
PerfettoTrace.instant(PerfettoTrace.MQ_CATEGORY, "message_queue_send")
- .addFlow(msg.mEventId.get())
+ .setFlow(msg.mEventId.get())
.beginProto()
.beginNested(2004 /* message_queue */)
- .addField(2 /* receiving_thread_name */, mThread.getName())
+ .addField(2 /* receiving_thread_name */, mThreadName)
.addField(3 /* message_code */, msg.what)
.addField(4 /* message_delay_ms */, when - SystemClock.uptimeMillis())
.endNested()
@@ -237,7 +234,7 @@
/** @hide */
private void traceMessageCount() {
PerfettoTrace.counter(PerfettoTrace.MQ_CATEGORY, mMessageCount.get())
- .usingThreadCounterTrack(mTid, mThread.getName())
+ .usingThreadCounterTrack(mTid, mThreadName)
.emit();
}
diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java
index 8cfd324..48e6249 100644
--- a/core/java/android/os/IBinder.java
+++ b/core/java/android/os/IBinder.java
@@ -451,7 +451,7 @@
* @param executor The executor on which to run the callback.
* @param callback The callback used to deliver state change notifications.
*
- * <p>@throws {@link UnsupportedOperationException} if the kernel binder driver does not support
+ * @throws {@link UnsupportedOperationException} if the kernel binder driver does not support
* this feature.
*/
@FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK)
diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java
index 1329b90..3ff6e05 100644
--- a/core/java/android/os/Looper.java
+++ b/core/java/android/os/Looper.java
@@ -205,7 +205,7 @@
.addField(1 /* sending_thread_name */, msg.mSendingThreadName)
.endNested()
.endProto()
- .addTerminatingFlow(msg.mEventId.get())
+ .setTerminatingFlow(msg.mEventId.get())
.emit();
// This must be in a local variabe, in case a UI event sets the logger
diff --git a/core/java/android/os/Message.java b/core/java/android/os/Message.java
index b22d177..69e84e3 100644
--- a/core/java/android/os/Message.java
+++ b/core/java/android/os/Message.java
@@ -23,7 +23,7 @@
import com.android.internal.annotations.VisibleForTesting;
-import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
/**
*
@@ -43,7 +43,7 @@
*
* @hide Only for use within the system server.
*/
- public final AtomicInteger mEventId = new AtomicInteger();
+ public final AtomicLong mEventId = new AtomicLong();
/**
* User-defined message code so that the recipient can identify
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index 727dcba..a6785ba 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -78,7 +78,7 @@
per-file PatternMatcher* = file:/PACKAGE_MANAGER_OWNERS
# PermissionEnforcer
-per-file PermissionEnforcer.java = tweek@google.com, brufino@google.com
+per-file PermissionEnforcer.java = tweek@google.com
# RemoteCallbackList
per-file RemoteCallbackList.java = shayba@google.com
diff --git a/core/java/android/os/PerfettoTrackEventExtra.java b/core/java/android/os/PerfettoTrackEventExtra.java
index f4b5dfe..2848bcb 100644
--- a/core/java/android/os/PerfettoTrackEventExtra.java
+++ b/core/java/android/os/PerfettoTrackEventExtra.java
@@ -172,7 +172,6 @@
private final Pool<FieldDouble> mFieldDoubleCache;
private final Pool<FieldString> mFieldStringCache;
private final Pool<FieldNested> mFieldNestedCache;
- private final Pool<Flow> mFlowCache;
private final Pool<Builder> mBuilderCache;
private Builder() {
@@ -187,7 +186,6 @@
mFieldDoubleCache = mExtra.mFieldDoubleCache;
mFieldStringCache = mExtra.mFieldStringCache;
mFieldNestedCache = mExtra.mFieldNestedCache;
- mFlowCache = mExtra.mFlowCache;
mBuilderCache = mExtra.mBuilderCache;
mCounterInt64 = mExtra.getCounterInt64();
@@ -227,7 +225,6 @@
mFieldStringCache.reset();
mFieldNestedCache.reset();
mBuilderCache.reset();
- mFlowCache.reset();
mExtra.reset();
// Reset after on init in case the thread created builders without calling emit
@@ -325,39 +322,7 @@
/**
* Adds a flow with {@code id}.
*/
- public Builder addFlow(int id) {
- if (!mIsCategoryEnabled) {
- return this;
- }
- if (DEBUG) {
- checkParent();
- }
- Flow flow = mFlowCache.get(sFlowSupplier);
- flow.setProcessFlow(id);
- mExtra.addPerfettoPointer(flow);
- return this;
- }
-
- /**
- * Adds a terminating flow with {@code id}.
- */
- public Builder addTerminatingFlow(int id) {
- if (!mIsCategoryEnabled) {
- return this;
- }
- if (DEBUG) {
- checkParent();
- }
- Flow flow = mFlowCache.get(sFlowSupplier);
- flow.setProcessTerminatingFlow(id);
- mExtra.addPerfettoPointer(flow);
- return this;
- }
-
- /**
- * Adds a flow with {@code id}.
- */
- public Builder setFlow(int id) {
+ public Builder setFlow(long id) {
if (!mIsCategoryEnabled) {
return this;
}
@@ -372,7 +337,7 @@
/**
* Adds a terminating flow with {@code id}.
*/
- public Builder setTerminatingFlow(int id) {
+ public Builder setTerminatingFlow(long id) {
if (!mIsCategoryEnabled) {
return this;
}
@@ -670,7 +635,6 @@
private final Pool<FieldDouble> mFieldDoubleCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE);
private final Pool<FieldString> mFieldStringCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE);
private final Pool<FieldNested> mFieldNestedCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE);
- private final Pool<Flow> mFlowCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE);
private final Pool<Builder> mBuilderCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE);
private static final NativeAllocationRegistry sRegistry =
diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java
index 77b6d70..2736b60 100644
--- a/core/java/android/os/PowerManagerInternal.java
+++ b/core/java/android/os/PowerManagerInternal.java
@@ -357,4 +357,9 @@
* This may affect dream eligibility.
*/
public abstract void setDevicePostured(boolean isPostured);
+
+ /**
+ * Notifies PowerManager that settings have changed and that it should refresh its state.
+ */
+ public abstract void updateSettings();
}
diff --git a/core/java/android/preference/OWNERS b/core/java/android/preference/OWNERS
index b4cb9ec..38a5abd 100644
--- a/core/java/android/preference/OWNERS
+++ b/core/java/android/preference/OWNERS
@@ -1,5 +1,4 @@
lpf@google.com
-pavlis@google.com
clarabayarri@google.com
-per-file SeekBarVolumizer.java = jmtrivi@google.com
\ No newline at end of file
+per-file SeekBarVolumizer.java = jmtrivi@google.com
diff --git a/core/java/android/print/OWNERS b/core/java/android/print/OWNERS
index ce79f5d..15f6406 100644
--- a/core/java/android/print/OWNERS
+++ b/core/java/android/print/OWNERS
@@ -1,5 +1,4 @@
# Bug component: 47273
anothermark@google.com
-kumarashishg@google.com
bmgordon@google.com
diff --git a/core/java/android/printservice/OWNERS b/core/java/android/printservice/OWNERS
index ce79f5d..15f6406 100644
--- a/core/java/android/printservice/OWNERS
+++ b/core/java/android/printservice/OWNERS
@@ -1,5 +1,4 @@
# Bug component: 47273
anothermark@google.com
-kumarashishg@google.com
bmgordon@google.com
diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl
index 3ca9d93..fdacd60 100644
--- a/core/java/android/service/dreams/IDreamManager.aidl
+++ b/core/java/android/service/dreams/IDreamManager.aidl
@@ -59,4 +59,6 @@
float screenBrightnessFloat, int screenBrightnessInt,
boolean useNormalBrightnessForDoze);
oneway void finishSelfOneway(in IBinder token, boolean immediate);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)")
+ void setScreensaverEnabled(boolean enabled);
}
diff --git a/core/java/android/speech/OWNERS b/core/java/android/speech/OWNERS
index 32f4822..f228ba46 100644
--- a/core/java/android/speech/OWNERS
+++ b/core/java/android/speech/OWNERS
@@ -1,3 +1,2 @@
-volnov@google.com
eugeniom@google.com
schfan@google.com
diff --git a/core/java/android/text/OWNERS b/core/java/android/text/OWNERS
index 0935ffd9..b493ef7 100644
--- a/core/java/android/text/OWNERS
+++ b/core/java/android/text/OWNERS
@@ -4,7 +4,6 @@
halilibo@google.com
haoyuchang@google.com
justinghan@google.com
-klippenstein@google.com
nona@google.com
seanmcq@google.com
siyamed@google.com
diff --git a/core/java/android/util/apk/OWNERS b/core/java/android/util/apk/OWNERS
index 0f4e869..f267f9a 100644
--- a/core/java/android/util/apk/OWNERS
+++ b/core/java/android/util/apk/OWNERS
@@ -1,3 +1,2 @@
include /core/java/android/content/pm/OWNERS
-cbrubaker@google.com
mpgroover@google.com
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index b98f4db..4f6c730 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -2247,6 +2247,27 @@
t.reparent(sc, mBlastSurfaceControl).show(sc);
}
+ /**
+ * Populates a {@link ViewStructure} for content capture.
+ *
+ * <p>If {@link #setSecure(boolean)} has been enabled, will add a property to the
+ * {@link android.app.assist.AssistStructure.ViewNode} to indicate that content will not
+ * be available for this part of the screen.
+ *
+ * @hide
+ */
+ @Override
+ protected void onProvideStructure(@NonNull ViewStructure structure,
+ @ViewStructureType int viewFor, int flags) {
+ super.onProvideStructure(structure, viewFor, flags);
+ if (android.app.contextualsearch.flags.Flags.reportSecureSurfacesInAssistStructure()) {
+ if ((mSurfaceFlags & SurfaceControl.SECURE) != 0) {
+ structure.getExtras().putBoolean(
+ ViewStructure.EXTRA_CONTAINS_SECURE_LAYERS, true);
+ }
+ }
+ }
+
/** @hide */
@Override
public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index 43a946a..53953a9 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -77,6 +77,19 @@
"android.view.ViewStructure.extra.FIRST_ACTIVE_POSITION";
/**
+ * Key used for confirming whether the view draws graphics containing secure layers.
+ *
+ * <p>Secure layers cannot be read back into main memory and will show up as blank regions
+ * in assist screenshots.
+ *
+ * @see android.view.SurfaceControl#SECURE
+ *
+ * @hide
+ */
+ public static final String EXTRA_CONTAINS_SECURE_LAYERS =
+ "android.view.ViewStructure.extra.CONTAINS_SECURE_LAYERS";
+
+ /**
* Key used for writing the type of the view that generated the virtual structure of its
* children.
*
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index c81c2bb..a4ea64e 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -203,3 +203,14 @@
description: "Adding animating insets types and report IME visibility at the beginning of hiding"
bug: "393049691"
}
+
+flag {
+ name: "lower_ime_oom_importance"
+ namespace: "input_method"
+ description: "Lower keyboard app process oom importance to PERCEPTIBLE_APP_ADJ + 1."
+ bug: "372511805"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/view/textclassifier/intent/OWNERS b/core/java/android/view/textclassifier/intent/OWNERS
index dc18514..4a5dfd8 100644
--- a/core/java/android/view/textclassifier/intent/OWNERS
+++ b/core/java/android/view/textclassifier/intent/OWNERS
@@ -2,5 +2,4 @@
toki@google.com
svetoslavganov@android.com
-svetoslavganov@google.com
joannechung@google.com
diff --git a/core/java/android/widget/flags/notification_widget_flags.aconfig b/core/java/android/widget/flags/notification_widget_flags.aconfig
index 56a2cf7..732eabe 100644
--- a/core/java/android/widget/flags/notification_widget_flags.aconfig
+++ b/core/java/android/widget/flags/notification_widget_flags.aconfig
@@ -66,4 +66,14 @@
metadata {
purpose: PURPOSE_BUGFIX
}
+}
+
+flag {
+ name: "drop_non_existing_messages"
+ namespace: "systemui"
+ description: "Drops all group and message entries that no longer exist."
+ bug: "378101061"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
\ No newline at end of file
diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index 42bf6d1..e74a875 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -43,6 +43,8 @@
public enum DesktopModeFlags {
// All desktop mode related flags to be overridden by developer option toggle will be added here
// go/keep-sorted start
+ DISABLE_DESKTOP_LAUNCH_PARAMS_OUTSIDE_DESKTOP_BUG_FIX(
+ Flags::disableDesktopLaunchParamsOutsideDesktopBugFix, false),
DISABLE_NON_RESIZABLE_APP_SNAP_RESIZE(Flags::disableNonResizableAppSnapResizing, true),
ENABLE_ACCESSIBLE_CUSTOM_HEADERS(Flags::enableAccessibleCustomHeaders, true),
ENABLE_APP_HEADER_WITH_TASK_DENSITY(Flags::enableAppHeaderWithTaskDensity, true),
@@ -113,6 +115,7 @@
ENABLE_START_LAUNCH_TRANSITION_FROM_TASKBAR_BUGFIX(
Flags::enableStartLaunchTransitionFromTaskbarBugfix, true),
ENABLE_TASKBAR_OVERFLOW(Flags::enableTaskbarOverflow, false),
+ ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION(Flags::enableTaskbarRecentsLayoutTransition, false),
ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS(Flags::enableTaskResizingKeyboardShortcuts, true),
ENABLE_TASK_STACK_OBSERVER_IN_SHELL(Flags::enableTaskStackObserverInShell, true),
ENABLE_THEMED_APP_HEADERS(Flags::enableThemedAppHeaders, true),
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 684f320..355a87d 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -100,6 +100,17 @@
}
flag {
+ name: "disable_desktop_launch_params_outside_desktop_bug_fix"
+ namespace: "lse_desktop_experience"
+ description: "Prevents DesktopModeLaunchParamsModifier from modifying launch params for non /n"
+ "desktop launches."
+ bug: "396108436"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "enable_desktop_windowing_wallpaper_activity"
namespace: "lse_desktop_experience"
description: "Enables desktop wallpaper activity to show wallpaper in the desktop mode"
@@ -812,6 +823,7 @@
purpose: PURPOSE_BUGFIX
}
}
+
flag {
name: "enable_drag_to_desktop_incoming_transitions_bugfix"
namespace: "lse_desktop_experience"
@@ -821,3 +833,23 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "enable_taskbar_recents_layout_transition"
+ namespace: "lse_desktop_experience"
+ description: "Enable Taskbar LayoutTransition for Recent Apps"
+ bug: "343521765"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "enable_desktop_opening_deeplink_minimize_animation_bugfix"
+ namespace: "lse_desktop_experience"
+ description: "Enabling a minimize animation when a new window is opened via deeplink and the Desktop Windowing open windows limit is reached."
+ bug: "360329773"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
\ No newline at end of file
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index dee1d8c..0e19eb2 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -471,4 +471,15 @@
metadata {
purpose: PURPOSE_BUGFIX
}
+}
+
+flag {
+ name: "reduce_task_snapshot_memory_usage"
+ namespace: "windowing_frontend"
+ description: "Reduce task snapshot memory usage in either heap and dmabuf."
+ bug: "238206323"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
\ No newline at end of file
diff --git a/core/java/com/android/internal/app/MediaRouteControllerContentManager.java b/core/java/com/android/internal/app/MediaRouteControllerContentManager.java
index 3a8b94f..11093f1 100644
--- a/core/java/com/android/internal/app/MediaRouteControllerContentManager.java
+++ b/core/java/com/android/internal/app/MediaRouteControllerContentManager.java
@@ -206,6 +206,13 @@
mDelegate.dismissView();
}
+ /**
+ * Request the media route to update volume.
+ */
+ public void requestUpdateRouteVolume(int direction) {
+ mRoute.requestUpdateVolume(direction);
+ }
+
private boolean isVolumeControlAvailable() {
return mRoute.getVolumeHandling() == MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE;
}
diff --git a/core/java/com/android/internal/app/MediaRouteControllerDialog.java b/core/java/com/android/internal/app/MediaRouteControllerDialog.java
index 5899963..73f9515 100644
--- a/core/java/com/android/internal/app/MediaRouteControllerDialog.java
+++ b/core/java/com/android/internal/app/MediaRouteControllerDialog.java
@@ -42,19 +42,11 @@
*/
public class MediaRouteControllerDialog extends AlertDialog implements
MediaRouteControllerContentManager.Delegate {
- // TODO(b/360050020): Eventually these 2 variables should be in the content manager instead of
- // here. So these should be removed when the migration is completed.
- private final MediaRouter mRouter;
- private final MediaRouter.RouteInfo mRoute;
-
private final MediaRouteControllerContentManager mContentManager;
public MediaRouteControllerDialog(Context context, int theme) {
super(context, theme);
-
mContentManager = new MediaRouteControllerContentManager(context, this);
- mRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
- mRoute = mRouter.getSelectedRoute();
}
@Override
@@ -91,7 +83,8 @@
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
|| keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
- mRoute.requestUpdateVolume(keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ? -1 : 1);
+ mContentManager.requestUpdateRouteVolume(
+ keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ? -1 : 1);
return true;
}
return super.onKeyDown(keyCode, event);
diff --git a/core/java/com/android/internal/infra/OWNERS b/core/java/com/android/internal/infra/OWNERS
index 4550358..e69de29 100644
--- a/core/java/com/android/internal/infra/OWNERS
+++ b/core/java/com/android/internal/infra/OWNERS
@@ -1,6 +0,0 @@
-per-file AndroidFuture.java = eugenesusla@google.com
-per-file RemoteStream.java = eugenesusla@google.com
-per-file PerUser.java = eugenesusla@google.com
-per-file ServiceConnector.java = eugenesusla@google.com
-per-file AndroidFuture.aidl = eugenesusla@google.com
-per-file IAndroidFuture.aidl = eugenesusla@google.com
\ No newline at end of file
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index 056a0e8..81ca231 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -789,7 +789,7 @@
*/
public boolean readFragmentToParcel(Parcel out, BatteryHistoryFragment fragment) {
byte[] data = mStore.readFragment(fragment);
- if (data == null) {
+ if (data == null || data.length == 0) {
return false;
}
out.unmarshall(data, 0, data.length);
@@ -934,6 +934,10 @@
continue;
}
+ if (data.length == 0) {
+ continue;
+ }
+
out.writeBoolean(true);
if (useBlobs) {
out.writeBlob(data, 0, data.length);
@@ -976,9 +980,11 @@
return false;
}
- parcel.unmarshall(data, 0, data.length);
- parcel.setDataPosition(0);
- readHistoryBuffer(parcel);
+ if (data.length > 0) {
+ parcel.unmarshall(data, 0, data.length);
+ parcel.setDataPosition(0);
+ readHistoryBuffer(parcel);
+ }
} catch (Exception e) {
Slog.e(TAG, "Error reading battery history", e);
reset();
diff --git a/core/java/com/android/internal/protolog/WmProtoLogGroups.java b/core/java/com/android/internal/protolog/WmProtoLogGroups.java
index 5edc2fb..b8b70b16 100644
--- a/core/java/com/android/internal/protolog/WmProtoLogGroups.java
+++ b/core/java/com/android/internal/protolog/WmProtoLogGroups.java
@@ -30,6 +30,8 @@
WM_ERROR(true, true, true, Consts.TAG_WM),
WM_DEBUG_ORIENTATION(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM),
+ WM_DEBUG_ORIENTATION_CHANGE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
+ Consts.TAG_WM),
WM_DEBUG_FOCUS_LIGHT(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM),
WM_DEBUG_BOOT(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
diff --git a/core/java/com/android/internal/util/OWNERS b/core/java/com/android/internal/util/OWNERS
index 9be8ea7..d174fe3 100644
--- a/core/java/com/android/internal/util/OWNERS
+++ b/core/java/com/android/internal/util/OWNERS
@@ -1,8 +1,8 @@
-per-file AsyncChannel* = lorenzo@google.com, satk@google.com, etancohen@google.com
+per-file AsyncChannel* = lorenzo@google.com, satk@google.com
per-file MessageUtils*, Protocol*, RingBuffer*, TokenBucket* = jchalard@google.com, lorenzo@google.com, satk@google.com
per-file *Notification* = file:/services/core/java/com/android/server/notification/OWNERS
per-file *ContrastColor* = file:/services/core/java/com/android/server/notification/OWNERS
-per-file Protocol* = etancohen@google.com, lorenzo@google.com
+per-file Protocol* =lorenzo@google.com
per-file State* = jchalard@google.com, lorenzo@google.com, satk@google.com
per-file *Dump* = file:/core/java/com/android/internal/util/dump/OWNERS
per-file *Screenshot* = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS
diff --git a/core/java/com/android/internal/util/function/pooled/OWNERS b/core/java/com/android/internal/util/function/pooled/OWNERS
index da723b3..e69de29 100644
--- a/core/java/com/android/internal/util/function/pooled/OWNERS
+++ b/core/java/com/android/internal/util/function/pooled/OWNERS
@@ -1 +0,0 @@
-eugenesusla@google.com
\ No newline at end of file
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index ce46da1..2cca3db 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -86,6 +86,7 @@
public static final Interpolator FAST_OUT_LINEAR_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
public static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
public static final Interpolator OVERSHOOT = new PathInterpolator(0.4f, 0f, 0.2f, 1.4f);
+ private static final int MAX_SUMMARIZATION_LINES = 3;
public static final int IMPORTANCE_ANIM_GROW_DURATION = 250;
public static final int IMPORTANCE_ANIM_SHRINK_DURATION = 200;
public static final int IMPORTANCE_ANIM_SHRINK_DELAY = 25;
@@ -401,7 +402,7 @@
public void setIsCollapsed(boolean isCollapsed) {
mIsCollapsed = isCollapsed;
mMessagingLinearLayout.setMaxDisplayedLines(isCollapsed
- ? TextUtils.isEmpty(mSummarizedContent) ? 1 : 2
+ ? TextUtils.isEmpty(mSummarizedContent) ? 1 : MAX_SUMMARIZATION_LINES
: Integer.MAX_VALUE);
updateExpandButton();
updateContentEndPaddings();
@@ -1188,6 +1189,12 @@
}
newGroup.setMessages(group);
}
+
+ if (Flags.dropNonExistingMessages()) {
+ // remove groups from mAddedGroups when they are no longer in mGroups.
+ mAddedGroups.removeIf(
+ messagingGroup -> !mGroups.contains(messagingGroup));
+ }
}
/**
diff --git a/core/java/com/android/internal/widget/MessagingGroup.java b/core/java/com/android/internal/widget/MessagingGroup.java
index b9a603cc..b31a200 100644
--- a/core/java/com/android/internal/widget/MessagingGroup.java
+++ b/core/java/com/android/internal/widget/MessagingGroup.java
@@ -569,6 +569,10 @@
mIsolatedMessage = isolatedMessage;
updateImageContainerVisibility();
mMessages = group;
+ if (android.widget.flags.Flags.dropNonExistingMessages()) {
+ // remove messages from mAddedMessages when they are no longer in mMessages.
+ mAddedMessages.removeIf(message -> !mMessages.contains(message));
+ }
updateMessageColor();
}
diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java
index eb22e7c..9fe2de8 100644
--- a/core/java/com/android/internal/widget/MessagingLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLayout.java
@@ -44,6 +44,7 @@
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.RemoteViews;
+import android.widget.flags.Flags;
import com.android.internal.R;
@@ -62,6 +63,7 @@
public static final Interpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0f, 0f, 0.2f, 1f);
public static final Interpolator FAST_OUT_LINEAR_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
public static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
+ private static final int MAX_SUMMARIZATION_LINES = 3;
public static final OnLayoutChangeListener MESSAGING_PROPERTY_ANIMATOR
= new MessagingPropertyAnimator();
private final PeopleHelper mPeopleHelper = new PeopleHelper();
@@ -222,7 +224,7 @@
List<MessagingMessage> newMessagingMessages;
mSummarizedContent = extras.getCharSequence(Notification.EXTRA_SUMMARIZED_CONTENT);
if (!TextUtils.isEmpty(mSummarizedContent) && mIsCollapsed) {
- mMessagingLinearLayout.setMaxDisplayedLines(2);
+ mMessagingLinearLayout.setMaxDisplayedLines(MAX_SUMMARIZATION_LINES);
Notification.MessagingStyle.Message summary =
new Notification.MessagingStyle.Message(mSummarizedContent, 0, "");
newMessagingMessages = createMessages(List.of(summary), false, usePrecomputedText);
@@ -559,6 +561,12 @@
}
newGroup.setMessages(group);
}
+
+ if (Flags.dropNonExistingMessages()) {
+ // remove groups from mAddedGroups when they are no longer in mGroups.
+ mAddedGroups.removeIf(
+ messagingGroup -> !mGroups.contains(messagingGroup));
+ }
}
private void findGroups(List<MessagingMessage> historicMessages,
diff --git a/core/java/com/android/internal/widget/NotificationRowIconView.java b/core/java/com/android/internal/widget/NotificationRowIconView.java
index c96e979..1b29b7f 100644
--- a/core/java/com/android/internal/widget/NotificationRowIconView.java
+++ b/core/java/com/android/internal/widget/NotificationRowIconView.java
@@ -225,6 +225,6 @@
boolean shouldShowAppIcon();
/** Get the app icon for this notification. */
- Drawable getAppIcon();
+ @Nullable Drawable getAppIcon();
}
}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 92a841f..748c5b4 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -21,7 +21,6 @@
config_namespace: "ANDROID",
bool_variables: [
"release_binder_death_recipient_weak_from_jni",
- "release_package_libandroid_runtime_punch_holes",
],
properties: [
"cflags",
@@ -66,9 +65,6 @@
release_binder_death_recipient_weak_from_jni: {
cflags: ["-DBINDER_DEATH_RECIPIENT_WEAK_FROM_JNI"],
},
- release_package_libandroid_runtime_punch_holes: {
- cflags: ["-DENABLE_PUNCH_HOLES"],
- },
},
cpp_std: "gnu++20",
diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
index 06fd80e..14132e6 100644
--- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
+++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
@@ -236,7 +236,6 @@
return INSTALL_FAILED_CONTAINER_ERROR;
}
-#ifdef ENABLE_PUNCH_HOLES
// punch extracted elf files as well. This will fail where compression is on (like f2fs) but it
// will be useful for ext4 based systems
struct statfs64 fsInfo;
@@ -253,7 +252,6 @@
zipFile->getZipFileName());
}
}
-#endif // ENABLE_PUNCH_HOLES
ALOGV("Successfully moved %s to %s\n", localTmpFileName, localFileName);
@@ -332,7 +330,6 @@
return INSTALL_FAILED_INVALID_APK;
}
-#ifdef ENABLE_PUNCH_HOLES
// if library is uncompressed, punch hole in it in place
if (!punchHolesInElf64(zipFile->getZipFileName(), offset)) {
ALOGW("Failed to punch uncompressed elf file :%s inside apk : %s at offset: "
@@ -345,7 +342,6 @@
if (!punchHolesInZip(zipFile->getZipFileName(), offset, extraFieldLength)) {
ALOGW("Failed to punch apk : %s at extra field", zipFile->getZipFileName());
}
-#endif // ENABLE_PUNCH_HOLES
return INSTALL_SUCCEEDED;
}
diff --git a/core/proto/OWNERS b/core/proto/OWNERS
index aa8f841..c804024 100644
--- a/core/proto/OWNERS
+++ b/core/proto/OWNERS
@@ -5,7 +5,6 @@
singhtejinder@google.com
yanmin@google.com
yaochen@google.com
-zhouwenjie@google.com
# Frameworks
ogunwale@google.com
diff --git a/core/res/OWNERS b/core/res/OWNERS
index faed4d8..a208f7f 100644
--- a/core/res/OWNERS
+++ b/core/res/OWNERS
@@ -9,7 +9,6 @@
ilyamaty@google.com
jbolinger@google.com
jsharkey@android.com
-jsharkey@google.com
juliacr@google.com
kchyn@google.com
michaelwr@google.com
diff --git a/core/res/res/values/locale_config.xml b/core/res/res/values/locale_config.xml
index 77d2e87..0efa1bc 100644
--- a/core/res/res/values/locale_config.xml
+++ b/core/res/res/values/locale_config.xml
@@ -163,15 +163,18 @@
<item>en-CM</item> <!-- English (Cameroon) -->
<item>en-CX</item> <!-- English (Christmas Island) -->
<item>en-CY</item> <!-- English (Cyprus) -->
+ <item>en-CZ</item> <!-- English (Czechia) -->
<item>en-DE</item> <!-- English (Germany) -->
<item>en-DG</item> <!-- English (Diego Garcia) -->
<item>en-DK</item> <!-- English (Denmark) -->
<item>en-DM</item> <!-- English (Dominica) -->
<item>en-ER</item> <!-- English (Eritrea) -->
+ <item>en-ES</item> <!-- English (Spain) -->
<item>en-FI</item> <!-- English (Finland) -->
<item>en-FJ</item> <!-- English (Fiji) -->
<item>en-FK</item> <!-- English (Falkland Islands (Islas Malvinas)) -->
<item>en-FM</item> <!-- English (Micronesia) -->
+ <item>en-FR</item> <!-- English (France) -->
<item>en-GB</item> <!-- English (United Kingdom) -->
<item>en-GD</item> <!-- English (Grenada) -->
<item>en-GG</item> <!-- English (Guernsey) -->
@@ -181,12 +184,14 @@
<item>en-GU</item> <!-- English (Guam) -->
<item>en-GY</item> <!-- English (Guyana) -->
<item>en-HK</item> <!-- English (Hong Kong) -->
+ <item>en-HU</item> <!-- English (Hungary) -->
<item>en-ID</item> <!-- English (Indonesia) -->
<item>en-IE</item> <!-- English (Ireland) -->
<item>en-IL</item> <!-- English (Israel) -->
<item>en-IM</item> <!-- English (Isle of Man) -->
<item>en-IN</item> <!-- English (India) -->
<item>en-IO</item> <!-- English (British Indian Ocean Territory) -->
+ <item>en-IT</item> <!-- English (Italy) -->
<item>en-JE</item> <!-- English (Jersey) -->
<item>en-JM</item> <!-- English (Jamaica) -->
<item>en-KE</item> <!-- English (Kenya) -->
@@ -210,15 +215,19 @@
<item>en-NF</item> <!-- English (Norfolk Island) -->
<item>en-NG</item> <!-- English (Nigeria) -->
<item>en-NL</item> <!-- English (Netherlands) -->
+ <item>en-NO</item> <!-- English (Norway) -->
<item>en-NR</item> <!-- English (Nauru) -->
<item>en-NU</item> <!-- English (Niue) -->
<item>en-NZ</item> <!-- English (New Zealand) -->
<item>en-PG</item> <!-- English (Papua New Guinea) -->
<item>en-PH</item> <!-- English (Philippines) -->
<item>en-PK</item> <!-- English (Pakistan) -->
+ <item>en-PL</item> <!-- English (Poland) -->
<item>en-PN</item> <!-- English (Pitcairn Islands) -->
<item>en-PR</item> <!-- English (Puerto Rico) -->
+ <item>en-PT</item> <!-- English (Portugal) -->
<item>en-PW</item> <!-- English (Palau) -->
+ <item>en-RO</item> <!-- English (Romania) -->
<item>en-RW</item> <!-- English (Rwanda) -->
<item>en-SB</item> <!-- English (Solomon Islands) -->
<item>en-SC</item> <!-- English (Seychelles) -->
@@ -227,6 +236,7 @@
<item>en-SG</item> <!-- English (Singapore) -->
<item>en-SH</item> <!-- English (St. Helena) -->
<item>en-SI</item> <!-- English (Slovenia) -->
+ <item>en-SK</item> <!-- English (Slovakia) -->
<item>en-SL</item> <!-- English (Sierra Leone) -->
<item>en-SS</item> <!-- English (South Sudan) -->
<item>en-SX</item> <!-- English (Sint Maarten) -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 6a83bae..cb3dfc7 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5063,8 +5063,6 @@
<!-- Notification action button. Click it will open the bluetooth device details page for this hearing device. It will be shown when making a phone call with the hearing device. [CHAR LIMIT=none] -->
<string name="hearing_device_notification_settings_button">Settings</string>
- <!-- Text spoken when the current user is switched if accessibility is enabled. [CHAR LIMIT=none] -->
- <string name="user_switched">Current user <xliff:g id="name" example="Bob">%1$s</xliff:g>.</string>
<!-- Message shown when switching to a user [CHAR LIMIT=none] -->
<string name="user_switching_message">Switching to <xliff:g id="name" example="Bob">%1$s</xliff:g>\u2026</string>
<!-- Message when logging out a user on a split user system -->
diff --git a/core/tests/coretests/src/android/content/pm/OWNERS b/core/tests/coretests/src/android/content/pm/OWNERS
index 8673365..c4c40dc 100644
--- a/core/tests/coretests/src/android/content/pm/OWNERS
+++ b/core/tests/coretests/src/android/content/pm/OWNERS
@@ -1,5 +1,4 @@
include /core/java/android/content/pm/OWNERS
per-file AppSearchPersonTest.java = file:/core/java/android/content/pm/SHORTCUT_OWNERS
-per-file SigningDetailsTest.java = cbrubaker@google.com
per-file SigningDetailsTest.java = mpgroover@google.com
diff --git a/core/tests/featureflagtests/OWNERS b/core/tests/featureflagtests/OWNERS
index 2ff4f5a..6784f28 100644
--- a/core/tests/featureflagtests/OWNERS
+++ b/core/tests/featureflagtests/OWNERS
@@ -1,2 +1 @@
-sbasi@google.com
-tmfang@google.com
\ No newline at end of file
+tmfang@google.com
diff --git a/data/etc/OWNERS b/data/etc/OWNERS
index 712042f..1251fce 100644
--- a/data/etc/OWNERS
+++ b/data/etc/OWNERS
@@ -1,6 +1,5 @@
include /PACKAGE_MANAGER_OWNERS
-cbrubaker@google.com
hackbod@android.com
hackbod@google.com
jeffv@google.com
diff --git a/data/keyboards/Vendor_0957_Product_0031.kl b/data/keyboards/Vendor_0957_Product_0031.kl
index b47ee58..dd9fbe5 100644
--- a/data/keyboards/Vendor_0957_Product_0031.kl
+++ b/data/keyboards/Vendor_0957_Product_0031.kl
@@ -15,7 +15,7 @@
# Key Layout file for Google Reference RCU Remote with customizable button.
#
-key 116 TV_POWER WAKE
+key 116 POWER WAKE
key 217 ASSIST WAKE
key 423 MACRO_1 WAKE
diff --git a/drm/java/android/drm/OWNERS b/drm/java/android/drm/OWNERS
index 4387100..b65cce7 100644
--- a/drm/java/android/drm/OWNERS
+++ b/drm/java/android/drm/OWNERS
@@ -1,4 +1,3 @@
# Bug component: 49079
-jtinker@google.com
robertshih@google.com
diff --git a/libs/WindowManager/Shell/res/layout/open_by_default_settings_dialog.xml b/libs/WindowManager/Shell/res/layout/open_by_default_settings_dialog.xml
index f6256e6..ed5b339 100644
--- a/libs/WindowManager/Shell/res/layout/open_by_default_settings_dialog.xml
+++ b/libs/WindowManager/Shell/res/layout/open_by_default_settings_dialog.xml
@@ -72,7 +72,7 @@
android:layout_height="wrap_content"
android:textSize="12sp"
android:textFontWeight="400"
- android:lineHeight="16dp"
+ android:lineHeight="28dp"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="16dp"
android:textColor="@androidprv:color/materialColorOnSurfaceVariant"
@@ -113,12 +113,13 @@
<Button
android:id="@+id/open_by_default_settings_dialog_confirm_button"
android:layout_width="wrap_content"
- android:layout_height="36dp"
+ android:layout_height="wrap_content"
+ android:minHeight="48dp"
android:text="@string/open_by_default_dialog_dismiss_button_text"
android:layout_gravity="end"
android:layout_marginHorizontal="24dp"
- android:layout_marginTop="32dp"
- android:layout_marginBottom="24dp"
+ android:layout_marginTop="26dp"
+ android:layout_marginBottom="18dp"
android:textSize="14sp"
android:textFontWeight="500"
android:textColor="@androidprv:color/materialColorOnPrimary"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OWNERS
index 6207e5b0..7e55786 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OWNERS
@@ -4,5 +4,4 @@
mattsziklay@google.com
mdehaini@google.com
pbdr@google.com
-tkachenkoi@google.com
vaniadesmonda@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
index aa50772..e3a71a1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
@@ -18,6 +18,7 @@
import android.app.ActivityTaskManager.INVALID_TASK_ID
import android.window.DesktopExperienceFlags
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.UNKNOWN
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.transition.FocusTransitionObserver
@@ -188,8 +189,12 @@
pw.println("Error: task id should be an integer")
return false
}
- pw.println("Not implemented.")
- return false
+ controller.moveTaskToFront(
+ /* taskId= */ taskId,
+ /* remoteTransition= */ null,
+ /* unminimizeReason= */ UnminimizeReason.UNKNOWN,
+ )
+ return true
}
private fun runMoveTaskOutOfDesk(args: Array<String>, pw: PrintWriter): Boolean {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 644c5b0..7e63250 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -472,15 +472,14 @@
remoteTransition: RemoteTransition? = null,
callback: IMoveToDesktopCallback? = null,
): Boolean {
- val runningTask = shellTaskOrganizer.getRunningTaskInfo(taskId)
- val backgroundTask = recentTasksController?.findTaskInBackground(taskId)
- if (runningTask == null && backgroundTask == null) {
+ val task =
+ shellTaskOrganizer.getRunningTaskInfo(taskId)
+ ?: recentTasksController?.findTaskInBackground(taskId)
+ if (task == null) {
logW("moveTaskToDefaultDeskAndActivate taskId=%d not found", taskId)
return false
}
- // TODO(342378842): Instead of using default display, support multiple displays
- val displayId = runningTask?.displayId ?: DEFAULT_DISPLAY
- val deskId = getDefaultDeskId(displayId)
+ val deskId = getDefaultDeskId(task.displayId)
return moveTaskToDesk(
taskId = taskId,
deskId = deskId,
@@ -532,14 +531,14 @@
remoteTransition: RemoteTransition? = null,
callback: IMoveToDesktopCallback? = null,
): Boolean {
- if (recentTasksController?.findTaskInBackground(taskId) == null) {
+ val task = recentTasksController?.findTaskInBackground(taskId)
+ if (task == null) {
logW("moveBackgroundTaskToDesktop taskId=%d not found", taskId)
return false
}
logV("moveBackgroundTaskToDesktop with taskId=%d", taskId)
- // TODO(342378842): Instead of using default display, support multiple displays
val taskIdToMinimize =
- bringDesktopAppsToFrontBeforeShowingNewTask(DEFAULT_DISPLAY, wct, taskId)
+ bringDesktopAppsToFrontBeforeShowingNewTask(task.displayId, wct, taskId)
val exitResult =
desktopImmersiveController.exitImmersiveIfApplicable(
wct = wct,
@@ -1122,12 +1121,13 @@
excludeTaskId = launchingTaskId,
reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH,
)
- var deskIdToActivate: Int? = null
- if (
- DesktopExperienceFlags.ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING.isTrue &&
+ var activationRunOnTransitStart: RunOnTransitStart? = null
+ val shouldActivateDesk =
+ (DesktopExperienceFlags.ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING.isTrue ||
+ DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) &&
!isDesktopModeShowing(displayId)
- ) {
- deskIdToActivate =
+ if (shouldActivateDesk) {
+ val deskIdToActivate =
checkNotNull(
launchingTaskId?.let { taskRepository.getDeskIdForTask(it) }
?: getDefaultDeskId(displayId)
@@ -1137,6 +1137,18 @@
// Desk activation must be handled before app launch-related transactions.
activateDeskWct.merge(launchTransaction, /* transfer= */ true)
launchTransaction = activateDeskWct
+ activationRunOnTransitStart = { transition ->
+ desksTransitionObserver.addPendingTransition(
+ DeskTransition.ActivateDesk(
+ token = transition,
+ displayId = displayId,
+ deskId = deskIdToActivate,
+ )
+ )
+ }
+ desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted(
+ FREEFORM_ANIMATION_DURATION
+ )
}
val t =
if (remoteTransition == null) {
@@ -1170,24 +1182,7 @@
if (launchingTaskId != null && taskRepository.isMinimizedTask(launchingTaskId)) {
addPendingUnminimizeTransition(t, displayId, launchingTaskId, unminimizeReason)
}
- if (
- DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue &&
- deskIdToActivate != null
- ) {
- if (DesktopExperienceFlags.ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING.isTrue) {
- desksTransitionObserver.addPendingTransition(
- DeskTransition.ActivateDesk(
- token = t,
- displayId = displayId,
- deskId = deskIdToActivate,
- )
- )
- }
-
- desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted(
- FREEFORM_ANIMATION_DURATION
- )
- }
+ activationRunOnTransitStart?.invoke(t)
exitImmersiveResult.asExit()?.runOnTransitionStart?.invoke(t)
return t
}
@@ -1321,25 +1316,23 @@
applyFreeformDisplayChange(wct, task, displayId)
}
- val activationRunnable: RunOnTransitStart?
if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
desksOrganizer.moveTaskToDesk(wct, destinationDeskId, task)
- prepareForDeskActivation(displayId, wct)
- desksOrganizer.activateDesk(wct, destinationDeskId)
- activationRunnable = { transition ->
- desksTransitionObserver.addPendingTransition(
- DeskTransition.ActiveDeskWithTask(
- token = transition,
- displayId = displayId,
- deskId = destinationDeskId,
- enterTaskId = task.taskId,
- )
- )
- }
} else {
wct.reparent(task.token, displayAreaInfo.token, /* onTop= */ true)
- activationRunnable = null
}
+ addDeskActivationChanges(destinationDeskId, wct)
+ val activationRunnable: RunOnTransitStart = { transition ->
+ desksTransitionObserver.addPendingTransition(
+ DeskTransition.ActiveDeskWithTask(
+ token = transition,
+ displayId = displayId,
+ deskId = destinationDeskId,
+ enterTaskId = task.taskId,
+ )
+ )
+ }
+
if (Flags.enableDisplayFocusInShellTransitions()) {
// Bring the destination display to top with includingParents=true, so that the
// destination display gains the display focus, which makes the top task in the display
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS
index 44d46ee..7a63ec5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS
@@ -2,7 +2,6 @@
atsjenk@google.com
jorgegil@google.com
madym@google.com
-nmusgrave@google.com
pbdr@google.com
vaniadesmonda@google.com
pragyabajoria@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/OWNERS
index 5aa3c4e..245669b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/OWNERS
@@ -1,3 +1,2 @@
# WM shell sub-module TV pip owner
-galinap@google.com
bronger@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 0438d16..9302347 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -93,6 +93,7 @@
import android.app.ActivityOptions;
import android.app.IActivityTaskManager;
import android.app.PendingIntent;
+import android.app.PictureInPictureParams;
import android.app.TaskInfo;
import android.content.ActivityNotFoundException;
import android.content.Context;
@@ -2890,6 +2891,16 @@
prepareEnterSplitScreen(out);
mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(),
TRANSIT_SPLIT_SCREEN_PAIR_OPEN, !mIsDropEntering);
+ } else if (isSplitScreenVisible() && isOpening) {
+ // launching into an existing split stage; possibly launchAdjacent
+ // If we're replacing a pip-able app, we need to let mixed handler take care of
+ // it. Otherwise we'll just treat it as an enter+resize
+ if (mSplitLayout.calculateCurrentSnapPosition() != SNAP_TO_2_50_50) {
+ // updated layout will get applied in startAnimation pendingResize
+ mSplitTransitions.setEnterTransition(transition,
+ request.getRemoteTransition(),
+ TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, true /*resizeAnim*/);
+ }
} else if (inFullscreen && isSplitScreenVisible()) {
// If the trigger task is in fullscreen and in split, exit split and place
// task on top
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/OWNERS
index 28be0ef..9dc0ebb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/OWNERS
@@ -1,3 +1,2 @@
# WM shell sub-module TV splitscreen owner
-galinap@google.com
bronger@google.com
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 8805071..b0785df 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -334,6 +334,16 @@
whenever(enterDesktopTransitionHandler.moveToDesktop(any(), any())).thenAnswer { Binder() }
whenever(exitDesktopTransitionHandler.startTransition(any(), any(), any(), any()))
.thenReturn(Binder())
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ any(),
+ any(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(Binder())
whenever(displayController.getDisplayLayout(anyInt())).thenReturn(displayLayout)
whenever(displayController.getDisplayContext(anyInt())).thenReturn(mockDisplayContext)
whenever(displayController.getDisplay(anyInt())).thenReturn(display)
@@ -1616,7 +1626,7 @@
@Test
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun moveTaskToDesktop_desktopWallpaperDisabled_nonRunningTask_launchesInFreeform() {
- val task = createTaskInfo(1)
+ val task = createRecentTaskInfo(1)
whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task)
@@ -1631,7 +1641,7 @@
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun moveTaskToDesktop_desktopWallpaperEnabled_nonRunningTask_launchesInFreeform() {
whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
- val task = createTaskInfo(1)
+ val task = createRecentTaskInfo(1)
whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task)
@@ -1803,7 +1813,7 @@
whenever(transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture()))
.thenReturn(Binder())
- val task = createTaskInfo(1)
+ val task = createRecentTaskInfo(1)
whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task)
controller.moveTaskToDefaultDeskAndActivate(
@@ -1818,6 +1828,34 @@
}
@Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ )
+ fun moveBackgroundTaskToDesktop_nonDefaultDisplay_reordersHomeAndWallpaperOfNonDefaultDisplay() {
+ val homeTask = setUpHomeTask(displayId = SECOND_DISPLAY)
+ val wallpaperToken = MockToken().token()
+ whenever(desktopWallpaperActivityTokenProvider.getToken(SECOND_DISPLAY))
+ .thenReturn(wallpaperToken)
+ val task = setUpFreeformTask(displayId = SECOND_DISPLAY, deskId = 2, background = true)
+
+ controller.moveTaskToDefaultDeskAndActivate(
+ taskId = task.taskId,
+ transitionSource = UNKNOWN,
+ remoteTransition = RemoteTransition(spy(TestRemoteTransition())),
+ )
+
+ val wct = getLatestTransition()
+ val homeReorderIndex = wct.indexOfReorder(homeTask, toTop = true)
+ val wallpaperReorderIndex = wct.indexOfReorder(wallpaperToken, toTop = true)
+ assertThat(homeReorderIndex).isNotEqualTo(-1)
+ assertThat(wallpaperReorderIndex).isNotEqualTo(-1)
+ // Wallpaper last, to be in front of Home.
+ assertThat(wallpaperReorderIndex).isGreaterThan(homeReorderIndex)
+ }
+
+ @Test
fun moveRunningTaskToDesktop_remoteTransition_usesOneShotHandler() {
val transitionHandlerArgCaptor = argumentCaptor<TransitionHandler>()
whenever(transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture()))
@@ -2468,7 +2506,7 @@
@Test
fun moveTaskToFront_backgroundTask_launchesTask() {
- val task = createTaskInfo(1)
+ val task = createRecentTaskInfo(1)
whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
whenever(
desktopMixedTransitionHandler.startLaunchTransition(
@@ -2490,7 +2528,7 @@
@Test
fun moveTaskToFront_backgroundTaskBringsTasksOverLimit_minimizesBackTask() {
val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
- val task = createTaskInfo(1001)
+ val task = createRecentTaskInfo(1001)
whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(null)
whenever(
desktopMixedTransitionHandler.startLaunchTransition(
@@ -2786,6 +2824,73 @@
}
@Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveToNextDisplay_toDesktopInOtherDisplay_bringsExistingTasksToFront() {
+ val transition = Binder()
+ val sourceDeskId = 0
+ val targetDeskId = 2
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = targetDeskId)
+ taskRepository.setDeskInactive(deskId = targetDeskId)
+ // Set up two display ids
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+ // Create a mock for the target display area: second display
+ val secondDisplayArea = DisplayAreaInfo(MockToken().token(), SECOND_DISPLAY, 0)
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECOND_DISPLAY))
+ .thenReturn(secondDisplayArea)
+ whenever(transitions.startTransition(eq(TRANSIT_CHANGE), any(), anyOrNull()))
+ .thenReturn(transition)
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = sourceDeskId)
+ val task2 = setUpFreeformTask(displayId = SECOND_DISPLAY, deskId = targetDeskId)
+
+ controller.moveToNextDisplay(task1.taskId)
+
+ // Existing desktop task in the target display is moved to front.
+ val wct = getLatestTransition()
+ wct.assertReorder(task2.token, /* toTop= */ true)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ )
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveToNextDisplay_toDesktopInOtherDisplay_movesHomeAndWallpaperToFront() {
+ val homeTask = setUpHomeTask(displayId = SECOND_DISPLAY)
+ whenever(desktopWallpaperActivityTokenProvider.getToken(SECOND_DISPLAY))
+ .thenReturn(wallpaperToken)
+ val transition = Binder()
+ val sourceDeskId = 0
+ val targetDeskId = 2
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = targetDeskId)
+ taskRepository.setDeskInactive(deskId = targetDeskId)
+ // Set up two display ids
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+ // Create a mock for the target display area: second display
+ val secondDisplayArea = DisplayAreaInfo(MockToken().token(), SECOND_DISPLAY, 0)
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECOND_DISPLAY))
+ .thenReturn(secondDisplayArea)
+ whenever(transitions.startTransition(eq(TRANSIT_CHANGE), any(), anyOrNull()))
+ .thenReturn(transition)
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = sourceDeskId)
+
+ controller.moveToNextDisplay(task1.taskId)
+
+ // Home / Wallpaper should be moved to front as the background of desktop tasks, otherwise
+ // fullscreen (non-desktop) tasks could remain visible.
+ val wct = getLatestTransition()
+ val homeReorderIndex = wct.indexOfReorder(homeTask, toTop = true)
+ val wallpaperReorderIndex = wct.indexOfReorder(wallpaperToken, toTop = true)
+ assertThat(homeReorderIndex).isNotEqualTo(-1)
+ assertThat(wallpaperReorderIndex).isNotEqualTo(-1)
+ // Wallpaper last, to be in front of Home.
+ assertThat(wallpaperReorderIndex).isGreaterThan(homeReorderIndex)
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun moveToNextDisplay_toDeskInOtherDisplay_movesToDeskAndActivates() {
val transition = Binder()
@@ -2859,6 +2964,35 @@
}
@Test
+ fun moveToNextDisplay_movingToDesktop_sendsTaskbarRoundingUpdate() {
+ val transition = Binder()
+ val sourceDeskId = 1
+ val targetDeskId = 2
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = targetDeskId)
+ taskRepository.setDeskInactive(deskId = targetDeskId)
+ // Set up two display ids
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+ // Create a mock for the target display area: second display
+ val secondDisplayArea = DisplayAreaInfo(MockToken().token(), SECOND_DISPLAY, 0)
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECOND_DISPLAY))
+ .thenReturn(secondDisplayArea)
+ whenever(transitions.startTransition(eq(TRANSIT_CHANGE), any(), anyOrNull()))
+ .thenReturn(transition)
+
+ val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = sourceDeskId)
+ taskRepository.addTaskToDesk(
+ displayId = DEFAULT_DISPLAY,
+ deskId = sourceDeskId,
+ taskId = task.taskId,
+ isVisible = true,
+ )
+ controller.moveToNextDisplay(task.taskId)
+
+ verify(taskbarDesktopTaskListener).onTaskbarCornerRoundingUpdate(anyBoolean())
+ }
+
+ @Test
fun getTaskWindowingMode() {
val fullscreenTask = setUpFullscreenTask()
val freeformTask = setUpFreeformTask()
@@ -6452,6 +6586,25 @@
@EnableFlags(
Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun startLaunchTransition_desktopNotShowing_updatesDesktopEnterExitListener() {
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0)
+ taskRepository.setDeskInactive(deskId = 0)
+
+ controller.startLaunchTransition(
+ transitionType = TRANSIT_OPEN,
+ wct = WindowContainerTransaction(),
+ launchingTaskId = null,
+ )
+
+ verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(any())
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
)
fun startLaunchTransition_desktopShowing_doesNotReorderWallpaper() {
val wct = WindowContainerTransaction()
@@ -6620,7 +6773,7 @@
if (background) {
whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(null)
whenever(recentTasksController.findTaskInBackground(task.taskId))
- .thenReturn(createTaskInfo(task.taskId))
+ .thenReturn(createRecentTaskInfo(taskId = task.taskId, displayId = displayId))
} else {
whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
}
@@ -6791,6 +6944,12 @@
return arg.lastValue
}
+ private fun getLatestTransition(): WindowContainerTransaction {
+ val arg = argumentCaptor<WindowContainerTransaction>()
+ verify(transitions).startTransition(any(), arg.capture(), anyOrNull())
+ return arg.lastValue
+ }
+
private fun getLatestEnterDesktopWct(): WindowContainerTransaction {
val arg = argumentCaptor<WindowContainerTransaction>()
verify(enterDesktopTransitionHandler).moveToDesktop(arg.capture(), any())
@@ -7049,8 +7208,9 @@
} ?: false
}
-private fun createTaskInfo(id: Int) =
+private fun createRecentTaskInfo(taskId: Int, displayId: Int = DEFAULT_DISPLAY) =
RecentTaskInfo().apply {
- taskId = id
+ this.taskId = taskId
+ this.displayId = displayId
token = WindowContainerToken(mock(IWindowContainerToken::class.java))
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/OWNERS b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/OWNERS
index 736d4cf..a7d1890 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/OWNERS
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/OWNERS
@@ -1,3 +1,2 @@
# WM shell sub-module TV pip owners
-galinap@google.com
-bronger@google.com
\ No newline at end of file
+bronger@google.com
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
index 414c014..ffef0d1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
@@ -16,6 +16,8 @@
package com.android.wm.shell.splitscreen;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
+
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -58,6 +60,7 @@
doReturn(leash).when(out).getDividerLeash();
doReturn(bounds1).when(out).getTopLeftBounds();
doReturn(bounds2).when(out).getBottomRightBounds();
+ doReturn(SNAP_TO_2_50_50).when(out).calculateCurrentSnapPosition();
return out;
}
diff --git a/libs/hwui/OWNERS b/libs/hwui/OWNERS
index 70d13ab..9c06fd5 100644
--- a/libs/hwui/OWNERS
+++ b/libs/hwui/OWNERS
@@ -3,7 +3,7 @@
alecmouri@google.com
djsollen@google.com
jreck@google.com
-njawad@google.com
+nscobie@google.com
sumir@google.com
# For text, e.g. Typeface, Font, Minikin, etc.
diff --git a/media/OWNERS b/media/OWNERS
index 5e39195..50995ea 100644
--- a/media/OWNERS
+++ b/media/OWNERS
@@ -18,5 +18,4 @@
include platform/frameworks/av:/media/janitors/media_solutions_OWNERS
# SEA/KIR/BVE
-jtinker@google.com
robertshih@google.com
diff --git a/media/java/android/media/MediaExtractor.java b/media/java/android/media/MediaExtractor.java
index b11a810..4e1d472 100644
--- a/media/java/android/media/MediaExtractor.java
+++ b/media/java/android/media/MediaExtractor.java
@@ -375,6 +375,9 @@
/**
* Extract DRM initialization data if it exists
*
+ * <p>If the media contains a PSSH box, only PSSH version 0 is supported. The result for media
+ * with other PSSH versions is undefined.
+ *
* @return DRM initialization data in the content, or {@code null}
* if no recognizable DRM format is found;
* @see DrmInitData
@@ -460,6 +463,10 @@
/**
* Get the PSSH info if present.
+ *
+ * <p>This method only supports version 0 PSSH boxes. The result for other versions is
+ * undefined.
+ *
* @return a map of uuid-to-bytes, with the uuid specifying
* the crypto scheme, and the bytes being the data specific to that scheme.
* This can be {@code null} if the source does not contain PSSH info.
diff --git a/media/java/android/media/musicrecognition/OWNERS b/media/java/android/media/musicrecognition/OWNERS
index 037b048..820be00 100644
--- a/media/java/android/media/musicrecognition/OWNERS
+++ b/media/java/android/media/musicrecognition/OWNERS
@@ -1,5 +1,4 @@
# Bug component: 830636
oni@google.com
-volnov@google.com
diff --git a/media/java/android/media/projection/MediaProjectionConfig.java b/media/java/android/media/projection/MediaProjectionConfig.java
index 30f34fe..598b534 100644
--- a/media/java/android/media/projection/MediaProjectionConfig.java
+++ b/media/java/android/media/projection/MediaProjectionConfig.java
@@ -27,7 +27,6 @@
import android.os.Parcelable;
import com.android.internal.util.AnnotationValidations;
-import com.android.internal.util.DataClass;
import java.lang.annotation.Retention;
@@ -35,36 +34,25 @@
* Configure the {@link MediaProjection} session requested from
* {@link MediaProjectionManager#createScreenCaptureIntent(MediaProjectionConfig)}.
*/
-@DataClass(
- genEqualsHashCode = true,
- genAidl = true,
- genSetters = false,
- genConstructor = false,
- genBuilder = false,
- genToString = false,
- genHiddenConstDefs = true,
- genHiddenGetters = true,
- genConstDefs = false
-)
public final class MediaProjectionConfig implements Parcelable {
/**
* The user, rather than the host app, determines which region of the display to capture.
+ *
* @hide
*/
public static final int CAPTURE_REGION_USER_CHOICE = 0;
/**
* The host app specifies a particular display to capture.
+ *
* @hide
*/
public static final int CAPTURE_REGION_FIXED_DISPLAY = 1;
/** @hide */
- @IntDef(prefix = "CAPTURE_REGION_", value = {
- CAPTURE_REGION_USER_CHOICE,
- CAPTURE_REGION_FIXED_DISPLAY
- })
+ @IntDef(prefix = "CAPTURE_REGION_", value = {CAPTURE_REGION_USER_CHOICE,
+ CAPTURE_REGION_FIXED_DISPLAY})
@Retention(SOURCE)
public @interface CaptureRegion {
}
@@ -72,7 +60,7 @@
/**
* The particular display to capture. Only used when {@link #getRegionToCapture()} is
* {@link #CAPTURE_REGION_FIXED_DISPLAY}; ignored otherwise.
- *
+ * <p>
* Only supports values of {@link android.view.Display#DEFAULT_DISPLAY}.
*/
@IntRange(from = DEFAULT_DISPLAY, to = DEFAULT_DISPLAY)
@@ -82,13 +70,7 @@
* The region to capture. Defaults to the user's choice.
*/
@CaptureRegion
- private int mRegionToCapture = CAPTURE_REGION_USER_CHOICE;
-
- /**
- * Default instance, with region set to the user's choice.
- */
- private MediaProjectionConfig() {
- }
+ private int mRegionToCapture;
/**
* Customized instance, with region set to the provided value.
@@ -129,51 +111,29 @@
*/
@NonNull
private static String captureRegionToString(int value) {
- switch (value) {
- case CAPTURE_REGION_USER_CHOICE:
- return "CAPTURE_REGION_USERS_CHOICE";
- case CAPTURE_REGION_FIXED_DISPLAY:
- return "CAPTURE_REGION_GIVEN_DISPLAY";
- default:
- return Integer.toHexString(value);
- }
+ return switch (value) {
+ case CAPTURE_REGION_USER_CHOICE -> "CAPTURE_REGION_USERS_CHOICE";
+ case CAPTURE_REGION_FIXED_DISPLAY -> "CAPTURE_REGION_GIVEN_DISPLAY";
+ default -> Integer.toHexString(value);
+ };
}
@Override
public String toString() {
- return "MediaProjectionConfig { "
- + "displayToCapture = " + mDisplayToCapture + ", "
- + "regionToCapture = " + captureRegionToString(mRegionToCapture)
- + " }";
+ return "MediaProjectionConfig { " + "displayToCapture = " + mDisplayToCapture + ", "
+ + "regionToCapture = " + captureRegionToString(mRegionToCapture) + " }";
}
-
-
-
- // Code below generated by codegen v1.0.23.
- //
- // DO NOT MODIFY!
- // CHECKSTYLE:OFF Generated code
- //
- // To regenerate run:
- // $ codegen $ANDROID_BUILD_TOP/frameworks/base/media/java/android/media/projection/MediaProjectionConfig.java
- //
- // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
- // Settings > Editor > Code Style > Formatter Control
- //@formatter:off
-
-
/**
* The particular display to capture. Only used when {@link #getRegionToCapture()} is
* {@link #CAPTURE_REGION_FIXED_DISPLAY}; ignored otherwise.
- *
+ * <p>
* Only supports values of {@link android.view.Display#DEFAULT_DISPLAY}.
*
* @hide
*/
- @DataClass.Generated.Member
- public @IntRange(from = DEFAULT_DISPLAY, to = DEFAULT_DISPLAY) int getDisplayToCapture() {
+ public int getDisplayToCapture() {
return mDisplayToCapture;
}
@@ -182,34 +142,21 @@
*
* @hide
*/
- @DataClass.Generated.Member
public @CaptureRegion int getRegionToCapture() {
return mRegionToCapture;
}
@Override
- @DataClass.Generated.Member
public boolean equals(@Nullable Object o) {
- // You can override field equality logic by defining either of the methods like:
- // boolean fieldNameEquals(MediaProjectionConfig other) { ... }
- // boolean fieldNameEquals(FieldType otherValue) { ... }
-
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
- @SuppressWarnings("unchecked")
MediaProjectionConfig that = (MediaProjectionConfig) o;
- //noinspection PointlessBooleanExpression
- return true
- && mDisplayToCapture == that.mDisplayToCapture
+ return mDisplayToCapture == that.mDisplayToCapture
&& mRegionToCapture == that.mRegionToCapture;
}
@Override
- @DataClass.Generated.Member
public int hashCode() {
- // You can override field hashCode logic by defining methods like:
- // int fieldNameHashCode() { ... }
-
int _hash = 1;
_hash = 31 * _hash + mDisplayToCapture;
_hash = 31 * _hash + mRegionToCapture;
@@ -217,65 +164,36 @@
}
@Override
- @DataClass.Generated.Member
public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
- // You can override field parcelling by defining methods like:
- // void parcelFieldName(Parcel dest, int flags) { ... }
-
dest.writeInt(mDisplayToCapture);
dest.writeInt(mRegionToCapture);
}
@Override
- @DataClass.Generated.Member
- public int describeContents() { return 0; }
+ public int describeContents() {
+ return 0;
+ }
/** @hide */
- @SuppressWarnings({"unchecked", "RedundantCast"})
- @DataClass.Generated.Member
/* package-private */ MediaProjectionConfig(@NonNull android.os.Parcel in) {
- // You can override field unparcelling by defining methods like:
- // static FieldType unparcelFieldName(Parcel in) { ... }
-
int displayToCapture = in.readInt();
int regionToCapture = in.readInt();
- this.mDisplayToCapture = displayToCapture;
- AnnotationValidations.validate(
- IntRange.class, null, mDisplayToCapture,
- "from", DEFAULT_DISPLAY,
- "to", DEFAULT_DISPLAY);
- this.mRegionToCapture = regionToCapture;
- AnnotationValidations.validate(
- CaptureRegion.class, null, mRegionToCapture);
-
- // onConstructed(); // You can define this method to get a callback
+ mDisplayToCapture = displayToCapture;
+ mRegionToCapture = regionToCapture;
+ AnnotationValidations.validate(CaptureRegion.class, null, mRegionToCapture);
}
- @DataClass.Generated.Member
- public static final @NonNull Parcelable.Creator<MediaProjectionConfig> CREATOR
- = new Parcelable.Creator<MediaProjectionConfig>() {
- @Override
- public MediaProjectionConfig[] newArray(int size) {
- return new MediaProjectionConfig[size];
- }
+ public static final @NonNull Parcelable.Creator<MediaProjectionConfig> CREATOR =
+ new Parcelable.Creator<>() {
+ @Override
+ public MediaProjectionConfig[] newArray(int size) {
+ return new MediaProjectionConfig[size];
+ }
- @Override
- public MediaProjectionConfig createFromParcel(@NonNull android.os.Parcel in) {
- return new MediaProjectionConfig(in);
- }
- };
-
- @DataClass.Generated(
- time = 1673548980960L,
- codegenVersion = "1.0.23",
- sourceFile = "frameworks/base/media/java/android/media/projection/MediaProjectionConfig.java",
- inputSignatures = "public static final int CAPTURE_REGION_USER_CHOICE\npublic static final int CAPTURE_REGION_FIXED_DISPLAY\nprivate @android.annotation.IntRange int mDisplayToCapture\nprivate @android.media.projection.MediaProjectionConfig.CaptureRegion int mRegionToCapture\npublic static @android.annotation.NonNull android.media.projection.MediaProjectionConfig createConfigForDefaultDisplay()\npublic static @android.annotation.NonNull android.media.projection.MediaProjectionConfig createConfigForUserChoice()\nprivate static @android.annotation.NonNull java.lang.String captureRegionToString(int)\npublic @java.lang.Override java.lang.String toString()\nclass MediaProjectionConfig extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genSetters=false, genConstructor=false, genBuilder=false, genToString=false, genHiddenConstDefs=true, genHiddenGetters=true, genConstDefs=false)")
- @Deprecated
- private void __metadata() {}
-
-
- //@formatter:on
- // End of generated code
-
+ @Override
+ public MediaProjectionConfig createFromParcel(@NonNull android.os.Parcel in) {
+ return new MediaProjectionConfig(in);
+ }
+ };
}
diff --git a/media/jni/OWNERS b/media/jni/OWNERS
index fdddf13..84618a3 100644
--- a/media/jni/OWNERS
+++ b/media/jni/OWNERS
@@ -1,5 +1,5 @@
# extra for MTP related files
-per-file android_mtp_*.cpp=aprasath@google.com,anothermark@google.com,kumarashishg@google.com,sarup@google.com,jsharkey@android.com,jameswei@google.com,rmojumder@google.com
+per-file android_mtp_*.cpp=aprasath@google.com,anothermark@google.com,sarup@google.com,jsharkey@android.com,jameswei@google.com,rmojumder@google.com
# extra for TV related files
per-file android_media_tv_*=hgchen@google.com,quxiangfang@google.com
diff --git a/opengl/java/android/opengl/OWNERS b/opengl/java/android/opengl/OWNERS
index e340bc6..4ec9e29 100644
--- a/opengl/java/android/opengl/OWNERS
+++ b/opengl/java/android/opengl/OWNERS
@@ -3,4 +3,3 @@
sumir@google.com
prahladk@google.com
ianelliott@google.com
-lpy@google.com
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background.xml
index 9aa0bc3..0446873 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background.xml
@@ -19,16 +19,12 @@
android:color="?android:colorControlHighlight">
<item
android:start="?android:attr/listPreferredItemPaddingStart"
- android:end="?android:attr/listPreferredItemPaddingEnd"
- android:top="2dp"
- android:bottom="16dp">
+ android:end="?android:attr/listPreferredItemPaddingEnd">
<shape android:shape="rectangle">
<solid
android:color="@color/settingslib_materialColorSurfaceBright" />
<corners
android:radius="@dimen/settingslib_preference_corner_radius" />
- <padding
- android:bottom="16dp"/>
</shape>
</item>
-</ripple>
\ No newline at end of file
+</ripple>
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_bottom.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_bottom.xml
index 554cba5..25a936d 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_bottom.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_bottom.xml
@@ -19,9 +19,7 @@
android:color="?android:colorControlHighlight">
<item
android:start="?android:attr/listPreferredItemPaddingStart"
- android:end="?android:attr/listPreferredItemPaddingEnd"
- android:top="2dp"
- android:bottom="16dp">
+ android:end="?android:attr/listPreferredItemPaddingEnd">
<shape android:shape="rectangle">
<solid
android:color="@color/settingslib_materialColorSurfaceBright" />
@@ -30,8 +28,6 @@
android:bottomLeftRadius="@dimen/settingslib_preference_corner_radius"
android:topRightRadius="4dp"
android:bottomRightRadius="@dimen/settingslib_preference_corner_radius" />
- <padding
- android:bottom="16dp"/>
</shape>
</item>
-</ripple>
\ No newline at end of file
+</ripple>
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_bottom_highlighted.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_bottom_highlighted.xml
index c0c0869..db2800e 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_bottom_highlighted.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_bottom_highlighted.xml
@@ -19,8 +19,7 @@
<item
android:bottom="16dp"
android:end="?android:attr/listPreferredItemPaddingEnd"
- android:start="?android:attr/listPreferredItemPaddingStart"
- android:top="2dp">
+ android:start="?android:attr/listPreferredItemPaddingStart">
<shape
android:shape="rectangle"
android:tint="?android:attr/colorAccent">
@@ -29,8 +28,7 @@
android:bottomRightRadius="@dimen/settingslib_preference_corner_radius"
android:topLeftRadius="4dp"
android:topRightRadius="4dp" />
- <padding android:bottom="16dp" />
<solid android:color="#42000000" />
</shape>
</item>
-</ripple>
\ No newline at end of file
+</ripple>
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_bottom_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_bottom_selected.xml
index 543b237..98f95d92 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_bottom_selected.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_bottom_selected.xml
@@ -19,9 +19,7 @@
android:color="?android:colorControlHighlight">
<item
android:start="?android:attr/listPreferredItemPaddingStart"
- android:end="?android:attr/listPreferredItemPaddingEnd"
- android:top="2dp"
- android:bottom="16dp">
+ android:end="?android:attr/listPreferredItemPaddingEnd">
<shape android:shape="rectangle">
<solid
android:color="@color/settingslib_materialColorSurfaceContainer" />
@@ -30,8 +28,6 @@
android:bottomLeftRadius="@dimen/settingslib_preference_corner_radius"
android:topRightRadius="4dp"
android:bottomRightRadius="@dimen/settingslib_preference_corner_radius" />
- <padding
- android:bottom="16dp"/>
</shape>
</item>
-</ripple>
\ No newline at end of file
+</ripple>
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_center.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_center.xml
index b89a0dd..c4286fd 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_center.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_center.xml
@@ -19,8 +19,7 @@
android:color="?android:colorControlHighlight">
<item
android:start="?android:attr/listPreferredItemPaddingStart"
- android:end="?android:attr/listPreferredItemPaddingEnd"
- android:top="2dp">
+ android:end="?android:attr/listPreferredItemPaddingEnd">
<shape android:shape="rectangle">
<solid
android:color="@color/settingslib_materialColorSurfaceBright" />
@@ -28,4 +27,4 @@
android:radius="4dp" />
</shape>
</item>
-</ripple>
\ No newline at end of file
+</ripple>
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_center_highlighted.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_center_highlighted.xml
index 8099d9b..194cdb0 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_center_highlighted.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_center_highlighted.xml
@@ -18,8 +18,7 @@
android:color="?android:colorControlHighlight">
<item
android:end="?android:attr/listPreferredItemPaddingEnd"
- android:start="?android:attr/listPreferredItemPaddingStart"
- android:top="2dp">
+ android:start="?android:attr/listPreferredItemPaddingStart">
<shape
android:shape="rectangle"
android:tint="?android:attr/colorAccent">
@@ -27,4 +26,4 @@
<solid android:color="#42000000" />
</shape>
</item>
-</ripple>
\ No newline at end of file
+</ripple>
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_center_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_center_selected.xml
index 6d2cd1a..8bc2f2f 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_center_selected.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_center_selected.xml
@@ -19,8 +19,7 @@
android:color="?android:colorControlHighlight">
<item
android:start="?android:attr/listPreferredItemPaddingStart"
- android:end="?android:attr/listPreferredItemPaddingEnd"
- android:top="2dp">
+ android:end="?android:attr/listPreferredItemPaddingEnd">
<shape android:shape="rectangle">
<solid
android:color="@color/settingslib_materialColorSurfaceContainer" />
@@ -28,4 +27,4 @@
android:radius="4dp" />
</shape>
</item>
-</ripple>
\ No newline at end of file
+</ripple>
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_highlighted.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_highlighted.xml
index a119a4a..2341661 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_highlighted.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_highlighted.xml
@@ -19,14 +19,12 @@
<item
android:bottom="16dp"
android:end="?android:attr/listPreferredItemPaddingEnd"
- android:start="?android:attr/listPreferredItemPaddingStart"
- android:top="2dp">
+ android:start="?android:attr/listPreferredItemPaddingStart">
<shape
android:shape="rectangle"
android:tint="?android:attr/colorAccent">
<corners android:radius="@dimen/settingslib_preference_corner_radius" />
- <padding android:bottom="16dp" />
<solid android:color="#42000000" />
</shape>
</item>
-</ripple>
\ No newline at end of file
+</ripple>
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_selected.xml
index bcdbf1d..99704f2d 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_selected.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_selected.xml
@@ -19,16 +19,12 @@
android:color="?android:colorControlHighlight">
<item
android:start="?android:attr/listPreferredItemPaddingStart"
- android:end="?android:attr/listPreferredItemPaddingEnd"
- android:top="2dp"
- android:bottom="16dp">
+ android:end="?android:attr/listPreferredItemPaddingEnd">
<shape android:shape="rectangle">
<solid
android:color="@color/settingslib_materialColorSurfaceContainer" />
<corners
android:radius="@dimen/settingslib_preference_corner_radius" />
- <padding
- android:bottom="16dp"/>
</shape>
</item>
-</ripple>
\ No newline at end of file
+</ripple>
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_top.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_top.xml
index 7955e44..3a59386 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_top.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_top.xml
@@ -19,8 +19,7 @@
android:color="?android:colorControlHighlight">
<item
android:start="?android:attr/listPreferredItemPaddingStart"
- android:end="?android:attr/listPreferredItemPaddingEnd"
- android:top="2dp">
+ android:end="?android:attr/listPreferredItemPaddingEnd">
<shape android:shape="rectangle">
<solid
android:color="@color/settingslib_materialColorSurfaceBright" />
@@ -31,4 +30,4 @@
android:bottomRightRadius="4dp" />
</shape>
</item>
-</ripple>
\ No newline at end of file
+</ripple>
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_top_highlighted.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_top_highlighted.xml
index 052eb01..edace29 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_top_highlighted.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_top_highlighted.xml
@@ -19,8 +19,7 @@
<item
android:color="?android:attr/colorAccent"
android:end="?android:attr/listPreferredItemPaddingEnd"
- android:start="?android:attr/listPreferredItemPaddingStart"
- android:top="2dp">
+ android:start="?android:attr/listPreferredItemPaddingStart">
<shape
android:shape="rectangle"
android:tint="?android:attr/colorAccent">
@@ -32,4 +31,4 @@
<solid android:color="#42000000" />
</shape>
</item>
-</ripple>
\ No newline at end of file
+</ripple>
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_top_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_top_selected.xml
index d4b658c..b2d6d9d 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_top_selected.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background_top_selected.xml
@@ -19,8 +19,7 @@
android:color="?android:colorControlHighlight">
<item
android:start="?android:attr/listPreferredItemPaddingStart"
- android:end="?android:attr/listPreferredItemPaddingEnd"
- android:top="2dp">
+ android:end="?android:attr/listPreferredItemPaddingEnd">
<shape android:shape="rectangle">
<solid
android:color="@color/settingslib_materialColorSurfaceContainer" />
@@ -31,4 +30,4 @@
android:bottomRightRadius="4dp" />
</shape>
</item>
-</ripple>
\ No newline at end of file
+</ripple>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml
index 3ccbbc0..2d6b6cf 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml
@@ -28,6 +28,7 @@
<item name="switchStyle">@style/SwitchCompat.SettingsLib</item>
<item name="android:progressBarStyleHorizontal">@style/HorizontalProgressBar.SettingsLib</item>
<item name="android:listDivider">@drawable/settingslib_list_divider</item>
+ <item name="android:colorBackground">@color/settingslib_materialColorSurfaceContainerLowest</item>
</style>
<style name="Theme.SettingsBase" parent="Theme.SettingsBase_v31" />
diff --git a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsBasePreferenceFragment.kt b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsBasePreferenceFragment.kt
index 8d12f01..22cd873 100644
--- a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsBasePreferenceFragment.kt
+++ b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsBasePreferenceFragment.kt
@@ -16,6 +16,7 @@
package com.android.settingslib.widget
+import android.graphics.Rect
import android.os.Bundle
import android.view.LayoutInflater;
import android.view.View
@@ -24,6 +25,7 @@
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceScreen
import androidx.recyclerview.widget.RecyclerView
+import com.android.settingslib.widget.theme.R
/** Base class for Settings to use PreferenceFragmentCompat */
abstract class SettingsBasePreferenceFragment : PreferenceFragmentCompat() {
@@ -43,6 +45,7 @@
if (SettingsThemeHelper.isExpressiveTheme(requireContext())) {
// Don't allow any divider in between the preferences in expressive design.
setDivider(null)
+ this.listView.addItemDecoration(MarginItemDecoration())
}
}
@@ -51,4 +54,18 @@
return SettingsPreferenceGroupAdapter(preferenceScreen)
return super.onCreateAdapter(preferenceScreen)
}
+
+ internal class MarginItemDecoration() : RecyclerView.ItemDecoration() {
+ override fun getItemOffsets(
+ outRect: Rect,
+ view: View,
+ parent: RecyclerView,
+ state: RecyclerView.State,
+ ) {
+ with(outRect) {
+ bottom =
+ view.resources.getDimensionPixelSize(R.dimen.settingslib_expressive_radius_extrasmall1)
+ }
+ }
+ }
}
diff --git a/packages/SettingsLib/Spa/OWNERS b/packages/SettingsLib/Spa/OWNERS
index 67386d1..d2941de 100644
--- a/packages/SettingsLib/Spa/OWNERS
+++ b/packages/SettingsLib/Spa/OWNERS
@@ -1,9 +1,6 @@
include platform/frameworks/base:/packages/SettingsLib/OWNERS
+zhibinliu@google.com
chaohuiw@google.com
-hanxu@google.com
-kellyz@google.com
pierreqian@google.com
-lijun@google.com
-songchenxi@google.com
-cyl@google.com
+lijun@google.com
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_actionButtons.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_actionButtons.png
index b2f3cf1..65418b9 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_actionButtons.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_actionButtons.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_mainSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_mainSwitchPreference.png
index 75c8e6e..c9091fb 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_mainSwitchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_mainSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_spinner.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_spinner.png
index 06f0059..cdc1e79 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_spinner.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_spinner.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_actionButtons.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_actionButtons.png
index fa06927..614fbe9 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_actionButtons.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_actionButtons.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_imageIllustration.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_imageIllustration.png
index a5f3fa5..ce1257f 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_imageIllustration.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_imageIllustration.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_mainSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_mainSwitchPreference.png
index b72c8db..6b1233c 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_mainSwitchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_mainSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_slider.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_slider.png
index 928e926..782957d 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_slider.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_slider.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_spinner.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_spinner.png
index 06f0059..cdc1e79 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_spinner.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_spinner.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_actionButtons.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_actionButtons.png
index 63983ee..427f202 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_actionButtons.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_actionButtons.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_barChart.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_barChart.png
index 8fcc350..e372f7b 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_barChart.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_barChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_footer.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_footer.png
index c2f6165..1ed1efc 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_footer.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_footer.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_imageIllustration.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_imageIllustration.png
index f32d7421..6283dd1 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_imageIllustration.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_imageIllustration.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_lineChart.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_lineChart.png
index 6659d7c..a4eb19e 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_lineChart.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_lineChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_mainSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_mainSwitchPreference.png
index 15c86dc..bd1bb28 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_mainSwitchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_mainSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_pieChart.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_pieChart.png
index 7b6e702..d0d5ac1 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_pieChart.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_pieChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_preference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_preference.png
index cd44fb8..5cf7381 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_preference.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_progressBar.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_progressBar.png
index d31f2c4..96f614c 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_progressBar.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_progressBar.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_slider.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_slider.png
index 8a2b800..3cbe715 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_slider.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_slider.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_spinner.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_spinner.png
index 5be3a21..f151798 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_spinner.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_spinner.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_switchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_switchPreference.png
index 9aee004..218e8e3 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_switchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_switchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_twoTargetSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_twoTargetSwitchPreference.png
index cc74aac..44b32e2 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_twoTargetSwitchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_twoTargetSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/TimeMeasurer.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/TimeMeasurer.kt
index b23f4e0..ef2745c 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/TimeMeasurer.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/TimeMeasurer.kt
@@ -14,14 +14,11 @@
* limitations under the License.
*/
-@file:OptIn(ExperimentalTime::class)
-
package com.android.settingslib.spa.framework.compose
import android.util.Log
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
-import kotlin.time.ExperimentalTime
import kotlin.time.TimeSource
const val ENABLE_MEASURE_TIME = false
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
index b1bb79d..068ff06 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
@@ -87,15 +87,28 @@
@Composable
internal fun BaseIcon(icon: @Composable (() -> Unit)?, modifier: Modifier, paddingStart: Dp) {
- if (icon != null) {
- Box(
- modifier = modifier.size(SettingsDimension.itemIconContainerSize),
- contentAlignment = Alignment.Center,
- ) {
- icon()
+ if (isSpaExpressiveEnabled) {
+ Spacer(modifier = Modifier.width(width = paddingStart))
+ if (icon != null) {
+ Box(
+ modifier = modifier.size(SettingsDimension.itemIconContainerSizeSmall),
+ contentAlignment = Alignment.Center,
+ ) {
+ icon()
+ }
+ Spacer(modifier = Modifier.width(width = SettingsDimension.paddingExtraSmall6))
}
} else {
- Spacer(modifier = Modifier.width(width = paddingStart))
+ if (icon != null) {
+ Box(
+ modifier = modifier.size(SettingsDimension.itemIconContainerSize),
+ contentAlignment = Alignment.Center,
+ ) {
+ icon()
+ }
+ } else {
+ Spacer(modifier = Modifier.width(width = paddingStart))
+ }
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/OWNERS b/packages/SettingsLib/src/com/android/settingslib/wifi/OWNERS
index b9449ac..50bfe8c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/OWNERS
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/OWNERS
@@ -1,6 +1,5 @@
# Default reviewers for this and subdirectories.
andychou@google.com
-arcwang@google.com
asapperstein@google.com
changbetty@google.com
qal@google.com
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index c98a741..99c4e21c 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -456,11 +456,16 @@
@GuardedBy("mLock")
private void loadAconfigDefaultValuesLocked(List<String> filePaths) {
for (String fileName : filePaths) {
- try (FileInputStream inputStream = new FileInputStream(fileName)) {
- loadAconfigDefaultValues(
- inputStream.readAllBytes(), mNamespaceDefaults, mAconfigDefaultFlags);
- } catch (IOException e) {
- Slog.e(LOG_TAG, "failed to read protobuf", e);
+ File f = new File(fileName);
+ if (f.isFile() && f.canRead()) {
+ try (FileInputStream inputStream = new FileInputStream(fileName)) {
+ loadAconfigDefaultValues(
+ inputStream.readAllBytes(), mNamespaceDefaults, mAconfigDefaultFlags);
+ } catch (IOException e) {
+ Slog.e(LOG_TAG, "failed to read protobuf", e);
+ }
+ } else {
+ Slog.d(LOG_TAG, "No protobuf file at " + fileName);
}
}
}
diff --git a/packages/Shell/OWNERS b/packages/Shell/OWNERS
index 576afdc..2fa707e 100644
--- a/packages/Shell/OWNERS
+++ b/packages/Shell/OWNERS
@@ -7,9 +7,7 @@
svetoslavganov@google.com
hackbod@google.com
yamasani@google.com
-toddke@google.com
patb@google.com
-cbrubaker@google.com
omakoto@google.com
michaelwr@google.com
ronish@google.com
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index f2c76ba..7e8d549 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -203,6 +203,16 @@
}
flag {
+ name: "notifications_hun_shared_animation_values"
+ namespace: "systemui"
+ description: "Adds a shared class for fetching HUN animation values."
+ bug: "393369891"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "notification_undo_guts_on_config_changed"
namespace: "systemui"
description: "Fixes a bug where a theme or font change while notification guts were open"
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ComposableControllerFactory.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ComposableControllerFactory.kt
new file mode 100644
index 0000000..c842159
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ComposableControllerFactory.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.animation
+
+import android.content.ComponentName
+import android.util.Log
+import com.android.systemui.animation.ActivityTransitionAnimator.Controller
+import com.android.systemui.animation.ActivityTransitionAnimator.ControllerFactory
+import kotlinx.coroutines.flow.MutableStateFlow
+
+private const val TAG = "ComposableControllerFactory"
+
+/**
+ * [ControllerFactory] extension for Compose. Since composables are not guaranteed to be part of the
+ * composition when [ControllerFactory.createController] is called, this class provides a way for
+ * the composable to register itself at the time of composition, and deregister itself when
+ * disposed.
+ */
+abstract class ComposableControllerFactory(
+ cookie: ActivityTransitionAnimator.TransitionCookie,
+ component: ComponentName?,
+ launchCujType: Int? = null,
+ returnCujType: Int? = null,
+) : ControllerFactory(cookie, component, launchCujType, returnCujType) {
+ /**
+ * The object to be used to create [Controller]s, when its associate composable is in the
+ * composition.
+ */
+ protected val expandable = MutableStateFlow<Expandable?>(null)
+
+ /** To be called when the composable to be animated enters composition. */
+ fun onCompose(expandable: Expandable) {
+ if (TransitionAnimator.DEBUG) {
+ Log.d(TAG, "Composable entered composition (expandable=$expandable")
+ }
+ this.expandable.value = expandable
+ }
+
+ /** To be called when the composable to be animated exits composition. */
+ fun onDispose() {
+ if (TransitionAnimator.DEBUG) {
+ Log.d(TAG, "Composable left composition (expandable=${this.expandable.value}")
+ }
+ this.expandable.value = null
+ }
+}
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 1e3c4c9..a352b1e 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
@@ -84,6 +84,7 @@
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
import com.android.compose.modifiers.thenIf
import com.android.compose.ui.graphics.FullScreenComposeViewInOverlay
+import com.android.systemui.animation.ComposableControllerFactory
import com.android.systemui.animation.Expandable
import com.android.systemui.animation.TransitionAnimator
import kotlin.math.max
@@ -119,6 +120,10 @@
* }
* ```
*
+ * [transitionControllerFactory] must be defined when this [Expandable] is registered for a
+ * long-term launch or return animation, to ensure that animation controllers can be created
+ * correctly.
+ *
* @sample com.android.systemui.compose.gallery.ActivityLaunchScreen
* @sample com.android.systemui.compose.gallery.DialogLaunchScreen
*/
@@ -134,10 +139,17 @@
// TODO(b/285250939): Default this to true then remove once the Compose QS expandables have
// proven that the new implementation is robust.
useModifierBasedImplementation: Boolean = false,
+ transitionControllerFactory: ComposableControllerFactory? = null,
content: @Composable (Expandable) -> Unit,
) {
Expandable(
- rememberExpandableController(color, shape, contentColor, borderStroke),
+ rememberExpandableController(
+ color,
+ shape,
+ contentColor,
+ borderStroke,
+ transitionControllerFactory,
+ ),
modifier,
onClick,
interactionSource,
@@ -183,6 +195,17 @@
) {
val controller = controller as ExpandableControllerImpl
+ if (controller.transitionControllerFactory != null) {
+ DisposableEffect(controller.transitionControllerFactory) {
+ // Notify the transition controller factory that the expandable is now available, so it
+ // can move forward with any pending requests.
+ controller.transitionControllerFactory.onCompose(controller.expandable)
+ // Once this composable is gone, the transition controller factory must be notified so
+ // it doesn't accepts requests providing stale content.
+ onDispose { controller.transitionControllerFactory.onDispose() }
+ }
+ }
+
if (useModifierBasedImplementation) {
Box(modifier.expandable(controller, onClick, interactionSource)) {
WrappedContent(controller.expandable, controller.contentColor, content)
@@ -308,34 +331,28 @@
interactionSource: MutableInteractionSource? = null,
): Modifier {
val controller = controller as ExpandableControllerImpl
+ val graphicsLayer = rememberGraphicsLayer()
val isAnimating = controller.isAnimating
- val drawInOverlayModifier =
- if (isAnimating) {
- val graphicsLayer = rememberGraphicsLayer()
-
- FullScreenComposeViewInOverlay(controller.overlay) { view ->
- Modifier.then(DrawExpandableInOverlayElement(view, controller, graphicsLayer))
- }
-
- Modifier.drawWithContent { graphicsLayer.record { this@drawWithContent.drawContent() } }
- } else {
- null
+ if (isAnimating) {
+ FullScreenComposeViewInOverlay(controller.overlay) { view ->
+ Modifier.then(DrawExpandableInOverlayElement(view, controller, graphicsLayer))
}
+ }
+ val drawContent = !isAnimating && !controller.isDialogShowing
return this.thenIf(onClick != null) { Modifier.minimumInteractiveComponentSize() }
- .thenIf(!isAnimating) {
+ .thenIf(drawContent) {
Modifier.border(controller)
.then(clickModifier(controller, onClick, interactionSource))
.background(controller.color, controller.shape)
}
- .thenIf(drawInOverlayModifier != null) { drawInOverlayModifier!! }
.onPlaced { controller.boundsInComposeViewRoot = it.boundsInRoot() }
- .thenIf(!isAnimating && controller.isDialogShowing) {
- Modifier.layout { measurable, constraints ->
- measurable.measure(constraints).run {
- layout(width, height) { /* Do not place/draw. */ }
- }
+ .drawWithContent {
+ graphicsLayer.record { this@drawWithContent.drawContent() }
+
+ if (drawContent) {
+ drawLayer(graphicsLayer)
}
}
}
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 a03c896..72da175e 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
@@ -47,6 +47,7 @@
import androidx.compose.ui.unit.LayoutDirection
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.animation.ComposableControllerFactory
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.animation.Expandable
@@ -77,6 +78,7 @@
shape: Shape,
contentColor: Color = contentColorFor(color),
borderStroke: BorderStroke? = null,
+ transitionControllerFactory: ComposableControllerFactory? = null,
): ExpandableController {
val composeViewRoot = LocalView.current
val density = LocalDensity.current
@@ -95,6 +97,7 @@
composeViewRoot,
density,
layoutDirection,
+ transitionControllerFactory,
) {
ExpandableControllerImpl(
color,
@@ -103,6 +106,7 @@
borderStroke,
composeViewRoot,
density,
+ transitionControllerFactory,
layoutDirection,
{ isComposed },
)
@@ -127,6 +131,7 @@
internal val borderStroke: BorderStroke?,
internal val composeViewRoot: View,
internal val density: Density,
+ internal val transitionControllerFactory: ComposableControllerFactory?,
private val layoutDirection: LayoutDirection,
private val isComposed: () -> Boolean,
) : ExpandableController {
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt
index f905527..b9200c1 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt
@@ -55,7 +55,6 @@
val layerCfg =
LayerConfig(
style = FontTextStyle(lineHeight = 147.25f),
- timespec = DigitalTimespec.DIGIT_PAIR,
alignment = DigitalAlignment(HorizontalAlignment.CENTER, VerticalAlignment.CENTER),
aodStyle =
FontTextStyle(
@@ -63,12 +62,23 @@
transitionDuration = 750,
),
- // Placeholder
+ // Placeholders
+ timespec = DigitalTimespec.TIME_FULL_FORMAT,
dateTimeFormat = "hh:mm",
)
- createController(layerCfg.copy(dateTimeFormat = "hh"))
- createController(layerCfg.copy(dateTimeFormat = "mm"))
+ createController(
+ layerCfg.copy(timespec = DigitalTimespec.FIRST_DIGIT, dateTimeFormat = "hh")
+ )
+ createController(
+ layerCfg.copy(timespec = DigitalTimespec.SECOND_DIGIT, dateTimeFormat = "hh")
+ )
+ createController(
+ layerCfg.copy(timespec = DigitalTimespec.FIRST_DIGIT, dateTimeFormat = "mm")
+ )
+ createController(
+ layerCfg.copy(timespec = DigitalTimespec.SECOND_DIGIT, dateTimeFormat = "mm")
+ )
}
private fun refreshTime() {
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
index ec99af1..2d0ca53 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
@@ -124,8 +124,10 @@
maxChildSize.y * AOD_VERTICAL_TRANSLATE_RATIO
)
*/
+
+ val xScale = if (childViews.size < 4) 1f else 2f
val yBuffer = context.resources.getDimensionPixelSize(R.dimen.clock_vertical_digit_buffer)
- return (maxChildSize + aodTranslate.abs()) * VPointF(1f, 2f) + VPointF(0f, yBuffer)
+ return (maxChildSize + aodTranslate.abs()) * VPointF(xScale, 2f) + VPointF(0f, yBuffer)
}
override fun onViewAdded(child: View?) {
@@ -226,19 +228,22 @@
when (child.id) {
R.id.HOUR_FIRST_DIGIT -> VPointF.ZERO
R.id.HOUR_SECOND_DIGIT -> VPointF(x, 0f)
- R.id.MINUTE_FIRST_DIGIT -> VPointF(0f, y + yBuffer)
- R.id.MINUTE_SECOND_DIGIT -> this
R.id.HOUR_DIGIT_PAIR -> VPointF.ZERO
- // Add a small vertical buffer for the second digit pair
+ // Add a small vertical buffer for second line views
R.id.MINUTE_DIGIT_PAIR -> VPointF(0f, y + yBuffer)
+ R.id.MINUTE_FIRST_DIGIT -> VPointF(0f, y + yBuffer)
+ R.id.MINUTE_SECOND_DIGIT -> VPointF(x, y + yBuffer)
else -> VPointF.ZERO
}
}
val childSize = child.measuredSize
- offset += VPointF((measuredWidth - childSize.x) / 2f, 0f)
offset += aodTranslate.abs()
+ // Horizontal offset to center each view in the available space
+ val midX = if (childViews.size < 4) measuredWidth / 2f else measuredWidth / 4f
+ offset += VPointF(midX - childSize.x / 2f, 0f)
+
val setPos = if (isLayout) child::layout else child::setLeftTopRightBottom
setPos(
offset.x.roundToInt(),
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
index 479e146..015a827 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
@@ -425,12 +425,10 @@
return VPointF(
when {
mode.x == EXACTLY -> MeasureSpec.getSize(widthMeasureSpec).toFloat()
- isSingleDigit() -> maxSingleDigitWidth
else -> interpBounds.width() + 2 * lockScreenPaint.strokeWidth
},
when {
mode.y == EXACTLY -> MeasureSpec.getSize(heightMeasureSpec).toFloat()
- isSingleDigit() -> maxSingleDigitHeight
else -> interpBounds.height() + 2 * lockScreenPaint.strokeWidth
},
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 88c9e74..0cfb36d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -36,6 +36,7 @@
import android.view.View
import android.view.WindowInsets
import android.view.WindowManager
+import android.view.accessibility.AccessibilityManager
import android.widget.ScrollView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -114,6 +115,7 @@
@Mock lateinit var selectedUserInteractor: SelectedUserInteractor
@Mock private lateinit var packageManager: PackageManager
@Mock private lateinit var activityTaskManager: ActivityTaskManager
+ @Mock private lateinit var accessibilityManager: AccessibilityManager
@Mock private lateinit var lazyViewCapture: Lazy<ViewCapture>
private lateinit var displayRepository: FakeDisplayRepository
@@ -678,6 +680,7 @@
udfpsUtils,
iconProvider,
activityTaskManager,
+ accessibilityManager,
),
{ credentialViewModel },
fakeExecutor,
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 dfea784..197b0ee 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
@@ -23,15 +23,16 @@
import android.view.Display.DEFAULT_DISPLAY
import android.view.Display.TYPE_EXTERNAL
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.systemui.SysuiTestCase
import com.android.systemui.coroutines.FlowValue
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.util.mockito.kotlinArgumentCaptor
import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
import com.android.systemui.utils.os.FakeHandler
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.TestScope
@@ -46,6 +47,7 @@
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.kotlin.eq
+import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
@@ -53,7 +55,11 @@
class DisplayRepositoryTest : SysuiTestCase() {
private val displayManager = mock<DisplayManager>()
+ private val commandQueue = mock<CommandQueue>()
+ private val windowManager = mock<IWindowManager>()
+
private val displayListener = kotlinArgumentCaptor<DisplayManager.DisplayListener>()
+ private val commandQueueCallbacks = kotlinArgumentCaptor<CommandQueue.Callbacks>()
private val connectedDisplayListener = kotlinArgumentCaptor<DisplayManager.DisplayListener>()
private val testHandler = FakeHandler(Looper.getMainLooper())
@@ -67,6 +73,8 @@
private val displayRepository: DisplayRepositoryImpl by lazy {
DisplayRepositoryImpl(
displayManager,
+ commandQueue,
+ windowManager,
testHandler,
TestScope(UnconfinedTestDispatcher()),
UnconfinedTestDispatcher(),
@@ -513,6 +521,115 @@
assertThat(displayRepository.getDisplay(2)).isNull()
}
+ @Test
+ fun displayIdsWithSystemDecorations_onStart_emitsDisplaysWithSystemDecorations() =
+ testScope.runTest {
+ setDisplays(0, 1, 2)
+ whenever(windowManager.shouldShowSystemDecors(0)).thenReturn(true)
+ whenever(windowManager.shouldShowSystemDecors(1)).thenReturn(false)
+ whenever(windowManager.shouldShowSystemDecors(2)).thenReturn(true)
+
+ val displayIdsWithSystemDecorations by latestDisplayIdsWithSystemDecorationsValue()
+
+ assertThat(displayIdsWithSystemDecorations).containsExactly(0, 2)
+ }
+
+ @Test
+ fun displayIdsWithSystemDecorations_systemDecorationAdded_emitsIncludingNewDisplayIds() =
+ testScope.runTest {
+ setDisplays(0)
+ whenever(windowManager.shouldShowSystemDecors(0)).thenReturn(true)
+ val lastDisplayIdsWithSystemDecorations by latestDisplayIdsWithSystemDecorationsValue()
+
+ sendOnDisplayAddSystemDecorations(2)
+ sendOnDisplayAddSystemDecorations(3)
+
+ assertThat(lastDisplayIdsWithSystemDecorations).containsExactly(0, 2, 3)
+ }
+
+ @Test
+ fun displayIdsWithSystemDecorations_systemDecorationAdded_emitsToNewSubscribers() =
+ testScope.runTest {
+ setDisplays(0)
+ whenever(windowManager.shouldShowSystemDecors(0)).thenReturn(true)
+
+ val priorDisplayIdsWithSystemDecorations by latestDisplayIdsWithSystemDecorationsValue()
+ sendOnDisplayAddSystemDecorations(1)
+ assertThat(priorDisplayIdsWithSystemDecorations).containsExactly(0, 1)
+
+ val lastDisplayIdsWithSystemDecorations by
+ collectLastValue(displayRepository.displayIdsWithSystemDecorations)
+ assertThat(lastDisplayIdsWithSystemDecorations).containsExactly(0, 1)
+ }
+
+ @Test
+ fun displayIdsWithSystemDecorations_systemDecorationRemoved_doesNotEmitRemovedDisplayId() =
+ testScope.runTest {
+ val lastDisplayIdsWithSystemDecorations by latestDisplayIdsWithSystemDecorationsValue()
+
+ sendOnDisplayAddSystemDecorations(1)
+ sendOnDisplayAddSystemDecorations(2)
+ sendOnDisplayRemoveSystemDecorations(2)
+
+ assertThat(lastDisplayIdsWithSystemDecorations).containsExactly(1)
+ }
+
+ @Test
+ fun displayIdsWithSystemDecorations_systemDecorationsRemoved_nonExistentDisplay_noEffect() =
+ testScope.runTest {
+ val lastDisplayIdsWithSystemDecorations by latestDisplayIdsWithSystemDecorationsValue()
+
+ sendOnDisplayAddSystemDecorations(1)
+ sendOnDisplayRemoveSystemDecorations(2)
+
+ assertThat(lastDisplayIdsWithSystemDecorations).containsExactly(1)
+ }
+
+ @Test
+ fun displayIdsWithSystemDecorations_displayRemoved_doesNotEmitRemovedDisplayId() =
+ testScope.runTest {
+ val lastDisplayIdsWithSystemDecorations by latestDisplayIdsWithSystemDecorationsValue()
+
+ sendOnDisplayAddSystemDecorations(1)
+ sendOnDisplayAddSystemDecorations(2)
+ sendOnDisplayRemoved(2)
+
+ assertThat(lastDisplayIdsWithSystemDecorations).containsExactly(1)
+ }
+
+ @Test
+ fun displayIdsWithSystemDecorations_displayRemoved_nonExistentDisplay_noEffect() =
+ testScope.runTest {
+ val lastDisplayIdsWithSystemDecorations by latestDisplayIdsWithSystemDecorationsValue()
+
+ sendOnDisplayAddSystemDecorations(1)
+ sendOnDisplayRemoved(2)
+
+ assertThat(lastDisplayIdsWithSystemDecorations).containsExactly(1)
+ }
+
+ @Test
+ fun displayIdsWithSystemDecorations_onFlowCollection_commandQueueCallbackRegistered() =
+ testScope.runTest {
+ val lastDisplayIdsWithSystemDecorations by latestDisplayIdsWithSystemDecorationsValue()
+
+ assertThat(lastDisplayIdsWithSystemDecorations).isEmpty()
+
+ verify(commandQueue, times(1)).addCallback(any())
+ }
+
+ @Test
+ fun displayIdsWithSystemDecorations_afterFlowCollection_commandQueueCallbackUnregistered() {
+ testScope.runTest {
+ val lastDisplayIdsWithSystemDecorations by latestDisplayIdsWithSystemDecorationsValue()
+
+ assertThat(lastDisplayIdsWithSystemDecorations).isEmpty()
+
+ verify(commandQueue, times(1)).addCallback(any())
+ }
+ verify(commandQueue, times(1)).removeCallback(any())
+ }
+
private fun Iterable<Display>.ids(): List<Int> = map { it.displayId }
private fun Iterable<Set<Display>>.toIdSets(): List<Set<Int>> = map { it.ids().toSet() }
@@ -550,6 +667,14 @@
return flowValue
}
+ // Wrapper to capture the displayListener and commandQueueCallbacks.
+ private fun TestScope.latestDisplayIdsWithSystemDecorationsValue(): FlowValue<Set<Int>?> {
+ val flowValue = collectLastValue(displayRepository.displayIdsWithSystemDecorations)
+ captureAddedRemovedListener()
+ captureCommandQueueCallbacks()
+ return flowValue
+ }
+
private fun captureAddedRemovedListener() {
verify(displayManager)
.registerDisplayListener(
@@ -563,6 +688,10 @@
)
}
+ private fun captureCommandQueueCallbacks() {
+ verify(commandQueue).addCallback(commandQueueCallbacks.capture())
+ }
+
private fun sendOnDisplayAdded(id: Int, displayType: Int) {
val mockDisplay = display(id = id, type = displayType)
whenever(displayManager.getDisplay(eq(id))).thenReturn(mockDisplay)
@@ -592,6 +721,14 @@
connectedDisplayListener.value.onDisplayChanged(id)
}
+ private fun sendOnDisplayRemoveSystemDecorations(id: Int) {
+ commandQueueCallbacks.value.onDisplayRemoveSystemDecorations(id)
+ }
+
+ private fun sendOnDisplayAddSystemDecorations(id: Int) {
+ commandQueueCallbacks.value.onDisplayAddSystemDecorations(id)
+ }
+
private fun setDisplays(displays: List<Display>) {
whenever(displayManager.displays).thenReturn(displays.toTypedArray())
displays.forEach { display ->
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
index 8058eca..1665895 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
@@ -130,7 +130,7 @@
}
@Test
- @DisableFlags(com.android.systemui.shared.Flags.FLAG_CLOCK_REACTIVE_VARIANTS)
+ @DisableFlags(com.android.systemui.shared.Flags.FLAG_CLOCK_REACTIVE_SMARTSPACE_LAYOUT)
fun testApplyDefaultConstraints_LargeClock_SplitShade() =
kosmos.testScope.runTest {
with(kosmos) {
@@ -147,7 +147,7 @@
}
@Test
- @EnableFlags(com.android.systemui.shared.Flags.FLAG_CLOCK_REACTIVE_VARIANTS)
+ @EnableFlags(com.android.systemui.shared.Flags.FLAG_CLOCK_REACTIVE_SMARTSPACE_LAYOUT)
fun testApplyDefaultConstraints_LargeClock_SplitShade_ReactiveVariantsOn() =
kosmos.testScope.runTest {
with(kosmos) {
@@ -165,7 +165,7 @@
}
@Test
- @DisableFlags(com.android.systemui.shared.Flags.FLAG_CLOCK_REACTIVE_VARIANTS)
+ @DisableFlags(com.android.systemui.shared.Flags.FLAG_CLOCK_REACTIVE_SMARTSPACE_LAYOUT)
fun testApplyDefaultConstraints_LargeClock_NonSplitShade() =
kosmos.testScope.runTest {
with(kosmos) {
@@ -189,7 +189,7 @@
}
@Test
- @EnableFlags(com.android.systemui.shared.Flags.FLAG_CLOCK_REACTIVE_VARIANTS)
+ @EnableFlags(com.android.systemui.shared.Flags.FLAG_CLOCK_REACTIVE_SMARTSPACE_LAYOUT)
fun testApplyDefaultConstraints_LargeClock_NonSplitShade_reactiveVariantsOn() =
kosmos.testScope.runTest {
with(kosmos) {
@@ -262,7 +262,7 @@
}
@Test
- @DisableFlags(com.android.systemui.shared.Flags.FLAG_CLOCK_REACTIVE_VARIANTS)
+ @DisableFlags(com.android.systemui.shared.Flags.FLAG_CLOCK_REACTIVE_SMARTSPACE_LAYOUT)
fun testApplyDefaultConstraints_SmallClock_SplitShade() =
kosmos.testScope.runTest {
with(kosmos) {
@@ -286,7 +286,7 @@
}
@Test
- @EnableFlags(com.android.systemui.shared.Flags.FLAG_CLOCK_REACTIVE_VARIANTS)
+ @EnableFlags(com.android.systemui.shared.Flags.FLAG_CLOCK_REACTIVE_SMARTSPACE_LAYOUT)
fun testApplyDefaultConstraints_SmallClock_SplitShade_ReactiveVariantsOn() =
kosmos.testScope.runTest {
with(kosmos) {
@@ -311,7 +311,7 @@
}
@Test
- @DisableFlags(com.android.systemui.shared.Flags.FLAG_CLOCK_REACTIVE_VARIANTS)
+ @DisableFlags(com.android.systemui.shared.Flags.FLAG_CLOCK_REACTIVE_SMARTSPACE_LAYOUT)
fun testApplyDefaultConstraints_SmallClock_NonSplitShade() =
kosmos.testScope.runTest {
with(kosmos) {
@@ -334,7 +334,7 @@
}
@Test
- @EnableFlags(com.android.systemui.shared.Flags.FLAG_CLOCK_REACTIVE_VARIANTS)
+ @EnableFlags(com.android.systemui.shared.Flags.FLAG_CLOCK_REACTIVE_SMARTSPACE_LAYOUT)
fun testApplyDefaultConstraints_SmallClock_NonSplitShade_ReactiveVariantsOn() =
kosmos.testScope.runTest {
with(kosmos) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
index f9bed23..374bcbf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
@@ -50,7 +50,7 @@
@RunWith(AndroidJUnit4::class)
@SmallTest
-@DisableFlags(com.android.systemui.shared.Flags.FLAG_CLOCK_REACTIVE_VARIANTS)
+@DisableFlags(com.android.systemui.shared.Flags.FLAG_CLOCK_REACTIVE_SMARTSPACE_LAYOUT)
class SmartspaceSectionTest : SysuiTestCase() {
private lateinit var underTest: SmartspaceSection
@Mock private lateinit var keyguardClockViewModel: KeyguardClockViewModel
@@ -102,7 +102,7 @@
}
@Test
- @DisableFlags(com.android.systemui.shared.Flags.FLAG_CLOCK_REACTIVE_VARIANTS)
+ @DisableFlags(com.android.systemui.shared.Flags.FLAG_CLOCK_REACTIVE_SMARTSPACE_LAYOUT)
fun testAddViews_notSmartspaceEnabled() {
whenever(keyguardSmartspaceViewModel.isSmartspaceEnabled).thenReturn(false)
val constraintLayout = ConstraintLayout(mContext)
@@ -113,7 +113,7 @@
}
@Test
- @DisableFlags(com.android.systemui.shared.Flags.FLAG_CLOCK_REACTIVE_VARIANTS)
+ @DisableFlags(com.android.systemui.shared.Flags.FLAG_CLOCK_REACTIVE_SMARTSPACE_LAYOUT)
fun testAddViews_smartspaceEnabled_dateWeatherDecoupled() {
whenever(keyguardSmartspaceViewModel.isDateWeatherDecoupled).thenReturn(true)
underTest.addViews(constraintLayout)
@@ -132,7 +132,7 @@
}
@Test
- @DisableFlags(com.android.systemui.shared.Flags.FLAG_CLOCK_REACTIVE_VARIANTS)
+ @DisableFlags(com.android.systemui.shared.Flags.FLAG_CLOCK_REACTIVE_SMARTSPACE_LAYOUT)
fun testConstraintsWhenShadeLayoutIsNotWide() {
underTest.addViews(constraintLayout)
underTest.applyConstraints(constraintSet)
@@ -142,7 +142,7 @@
}
@Test
- @DisableFlags(com.android.systemui.shared.Flags.FLAG_CLOCK_REACTIVE_VARIANTS)
+ @DisableFlags(com.android.systemui.shared.Flags.FLAG_CLOCK_REACTIVE_SMARTSPACE_LAYOUT)
fun testConstraintsWhenShadeLayoutIsWide() {
isShadeLayoutWide.value = true
@@ -154,7 +154,7 @@
}
@Test
- @DisableFlags(com.android.systemui.shared.Flags.FLAG_CLOCK_REACTIVE_VARIANTS)
+ @DisableFlags(com.android.systemui.shared.Flags.FLAG_CLOCK_REACTIVE_SMARTSPACE_LAYOUT)
fun testConstraintsWhenNotHasCustomWeatherDataDisplay() {
whenever(keyguardSmartspaceViewModel.isDateWeatherDecoupled).thenReturn(true)
underTest.addViews(constraintLayout)
@@ -169,7 +169,7 @@
}
@Test
- @DisableFlags(com.android.systemui.shared.Flags.FLAG_CLOCK_REACTIVE_VARIANTS)
+ @DisableFlags(com.android.systemui.shared.Flags.FLAG_CLOCK_REACTIVE_SMARTSPACE_LAYOUT)
fun testConstraintsWhenHasCustomWeatherDataDisplay() {
hasCustomWeatherDataDisplay.value = true
underTest.addViews(constraintLayout)
@@ -180,7 +180,7 @@
}
@Test
- @DisableFlags(com.android.systemui.shared.Flags.FLAG_CLOCK_REACTIVE_VARIANTS)
+ @DisableFlags(com.android.systemui.shared.Flags.FLAG_CLOCK_REACTIVE_SMARTSPACE_LAYOUT)
fun testNormalDateWeatherVisibility() {
isWeatherVisibleFlow.value = true
underTest.addViews(constraintLayout)
@@ -194,7 +194,7 @@
}
@Test
- @DisableFlags(com.android.systemui.shared.Flags.FLAG_CLOCK_REACTIVE_VARIANTS)
+ @DisableFlags(com.android.systemui.shared.Flags.FLAG_CLOCK_REACTIVE_SMARTSPACE_LAYOUT)
fun testCustomDateWeatherVisibility() {
hasCustomWeatherDataDisplay.value = true
underTest.addViews(constraintLayout)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt
index 4e14fec..c71b107 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt
@@ -61,11 +61,11 @@
fun setUp() {
context.orCreateTestableResources.addOverride(
R.dimen.qs_media_enabled_seekbar_height,
- enabledHeight
+ enabledHeight,
)
context.orCreateTestableResources.addOverride(
R.dimen.qs_media_disabled_seekbar_height,
- disabledHeight
+ disabledHeight,
)
seekBarView = SeekBar(context)
@@ -116,9 +116,6 @@
// THEN seek bar shows the progress
assertThat(seekBarView.progress).isEqualTo(3000)
assertThat(seekBarView.max).isEqualTo(120000)
-
- val desc = context.getString(R.string.controls_media_seekbar_description, "00:03", "02:00")
- assertThat(seekBarView.contentDescription).isEqualTo(desc)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/ConversationNotificationProcessorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/ConversationNotificationProcessorTest.kt
index 67d0ade..0caddf4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/ConversationNotificationProcessorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/ConversationNotificationProcessorTest.kt
@@ -111,13 +111,13 @@
.isNotNull()
val processedSummary = nb.build().extras.getCharSequence(EXTRA_SUMMARIZED_CONTENT)
- assertThat(processedSummary.toString()).isEqualTo("x$summarization")
+ assertThat(processedSummary.toString()).isEqualTo("x $summarization")
val checkSpans = SpannableStringBuilder(processedSummary)
assertThat(
checkSpans.getSpans(
/* queryStart = */ 0,
- /* queryEnd = */ 1,
+ /* queryEnd = */ 2,
/* kind = */ ImageSpan::class.java,
)
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryTest.kt
index 5d8b68e..83fd5dc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryTest.kt
@@ -105,4 +105,52 @@
fun getWhen_adapter() {
assertThat(underTest.entryAdapter.`when`).isEqualTo(0)
}
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun isColorized() {
+ assertThat(underTest.entryAdapter.isColorized).isFalse()
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun getSbn() {
+ assertThat(underTest.entryAdapter.sbn).isNull()
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun canDragAndDrop() {
+ assertThat(underTest.entryAdapter.canDragAndDrop()).isFalse()
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun isBubble() {
+ assertThat(underTest.entryAdapter.isBubbleCapable).isFalse()
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun getStyle() {
+ assertThat(underTest.entryAdapter.style).isNull()
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun getSectionBucket() {
+ assertThat(underTest.entryAdapter.sectionBucket).isEqualTo(underTest.bucket)
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun isAmbient() {
+ assertThat(underTest.entryAdapter.isAmbient).isFalse()
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun canShowFullScreen() {
+ assertThat(underTest.entryAdapter.isFullScreenCapable()).isFalse()
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
index 34dff24..4810813 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
@@ -21,12 +21,15 @@
import static android.app.Notification.CATEGORY_EVENT;
import static android.app.Notification.CATEGORY_MESSAGE;
import static android.app.Notification.CATEGORY_REMINDER;
+import static android.app.Notification.FLAG_BUBBLE;
import static android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED;
import static android.app.Notification.FLAG_PROMOTED_ONGOING;
+import static android.app.NotificationManager.IMPORTANCE_MIN;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
import static com.android.systemui.statusbar.NotificationEntryHelper.modifySbn;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_ALERTING;
import static com.google.common.truth.Truth.assertThat;
@@ -35,6 +38,7 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.app.Notification;
@@ -664,6 +668,126 @@
assertThat(entry.getEntryAdapter().getIcons()).isEqualTo(entry.getIcons());
}
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ public void isColorized() {
+ Notification notification = new Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .setColorized(true)
+ .build();
+
+ NotificationEntry entry = new NotificationEntryBuilder()
+ .setNotification(notification)
+ .build();
+ assertThat(entry.getEntryAdapter().isColorized()).isEqualTo(
+ entry.getSbn().getNotification().isColorized());
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ public void getSbn() {
+ Notification notification = new Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .build();
+
+ NotificationEntry entry = new NotificationEntryBuilder()
+ .setNotification(notification)
+ .build();
+ assertThat(entry.getEntryAdapter().getSbn()).isEqualTo(
+ entry.getSbn());
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ public void canDragAndDrop() {
+ PendingIntent pi = mock(PendingIntent.class);
+ when(pi.isActivity()).thenReturn(true);
+ Notification notification = new Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .setContentIntent(pi)
+ .build();
+
+ NotificationEntry entry = new NotificationEntryBuilder()
+ .setNotification(notification)
+ .build();
+ assertThat(entry.getEntryAdapter().canDragAndDrop()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ public void isBubble() {
+ Notification notification = new Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .setFlag(FLAG_BUBBLE, true)
+ .build();
+
+ NotificationEntry entry = new NotificationEntryBuilder()
+ .setNotification(notification)
+ .build();
+ assertThat(entry.getEntryAdapter().isBubbleCapable()).isEqualTo(entry.isBubble());
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ public void getStyle() {
+ Notification notification = new Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .setStyle(new Notification.BigTextStyle())
+ .build();
+
+ NotificationEntry entry = new NotificationEntryBuilder()
+ .setNotification(notification)
+ .build();
+ assertThat(entry.getEntryAdapter().getStyle()).isEqualTo(entry.getNotificationStyle());
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ public void getSectionBucket() {
+ Notification notification = new Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .setStyle(new Notification.BigTextStyle())
+ .build();
+
+ NotificationEntry entry = new NotificationEntryBuilder()
+ .setNotification(notification)
+ .build();
+ entry.setBucket(BUCKET_ALERTING);
+
+ assertThat(entry.getEntryAdapter().getSectionBucket()).isEqualTo(BUCKET_ALERTING);
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ public void isAmbient() {
+ Notification notification = new Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .build();
+
+ NotificationEntry entry = new NotificationEntryBuilder()
+ .setNotification(notification)
+ .setImportance(IMPORTANCE_MIN)
+ .build();
+
+ assertThat(entry.getEntryAdapter().isAmbient()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
+ public void canShowFullScreen() {
+ Notification notification = new Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .setFullScreenIntent(mock(PendingIntent.class), true)
+ .build();
+
+ NotificationEntry entry = new NotificationEntryBuilder()
+ .setNotification(notification)
+ .setImportance(IMPORTANCE_MIN)
+ .build();
+
+ assertThat(entry.getEntryAdapter().isFullScreenCapable()).isTrue();
+ }
+
private Notification.Action createContextualAction(String title) {
return new Notification.Action.Builder(
Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
index d5b2d3a..4e13dcd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
@@ -20,7 +20,6 @@
import android.media.session.MediaSession
import android.platform.test.flag.junit.FlagsParameterization
import android.provider.Settings
-import android.service.notification.StatusBarNotification
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.dumpManager
@@ -222,16 +221,19 @@
mock<ExpandableNotificationRow>().apply {
whenever(isMediaRow).thenReturn(true)
}
- sbn = SbnBuilder().setNotification(
- Notification.Builder(context, "channel").setStyle(
- MediaStyle().setMediaSession(
- MediaSession(
- context,
- "tag"
- ).sessionToken
+ sbn =
+ SbnBuilder()
+ .setNotification(
+ Notification.Builder(context, "channel")
+ .setStyle(
+ MediaStyle()
+ .setMediaSession(
+ MediaSession(context, "tag").sessionToken
+ )
+ )
+ .build()
)
- ).build()
- ).build()
+ .build()
}
collectionListener.onEntryAdded(fakeEntry)
@@ -631,6 +633,132 @@
}
}
+ @Test
+ fun seenNotificationOnKeyguardMarkedAsSeenIfUpdatedBySystemServer() {
+ // GIVEN: Keyguard is showing, not dozing, unseen notification is present
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setIsDozing(false)
+ runKeyguardCoordinatorTest {
+ val fakeEntry = NotificationEntryBuilder().build()
+ collectionListener.onEntryAdded(fakeEntry)
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ this.testScheduler,
+ )
+ testScheduler.runCurrent()
+
+ // WHEN: five seconds have passed
+ testScheduler.advanceTimeBy(5.seconds)
+ testScheduler.runCurrent()
+
+ // WHEN: Keyguard is no longer showing
+ keyguardRepository.setKeyguardShowing(false)
+ kosmos.setTransition(
+ sceneTransition = Idle(Scenes.Gone),
+ stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE),
+ )
+
+ // WHEN: the notification is updated by the Server
+ collectionListener.onEntryUpdated(fakeEntry, UpdateSource.SystemServer)
+ testScheduler.runCurrent()
+
+ // WHEN: Keyguard is shown again
+ keyguardRepository.setKeyguardShowing(true)
+ kosmos.setTransition(
+ sceneTransition = Idle(Scenes.Lockscreen),
+ stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.AOD),
+ )
+
+ // THEN: The notification is still recognized as "seen" and is filtered out.
+ assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue()
+ }
+ }
+
+ @Test
+ fun seenNotificationOnKeyguardMarkedAsSeenIfUpdatedBySystemUi() {
+ // GIVEN: Keyguard is showing, not dozing, unseen notification is present
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setIsDozing(false)
+ runKeyguardCoordinatorTest {
+ val fakeEntry = NotificationEntryBuilder().build()
+ collectionListener.onEntryAdded(fakeEntry)
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ this.testScheduler,
+ )
+ testScheduler.runCurrent()
+
+ // WHEN: five seconds have passed
+ testScheduler.advanceTimeBy(5.seconds)
+ testScheduler.runCurrent()
+
+ // WHEN: Keyguard is no longer showing
+ keyguardRepository.setKeyguardShowing(false)
+ kosmos.setTransition(
+ sceneTransition = Idle(Scenes.Gone),
+ stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE),
+ )
+
+ // WHEN: the notification is updated by the SystemUi
+ collectionListener.onEntryUpdated(fakeEntry, UpdateSource.SystemUi)
+ testScheduler.runCurrent()
+
+ // WHEN: Keyguard is shown again
+ keyguardRepository.setKeyguardShowing(true)
+ kosmos.setTransition(
+ sceneTransition = Idle(Scenes.Lockscreen),
+ stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.AOD),
+ )
+
+ // THEN: The notification is still recognized as "seen" and is filtered out.
+ assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue()
+ }
+ }
+
+ @Test
+ fun seenNotificationOnKeyguardMarkedAsUnseenIfUpdatedByApp() {
+ // GIVEN: Keyguard is showing, not dozing, unseen notification is present
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setIsDozing(false)
+ runKeyguardCoordinatorTest {
+ val fakeEntry = NotificationEntryBuilder().build()
+ collectionListener.onEntryAdded(fakeEntry)
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ this.testScheduler,
+ )
+ testScheduler.runCurrent()
+
+ // WHEN: five seconds have passed
+ testScheduler.advanceTimeBy(5.seconds)
+ testScheduler.runCurrent()
+
+ // WHEN: Keyguard is no longer showing
+ keyguardRepository.setKeyguardShowing(false)
+ kosmos.setTransition(
+ sceneTransition = Idle(Scenes.Gone),
+ stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE),
+ )
+
+ // WHEN: the notification is updated by the App
+ collectionListener.onEntryUpdated(fakeEntry, UpdateSource.App)
+ testScheduler.runCurrent()
+
+ // WHEN: Keyguard is shown again
+ keyguardRepository.setKeyguardShowing(true)
+ kosmos.setTransition(
+ sceneTransition = Idle(Scenes.Lockscreen),
+ stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.AOD),
+ )
+
+ // THEN: The notification is now recognized as "unseen" and is not filtered out.
+ assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+ }
+ }
+
private fun runKeyguardCoordinatorTest(
testBlock: suspend KeyguardCoordinatorTestScope.() -> Unit
) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
deleted file mode 100644
index 2aa405a..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
+++ /dev/null
@@ -1,1001 +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.
- */
-
-package com.android.systemui.statusbar.notification.collection.coordinator;
-
-import static android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static junit.framework.Assert.assertFalse;
-
-import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
-
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeFalse;
-import static org.junit.Assume.assumeTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.FlagsParameterization;
-import android.testing.TestableLooper;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.compose.animation.scene.ObservableTransitionState;
-import com.android.systemui.Flags;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.communal.shared.model.CommunalScenes;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.BrokenWithSceneContainer;
-import com.android.systemui.flags.DisableSceneContainer;
-import com.android.systemui.flags.EnableSceneContainer;
-import com.android.systemui.flags.SceneContainerFlagParameterizationKt;
-import com.android.systemui.keyguard.WakefulnessLifecycle;
-import com.android.systemui.keyguard.shared.model.KeyguardState;
-import com.android.systemui.keyguard.shared.model.TransitionState;
-import com.android.systemui.keyguard.shared.model.TransitionStep;
-import com.android.systemui.kosmos.KosmosJavaAdapter;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.scene.shared.flag.SceneContainerFlag;
-import com.android.systemui.shade.data.repository.FakeShadeRepository;
-import com.android.systemui.shade.data.repository.ShadeAnimationRepository;
-import com.android.systemui.shade.data.repository.ShadeRepository;
-import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor;
-import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorLegacyImpl;
-import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
-import com.android.systemui.statusbar.notification.collection.GroupEntry;
-import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
-import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
-import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository;
-import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.kotlin.JavaAdapter;
-import com.android.systemui.util.time.FakeSystemClock;
-
-import kotlinx.coroutines.flow.MutableStateFlow;
-import kotlinx.coroutines.test.TestScope;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.verification.VerificationMode;
-
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
-import platform.test.runner.parameterized.Parameters;
-
-import java.util.List;
-import java.util.Set;
-
-@SmallTest
-@RunWith(ParameterizedAndroidJunit4.class)
-@TestableLooper.RunWithLooper
-public class VisualStabilityCoordinatorTest extends SysuiTestCase {
-
- @Parameters(name = "{0}")
- public static List<FlagsParameterization> getParams() {
- return SceneContainerFlagParameterizationKt
- .andSceneContainer(allCombinationsOf(Flags.FLAG_STABILIZE_HEADS_UP_GROUP_V2));
- }
-
- private VisualStabilityCoordinator mCoordinator;
-
- @Mock private DumpManager mDumpManager;
- @Mock private NotifPipeline mNotifPipeline;
- @Mock private WakefulnessLifecycle mWakefulnessLifecycle;
- @Mock private StatusBarStateController mStatusBarStateController;
- @Mock private Pluggable.PluggableListener<NotifStabilityManager> mInvalidateListener;
- @Mock private SeenNotificationsInteractor mSeenNotificationsInteractor;
- @Mock private HeadsUpRepository mHeadsUpRepository;
- @Mock private VisibilityLocationProvider mVisibilityLocationProvider;
- @Mock private VisualStabilityProvider mVisualStabilityProvider;
- @Mock private VisualStabilityCoordinatorLogger mLogger;
- @Mock private KeyguardStateController mKeyguardStateController;
-
- @Captor private ArgumentCaptor<WakefulnessLifecycle.Observer> mWakefulnessObserverCaptor;
- @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mSBStateListenerCaptor;
- @Captor private ArgumentCaptor<NotifStabilityManager> mNotifStabilityManagerCaptor;
-
- private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
- private FakeSystemClock mFakeSystemClock = new FakeSystemClock();
- private FakeExecutor mFakeBackgroundExecutor = new FakeExecutor(mFakeSystemClock);
- private FakeExecutor mFakeMainExecutor = new FakeExecutor(mFakeSystemClock);
- private final TestScope mTestScope = mKosmos.getTestScope();
- private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope());
-
- private ShadeAnimationInteractor mShadeAnimationInteractor;
- private ShadeRepository mShadeRepository;
- private WakefulnessLifecycle.Observer mWakefulnessObserver;
- private StatusBarStateController.StateListener mStatusBarStateListener;
- private NotifStabilityManager mNotifStabilityManager;
- private NotificationEntry mEntry;
- private GroupEntry mGroupEntry;
-
- public VisualStabilityCoordinatorTest(FlagsParameterization flags) {
- super();
- mSetFlagsRule.setFlagsParameterization(flags);
- }
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
-
- mShadeRepository = new FakeShadeRepository();
- mShadeAnimationInteractor = new ShadeAnimationInteractorLegacyImpl(
- new ShadeAnimationRepository(), mShadeRepository);
- mCoordinator = new VisualStabilityCoordinator(
- mFakeBackgroundExecutor,
- mFakeMainExecutor,
- mDumpManager,
- mHeadsUpRepository,
- mShadeAnimationInteractor,
- mJavaAdapter,
- mSeenNotificationsInteractor,
- mStatusBarStateController,
- mVisibilityLocationProvider,
- mVisualStabilityProvider,
- mWakefulnessLifecycle,
- mKosmos.getCommunalSceneInteractor(),
- mKosmos.getShadeInteractor(),
- mKosmos.getKeyguardTransitionInteractor(),
- mKeyguardStateController,
- mLogger);
-
- when(mHeadsUpRepository.isTrackingHeadsUp()).thenReturn(MutableStateFlow(false));
- mCoordinator.attach(mNotifPipeline);
- mTestScope.getTestScheduler().runCurrent();
-
- // capture arguments:
- verify(mWakefulnessLifecycle).addObserver(mWakefulnessObserverCaptor.capture());
- mWakefulnessObserver = mWakefulnessObserverCaptor.getValue();
-
- verify(mStatusBarStateController).addCallback(mSBStateListenerCaptor.capture());
- mStatusBarStateListener = mSBStateListenerCaptor.getValue();
-
- verify(mNotifPipeline).setVisualStabilityManager(mNotifStabilityManagerCaptor.capture());
- mNotifStabilityManager = mNotifStabilityManagerCaptor.getValue();
- mNotifStabilityManager.setInvalidationListener(mInvalidateListener);
-
- mEntry = new NotificationEntryBuilder()
- .setPkg("testPkg1")
- .build();
-
- mGroupEntry = new GroupEntryBuilder()
- .setSummary(mEntry)
- .build();
-
- when(mHeadsUpRepository.isHeadsUpEntry(mEntry.getKey())).thenReturn(false);
-
- // Whenever we invalidate, the pipeline runs again, so we invalidate the state
- doAnswer(i -> {
- mNotifStabilityManager.onBeginRun();
- return null;
- }).when(mInvalidateListener).onPluggableInvalidated(eq(mNotifStabilityManager), any());
- }
-
- @Test
- public void testScreenOff_groupAndSectionChangesAllowed() {
- // GIVEN screen is off, panel isn't expanded and device isn't pulsing
- setFullyDozed(true);
- setSleepy(true);
- setPanelExpanded(false);
- setPulsing(false);
-
- // THEN group changes are allowed
- assertTrue(mNotifStabilityManager.isGroupChangeAllowed(mEntry));
- assertTrue(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry));
-
- // THEN section changes are allowed
- assertTrue(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
- }
-
- @Test
- public void testScreenTurningOff_groupAndSectionChangesNotAllowed() {
- // GIVEN the screen is turning off (sleepy but partially dozed)
- setFullyDozed(false);
- setSleepy(true);
- setPanelExpanded(true);
- setPulsing(false);
-
- // THEN group changes are NOT allowed
- assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry));
- assertFalse(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry));
-
- // THEN section changes are NOT allowed
- assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
- }
-
- @Test
- public void testScreenTurningOn_groupAndSectionChangesNotAllowed() {
- // GIVEN the screen is turning on (still fully dozed, not sleepy)
- setFullyDozed(true);
- setSleepy(false);
- setPanelExpanded(true);
- setPulsing(false);
-
- // THEN group changes are NOT allowed
- assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry));
- assertFalse(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry));
-
- // THEN section changes are NOT allowed
- assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
- }
-
- @Test
- public void testPanelNotExpanded_groupAndSectionChangesAllowed() {
- // GIVEN screen is on but the panel isn't expanded and device isn't pulsing
- setFullyDozed(false);
- setSleepy(false);
- setPanelExpanded(false);
- setPulsing(false);
-
- // THEN group changes are allowed
- assertTrue(mNotifStabilityManager.isGroupChangeAllowed(mEntry));
- assertTrue(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry));
-
- // THEN section changes are allowed
- assertTrue(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
- }
-
- @Test
- public void testPanelExpanded_groupAndSectionChangesNotAllowed() {
- // GIVEN the panel true expanded and device isn't pulsing
- setFullyDozed(false);
- setSleepy(false);
- setPanelExpanded(true);
- setPulsing(false);
-
- // THEN group changes are NOT allowed
- assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry));
- assertFalse(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry));
-
- // THEN section changes are NOT allowed
- assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
- }
-
- @Test
- @BrokenWithSceneContainer(bugId = 377868472) // mReorderingAllowed is broken with SceneContainer
- public void testLockscreenPartlyShowing_groupAndSectionChangesNotAllowed() {
- // GIVEN the panel true expanded and device isn't pulsing
- setFullyDozed(false);
- setSleepy(false);
- setLockscreenShowing(0.5f);
- setPulsing(false);
-
- // THEN group changes are NOT allowed
- assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry));
- assertFalse(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry));
-
- // THEN section changes are NOT allowed
- assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
- }
-
- @Test
- @BrokenWithSceneContainer(bugId = 377868472) // mReorderingAllowed is broken with SceneContainer
- public void testLockscreenFullyShowing_groupAndSectionChangesNotAllowed() {
- // GIVEN the panel true expanded and device isn't pulsing
- setFullyDozed(false);
- setSleepy(false);
- setLockscreenShowing(1.0f);
- setPulsing(false);
-
- // THEN group changes are NOT allowed
- assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry));
- assertFalse(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry));
-
- // THEN section changes are NOT allowed
- assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
- }
-
- @Test
- public void testPulsing_screenOff_groupAndSectionChangesNotAllowed() {
- // GIVEN the device is pulsing and screen is off
- setFullyDozed(true);
- setSleepy(true);
- setPulsing(true);
-
- // THEN group changes are NOT allowed
- assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry));
- assertFalse(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry));
-
- // THEN section changes are NOT allowed
- assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
- }
-
- @Test
- public void testPulsing_panelNotExpanded_groupAndSectionChangesNotAllowed() {
- // GIVEN the device is pulsing and screen is off with the panel not expanded
- setFullyDozed(true);
- setSleepy(true);
- setPanelExpanded(false);
- setPulsing(true);
-
- // THEN group changes are NOT allowed
- assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry));
- assertFalse(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry));
-
- // THEN section changes are NOT allowed
- assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
- }
-
- @Test
- public void testOverrideReorderingSuppression_onlySectionChangesAllowed() {
- // GIVEN section changes typically wouldn't be allowed because the panel is expanded and
- // we're not pulsing
- setFullyDozed(false);
- setSleepy(false);
- setPanelExpanded(true);
- setPulsing(true);
-
- // WHEN we temporarily allow section changes for this notification entry
- mCoordinator.temporarilyAllowSectionChanges(mEntry, mFakeSystemClock.currentTimeMillis());
-
- // THEN group changes aren't allowed
- assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry));
- assertFalse(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry));
-
- // THEN section changes are allowed for this notification but not other notifications
- assertTrue(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
- assertFalse(mNotifStabilityManager.isSectionChangeAllowed(
- new NotificationEntryBuilder()
- .setPkg("testPkg2")
- .build()));
- }
-
- @Test
- public void testTemporarilyAllowSectionChanges_callsInvalidate() {
- // GIVEN section changes typically wouldn't be allowed because the panel is expanded
- setFullyDozed(false);
- setSleepy(false);
- setPanelExpanded(true);
- setPulsing(false);
-
- // WHEN we temporarily allow section changes for this notification entry
- mCoordinator.temporarilyAllowSectionChanges(mEntry, mFakeSystemClock.uptimeMillis());
-
- // THEN the notification list is invalidated
- verifyStabilityManagerWasInvalidated(times(1));
- }
-
- @Test
- public void testTemporarilyAllowSectionChanges_noInvalidationCalled() {
- // GIVEN section changes typically WOULD be allowed
- setFullyDozed(true);
- setSleepy(true);
- setPanelExpanded(false);
- setPulsing(false);
-
- // WHEN we temporarily allow section changes for this notification entry
- mCoordinator.temporarilyAllowSectionChanges(mEntry, mFakeSystemClock.currentTimeMillis());
-
- // THEN invalidate is not called because this entry was never suppressed from reordering
- verifyStabilityManagerWasInvalidated(never());
- }
-
- @Test
- public void testTemporarilyAllowSectionChangesTimeout() {
- // GIVEN section changes typically WOULD be allowed
- setFullyDozed(true);
- setSleepy(true);
- setPanelExpanded(false);
- setPulsing(false);
- assertTrue(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
-
- // WHEN we temporarily allow section changes for this notification entry
- mCoordinator.temporarilyAllowSectionChanges(mEntry, mFakeSystemClock.currentTimeMillis());
-
- // THEN invalidate is not called because this entry was never suppressed from reordering;
- // THEN section changes are allowed for this notification
- verifyStabilityManagerWasInvalidated(never());
- assertTrue(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
-
- // WHEN we're pulsing (now disallowing reordering)
- setPulsing(true);
-
- // THEN we're still allowed to reorder this section because it's still in the list of
- // notifications to allow section changes
- assertTrue(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
-
- // WHEN the timeout for the temporarily allow section reordering runnable is finsihed
- mFakeBackgroundExecutor.advanceClockToNext();
- mFakeBackgroundExecutor.runNextReady();
-
- // THEN section changes aren't allowed anymore
- assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
- }
-
- @Test
- public void testTemporarilyAllowSectionChanges_isPulsingChangeBeforeTimeout() {
- // GIVEN section changes typically wouldn't be allowed because the device is pulsing
- setFullyDozed(true);
- setSleepy(true);
- setPanelExpanded(false);
- setPulsing(true);
-
- // WHEN we temporarily allow section changes for this notification entry
- mCoordinator.temporarilyAllowSectionChanges(mEntry, mFakeSystemClock.currentTimeMillis());
- // can now reorder, so invalidates
- verifyStabilityManagerWasInvalidated(times(1));
-
- // WHEN reordering is now allowed because device isn't pulsing anymore
- setPulsing(false);
-
- // THEN invalidate isn't called a second time since reordering was already allowed
- verifyStabilityManagerWasInvalidated(times(1));
- }
-
- @Test
- public void testMovingVisibleHeadsUpNotAllowed() {
- // GIVEN stability enforcing conditions
- setPanelExpanded(true);
- setSleepy(false);
-
- // WHEN a notification is alerting and visible
- when(mHeadsUpRepository.isHeadsUpEntry(mEntry.getKey())).thenReturn(true);
- when(mVisibilityLocationProvider.isInVisibleLocation(any(NotificationEntry.class)))
- .thenReturn(true);
-
- // VERIFY the notification cannot be reordered
- assertThat(mNotifStabilityManager.isEntryReorderingAllowed(mEntry)).isFalse();
- assertThat(mNotifStabilityManager.isSectionChangeAllowed(mEntry)).isFalse();
- }
-
- @Test
- public void testMovingInvisibleHeadsUpAllowed() {
- // GIVEN stability enforcing conditions
- setPanelExpanded(true);
- setSleepy(false);
-
- // WHEN a notification is alerting but not visible
- when(mHeadsUpRepository.isHeadsUpEntry(mEntry.getKey())).thenReturn(true);
- when(mVisibilityLocationProvider.isInVisibleLocation(any(NotificationEntry.class)))
- .thenReturn(false);
-
- // VERIFY the notification can be reordered
- assertThat(mNotifStabilityManager.isEntryReorderingAllowed(mEntry)).isTrue();
- assertThat(mNotifStabilityManager.isSectionChangeAllowed(mEntry)).isTrue();
- }
-
- @Test
- public void testNeverSuppressedChanges_noInvalidationCalled() {
- // GIVEN no notifications are currently being suppressed from grouping nor being sorted
-
- // WHEN device isn't pulsing anymore
- setPulsing(false);
-
- // WHEN fully dozed
- setFullyDozed(true);
-
- // WHEN sleepy
- setSleepy(true);
-
- // WHEN panel isn't expanded
- setPanelExpanded(false);
-
- // THEN we never see any calls to invalidate since there weren't any notifications that
- // were being suppressed from grouping or section changes
- verifyStabilityManagerWasInvalidated(never());
- }
-
- @Test
- public void testNotSuppressingGroupChangesAnymore_invalidationCalled() {
- // GIVEN visual stability is being maintained b/c panel is expanded
- setPulsing(false);
- setFullyDozed(false);
- setSleepy(false);
- setPanelExpanded(true);
-
- assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry));
- assertFalse(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry));
-
- // WHEN the panel isn't expanded anymore
- setPanelExpanded(false);
-
- // invalidate is called because we were previously suppressing a group change
- verifyStabilityManagerWasInvalidated(times(1));
- }
-
- @Test
- public void testNotLaunchingActivityAnymore_invalidationCalled() {
- // GIVEN visual stability is being maintained b/c animation is playing
- setActivityLaunching(true);
-
- assertFalse(mNotifStabilityManager.isPipelineRunAllowed());
-
- // WHEN the animation has stopped playing
- setActivityLaunching(false);
-
- // invalidate is called, b/c we were previously suppressing the pipeline from running
- verifyStabilityManagerWasInvalidated(times(1));
- }
-
- @Test
- public void testNotCollapsingPanelAnymore_invalidationCalled() {
- // GIVEN visual stability is being maintained b/c animation is playing
- setPanelCollapsing(true);
-
- assertFalse(mNotifStabilityManager.isPipelineRunAllowed());
-
- // WHEN the animation has stopped playing
- setPanelCollapsing(false);
-
- // invalidate is called, b/c we were previously suppressing the pipeline from running
- verifyStabilityManagerWasInvalidated(times(1));
- }
-
- @Test
- @EnableFlags(Flags.FLAG_CHECK_LOCKSCREEN_GONE_TRANSITION)
- @DisableSceneContainer
- public void testNotLockscreenInGoneTransitionLegacy_invalidationCalled() {
- // GIVEN visual stability is being maintained b/c animation is playing
- doReturn(true).when(mKeyguardStateController).isKeyguardFadingAway();
- mCoordinator.mKeyguardFadeAwayAnimationCallback.onKeyguardFadingAwayChanged();
-
- assertFalse(mNotifStabilityManager.isPipelineRunAllowed());
-
- // WHEN the animation has stopped playing
- doReturn(false).when(mKeyguardStateController).isKeyguardFadingAway();
- mCoordinator.mKeyguardFadeAwayAnimationCallback.onKeyguardFadingAwayChanged();
-
- // invalidate is called, b/c we were previously suppressing the pipeline from running
- verifyStabilityManagerWasInvalidated(times(1));
- }
-
- @Test
- @EnableFlags(Flags.FLAG_CHECK_LOCKSCREEN_GONE_TRANSITION)
- @EnableSceneContainer
- @BrokenWithSceneContainer(bugId = 377868472) // mReorderingAllowed is broken with SceneContainer
- public void testNotLockscreenInGoneTransition_invalidationCalled() {
- // GIVEN visual stability is being maintained b/c animation is playing
- mKosmos.getKeyguardTransitionRepository().sendTransitionStepJava(
- mTestScope, new TransitionStep(
- KeyguardState.LOCKSCREEN,
- KeyguardState.GONE,
- 1f,
- TransitionState.RUNNING), /* validateStep = */ false);
- mTestScope.getTestScheduler().runCurrent();
- assertFalse(mNotifStabilityManager.isPipelineRunAllowed());
-
- // WHEN the animation has stopped playing
- mKosmos.getKeyguardTransitionRepository().sendTransitionStepJava(
- mTestScope, new TransitionStep(
- KeyguardState.LOCKSCREEN,
- KeyguardState.GONE,
- 1f,
- TransitionState.FINISHED), /* validateStep = */ false);
- mTestScope.getTestScheduler().runCurrent();
-
- // invalidate is called, b/c we were previously suppressing the pipeline from running
- verifyStabilityManagerWasInvalidated(times(1));
- }
-
- @Test
- public void testNeverSuppressPipelineRunFromPanelCollapse_noInvalidationCalled() {
- // GIVEN animation is playing
- setPanelCollapsing(true);
-
- // WHEN the animation has stopped playing
- setPanelCollapsing(false);
-
- // THEN invalidate is not called, b/c nothing has been suppressed
- verifyStabilityManagerWasInvalidated(never());
- }
-
- @Test
- public void testNeverSuppressPipelineRunFromLaunchActivity_noInvalidationCalled() {
- // GIVEN animation is playing
- setActivityLaunching(true);
-
- // WHEN the animation has stopped playing
- setActivityLaunching(false);
-
- // THEN invalidate is not called, b/c nothing has been suppressed
- verifyStabilityManagerWasInvalidated(never());
- }
-
- @Test
- public void testNotSuppressingEntryReorderingAnymoreWillInvalidate() {
- // GIVEN visual stability is being maintained b/c panel is expanded
- setPulsing(false);
- setFullyDozed(false);
- setSleepy(false);
- setPanelExpanded(true);
- setCommunalShowing(false);
-
- assertFalse(mNotifStabilityManager.isEntryReorderingAllowed(mEntry));
- // The pipeline still has to report back that entry reordering was suppressed
- mNotifStabilityManager.onEntryReorderSuppressed();
-
- // WHEN the panel isn't expanded anymore
- setPanelExpanded(false);
-
- // invalidate is called because we were previously suppressing an entry reorder
- verifyStabilityManagerWasInvalidated(times(1));
- }
-
- @Test
- @BrokenWithSceneContainer(bugId = 377868472) // mReorderingAllowed is broken with SceneContainer
- public void testCommunalShowingWillNotSuppressReordering() {
- // GIVEN panel is expanded, communal is showing, and QS is collapsed
- setPulsing(false);
- setFullyDozed(false);
- setSleepy(false);
- setPanelExpanded(true);
- setQsExpanded(false);
- setCommunalShowing(true);
-
- // Reordering should be allowed
- assertTrue(mNotifStabilityManager.isEntryReorderingAllowed(mEntry));
- }
-
- @Test
- public void testQsExpandedOverCommunalWillSuppressReordering() {
- // GIVEN panel is expanded and communal is showing, but QS is expanded
- setPulsing(false);
- setFullyDozed(false);
- setSleepy(false);
- setPanelExpanded(true);
- setQsExpanded(true);
- setCommunalShowing(true);
-
- // Reordering should not be allowed
- assertFalse(mNotifStabilityManager.isEntryReorderingAllowed(mEntry));
- }
-
- @Test
- public void testQueryingEntryReorderingButNotReportingReorderSuppressedDoesNotInvalidate() {
- // GIVEN visual stability is being maintained b/c panel is expanded
- setPulsing(false);
- setFullyDozed(false);
- setSleepy(false);
- setPanelExpanded(true);
-
- assertFalse(mNotifStabilityManager.isEntryReorderingAllowed(mEntry));
-
- // WHEN the panel isn't expanded anymore
- setPanelExpanded(false);
-
- // invalidate is not called because we were not told that an entry reorder was suppressed
- verifyStabilityManagerWasInvalidated(never());
- }
-
- @Test
- public void testHeadsUp_allowedToChangeGroupAndSection() {
- // GIVEN group + section changes disallowed
- setFullyDozed(false);
- setSleepy(false);
- setPanelExpanded(true);
- setPulsing(true);
- assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry));
- assertFalse(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry));
- assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
-
- // GIVEN mEntry is a HUN
- when(mHeadsUpRepository.isHeadsUpEntry(mEntry.getKey())).thenReturn(true);
-
- // THEN group + section changes are allowed
- assertTrue(mNotifStabilityManager.isGroupChangeAllowed(mEntry));
- assertTrue(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
-
- // BUT pruning the group for which this is the summary would still NOT be allowed.
- assertFalse(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry));
- }
-
- @Test
- public void everyChangeAllowed_onReorderingEnabled_legacy() {
- assumeFalse(StabilizeHeadsUpGroup.isEnabled());
- // GIVEN - reordering is allowed.
- setPulsing(false);
- setPanelExpanded(false);
-
- // THEN
- assertThat(mNotifStabilityManager.isEveryChangeAllowed()).isTrue();
- assertThat(mNotifStabilityManager.isGroupChangeAllowed(any())).isTrue();
- assertThat(mNotifStabilityManager.isGroupPruneAllowed(any())).isTrue();
- assertThat(mNotifStabilityManager.isSectionChangeAllowed(any())).isTrue();
- assertThat(mNotifStabilityManager.isEntryReorderingAllowed(any())).isTrue();
- }
-
- @Test
- public void everyChangeAllowed_noActiveHeadsUpGroup_onReorderingEnabled() {
- assumeTrue(StabilizeHeadsUpGroup.isEnabled());
- // GIVEN - reordering is allowed.
- setPulsing(false);
- setPanelExpanded(false);
-
- // GIVEN - empty heads-up-group keys
- mCoordinator.setHeadsUpGroupKeys(Set.of());
-
- // THEN
- assertThat(mNotifStabilityManager.isEveryChangeAllowed()).isTrue();
- assertThat(mNotifStabilityManager.isGroupChangeAllowed(any())).isTrue();
- assertThat(mNotifStabilityManager.isGroupPruneAllowed(any())).isTrue();
- assertThat(mNotifStabilityManager.isSectionChangeAllowed(any())).isTrue();
- assertThat(mNotifStabilityManager.isEntryReorderingAllowed(any())).isTrue();
- }
-
- @Test
- public void everyChangeDisallowed_activeHeadsUpGroup_onReorderingEnabled() {
- assumeTrue(StabilizeHeadsUpGroup.isEnabled());
- // GIVEN - reordering is allowed.
- setPulsing(false);
- setPanelExpanded(false);
-
- // GIVEN - there is a group heads-up.
- mCoordinator.setHeadsUpGroupKeys(Set.of("heads_up_group_key"));
-
- // THEN
- assertThat(mNotifStabilityManager.isEveryChangeAllowed()).isFalse();
- }
-
- @Test
- public void nonHeadsUpGroup_changesAllowed_onReorderingEnabled() {
- assumeTrue(StabilizeHeadsUpGroup.isEnabled());
- // GIVEN - reordering is allowed.
- setPulsing(false);
- setPanelExpanded(false);
-
- // GIVEN - there is a group heads-up.
- String headsUpGroupKey = "heads_up_group_key";
- mCoordinator.setHeadsUpGroupKeys(Set.of(headsUpGroupKey));
- when(mHeadsUpRepository.isHeadsUpEntry(headsUpGroupKey)).thenReturn(true);
-
- // GIVEN - HUN Group Summary
- final NotificationEntry nonHeadsUpGroupSummary = mock(NotificationEntry.class);
- when(nonHeadsUpGroupSummary.getKey()).thenReturn("non_heads_up_group_key");
- when(nonHeadsUpGroupSummary.isSummaryWithChildren()).thenReturn(true);
- final GroupEntry nonHeadsUpGroupEntry = mock(GroupEntry.class);
- when(nonHeadsUpGroupEntry.getSummary()).thenReturn(nonHeadsUpGroupSummary);
- when(nonHeadsUpGroupEntry.getRepresentativeEntry()).thenReturn(nonHeadsUpGroupSummary);
-
- // THEN
- assertThat(mNotifStabilityManager.isGroupPruneAllowed(nonHeadsUpGroupEntry)).isTrue();
- assertThat(mNotifStabilityManager.isEntryReorderingAllowed(nonHeadsUpGroupEntry)).isTrue();
- }
-
- @Test
- public void headsUpGroup_changesDisallowed_onReorderingEnabled() {
- assumeTrue(StabilizeHeadsUpGroup.isEnabled());
- // GIVEN - reordering is allowed.
- setPulsing(false);
- setPanelExpanded(false);
-
- // GIVEN - there is a group heads-up.
- final String headsUpGroupKey = "heads_up_group_key";
- mCoordinator.setHeadsUpGroupKeys(Set.of(headsUpGroupKey));
- when(mHeadsUpRepository.isHeadsUpEntry(headsUpGroupKey)).thenReturn(true);
-
- // GIVEN - HUN Group
- final NotificationEntry headsUpGroupSummary = mock(NotificationEntry.class);
- when(headsUpGroupSummary.rowIsChildInGroup()).thenReturn(false);
- when(headsUpGroupSummary.getKey()).thenReturn(headsUpGroupKey);
- when(headsUpGroupSummary.isSummaryWithChildren()).thenReturn(true);
-
- final GroupEntry headsUpGroupEntry = mock(GroupEntry.class);
- when(headsUpGroupEntry.getSummary()).thenReturn(headsUpGroupSummary);
- when(headsUpGroupEntry.getRepresentativeEntry()).thenReturn(headsUpGroupSummary);
-
- when(headsUpGroupSummary.getParent()).thenReturn(headsUpGroupEntry);
-
- // GIVEN - HUN is in visible location
- when(mVisibilityLocationProvider.isInVisibleLocation(headsUpGroupSummary)).thenReturn(true);
-
- // THEN
- assertThat(mNotifStabilityManager.isGroupPruneAllowed(headsUpGroupEntry)).isFalse();
- assertThat(mNotifStabilityManager.isEntryReorderingAllowed(headsUpGroupEntry)).isFalse();
- }
-
- @Test
- public void headsUpGroupSummaries_changesDisallowed_onReorderingEnabled() {
- assumeTrue(StabilizeHeadsUpGroup.isEnabled());
- // GIVEN - reordering is allowed.
- setPulsing(false);
- setPanelExpanded(false);
-
- // GIVEN - there is a group heads-up.
- final String headsUpGroupKey = "heads_up_group_key";
- mCoordinator.setHeadsUpGroupKeys(Set.of(headsUpGroupKey));
- when(mHeadsUpRepository.isHeadsUpEntry(headsUpGroupKey)).thenReturn(true);
-
- // GIVEN - HUN Group
- final NotificationEntry headsUpGroupSummary = mock(NotificationEntry.class);
- when(headsUpGroupSummary.rowIsChildInGroup()).thenReturn(false);
- when(headsUpGroupSummary.getKey()).thenReturn(headsUpGroupKey);
- when(headsUpGroupSummary.isSummaryWithChildren()).thenReturn(true);
-
- final GroupEntry headsUpGroupEntry = mock(GroupEntry.class);
- when(headsUpGroupEntry.getSummary()).thenReturn(headsUpGroupSummary);
- when(headsUpGroupEntry.getRepresentativeEntry()).thenReturn(headsUpGroupSummary);
-
- when(headsUpGroupSummary.getParent()).thenReturn(headsUpGroupEntry);
-
- // GIVEN - HUN is in visible location
- when(mVisibilityLocationProvider.isInVisibleLocation(headsUpGroupSummary)).thenReturn(true);
-
- // THEN
- assertThat(mNotifStabilityManager.isGroupChangeAllowed(headsUpGroupSummary)).isFalse();
- assertThat(mNotifStabilityManager.isEntryReorderingAllowed(headsUpGroupSummary)).isFalse();
- assertThat(mNotifStabilityManager.isSectionChangeAllowed(headsUpGroupSummary)).isFalse();
- }
-
- @Test
- public void notificationInNonHUNGroup_changesAllowed_onReorderingEnabled() {
- assumeTrue(StabilizeHeadsUpGroup.isEnabled());
- // GIVEN - reordering is allowed.
- setPulsing(false);
- setPanelExpanded(false);
-
- // GIVEN - there is a group heads-up.
- String headsUpGroupKey = "heads_up_group_key";
- mCoordinator.setHeadsUpGroupKeys(Set.of(headsUpGroupKey));
- when(mHeadsUpRepository.isHeadsUpEntry(headsUpGroupKey)).thenReturn(true);
-
- // GIVEN - non HUN parent Group Summary
- final NotificationEntry groupSummary = mock(NotificationEntry.class);
- when(groupSummary.getKey()).thenReturn("non_heads_up_group_key");
- when(groupSummary.isSummaryWithChildren()).thenReturn(true);
-
- final GroupEntry nonHeadsUpGroupEntry = mock(GroupEntry.class);
- when(nonHeadsUpGroupEntry.getSummary()).thenReturn(groupSummary);
- when(nonHeadsUpGroupEntry.getRepresentativeEntry()).thenReturn(groupSummary);
-
- // GIVEN - child entry in a non heads-up group.
- final NotificationEntry childEntry = mock(NotificationEntry.class);
- when(childEntry.rowIsChildInGroup()).thenReturn(true);
- when(childEntry.getParent()).thenReturn(nonHeadsUpGroupEntry);
- when(childEntry.getParent()).thenReturn(nonHeadsUpGroupEntry);
-
- // THEN
- assertThat(mNotifStabilityManager.isGroupChangeAllowed(childEntry)).isTrue();
- assertThat(mNotifStabilityManager.isSectionChangeAllowed(childEntry)).isTrue();
- assertThat(mNotifStabilityManager.isEntryReorderingAllowed(nonHeadsUpGroupEntry)).isTrue();
- }
-
- @Test
- public void notificationInHUNGroup_changesDisallowed_reorderingEnabled() {
- assumeTrue(StabilizeHeadsUpGroup.isEnabled());
- // GIVEN - reordering is allowed.
- setPulsing(false);
- setPanelExpanded(false);
-
- // GIVEN - there is a group heads-up.
- final String headsUpGroupKey = "heads_up_group_key";
- mCoordinator.setHeadsUpGroupKeys(Set.of(headsUpGroupKey));
- when(mHeadsUpRepository.isHeadsUpEntry(headsUpGroupKey)).thenReturn(true);
-
- // GIVEN - HUN Group Summary
- final NotificationEntry headsUpGroupSummary = mock(NotificationEntry.class);
- when(headsUpGroupSummary.rowIsChildInGroup()).thenReturn(false);
- when(headsUpGroupSummary.getKey()).thenReturn(headsUpGroupKey);
- when(headsUpGroupSummary.isSummaryWithChildren()).thenReturn(true);
-
- final GroupEntry nonHeadsUpGroupEntry = mock(GroupEntry.class);
- when(nonHeadsUpGroupEntry.getSummary()).thenReturn(headsUpGroupSummary);
- when(nonHeadsUpGroupEntry.getRepresentativeEntry()).thenReturn(headsUpGroupSummary);
-
- // GIVEN - child entry in a non heads-up group.
- final NotificationEntry childEntry = mock(NotificationEntry.class);
- when(childEntry.rowIsChildInGroup()).thenReturn(true);
- when(childEntry.getParent()).thenReturn(nonHeadsUpGroupEntry);
-
- // GIVEN - HUN is in visible location
- when(mVisibilityLocationProvider.isInVisibleLocation(headsUpGroupSummary)).thenReturn(true);
-
- // THEN
- assertThat(mNotifStabilityManager.isGroupChangeAllowed(childEntry)).isFalse();
- assertThat(mNotifStabilityManager.isSectionChangeAllowed(childEntry)).isFalse();
- assertThat(mNotifStabilityManager.isEntryReorderingAllowed(childEntry)).isFalse();
- }
-
- private void verifyStabilityManagerWasInvalidated(VerificationMode mode) {
- verify(mInvalidateListener, mode).onPluggableInvalidated(eq(mNotifStabilityManager), any());
- }
-
- private void setActivityLaunching(boolean activityLaunching) {
- mShadeAnimationInteractor.setIsLaunchingActivity(activityLaunching);
- mTestScope.getTestScheduler().runCurrent();
- }
-
- private void setPanelCollapsing(boolean collapsing) {
- mShadeRepository.setLegacyIsClosing(collapsing);
- mTestScope.getTestScheduler().runCurrent();
- }
-
- private void setCommunalShowing(boolean isShowing) {
- final MutableStateFlow<ObservableTransitionState> showingFlow =
- MutableStateFlow(
- new ObservableTransitionState.Idle(
- isShowing ? CommunalScenes.Communal : CommunalScenes.Blank)
- );
- mKosmos.getCommunalSceneInteractor().setTransitionState(showingFlow);
- mTestScope.getTestScheduler().runCurrent();
- }
-
- private void setQsExpanded(boolean isExpanded) {
- mKosmos.getShadeRepository().setQsExpansion(isExpanded ? 1.0f : 0.0f);
- mTestScope.getTestScheduler().runCurrent();
- }
-
- private void setPulsing(boolean pulsing) {
- mStatusBarStateListener.onPulsingChanged(pulsing);
- }
-
- private void setFullyDozed(boolean fullyDozed) {
- float dozeAmount = fullyDozed ? 1 : 0;
- mStatusBarStateListener.onDozeAmountChanged(dozeAmount, dozeAmount);
- }
-
- private void setSleepy(boolean sleepy) {
- if (sleepy) {
- mWakefulnessObserver.onFinishedGoingToSleep();
- } else {
- mWakefulnessObserver.onStartedWakingUp();
- }
- }
-
- private void setPanelExpanded(boolean expanded) {
- setPanelExpandedAndLockscreenShowing(expanded, /* lockscreenShowing = */ 0.0f);
- }
-
- private void setLockscreenShowing(float lockscreenShowing) {
- setPanelExpandedAndLockscreenShowing(/* panelExpanded = */ false, lockscreenShowing);
- }
-
- private void setPanelExpandedAndLockscreenShowing(boolean panelExpanded,
- float lockscreenShowing) {
- if (SceneContainerFlag.isEnabled()) {
- mStatusBarStateListener.onExpandedChanged(panelExpanded);
- mKosmos.getKeyguardTransitionRepository().sendTransitionStepJava(
- mTestScope,
- makeLockscreenTransitionStep(lockscreenShowing),
- /* validateStep = */ false);
- } else {
- mStatusBarStateListener.onExpandedChanged(panelExpanded || lockscreenShowing > 0.0f);
- }
- }
-
- private TransitionStep makeLockscreenTransitionStep(float value) {
- if (value <= 0.0f) {
- return new TransitionStep(KeyguardState.GONE);
- } else if (value >= 1.0f) {
- return new TransitionStep(KeyguardState.LOCKSCREEN);
- } else {
- return new TransitionStep(
- KeyguardState.GONE,
- KeyguardState.LOCKSCREEN,
- value,
- TransitionState.RUNNING);
- }
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.kt
new file mode 100644
index 0000000..ef0a416
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.kt
@@ -0,0 +1,1007 @@
+/*
+ * 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.
+ */
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.flags.BrokenWithSceneContainer
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.flags.andSceneContainer
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.wakefulnessLifecycle
+import com.android.systemui.kosmos.testCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.shade.data.repository.ShadeAnimationRepository
+import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorLegacyImpl
+import com.android.systemui.shade.domain.interactor.shadeAnimationInteractor
+import com.android.systemui.statusbar.notification.VisibilityLocationProvider
+import com.android.systemui.statusbar.notification.collection.GroupEntry
+import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable.PluggableListener
+import com.android.systemui.statusbar.notification.collection.notifPipeline
+import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
+import com.android.systemui.statusbar.notification.stack.data.repository.FakeHeadsUpNotificationRepository
+import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
+import com.android.systemui.statusbar.notification.visibilityLocationProvider
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.policy.keyguardStateController
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.time.fakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runTest
+import org.junit.Assume.assumeFalse
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+import org.mockito.verification.VerificationMode
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+
+@SmallTest
+@RunWith(ParameterizedAndroidJunit4::class)
+@RunWithLooper
+class VisualStabilityCoordinatorTest(flags: FlagsParameterization) : SysuiTestCase() {
+ private val kosmos = testKosmos().apply { testCase = this@VisualStabilityCoordinatorTest }
+
+ private val invalidateListener: PluggableListener<NotifStabilityManager> = mock()
+ private val headsUpRepository = kosmos.headsUpNotificationRepository
+ private val visibilityLocationProvider: VisibilityLocationProvider =
+ kosmos.visibilityLocationProvider
+ private val keyguardStateController: KeyguardStateController = kosmos.keyguardStateController
+
+ private val fakeSystemClock = kosmos.fakeSystemClock
+ private val fakeBackgroundExecutor = kosmos.fakeExecutor
+ private val testScope = kosmos.testScope
+
+ private val shadeRepository = kosmos.fakeShadeRepository
+ private lateinit var wakefulnessObserver: WakefulnessLifecycle.Observer
+ private lateinit var statusBarStateListener: StatusBarStateController.StateListener
+ private lateinit var notifStabilityManager: NotifStabilityManager
+ private lateinit var entry: NotificationEntry
+ private lateinit var groupEntry: GroupEntry
+
+ private val underTest: VisualStabilityCoordinator by lazy { kosmos.visualStabilityCoordinator }
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf(Flags.FLAG_STABILIZE_HEADS_UP_GROUP_V2)
+ .andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
+
+ @Before
+ fun setUp() {
+ configureKosmos()
+
+ underTest.attach(kosmos.notifPipeline)
+ testScope.testScheduler.runCurrent()
+
+ // capture arguments:
+ wakefulnessObserver = withArgCaptor {
+ verify(kosmos.wakefulnessLifecycle).addObserver(capture())
+ }
+ statusBarStateListener = withArgCaptor {
+ verify(kosmos.statusBarStateController).addCallback(capture())
+ }
+ notifStabilityManager = withArgCaptor {
+ verify(kosmos.notifPipeline).setVisualStabilityManager(capture())
+ }
+ notifStabilityManager.setInvalidationListener(invalidateListener)
+
+ entry = NotificationEntryBuilder().setPkg("testPkg1").build()
+
+ groupEntry = GroupEntryBuilder().setSummary(entry).build()
+
+ // Whenever we invalidate, the pipeline runs again, so we invalidate the state
+ whenever(invalidateListener.onPluggableInvalidated(eq(notifStabilityManager), any()))
+ .doAnswer { _ ->
+ notifStabilityManager.onBeginRun()
+ null
+ }
+ }
+
+ private fun configureKosmos() {
+ kosmos.statusBarStateController = mock()
+ // TODO(377868472) only override this when SceneContainer is disabled
+ kosmos.shadeAnimationInteractor =
+ ShadeAnimationInteractorLegacyImpl(ShadeAnimationRepository(), shadeRepository)
+ }
+
+ @Test
+ fun testScreenOff_groupAndSectionChangesAllowed() =
+ testScope.runTest {
+ // GIVEN screen is off, panel isn't expanded and device isn't pulsing
+ setFullyDozed(true)
+ setSleepy(true)
+ setPanelExpanded(false)
+ setPulsing(false)
+
+ // THEN group changes are allowed
+ assertThat(notifStabilityManager.isGroupChangeAllowed(entry)).isTrue()
+ assertThat(notifStabilityManager.isGroupPruneAllowed(groupEntry)).isTrue()
+
+ // THEN section changes are allowed
+ assertThat(notifStabilityManager.isSectionChangeAllowed(entry)).isTrue()
+ }
+
+ @Test
+ fun testScreenTurningOff_groupAndSectionChangesNotAllowed() =
+ testScope.runTest {
+ // GIVEN the screen is turning off (sleepy but partially dozed)
+ setFullyDozed(false)
+ setSleepy(true)
+ setPanelExpanded(true)
+ setPulsing(false)
+
+ // THEN group changes are NOT allowed
+ assertThat(notifStabilityManager.isGroupChangeAllowed(entry)).isFalse()
+ assertThat(notifStabilityManager.isGroupPruneAllowed(groupEntry)).isFalse()
+
+ // THEN section changes are NOT allowed
+ assertThat(notifStabilityManager.isSectionChangeAllowed(entry)).isFalse()
+ }
+
+ @Test
+ fun testScreenTurningOn_groupAndSectionChangesNotAllowed() =
+ testScope.runTest {
+ // GIVEN the screen is turning on (still fully dozed, not sleepy)
+ setFullyDozed(true)
+ setSleepy(false)
+ setPanelExpanded(true)
+ setPulsing(false)
+
+ // THEN group changes are NOT allowed
+ assertThat(notifStabilityManager.isGroupChangeAllowed(entry)).isFalse()
+ assertThat(notifStabilityManager.isGroupPruneAllowed(groupEntry)).isFalse()
+
+ // THEN section changes are NOT allowed
+ assertThat(notifStabilityManager.isSectionChangeAllowed(entry)).isFalse()
+ }
+
+ @Test
+ fun testPanelNotExpanded_groupAndSectionChangesAllowed() =
+ testScope.runTest {
+ // GIVEN screen is on but the panel isn't expanded and device isn't pulsing
+ setFullyDozed(false)
+ setSleepy(false)
+ setPanelExpanded(false)
+ setPulsing(false)
+
+ // THEN group changes are allowed
+ assertThat(notifStabilityManager.isGroupChangeAllowed(entry)).isTrue()
+ assertThat(notifStabilityManager.isGroupPruneAllowed(groupEntry)).isTrue()
+
+ // THEN section changes are allowed
+ assertThat(notifStabilityManager.isSectionChangeAllowed(entry)).isTrue()
+ }
+
+ @Test
+ fun testPanelExpanded_groupAndSectionChangesNotAllowed() =
+ testScope.runTest {
+ // GIVEN the panel true expanded and device isn't pulsing
+ setFullyDozed(false)
+ setSleepy(false)
+ setPanelExpanded(true)
+ setPulsing(false)
+
+ // THEN group changes are NOT allowed
+ assertThat(notifStabilityManager.isGroupChangeAllowed(entry)).isFalse()
+ assertThat(notifStabilityManager.isGroupPruneAllowed(groupEntry)).isFalse()
+
+ // THEN section changes are NOT allowed
+ assertThat(notifStabilityManager.isSectionChangeAllowed(entry)).isFalse()
+ }
+
+ @Test
+ @BrokenWithSceneContainer(bugId = 377868472) // mReorderingAllowed is broken with SceneContainer
+ fun testLockscreenPartlyShowing_groupAndSectionChangesNotAllowed() =
+ testScope.runTest {
+ // GIVEN the panel true expanded and device isn't pulsing
+ setFullyDozed(false)
+ setSleepy(false)
+ setLockscreenShowing(0.5f)
+ setPulsing(false)
+
+ // THEN group changes are NOT allowed
+ assertThat(notifStabilityManager.isGroupChangeAllowed(entry)).isFalse()
+ assertThat(notifStabilityManager.isGroupPruneAllowed(groupEntry)).isFalse()
+
+ // THEN section changes are NOT allowed
+ assertThat(notifStabilityManager.isSectionChangeAllowed(entry)).isFalse()
+ }
+
+ @Test
+ @BrokenWithSceneContainer(bugId = 377868472) // mReorderingAllowed is broken with SceneContainer
+ fun testLockscreenFullyShowing_groupAndSectionChangesNotAllowed() =
+ testScope.runTest {
+ // GIVEN the panel true expanded and device isn't pulsing
+ setFullyDozed(false)
+ setSleepy(false)
+ setLockscreenShowing(1.0f)
+ setPulsing(false)
+
+ // THEN group changes are NOT allowed
+ assertThat(notifStabilityManager.isGroupChangeAllowed(entry)).isFalse()
+ assertThat(notifStabilityManager.isGroupPruneAllowed(groupEntry)).isFalse()
+
+ // THEN section changes are NOT allowed
+ assertThat(notifStabilityManager.isSectionChangeAllowed(entry)).isFalse()
+ }
+
+ @Test
+ fun testPulsing_screenOff_groupAndSectionChangesNotAllowed() =
+ testScope.runTest {
+ // GIVEN the device is pulsing and screen is off
+ setFullyDozed(true)
+ setSleepy(true)
+ setPulsing(true)
+
+ // THEN group changes are NOT allowed
+ assertThat(notifStabilityManager.isGroupChangeAllowed(entry)).isFalse()
+ assertThat(notifStabilityManager.isGroupPruneAllowed(groupEntry)).isFalse()
+
+ // THEN section changes are NOT allowed
+ assertThat(notifStabilityManager.isSectionChangeAllowed(entry)).isFalse()
+ }
+
+ @Test
+ fun testPulsing_panelNotExpanded_groupAndSectionChangesNotAllowed() =
+ testScope.runTest {
+ // GIVEN the device is pulsing and screen is off with the panel not expanded
+ setFullyDozed(true)
+ setSleepy(true)
+ setPanelExpanded(false)
+ setPulsing(true)
+
+ // THEN group changes are NOT allowed
+ assertThat(notifStabilityManager.isGroupChangeAllowed(entry)).isFalse()
+ assertThat(notifStabilityManager.isGroupPruneAllowed(groupEntry)).isFalse()
+
+ // THEN section changes are NOT allowed
+ assertThat(notifStabilityManager.isSectionChangeAllowed(entry)).isFalse()
+ }
+
+ @Test
+ fun testOverrideReorderingSuppression_onlySectionChangesAllowed() =
+ testScope.runTest {
+ // GIVEN section changes typically wouldn't be allowed because the panel is expanded and
+ // we're not pulsing
+ setFullyDozed(false)
+ setSleepy(false)
+ setPanelExpanded(true)
+ setPulsing(true)
+
+ // WHEN we temporarily allow section changes for this notification entry
+ underTest.temporarilyAllowSectionChanges(entry, fakeSystemClock.currentTimeMillis())
+
+ // THEN group changes aren't allowed
+ assertThat(notifStabilityManager.isGroupChangeAllowed(entry)).isFalse()
+ assertThat(notifStabilityManager.isGroupPruneAllowed(groupEntry)).isFalse()
+
+ // THEN section changes are allowed for this notification but not other notifications
+ assertThat(notifStabilityManager.isSectionChangeAllowed(entry)).isTrue()
+ assertThat(
+ notifStabilityManager.isSectionChangeAllowed(
+ NotificationEntryBuilder().setPkg("testPkg2").build()
+ )
+ )
+ .isFalse()
+ }
+
+ @Test
+ fun testTemporarilyAllowSectionChanges_callsInvalidate() =
+ testScope.runTest {
+ // GIVEN section changes typically wouldn't be allowed because the panel is expanded
+ setFullyDozed(false)
+ setSleepy(false)
+ setPanelExpanded(true)
+ setPulsing(false)
+
+ // WHEN we temporarily allow section changes for this notification entry
+ underTest.temporarilyAllowSectionChanges(entry, fakeSystemClock.uptimeMillis())
+
+ // THEN the notification list is invalidated
+ verifyStabilityManagerWasInvalidated(times(1))
+ }
+
+ @Test
+ fun testTemporarilyAllowSectionChanges_noInvalidationCalled() =
+ testScope.runTest {
+ // GIVEN section changes typically WOULD be allowed
+ setFullyDozed(true)
+ setSleepy(true)
+ setPanelExpanded(false)
+ setPulsing(false)
+
+ // WHEN we temporarily allow section changes for this notification entry
+ underTest.temporarilyAllowSectionChanges(entry, fakeSystemClock.currentTimeMillis())
+
+ // THEN invalidate is not called because this entry was never suppressed from reordering
+ verifyStabilityManagerWasInvalidated(never())
+ }
+
+ @Test
+ fun testTemporarilyAllowSectionChangesTimeout() =
+ testScope.runTest {
+ // GIVEN section changes typically WOULD be allowed
+ setFullyDozed(true)
+ setSleepy(true)
+ setPanelExpanded(false)
+ setPulsing(false)
+ assertThat(notifStabilityManager.isSectionChangeAllowed(entry)).isTrue()
+
+ // WHEN we temporarily allow section changes for this notification entry
+ underTest.temporarilyAllowSectionChanges(entry, fakeSystemClock.currentTimeMillis())
+
+ // THEN invalidate is not called because this entry was never suppressed from
+ // reordering;
+ // THEN section changes are allowed for this notification
+ verifyStabilityManagerWasInvalidated(never())
+ assertThat(notifStabilityManager.isSectionChangeAllowed(entry)).isTrue()
+
+ // WHEN we're pulsing (now disallowing reordering)
+ setPulsing(true)
+
+ // THEN we're still allowed to reorder this section because it's still in the list of
+ // notifications to allow section changes
+ assertThat(notifStabilityManager.isSectionChangeAllowed(entry)).isTrue()
+
+ // WHEN the timeout for the temporarily allow section reordering runnable is finsihed
+ fakeBackgroundExecutor.advanceClockToNext()
+ fakeBackgroundExecutor.runNextReady()
+
+ // THEN section changes aren't allowed anymore
+ assertThat(notifStabilityManager.isSectionChangeAllowed(entry)).isFalse()
+ }
+
+ @Test
+ fun testTemporarilyAllowSectionChanges_isPulsingChangeBeforeTimeout() =
+ testScope.runTest {
+ // GIVEN section changes typically wouldn't be allowed because the device is pulsing
+ setFullyDozed(true)
+ setSleepy(true)
+ setPanelExpanded(false)
+ setPulsing(true)
+
+ // WHEN we temporarily allow section changes for this notification entry
+ underTest.temporarilyAllowSectionChanges(entry, fakeSystemClock.currentTimeMillis())
+ // can now reorder, so invalidates
+ verifyStabilityManagerWasInvalidated(times(1))
+
+ // WHEN reordering is now allowed because device isn't pulsing anymore
+ setPulsing(false)
+
+ // THEN invalidate isn't called a second time since reordering was already allowed
+ verifyStabilityManagerWasInvalidated(times(1))
+ }
+
+ @Test
+ fun testMovingVisibleHeadsUpNotAllowed() =
+ testScope.runTest {
+ // GIVEN stability enforcing conditions
+ setPanelExpanded(true)
+ setSleepy(false)
+
+ // WHEN a notification is alerting and visible
+ headsUpRepository.setHeadsUpKeys(entry.key)
+ whenever(visibilityLocationProvider.isInVisibleLocation(any())).thenReturn(true)
+
+ // THEN the notification cannot be reordered
+ assertThat(notifStabilityManager.isEntryReorderingAllowed(entry)).isFalse()
+ assertThat(notifStabilityManager.isSectionChangeAllowed(entry)).isFalse()
+ }
+
+ @Test
+ fun testMovingInvisibleHeadsUpAllowed() =
+ testScope.runTest {
+ // GIVEN stability enforcing conditions
+ setPanelExpanded(true)
+ setSleepy(false)
+
+ // WHEN a notification is alerting but not visible
+ headsUpRepository.setHeadsUpKeys(entry.key)
+ whenever(visibilityLocationProvider.isInVisibleLocation(any())).thenReturn(false)
+
+ // THEN the notification can be reordered
+ assertThat(notifStabilityManager.isEntryReorderingAllowed(entry)).isTrue()
+ assertThat(notifStabilityManager.isSectionChangeAllowed(entry)).isTrue()
+ }
+
+ @Test
+ fun testNeverSuppressedChanges_noInvalidationCalled() =
+ testScope.runTest {
+ // GIVEN no notifications are currently being suppressed from grouping nor being sorted
+
+ // WHEN device isn't pulsing anymore
+
+ setPulsing(false)
+
+ // WHEN fully dozed
+ setFullyDozed(true)
+
+ // WHEN sleepy
+ setSleepy(true)
+
+ // WHEN panel isn't expanded
+ setPanelExpanded(false)
+
+ // THEN we never see any calls to invalidate since there weren't any notifications that
+ // were being suppressed from grouping or section changes
+ verifyStabilityManagerWasInvalidated(never())
+ }
+
+ @Test
+ fun testNotSuppressingGroupChangesAnymore_invalidationCalled() =
+ testScope.runTest {
+ // GIVEN visual stability is being maintained b/c panel is expanded
+ setPulsing(false)
+ setFullyDozed(false)
+ setSleepy(false)
+ setPanelExpanded(true)
+
+ assertThat(notifStabilityManager.isGroupChangeAllowed(entry)).isFalse()
+ assertThat(notifStabilityManager.isGroupPruneAllowed(groupEntry)).isFalse()
+
+ // WHEN the panel isn't expanded anymore
+ setPanelExpanded(false)
+
+ // THEN invalidate is called because we were previously suppressing a group change
+ verifyStabilityManagerWasInvalidated(times(1))
+ }
+
+ @Test
+ fun testNotLaunchingActivityAnymore_invalidationCalled() {
+ // GIVEN visual stability is being maintained b/c animation is playing
+ setActivityLaunching(true)
+
+ assertThat(notifStabilityManager.isPipelineRunAllowed()).isFalse()
+
+ // WHEN the animation has stopped playing
+ setActivityLaunching(false)
+
+ // THEN invalidate is called, b/c we were previously suppressing the pipeline from running
+ verifyStabilityManagerWasInvalidated(times(1))
+ }
+
+ @Test
+ fun testNotCollapsingPanelAnymore_invalidationCalled() {
+ // GIVEN visual stability is being maintained b/c animation is playing
+ setPanelCollapsing(true)
+
+ assertThat(notifStabilityManager.isPipelineRunAllowed()).isFalse()
+
+ // WHEN the animation has stopped playing
+ setPanelCollapsing(false)
+
+ // THEN invalidate is called, b/c we were previously suppressing the pipeline from running
+ verifyStabilityManagerWasInvalidated(times(1))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_CHECK_LOCKSCREEN_GONE_TRANSITION)
+ @DisableSceneContainer
+ fun testNotLockscreenInGoneTransitionLegacy_invalidationCalled() {
+ // GIVEN visual stability is being maintained b/c animation is playing
+ whenever(keyguardStateController.isKeyguardFadingAway).thenReturn(true)
+ underTest.mKeyguardFadeAwayAnimationCallback.onKeyguardFadingAwayChanged()
+
+ assertThat(notifStabilityManager.isPipelineRunAllowed()).isFalse()
+
+ // WHEN the animation has stopped playing
+ whenever(keyguardStateController.isKeyguardFadingAway).thenReturn(false)
+ underTest.mKeyguardFadeAwayAnimationCallback.onKeyguardFadingAwayChanged()
+
+ // THEN invalidate is called, b/c we were previously suppressing the pipeline from running
+ verifyStabilityManagerWasInvalidated(times(1))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_CHECK_LOCKSCREEN_GONE_TRANSITION)
+ @EnableSceneContainer
+ @BrokenWithSceneContainer(bugId = 377868472) // mReorderingAllowed is broken with SceneContainer
+ fun testNotLockscreenInGoneTransition_invalidationCalled() =
+ testScope.runTest {
+ // GIVEN visual stability is being maintained b/c animation is playing
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.GONE,
+ 1f,
+ TransitionState.RUNNING,
+ ),
+ /* validateStep = */ false,
+ )
+ testScope.testScheduler.runCurrent()
+ assertThat(notifStabilityManager.isPipelineRunAllowed()).isFalse()
+
+ // WHEN the animation has stopped playing
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.GONE,
+ 1f,
+ TransitionState.FINISHED,
+ ),
+ /* validateStep = */ false,
+ )
+ testScope.testScheduler.runCurrent()
+
+ // THEN invalidate is called, b/c we were previously suppressing the pipeline from
+ // running
+ verifyStabilityManagerWasInvalidated(times(1))
+ }
+
+ @Test
+ fun testNeverSuppressPipelineRunFromPanelCollapse_noInvalidationCalled() {
+ // GIVEN animation is playing
+ setPanelCollapsing(true)
+
+ // WHEN the animation has stopped playing
+ setPanelCollapsing(false)
+
+ // THEN invalidate is not called, b/c nothing has been suppressed
+ verifyStabilityManagerWasInvalidated(never())
+ }
+
+ @Test
+ fun testNeverSuppressPipelineRunFromLaunchActivity_noInvalidationCalled() {
+ // GIVEN animation is playing
+ setActivityLaunching(true)
+
+ // WHEN the animation has stopped playing
+ setActivityLaunching(false)
+
+ // THEN invalidate is not called, b/c nothing has been suppressed
+ verifyStabilityManagerWasInvalidated(never())
+ }
+
+ @Test
+ fun testNotSuppressingEntryReorderingAnymoreWillInvalidate() =
+ testScope.runTest {
+ // GIVEN visual stability is being maintained b/c panel is expanded
+ setPulsing(false)
+ setFullyDozed(false)
+ setSleepy(false)
+ setPanelExpanded(true)
+ setCommunalShowing(false)
+
+ assertThat(notifStabilityManager.isEntryReorderingAllowed(entry)).isFalse()
+ // The pipeline still has to report back that entry reordering was suppressed
+ notifStabilityManager.onEntryReorderSuppressed()
+
+ // WHEN the panel isn't expanded anymore
+ setPanelExpanded(false)
+
+ // THEN invalidate is called because we were previously suppressing an entry reorder
+ verifyStabilityManagerWasInvalidated(times(1))
+ }
+
+ @Test
+ @BrokenWithSceneContainer(bugId = 377868472) // mReorderingAllowed is broken with SceneContainer
+ fun testCommunalShowingWillNotSuppressReordering() =
+ testScope.runTest {
+ // GIVEN panel is expanded, communal is showing, and QS is collapsed
+ setPulsing(false)
+ setFullyDozed(false)
+ setSleepy(false)
+ setPanelExpanded(true)
+ setQsExpanded(false)
+ setCommunalShowing(true)
+
+ // THEN Reordering should be allowed
+ assertThat(notifStabilityManager.isEntryReorderingAllowed(entry)).isTrue()
+ }
+
+ @Test
+ fun testQsExpandedOverCommunalWillSuppressReordering() =
+ testScope.runTest {
+ // GIVEN panel is expanded and communal is showing, but QS is expanded
+ setPulsing(false)
+ setFullyDozed(false)
+ setSleepy(false)
+ setPanelExpanded(true)
+ setQsExpanded(true)
+ setCommunalShowing(true)
+
+ // THEN Reordering should not be allowed
+ assertThat(notifStabilityManager.isEntryReorderingAllowed(entry)).isFalse()
+ }
+
+ @Test
+ fun testQueryingEntryReorderingButNotReportingReorderSuppressedDoesNotInvalidate() =
+ testScope.runTest {
+ // GIVEN visual stability is being maintained b/c panel is expanded
+ setPulsing(false)
+ setFullyDozed(false)
+ setSleepy(false)
+ setPanelExpanded(true)
+
+ assertThat(notifStabilityManager.isEntryReorderingAllowed(entry)).isFalse()
+
+ // WHEN the panel isn't expanded anymore
+ setPanelExpanded(false)
+
+ // THEN invalidate is not called because we were not told that an entry reorder was
+ // suppressed
+ verifyStabilityManagerWasInvalidated(never())
+ }
+
+ @Test
+ fun testHeadsUp_allowedToChangeGroupAndSection() =
+ testScope.runTest {
+ // GIVEN group + section changes disallowed
+ setFullyDozed(false)
+ setSleepy(false)
+ setPanelExpanded(true)
+ setPulsing(true)
+ assertThat(notifStabilityManager.isGroupChangeAllowed(entry)).isFalse()
+ assertThat(notifStabilityManager.isGroupPruneAllowed(groupEntry)).isFalse()
+ assertThat(notifStabilityManager.isSectionChangeAllowed(entry)).isFalse()
+
+ // GIVEN mEntry is a HUN
+ headsUpRepository.setHeadsUpKeys(entry.key)
+
+ // THEN group + section changes are allowed
+ assertThat(notifStabilityManager.isGroupChangeAllowed(entry)).isTrue()
+ assertThat(notifStabilityManager.isSectionChangeAllowed(entry)).isTrue()
+
+ // BUT pruning the group for which this is the summary would still NOT be allowed.
+ assertThat(notifStabilityManager.isGroupPruneAllowed(groupEntry)).isFalse()
+ }
+
+ @Test
+ fun everyChangeAllowed_onReorderingEnabled_legacy() =
+ testScope.runTest {
+ assumeFalse(StabilizeHeadsUpGroup.isEnabled)
+ // GIVEN - reordering is allowed.
+ setPulsing(false)
+ setPanelExpanded(false)
+
+ // THEN
+ assertThat(notifStabilityManager.isEveryChangeAllowed()).isTrue()
+ assertThat(notifStabilityManager.isGroupChangeAllowed(entry)).isTrue()
+ assertThat(notifStabilityManager.isGroupPruneAllowed(groupEntry)).isTrue()
+ assertThat(notifStabilityManager.isSectionChangeAllowed(entry)).isTrue()
+ assertThat(notifStabilityManager.isEntryReorderingAllowed(entry)).isTrue()
+ }
+
+ @Test
+ fun everyChangeAllowed_noActiveHeadsUpGroup_onReorderingEnabled() =
+ testScope.runTest {
+ assumeTrue(StabilizeHeadsUpGroup.isEnabled)
+ // GIVEN - reordering is allowed.
+ setPulsing(false)
+ setPanelExpanded(false)
+
+ // GIVEN - empty heads-up-group keys
+ underTest.setHeadsUpGroupKeys(setOf())
+
+ // THEN
+ assertThat(notifStabilityManager.isEveryChangeAllowed()).isTrue()
+ assertThat(notifStabilityManager.isGroupChangeAllowed(entry)).isTrue()
+ assertThat(notifStabilityManager.isGroupPruneAllowed(groupEntry)).isTrue()
+ assertThat(notifStabilityManager.isSectionChangeAllowed(entry)).isTrue()
+ assertThat(notifStabilityManager.isEntryReorderingAllowed(entry)).isTrue()
+ }
+
+ @Test
+ fun everyChangeDisallowed_activeHeadsUpGroup_onReorderingEnabled() =
+ testScope.runTest {
+ assumeTrue(StabilizeHeadsUpGroup.isEnabled)
+ // GIVEN - reordering is allowed.
+ setPulsing(false)
+ setPanelExpanded(false)
+
+ // GIVEN - there is a group heads-up.
+ underTest.setHeadsUpGroupKeys(setOf("heads_up_group_key"))
+
+ // THEN
+ assertThat(notifStabilityManager.isEveryChangeAllowed()).isFalse()
+ }
+
+ @Test
+ fun nonHeadsUpGroup_changesAllowed_onReorderingEnabled() =
+ testScope.runTest {
+ assumeTrue(StabilizeHeadsUpGroup.isEnabled)
+ // GIVEN - reordering is allowed.
+ setPulsing(false)
+ setPanelExpanded(false)
+
+ // GIVEN - there is a group heads-up.
+ val headsUpGroupKey = "heads_up_group_key"
+ underTest.setHeadsUpGroupKeys(setOf(headsUpGroupKey))
+ headsUpRepository.setHeadsUpKeys(headsUpGroupKey)
+
+ // GIVEN - HUN Group Summary
+ val nonHeadsUpGroupSummary: NotificationEntry = mock()
+ whenever(nonHeadsUpGroupSummary.key).thenReturn("non_heads_up_group_key")
+ whenever(nonHeadsUpGroupSummary.isSummaryWithChildren).thenReturn(true)
+ val nonHeadsUpGroupEntry: GroupEntry = mock()
+ whenever(nonHeadsUpGroupEntry.summary).thenReturn(nonHeadsUpGroupSummary)
+ whenever(nonHeadsUpGroupEntry.representativeEntry).thenReturn(nonHeadsUpGroupSummary)
+
+ // THEN
+ assertThat(notifStabilityManager.isGroupPruneAllowed(nonHeadsUpGroupEntry)).isTrue()
+ assertThat(notifStabilityManager.isEntryReorderingAllowed(nonHeadsUpGroupEntry))
+ .isTrue()
+ }
+
+ @Test
+ fun headsUpGroup_changesDisallowed_onReorderingEnabled() =
+ testScope.runTest {
+ assumeTrue(StabilizeHeadsUpGroup.isEnabled)
+ // GIVEN - reordering is allowed.
+ setPulsing(false)
+ setPanelExpanded(false)
+
+ // GIVEN - there is a group heads-up.
+ val headsUpGroupKey = "heads_up_group_key"
+ underTest.setHeadsUpGroupKeys(setOf(headsUpGroupKey))
+ headsUpRepository.setHeadsUpKeys(headsUpGroupKey)
+
+ // GIVEN - HUN Group
+ val headsUpGroupSummary: NotificationEntry = mock()
+ whenever(headsUpGroupSummary.rowIsChildInGroup()).thenReturn(false)
+ whenever(headsUpGroupSummary.key).thenReturn(headsUpGroupKey)
+ whenever(headsUpGroupSummary.isSummaryWithChildren).thenReturn(true)
+
+ val headsUpGroupEntry: GroupEntry = mock()
+ whenever(headsUpGroupEntry.summary).thenReturn(headsUpGroupSummary)
+ whenever(headsUpGroupEntry.representativeEntry).thenReturn(headsUpGroupSummary)
+
+ whenever(headsUpGroupSummary.parent).thenReturn(headsUpGroupEntry)
+
+ // GIVEN - HUN is in visible location
+ whenever(visibilityLocationProvider.isInVisibleLocation(headsUpGroupSummary))
+ .thenReturn(true)
+
+ // THEN
+ assertThat(notifStabilityManager.isGroupPruneAllowed(headsUpGroupEntry)).isFalse()
+ assertThat(notifStabilityManager.isEntryReorderingAllowed(headsUpGroupEntry)).isFalse()
+ }
+
+ @Test
+ fun headsUpGroupSummaries_changesDisallowed_onReorderingEnabled() =
+ testScope.runTest {
+ assumeTrue(StabilizeHeadsUpGroup.isEnabled)
+ // GIVEN - reordering is allowed.
+ setPulsing(false)
+ setPanelExpanded(false)
+
+ // GIVEN - there is a group heads-up.
+ val headsUpGroupKey = "heads_up_group_key"
+ underTest.setHeadsUpGroupKeys(setOf(headsUpGroupKey))
+ headsUpRepository.setHeadsUpKeys(headsUpGroupKey)
+
+ // GIVEN - HUN Group
+ val headsUpGroupSummary: NotificationEntry = mock()
+ whenever(headsUpGroupSummary.rowIsChildInGroup()).thenReturn(false)
+ whenever(headsUpGroupSummary.key).thenReturn(headsUpGroupKey)
+ whenever(headsUpGroupSummary.isSummaryWithChildren).thenReturn(true)
+
+ val headsUpGroupEntry: GroupEntry = mock()
+ whenever(headsUpGroupEntry.summary).thenReturn(headsUpGroupSummary)
+ whenever(headsUpGroupEntry.representativeEntry).thenReturn(headsUpGroupSummary)
+
+ whenever(headsUpGroupSummary.parent).thenReturn(headsUpGroupEntry)
+
+ // GIVEN - HUN is in visible location
+ whenever(visibilityLocationProvider.isInVisibleLocation(headsUpGroupSummary))
+ .thenReturn(true)
+
+ // THEN
+ assertThat(notifStabilityManager.isGroupChangeAllowed(headsUpGroupSummary)).isFalse()
+ assertThat(notifStabilityManager.isEntryReorderingAllowed(headsUpGroupSummary))
+ .isFalse()
+ assertThat(notifStabilityManager.isSectionChangeAllowed(headsUpGroupSummary)).isFalse()
+ }
+
+ @Test
+ fun notificationInNonHUNGroup_changesAllowed_onReorderingEnabled() =
+ testScope.runTest {
+ assumeTrue(StabilizeHeadsUpGroup.isEnabled)
+ // GIVEN - reordering is allowed.
+ setPulsing(false)
+ setPanelExpanded(false)
+
+ // GIVEN - there is a group heads-up.
+ val headsUpGroupKey = "heads_up_group_key"
+ underTest.setHeadsUpGroupKeys(setOf(headsUpGroupKey))
+ headsUpRepository.setHeadsUpKeys(headsUpGroupKey)
+
+ // GIVEN - non HUN parent Group Summary
+ val groupSummary: NotificationEntry = mock()
+ whenever(groupSummary.key).thenReturn("non_heads_up_group_key")
+ whenever(groupSummary.isSummaryWithChildren).thenReturn(true)
+
+ val nonHeadsUpGroupEntry: GroupEntry = mock()
+ whenever(nonHeadsUpGroupEntry.summary).thenReturn(groupSummary)
+ whenever(nonHeadsUpGroupEntry.representativeEntry).thenReturn(groupSummary)
+
+ // GIVEN - child entry in a non heads-up group.
+ val childEntry: NotificationEntry = mock()
+ whenever(childEntry.rowIsChildInGroup()).thenReturn(true)
+ whenever(childEntry.parent).thenReturn(nonHeadsUpGroupEntry)
+ whenever(childEntry.parent).thenReturn(nonHeadsUpGroupEntry)
+
+ // THEN
+ assertThat(notifStabilityManager.isGroupChangeAllowed(childEntry)).isTrue()
+ assertThat(notifStabilityManager.isSectionChangeAllowed(childEntry)).isTrue()
+ assertThat(notifStabilityManager.isEntryReorderingAllowed(nonHeadsUpGroupEntry))
+ .isTrue()
+ }
+
+ @Test
+ fun notificationInHUNGroup_changesDisallowed_reorderingEnabled() =
+ testScope.runTest {
+ assumeTrue(StabilizeHeadsUpGroup.isEnabled)
+ // GIVEN - reordering is allowed.
+ setPulsing(false)
+ setPanelExpanded(false)
+
+ // GIVEN - there is a group heads-up.
+ val headsUpGroupKey = "heads_up_group_key"
+ underTest.setHeadsUpGroupKeys(setOf(headsUpGroupKey))
+ headsUpRepository.setHeadsUpKeys(headsUpGroupKey)
+
+ // GIVEN - HUN Group Summary
+ val headsUpGroupSummary: NotificationEntry = mock()
+ whenever(headsUpGroupSummary.rowIsChildInGroup()).thenReturn(false)
+ whenever(headsUpGroupSummary.key).thenReturn(headsUpGroupKey)
+ whenever(headsUpGroupSummary.isSummaryWithChildren).thenReturn(true)
+
+ val nonHeadsUpGroupEntry: GroupEntry = mock()
+ whenever(nonHeadsUpGroupEntry.summary).thenReturn(headsUpGroupSummary)
+ whenever(nonHeadsUpGroupEntry.representativeEntry).thenReturn(headsUpGroupSummary)
+
+ // GIVEN - child entry in a non heads-up group.
+ val childEntry: NotificationEntry =
+ mock<NotificationEntry>().apply { whenever(key).thenReturn("child") }
+ whenever(childEntry.rowIsChildInGroup()).thenReturn(true)
+ whenever(childEntry.parent).thenReturn(nonHeadsUpGroupEntry)
+
+ // GIVEN - HUN is in visible location
+ whenever(visibilityLocationProvider.isInVisibleLocation(headsUpGroupSummary))
+ .thenReturn(true)
+
+ // THEN
+ assertThat(notifStabilityManager.isGroupChangeAllowed(childEntry)).isFalse()
+ assertThat(notifStabilityManager.isSectionChangeAllowed(childEntry)).isFalse()
+ assertThat(notifStabilityManager.isEntryReorderingAllowed(childEntry)).isFalse()
+ }
+
+ private fun verifyStabilityManagerWasInvalidated(mode: VerificationMode) {
+ verify(invalidateListener, mode).onPluggableInvalidated(eq(notifStabilityManager), any())
+ }
+
+ private fun setActivityLaunching(activityLaunching: Boolean) {
+ kosmos.shadeAnimationInteractor.setIsLaunchingActivity(activityLaunching)
+ testScope.testScheduler.runCurrent()
+ }
+
+ private fun setPanelCollapsing(collapsing: Boolean) {
+ shadeRepository.setLegacyIsClosing(collapsing)
+ testScope.testScheduler.runCurrent()
+ }
+
+ private fun setCommunalShowing(isShowing: Boolean) {
+ val showingFlow =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(
+ if (isShowing) CommunalScenes.Communal else CommunalScenes.Blank
+ )
+ )
+ kosmos.communalSceneInteractor.setTransitionState(showingFlow)
+ testScope.testScheduler.runCurrent()
+ }
+
+ private fun setQsExpanded(isExpanded: Boolean) {
+ kosmos.shadeRepository.setQsExpansion(if (isExpanded) 1.0f else 0.0f)
+ testScope.testScheduler.runCurrent()
+ }
+
+ private fun setPulsing(pulsing: Boolean) = statusBarStateListener.onPulsingChanged(pulsing)
+
+ private fun setFullyDozed(fullyDozed: Boolean) {
+ val dozeAmount = if (fullyDozed) 1f else 0f
+ statusBarStateListener.onDozeAmountChanged(dozeAmount, dozeAmount)
+ }
+
+ private fun setSleepy(sleepy: Boolean) {
+ if (sleepy) {
+ wakefulnessObserver.onFinishedGoingToSleep()
+ } else {
+ wakefulnessObserver.onStartedWakingUp()
+ }
+ }
+
+ private suspend fun setPanelExpanded(expanded: Boolean) =
+ setPanelExpandedAndLockscreenShowing(expanded, /* lockscreenShowing= */ 0.0f)
+
+ private suspend fun setLockscreenShowing(lockscreenShowing: Float) =
+ setPanelExpandedAndLockscreenShowing(/* panelExpanded= */ false, lockscreenShowing)
+
+ private suspend fun setPanelExpandedAndLockscreenShowing(
+ panelExpanded: Boolean,
+ lockscreenShowing: Float,
+ ) {
+ if (SceneContainerFlag.isEnabled) {
+ statusBarStateListener.onExpandedChanged(panelExpanded)
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(
+ makeLockscreenTransitionStep(lockscreenShowing),
+ /* validateStep = */ false,
+ )
+ } else {
+ statusBarStateListener.onExpandedChanged(panelExpanded || lockscreenShowing > 0.0f)
+ }
+ }
+
+ private fun makeLockscreenTransitionStep(value: Float): TransitionStep {
+ return when (value) {
+ 0.0f -> TransitionStep(KeyguardState.GONE)
+ 1.0f -> TransitionStep(KeyguardState.LOCKSCREEN)
+ else ->
+ TransitionStep(
+ KeyguardState.GONE,
+ KeyguardState.LOCKSCREEN,
+ value,
+ TransitionState.RUNNING,
+ )
+ }
+ }
+}
+
+private fun FakeHeadsUpNotificationRepository.setHeadsUpKeys(vararg keys: String) {
+ setNotifications(keys.map { FakeHeadsUpRowRepository(key = it) })
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpAnimatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpAnimatorTest.kt
new file mode 100644
index 0000000..206eb89
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpAnimatorTest.kt
@@ -0,0 +1,87 @@
+/*
+ * 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.statusbar.notification.headsup
+
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import org.junit.Before
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(NotificationsHunSharedAnimationValues.FLAG_NAME)
+class HeadsUpAnimatorTest : SysuiTestCase() {
+ @Before
+ fun setUp() {
+ context.getOrCreateTestableResources().apply {
+ this.addOverride(R.dimen.heads_up_appear_y_above_screen, TEST_Y_ABOVE_SCREEN)
+ }
+ }
+
+ @Test
+ fun getHeadsUpYTranslation_fromBottomTrue_usesBottomAndYAbove() {
+ val underTest = HeadsUpAnimator(context)
+ underTest.stackTopMargin = 30
+ underTest.headsUpAppearHeightBottom = 300
+
+ val yTranslation = underTest.getHeadsUpYTranslation(isHeadsUpFromBottom = true)
+
+ assertThat(yTranslation).isEqualTo(TEST_Y_ABOVE_SCREEN + 300)
+ }
+
+ @Test
+ fun getHeadsUpYTranslation_fromBottomFalse_usesTopMarginAndYAbove() {
+ val underTest = HeadsUpAnimator(context)
+ underTest.stackTopMargin = 30
+ underTest.headsUpAppearHeightBottom = 300
+
+ val yTranslation = underTest.getHeadsUpYTranslation(isHeadsUpFromBottom = false)
+
+ assertThat(yTranslation).isEqualTo(-30 - TEST_Y_ABOVE_SCREEN)
+ }
+
+ @Test
+ fun getHeadsUpYTranslation_resourcesUpdated() {
+ val underTest = HeadsUpAnimator(context)
+ underTest.stackTopMargin = 30
+ underTest.headsUpAppearHeightBottom = 300
+
+ val yTranslation = underTest.getHeadsUpYTranslation(isHeadsUpFromBottom = true)
+
+ assertThat(yTranslation).isEqualTo(TEST_Y_ABOVE_SCREEN + 300)
+
+ // WHEN the resource is updated
+ val newYAbove = 600
+ context.getOrCreateTestableResources().apply {
+ this.addOverride(R.dimen.heads_up_appear_y_above_screen, newYAbove)
+ }
+ underTest.updateResources(context)
+
+ // THEN HeadsUpAnimator knows about it
+ assertThat(underTest.getHeadsUpYTranslation(isHeadsUpFromBottom = true))
+ .isEqualTo(newYAbove + 300)
+ }
+
+ companion object {
+ private const val TEST_Y_ABOVE_SCREEN = 50
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
index 25ae13f..f060cae 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
@@ -21,6 +21,7 @@
import android.net.Uri
import android.os.UserHandle
import android.os.UserHandle.USER_ALL
+import android.service.notification.StatusBarNotification
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -36,6 +37,7 @@
import com.android.systemui.statusbar.SbnBuilder
import com.android.systemui.statusbar.SmartReplyController
import com.android.systemui.statusbar.notification.ColorUpdateLogger
+import com.android.systemui.statusbar.notification.collection.EntryAdapter
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider
@@ -229,6 +231,10 @@
@Test
fun registerSettingsListener_forBubbles() {
controller.init(mock(NotificationEntry::class.java))
+ val entryAdapter = mock(EntryAdapter::class.java)
+ whenever(entryAdapter.sbn).thenReturn(mock(StatusBarNotification::class.java))
+ whenever(view.entryAdapter).thenReturn(entryAdapter)
+
val viewStateObserver = withArgCaptor {
verify(view).addOnAttachStateChangeListener(capture())
}
@@ -239,6 +245,9 @@
@Test
fun unregisterSettingsListener_forBubbles() {
controller.init(mock(NotificationEntry::class.java))
+ val entryAdapter = mock(EntryAdapter::class.java)
+ whenever(entryAdapter.sbn).thenReturn(mock(StatusBarNotification::class.java))
+ whenever(view.entryAdapter).thenReturn(entryAdapter)
val viewStateObserver = withArgCaptor {
verify(view).addOnAttachStateChangeListener(capture())
}
@@ -255,6 +264,7 @@
@Test
fun settingsListener_invalidUserId() {
+ whenever(view.entryAdapter).thenReturn(mock(EntryAdapter::class.java))
controller.mSettingsListener.onSettingChanged(BUBBLES_SETTING_URI, -1000, "1")
controller.mSettingsListener.onSettingChanged(BUBBLES_SETTING_URI, -1000, null)
@@ -265,6 +275,12 @@
fun settingsListener_validUserId() {
val childView: NotificationContentView = mock()
whenever(view.privateLayout).thenReturn(childView)
+ val entryAdapter = mock(EntryAdapter::class.java)
+ val sbn =
+ SbnBuilder().setNotification(Notification.Builder(mContext).build())
+ .setUser(UserHandle.of(view.entry.sbn.userId)).build()
+ whenever(entryAdapter.sbn).thenReturn(sbn)
+ whenever(view.entryAdapter).thenReturn(entryAdapter)
controller.mSettingsListener.onSettingChanged(
BUBBLES_SETTING_URI,
@@ -293,6 +309,9 @@
.thenReturn(
NotificationEntryBuilder().setSbn(sbn).setUser(UserHandle.of(USER_ALL)).build()
)
+ val entryAdapter = mock(EntryAdapter::class.java)
+ whenever(entryAdapter.sbn).thenReturn(sbn)
+ whenever(view.entryAdapter).thenReturn(entryAdapter)
controller.mSettingsListener.onSettingChanged(BUBBLES_SETTING_URI, 9, "1")
verify(childView).setBubblesEnabledForUser(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
index 979a1d0..fd49f60 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
@@ -79,6 +79,7 @@
mRow = spy(mNotificationTestHelper.createRow());
Notification notification = mRow.getEntry().getSbn().getNotification();
notification.contentIntent = mock(PendingIntent.class);
+ when(notification.contentIntent.isActivity()).thenReturn(true);
doReturn(true).when(mRow).startDragAndDrop(any(), any(), any(), anyInt());
mGroupRow = mNotificationTestHelper.createGroup(4);
when(mMenuRow.getLongpressMenuItem(any(Context.class))).thenReturn(mMenuItem);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 92ceb60..c376fad 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -70,6 +70,8 @@
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.notification.ColorUpdateLogger;
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
+import com.android.systemui.statusbar.notification.collection.GroupEntry;
+import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
@@ -106,6 +108,7 @@
import org.mockito.ArgumentCaptor;
+import java.util.List;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
@@ -338,6 +341,46 @@
}
/**
+ * Returns an {@link GroupEntry} group with the given number of child
+ * notifications.
+ */
+ public GroupEntry createGroupEntry(int numChildren,
+ @Nullable List<NotificationEntry> additionalChildren) {
+ Notification summary = new Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .setGroupSummary(true)
+ .setGroup(GROUP_KEY)
+ .build();
+
+ NotificationEntry summaryEntry = new NotificationEntryBuilder()
+ .setPkg(PKG)
+ .setOpPkg(PKG)
+ .setId(mId++)
+ .setUid(UID)
+ .setInitialPid(2000)
+ .setNotification(summary)
+ .setParent(GroupEntry.ROOT_ENTRY)
+ .build();
+ GroupEntryBuilder groupEntry = new GroupEntryBuilder()
+ .setSummary(summaryEntry);
+
+ for (int i = 0; i < numChildren; i++) {
+ NotificationEntry child = new NotificationEntryBuilder()
+ .setParent(GroupEntry.ROOT_ENTRY)
+ .setNotification(new Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .setGroup(GROUP_KEY)
+ .build())
+ .build();
+ groupEntry.addChild(child);
+ }
+ for (NotificationEntry entry : additionalChildren) {
+ groupEntry.addChild(entry);
+ }
+ return groupEntry.build();
+ }
+
+ /**
* Returns an {@link ExpandableNotificationRow} group with the given number of child
* notifications.
*/
@@ -411,6 +454,23 @@
}
/**
+ * Returns an {@link NotificationEntry} that should be shown as a bubble and is part
+ * of a group of notifications.
+ */
+ public NotificationEntry createBubbleEntryInGroup() throws Exception {
+ Notification n = createNotification(false /* isGroupSummary */,
+ GROUP_KEY /* groupKey */,
+ makeBubbleMetadata(null /* deleteIntent */, false /* autoExpand */));
+ n.flags |= FLAG_BUBBLE;
+ NotificationEntry entry = generateEntry(n, PKG, UID, USER_HANDLE,
+ mDefaultInflationFlags, IMPORTANCE_HIGH);
+ modifyRanking(entry)
+ .setCanBubble(true)
+ .build();
+ return entry;
+ }
+
+ /**
* Returns an {@link ExpandableNotificationRow} that should be shown as a bubble and is part
* of a group of notifications.
*/
@@ -573,6 +633,41 @@
return mKeyguardBypassController;
}
+ private NotificationEntry generateEntry(
+ Notification notification,
+ String pkg,
+ int uid,
+ UserHandle userHandle,
+ @InflationFlag int extraInflationFlags,
+ int importance)
+ throws Exception {
+ final NotificationChannel channel =
+ new NotificationChannel(
+ notification.getChannelId(),
+ notification.getChannelId(),
+ importance);
+ channel.setBlockable(true);
+
+ NotificationEntry entry = new NotificationEntryBuilder()
+ .setPkg(pkg)
+ .setOpPkg(pkg)
+ .setId(mId++)
+ .setUid(uid)
+ .setInitialPid(2000)
+ .setNotification(notification)
+ .setUser(userHandle)
+ .setPostTime(System.currentTimeMillis())
+ .setChannel(channel)
+ .updateRanking(rankingBuilder -> rankingBuilder.setIsConversation(
+ notification.isStyle(Notification.MessagingStyle.class)
+ ))
+ .build();
+
+
+ return entry;
+ }
+
+
private ExpandableNotificationRow generateRow(
Notification notification,
String pkg,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
index 4b8a0c2..f7bbf98 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
@@ -29,6 +29,7 @@
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.notification.collection.EntryAdapter
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded
@@ -436,6 +437,9 @@
val row = mock(ExpandableNotificationRow::class.java)
val entry = mock(NotificationEntry::class.java)
whenever(entry.isStickyAndNotDemoted).thenReturn(isSticky)
+ val entryAdapter = mock(EntryAdapter::class.java)
+ whenever(entryAdapter.canPeek()).thenReturn(isSticky)
+ whenever(row.entryAdapter).thenReturn(entryAdapter)
val sbn = mock(StatusBarNotification::class.java)
whenever(entry.sbn).thenReturn(sbn)
whenever(row.entry).thenReturn(entry)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index ce655ef..9545150 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -27,6 +27,8 @@
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView.FooterViewState
import com.android.systemui.statusbar.notification.headsup.AvalancheController
+import com.android.systemui.statusbar.notification.headsup.HeadsUpAnimator
+import com.android.systemui.statusbar.notification.headsup.NotificationsHunSharedAnimationValues
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
@@ -55,7 +57,8 @@
private val avalancheController = mock<AvalancheController>()
private val hostView = FrameLayout(context)
- private val stackScrollAlgorithm = StackScrollAlgorithm(context, hostView)
+ private lateinit var headsUpAnimator: HeadsUpAnimator
+ private lateinit var stackScrollAlgorithm: StackScrollAlgorithm
private val notificationRow = mock<ExpandableNotificationRow>()
private val notificationEntry = mock<NotificationEntry>()
private val notificationEntryAdapter = mock<EntryAdapter>()
@@ -101,7 +104,10 @@
@JvmStatic
@Parameters(name = "{0}")
fun getParams(): List<FlagsParameterization> {
- return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ return FlagsParameterization.allCombinationsOf(
+ NotificationsHunSharedAnimationValues.FLAG_NAME
+ )
+ .andSceneContainer()
}
}
@@ -123,6 +129,15 @@
ambientState.isSmallScreen = true
hostView.addView(notificationRow)
+
+ if (NotificationsHunSharedAnimationValues.isEnabled) {
+ headsUpAnimator = HeadsUpAnimator(context)
+ }
+ stackScrollAlgorithm = StackScrollAlgorithm(
+ context,
+ hostView,
+ if (::headsUpAnimator.isInitialized) headsUpAnimator else null,
+ )
}
private fun isTv(): Boolean {
@@ -403,7 +418,11 @@
ambientState.setLayoutMinHeight(2500) // Mock the height of shade
ambientState.stackY = 2500f // Scroll over the max translation
stackScrollAlgorithm.setIsExpanded(true) // Mark the shade open
- stackScrollAlgorithm.setHeadsUpAppearHeightBottom(bottomOfScreen.toInt())
+ if (NotificationsHunSharedAnimationValues.isEnabled) {
+ headsUpAnimator.headsUpAppearHeightBottom = bottomOfScreen.toInt()
+ } else {
+ stackScrollAlgorithm.setHeadsUpAppearHeightBottom(bottomOfScreen.toInt())
+ }
whenever(notificationRow.mustStayOnScreen()).thenReturn(true)
whenever(notificationRow.isHeadsUp).thenReturn(true)
whenever(notificationRow.isAboveShelf).thenReturn(true)
@@ -419,6 +438,9 @@
val topMargin = 100f
ambientState.maxHeadsUpTranslation = 2000f
ambientState.stackTopMargin = topMargin.toInt()
+ if (NotificationsHunSharedAnimationValues.isEnabled) {
+ headsUpAnimator.stackTopMargin = topMargin.toInt()
+ }
whenever(notificationRow.intrinsicHeight).thenReturn(100)
whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
index e7be20e..4a761917 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
@@ -16,12 +16,16 @@
package com.android.systemui.statusbar.notification.stack
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.testing.TestableLooper.RunWithLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.AnimatorTestRule
import com.android.systemui.res.R
+import com.android.systemui.statusbar.notification.headsup.HeadsUpAnimator
+import com.android.systemui.statusbar.notification.headsup.NotificationsHunSharedAnimationValues
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent
import com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_HEADS_UP_APPEAR
@@ -49,10 +53,10 @@
@RunWith(AndroidJUnit4::class)
@RunWithLooper
class StackStateAnimatorTest : SysuiTestCase() {
-
@get:Rule val animatorTestRule = AnimatorTestRule(this)
private lateinit var stackStateAnimator: StackStateAnimator
+ private lateinit var headsUpAnimator: HeadsUpAnimator
private val stackScroller: NotificationStackScrollLayout = mock()
private val view: ExpandableView = mock()
private val viewState: ExpandableViewState =
@@ -69,11 +73,20 @@
whenever(stackScroller.context).thenReturn(context)
whenever(view.viewState).thenReturn(viewState)
- stackStateAnimator = StackStateAnimator(mContext, stackScroller)
+
+ if (NotificationsHunSharedAnimationValues.isEnabled) {
+ headsUpAnimator = HeadsUpAnimator(context)
+ }
+ stackStateAnimator = StackStateAnimator(
+ mContext,
+ stackScroller,
+ if (::headsUpAnimator.isInitialized) headsUpAnimator else null,
+ )
}
@Test
- fun startAnimationForEvents_headsUpFromTop_startsHeadsUpAppearAnim() {
+ @DisableFlags(NotificationsHunSharedAnimationValues.FLAG_NAME)
+ fun startAnimationForEvents_headsUpFromTop_startsHeadsUpAppearAnim_flagOff() {
val topMargin = 50f
val expectedStartY = -topMargin - stackStateAnimator.mHeadsUpAppearStartAboveScreen
val event = AnimationEvent(view, AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR)
@@ -94,7 +107,30 @@
}
@Test
- fun startAnimationForEvents_headsUpFromBottom_startsHeadsUpAppearAnim() {
+ @EnableFlags(NotificationsHunSharedAnimationValues.FLAG_NAME)
+ fun startAnimationForEvents_headsUpFromTop_startsHeadsUpAppearAnim_flagOn() {
+ val topMargin = 50f
+ val expectedStartY = -topMargin - HEADS_UP_ABOVE_SCREEN
+ val event = AnimationEvent(view, AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR)
+ headsUpAnimator.stackTopMargin = topMargin.toInt()
+
+ stackStateAnimator.startAnimationForEvents(arrayListOf(event), 0)
+
+ verify(view).setFinalActualHeight(VIEW_HEIGHT)
+ verify(view, description("should animate from the top")).translationY = expectedStartY
+ verify(view)
+ .performAddAnimation(
+ /* delay= */ 0L,
+ /* duration= */ ANIMATION_DURATION_HEADS_UP_APPEAR.toLong(),
+ /* isHeadsUpAppear= */ true,
+ /* isHeadsUpCycling= */ false,
+ /* onEndRunnable= */ null,
+ )
+ }
+
+ @Test
+ @DisableFlags(NotificationsHunSharedAnimationValues.FLAG_NAME)
+ fun startAnimationForEvents_headsUpFromBottom_startsHeadsUpAppearAnim_flagOff() {
val screenHeight = 2000f
val expectedStartY = screenHeight + stackStateAnimator.mHeadsUpAppearStartAboveScreen
val event =
@@ -118,7 +154,63 @@
}
@Test
- fun startAnimationForEvents_startsHeadsUpDisappearAnim() {
+ @EnableFlags(NotificationsHunSharedAnimationValues.FLAG_NAME)
+ fun startAnimationForEvents_headsUpFromBottom_startsHeadsUpAppearAnim_flagOn() {
+ val screenHeight = 2000f
+ val expectedStartY = screenHeight + HEADS_UP_ABOVE_SCREEN
+ val event =
+ AnimationEvent(view, AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR).apply {
+ headsUpFromBottom = true
+ }
+ headsUpAnimator.headsUpAppearHeightBottom = screenHeight.toInt()
+
+ stackStateAnimator.startAnimationForEvents(arrayListOf(event), 0)
+
+ verify(view).setFinalActualHeight(VIEW_HEIGHT)
+ verify(view, description("should animate from the bottom")).translationY = expectedStartY
+ verify(view)
+ .performAddAnimation(
+ /* delay= */ 0L,
+ /* duration= */ ANIMATION_DURATION_HEADS_UP_APPEAR.toLong(),
+ /* isHeadsUpAppear= */ true,
+ /* isHeadsUpCycling= */ false,
+ /* onEndRunnable= */ null,
+ )
+ }
+
+ @Test
+ @DisableFlags(NotificationsHunSharedAnimationValues.FLAG_NAME)
+ fun startAnimationForEvents_startsHeadsUpDisappearAnim_flagOff() {
+ val disappearDuration = ANIMATION_DURATION_HEADS_UP_DISAPPEAR.toLong()
+ val event = AnimationEvent(view, AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR)
+ clearInvocations(view)
+ stackStateAnimator.startAnimationForEvents(arrayListOf(event), 0)
+
+ verify(view)
+ .performRemoveAnimation(
+ /* duration= */ eq(disappearDuration),
+ /* delay= */ eq(0L),
+ /* translationDirection= */ eq(0f),
+ /* isHeadsUpAnimation= */ eq(true),
+ /* isHeadsUpCycling= */ eq(false),
+ /* onStartedRunnable= */ any(),
+ /* onFinishedRunnable= */ runnableCaptor.capture(),
+ /* animationListener= */ any(),
+ /* clipSide= */ eq(ExpandableView.ClipSide.BOTTOM),
+ )
+
+ animatorTestRule.advanceTimeBy(disappearDuration) // move to the end of SSA animations
+ runnableCaptor.value.run() // execute the end runnable
+
+ verify(view, description("should be translated to the heads up appear start"))
+ .translationY = -stackStateAnimator.mHeadsUpAppearStartAboveScreen
+ verify(view, description("should be called at the end of the disappear animation"))
+ .removeFromTransientContainer()
+ }
+
+ @Test
+ @EnableFlags(NotificationsHunSharedAnimationValues.FLAG_NAME)
+ fun startAnimationForEvents_startsHeadsUpDisappearAnim_flagOn() {
val disappearDuration = ANIMATION_DURATION_HEADS_UP_DISAPPEAR.toLong()
val event = AnimationEvent(view, AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR)
clearInvocations(view)
diff --git a/packages/SystemUI/pods/Android.bp b/packages/SystemUI/pods/Android.bp
index 1547ae2..fba8962 100644
--- a/packages/SystemUI/pods/Android.bp
+++ b/packages/SystemUI/pods/Android.bp
@@ -31,7 +31,7 @@
warning_checks: ["MissingApacheLicenseDetector"],
},
kotlincflags: [
- "-Xexplicit-api=warning",
+ "-Xexplicit-api=strict",
"-Xjvm-default=all",
],
defaults_visibility: [":__subpackages__"],
diff --git a/packages/SystemUI/pods/com/android/systemui/retail/RetailModeModule.kt b/packages/SystemUI/pods/com/android/systemui/retail/RetailModeModule.kt
index c20e368..fe78bb3 100644
--- a/packages/SystemUI/pods/com/android/systemui/retail/RetailModeModule.kt
+++ b/packages/SystemUI/pods/com/android/systemui/retail/RetailModeModule.kt
@@ -24,11 +24,15 @@
import dagger.Module
@Module
-abstract class RetailModeModule {
+public abstract class RetailModeModule {
@Binds
- abstract fun bindsRetailModeRepository(impl: RetailModeSettingsRepository): RetailModeRepository
+ public abstract fun bindsRetailModeRepository(
+ impl: RetailModeSettingsRepository
+ ): RetailModeRepository
@Binds
- abstract fun bindsRetailModeInteractor(impl: RetailModeInteractorImpl): RetailModeInteractor
+ public abstract fun bindsRetailModeInteractor(
+ impl: RetailModeInteractorImpl
+ ): RetailModeInteractor
}
diff --git a/packages/SystemUI/pods/com/android/systemui/retail/data/repository/RetailModeRepository.kt b/packages/SystemUI/pods/com/android/systemui/retail/data/repository/RetailModeRepository.kt
index c9eac25..4fd6985 100644
--- a/packages/SystemUI/pods/com/android/systemui/retail/data/repository/RetailModeRepository.kt
+++ b/packages/SystemUI/pods/com/android/systemui/retail/data/repository/RetailModeRepository.kt
@@ -19,11 +19,11 @@
import kotlinx.coroutines.flow.StateFlow
/** Repository to track if the device is in Retail mode */
-interface RetailModeRepository {
+public interface RetailModeRepository {
/** Flow of whether the device is currently in retail mode. */
- val retailMode: StateFlow<Boolean>
+ public val retailMode: StateFlow<Boolean>
/** Last value of whether the device is in retail mode. */
- val inRetailMode: Boolean
+ public val inRetailMode: Boolean
get() = retailMode.value
}
diff --git a/packages/SystemUI/pods/com/android/systemui/retail/data/repository/impl/RetailModeSettingsRepository.kt b/packages/SystemUI/pods/com/android/systemui/retail/data/repository/impl/RetailModeSettingsRepository.kt
index 8955263..7296835 100644
--- a/packages/SystemUI/pods/com/android/systemui/retail/data/repository/impl/RetailModeSettingsRepository.kt
+++ b/packages/SystemUI/pods/com/android/systemui/retail/data/repository/impl/RetailModeSettingsRepository.kt
@@ -38,17 +38,17 @@
/**
* Tracks [Settings.Global.DEVICE_DEMO_MODE].
*
- * @see UserManager.isDeviceInDemoMode
+ * @see android.os.UserManager.isDeviceInDemoMode
*/
@SysUISingleton
-class RetailModeSettingsRepository
+public class RetailModeSettingsRepository
@Inject
constructor(
globalSettings: GlobalSettings,
@Background backgroundDispatcher: CoroutineDispatcher,
@Application scope: CoroutineScope,
) : RetailModeRepository {
- override val retailMode =
+ override val retailMode: StateFlow<Boolean> =
conflatedCallbackFlow {
val observer =
object : ContentObserver(null) {
@@ -66,7 +66,7 @@
.flowOn(backgroundDispatcher)
.stateIn(scope, SharingStarted.Eagerly, false)
- companion object {
+ public companion object {
private const val RETAIL_MODE_SETTING = Settings.Global.DEVICE_DEMO_MODE
}
}
diff --git a/packages/SystemUI/pods/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt b/packages/SystemUI/pods/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt
index 748e34d..3e44f8d 100644
--- a/packages/SystemUI/pods/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt
+++ b/packages/SystemUI/pods/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt
@@ -17,7 +17,7 @@
package com.android.systemui.retail.domain.interactor
/** Interactor to determine if the device is currently in retail mode */
-interface RetailModeInteractor {
+public interface RetailModeInteractor {
/** Whether the device is currently in retail mode */
- val isInRetailMode: Boolean
+ public val isInRetailMode: Boolean
}
diff --git a/packages/SystemUI/pods/com/android/systemui/retail/domain/interactor/impl/RetailModeInteractorImpl.kt b/packages/SystemUI/pods/com/android/systemui/retail/domain/interactor/impl/RetailModeInteractorImpl.kt
index 8dbe562..52b4bcc 100644
--- a/packages/SystemUI/pods/com/android/systemui/retail/domain/interactor/impl/RetailModeInteractorImpl.kt
+++ b/packages/SystemUI/pods/com/android/systemui/retail/domain/interactor/impl/RetailModeInteractorImpl.kt
@@ -22,11 +22,9 @@
import javax.inject.Inject
@SysUISingleton
-class RetailModeInteractorImpl
+public class RetailModeInteractorImpl
@Inject
-constructor(
- private val repository: RetailModeRepository,
-) : RetailModeInteractor {
+constructor(private val repository: RetailModeRepository) : RetailModeInteractor {
override val isInRetailMode: Boolean
get() = repository.inRetailMode
}
diff --git a/packages/SystemUI/pods/com/android/systemui/util/settings/SettingsProxy.kt b/packages/SystemUI/pods/com/android/systemui/util/settings/SettingsProxy.kt
index 597276a..a8d4f79 100644
--- a/packages/SystemUI/pods/com/android/systemui/util/settings/SettingsProxy.kt
+++ b/packages/SystemUI/pods/com/android/systemui/util/settings/SettingsProxy.kt
@@ -19,6 +19,7 @@
import android.content.ContentResolver
import android.database.ContentObserver
import android.net.Uri
+import android.provider.Settings
import android.provider.Settings.SettingNotFoundException
import androidx.annotation.AnyThread
import androidx.annotation.WorkerThread
@@ -28,6 +29,7 @@
import kotlin.coroutines.coroutineContext
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
/**
* Used to interact with mainly with Settings.Global, but can also be used for Settings.System and
@@ -43,15 +45,15 @@
* This class also provides [.registerContentObserver] methods, normally found on [ContentResolver]
* instances, unifying setting related actions in one place.
*/
-interface SettingsProxy {
+public interface SettingsProxy {
/** Returns the [ContentResolver] this instance was constructed with. */
- fun getContentResolver(): ContentResolver
+ public fun getContentResolver(): ContentResolver
/** Returns the [CoroutineScope] that the async APIs will use. */
- val settingsScope: CoroutineScope
+ public val settingsScope: CoroutineScope
@OptIn(ExperimentalStdlibApi::class)
- suspend fun executeOnSettingsScopeDispatcher(name: String, block: () -> Unit) {
+ public suspend fun executeOnSettingsScopeDispatcher(name: String, block: () -> Unit) {
val settingsDispatcher = settingsScope.coroutineContext[CoroutineDispatcher]
if (
settingsDispatcher != null &&
@@ -70,7 +72,7 @@
* @param name to look up in the table
* @return the corresponding content URI, or null if not present
*/
- @AnyThread fun getUriFor(name: String): Uri
+ @AnyThread public fun getUriFor(name: String): Uri
/**
* Registers listener for a given content observer <b>while blocking the current thread</b>.
@@ -80,7 +82,7 @@
* [registerContentObserverAsync] instead.
*/
@WorkerThread
- fun registerContentObserverSync(name: String, settingsObserver: ContentObserver) {
+ public fun registerContentObserverSync(name: String, settingsObserver: ContentObserver) {
registerContentObserverSync(getUriFor(name), settingsObserver)
}
@@ -91,7 +93,7 @@
* registration happens on a worker thread. Caller may wrap the API in an async block if they
* wish to synchronize execution.
*/
- suspend fun registerContentObserver(name: String, settingsObserver: ContentObserver) {
+ public suspend fun registerContentObserver(name: String, settingsObserver: ContentObserver) {
executeOnSettingsScopeDispatcher("registerContentObserver-A") {
registerContentObserverSync(getUriFor(name), settingsObserver)
}
@@ -103,7 +105,7 @@
* API corresponding to [registerContentObserver] for Java usage.
*/
@AnyThread
- fun registerContentObserverAsync(name: String, settingsObserver: ContentObserver) =
+ public fun registerContentObserverAsync(name: String, settingsObserver: ContentObserver): Job =
settingsScope.launch("registerContentObserverAsync-A") {
registerContentObserverSync(getUriFor(name), settingsObserver)
}
@@ -116,11 +118,11 @@
* value.
*/
@AnyThread
- fun registerContentObserverAsync(
+ public fun registerContentObserverAsync(
name: String,
settingsObserver: ContentObserver,
@WorkerThread registered: Runnable,
- ) =
+ ): Job =
settingsScope.launch("registerContentObserverAsync-B") {
registerContentObserverSync(getUriFor(name), settingsObserver)
registered.run()
@@ -133,8 +135,9 @@
* [registerContentObserverAsync] instead.
*/
@WorkerThread
- fun registerContentObserverSync(uri: Uri, settingsObserver: ContentObserver) =
+ public fun registerContentObserverSync(uri: Uri, settingsObserver: ContentObserver) {
registerContentObserverSync(uri, false, settingsObserver)
+ }
/**
* Convenience wrapper around [ContentResolver.registerContentObserver].'
@@ -143,7 +146,7 @@
* registration happens on a worker thread. Caller may wrap the API in an async block if they
* wish to synchronize execution.
*/
- suspend fun registerContentObserver(uri: Uri, settingsObserver: ContentObserver) {
+ public suspend fun registerContentObserver(uri: Uri, settingsObserver: ContentObserver) {
executeOnSettingsScopeDispatcher("registerContentObserver-B") {
registerContentObserverSync(uri, settingsObserver)
}
@@ -155,7 +158,7 @@
* API corresponding to [registerContentObserver] for Java usage.
*/
@AnyThread
- fun registerContentObserverAsync(uri: Uri, settingsObserver: ContentObserver) =
+ public fun registerContentObserverAsync(uri: Uri, settingsObserver: ContentObserver): Job =
settingsScope.launch("registerContentObserverAsync-C") {
registerContentObserverSync(uri, settingsObserver)
}
@@ -168,11 +171,11 @@
* value.
*/
@AnyThread
- fun registerContentObserverAsync(
+ public fun registerContentObserverAsync(
uri: Uri,
settingsObserver: ContentObserver,
@WorkerThread registered: Runnable,
- ) =
+ ): Job =
settingsScope.launch("registerContentObserverAsync-D") {
registerContentObserverSync(uri, settingsObserver)
registered.run()
@@ -184,11 +187,13 @@
* Implicitly calls [getUriFor] on the passed in name.
*/
@WorkerThread
- fun registerContentObserverSync(
+ public fun registerContentObserverSync(
name: String,
notifyForDescendants: Boolean,
settingsObserver: ContentObserver,
- ) = registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver)
+ ) {
+ registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver)
+ }
/**
* Convenience wrapper around [ContentResolver.registerContentObserver].'
@@ -197,7 +202,7 @@
* registration happens on a worker thread. Caller may wrap the API in an async block if they
* wish to synchronize execution.
*/
- suspend fun registerContentObserver(
+ public suspend fun registerContentObserver(
name: String,
notifyForDescendants: Boolean,
settingsObserver: ContentObserver,
@@ -213,11 +218,11 @@
* API corresponding to [registerContentObserver] for Java usage.
*/
@AnyThread
- fun registerContentObserverAsync(
+ public fun registerContentObserverAsync(
name: String,
notifyForDescendants: Boolean,
settingsObserver: ContentObserver,
- ) =
+ ): Job =
settingsScope.launch("registerContentObserverAsync-E") {
registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver)
}
@@ -230,12 +235,12 @@
* value.
*/
@AnyThread
- fun registerContentObserverAsync(
+ public fun registerContentObserverAsync(
name: String,
notifyForDescendants: Boolean,
settingsObserver: ContentObserver,
@WorkerThread registered: Runnable,
- ) =
+ ): Job =
settingsScope.launch("registerContentObserverAsync-F") {
registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver)
registered.run()
@@ -248,7 +253,7 @@
* [registerContentObserverAsync] instead.
*/
@WorkerThread
- fun registerContentObserverSync(
+ public fun registerContentObserverSync(
uri: Uri,
notifyForDescendants: Boolean,
settingsObserver: ContentObserver,
@@ -266,7 +271,7 @@
* registration happens on a worker thread. Caller may wrap the API in an async block if they
* wish to synchronize execution.
*/
- suspend fun registerContentObserver(
+ public suspend fun registerContentObserver(
uri: Uri,
notifyForDescendants: Boolean,
settingsObserver: ContentObserver,
@@ -282,11 +287,11 @@
* API corresponding to [registerContentObserver] for Java usage.
*/
@AnyThread
- fun registerContentObserverAsync(
+ public fun registerContentObserverAsync(
uri: Uri,
notifyForDescendants: Boolean,
settingsObserver: ContentObserver,
- ) =
+ ): Job =
settingsScope.launch("registerContentObserverAsync-G") {
registerContentObserverSync(uri, notifyForDescendants, settingsObserver)
}
@@ -299,12 +304,12 @@
* value.
*/
@AnyThread
- fun registerContentObserverAsync(
+ public fun registerContentObserverAsync(
uri: Uri,
notifyForDescendants: Boolean,
settingsObserver: ContentObserver,
@WorkerThread registered: Runnable,
- ) =
+ ): Job =
settingsScope.launch("registerContentObserverAsync-H") {
registerContentObserverSync(uri, notifyForDescendants, settingsObserver)
registered.run()
@@ -317,7 +322,7 @@
* [unregisterContentObserverAsync] instead.
*/
@WorkerThread
- fun unregisterContentObserverSync(settingsObserver: ContentObserver) {
+ public fun unregisterContentObserverSync(settingsObserver: ContentObserver) {
trace({ "SP#unregisterObserver" }) {
getContentResolver().unregisterContentObserver(settingsObserver)
}
@@ -330,7 +335,7 @@
* [ContentObserver] un-registration happens on a worker thread. Caller may wrap the API in an
* async block if they wish to synchronize execution.
*/
- suspend fun unregisterContentObserver(settingsObserver: ContentObserver) {
+ public suspend fun unregisterContentObserver(settingsObserver: ContentObserver) {
executeOnSettingsScopeDispatcher("unregisterContentObserver") {
unregisterContentObserverSync(settingsObserver)
}
@@ -343,7 +348,7 @@
* [ContentObserver] registration happens on a worker thread.
*/
@AnyThread
- fun unregisterContentObserverAsync(settingsObserver: ContentObserver) =
+ public fun unregisterContentObserverAsync(settingsObserver: ContentObserver): Job =
settingsScope.launch("unregisterContentObserverAsync") {
unregisterContentObserver(settingsObserver)
}
@@ -354,7 +359,7 @@
* @param name to look up in the table
* @return the corresponding value, or null if not present
*/
- fun getString(name: String): String?
+ public fun getString(name: String): String?
/**
* Store a name/value pair into the database.
@@ -363,7 +368,7 @@
* @param value to associate with the name
* @return true if the value was set, false on database errors
*/
- fun putString(name: String, value: String?): Boolean
+ public fun putString(name: String, value: String?): Boolean
/**
* Store a name/value pair into the database.
@@ -394,7 +399,7 @@
* @return true if the value was set, false on database errors.
* @see .resetToDefaults
*/
- fun putString(name: String, value: String?, tag: String?, makeDefault: Boolean): Boolean
+ public fun putString(name: String, value: String?, tag: String?, makeDefault: Boolean): Boolean
/**
* Convenience function for retrieving a single secure settings value as an integer. Note that
@@ -406,7 +411,7 @@
* @param default Value to return if the setting is not defined.
* @return The setting's current value, or default if it is not defined or not a valid integer.
*/
- fun getInt(name: String, default: Int): Int {
+ public fun getInt(name: String, default: Int): Int {
val v = getString(name)
return try {
v?.toInt() ?: default
@@ -429,7 +434,7 @@
* found or the setting value is not an integer.
*/
@Throws(SettingNotFoundException::class)
- fun getInt(name: String): Int {
+ public fun getInt(name: String): Int {
val v = getString(name) ?: throw SettingNotFoundException(name)
return try {
v.toInt()
@@ -448,7 +453,7 @@
* @param value The new value for the setting.
* @return true if the value was set, false on database errors
*/
- fun putInt(name: String, value: Int): Boolean {
+ public fun putInt(name: String, value: Int): Boolean {
return putString(name, value.toString())
}
@@ -462,7 +467,7 @@
* @param default Value to return if the setting is not defined.
* @return The setting's current value, or default if it is not defined or not a valid boolean.
*/
- fun getBool(name: String, default: Boolean): Boolean {
+ public fun getBool(name: String, default: Boolean): Boolean {
return getInt(name, if (default) 1 else 0) != 0
}
@@ -480,7 +485,7 @@
* found or the setting value is not a boolean.
*/
@Throws(SettingNotFoundException::class)
- fun getBool(name: String): Boolean {
+ public fun getBool(name: String): Boolean {
return getInt(name) != 0
}
@@ -494,7 +499,7 @@
* @param value The new value for the setting.
* @return true if the value was set, false on database errors
*/
- fun putBool(name: String, value: Boolean): Boolean {
+ public fun putBool(name: String, value: Boolean): Boolean {
return putInt(name, if (value) 1 else 0)
}
@@ -508,7 +513,7 @@
* @param def Value to return if the setting is not defined.
* @return The setting's current value, or 'def' if it is not defined or not a valid `long`.
*/
- fun getLong(name: String, def: Long): Long {
+ public fun getLong(name: String, def: Long): Long {
val valString = getString(name)
return parseLongOrUseDefault(valString, def)
}
@@ -527,7 +532,7 @@
* found or the setting value is not an integer.
*/
@Throws(SettingNotFoundException::class)
- fun getLong(name: String): Long {
+ public fun getLong(name: String): Long {
val valString = getString(name)
return parseLongOrThrow(name, valString)
}
@@ -542,7 +547,7 @@
* @param value The new value for the setting.
* @return true if the value was set, false on database errors
*/
- fun putLong(name: String, value: Long): Boolean {
+ public fun putLong(name: String, value: Long): Boolean {
return putString(name, value.toString())
}
@@ -556,7 +561,7 @@
* @param def Value to return if the setting is not defined.
* @return The setting's current value, or 'def' if it is not defined or not a valid float.
*/
- fun getFloat(name: String, def: Float): Float {
+ public fun getFloat(name: String, def: Float): Float {
val v = getString(name)
return parseFloat(v, def)
}
@@ -575,7 +580,7 @@
* found or the setting value is not a float.
*/
@Throws(SettingNotFoundException::class)
- fun getFloat(name: String): Float {
+ public fun getFloat(name: String): Float {
val v = getString(name)
return parseFloatOrThrow(name, v)
}
@@ -590,14 +595,14 @@
* @param value The new value for the setting.
* @return true if the value was set, false on database errors
*/
- fun putFloat(name: String, value: Float): Boolean {
+ public fun putFloat(name: String, value: Float): Boolean {
return putString(name, value.toString())
}
- companion object {
+ public companion object {
/** Convert a string to a long, or uses a default if the string is malformed or null */
@JvmStatic
- fun parseLongOrUseDefault(valString: String?, default: Long): Long {
+ public fun parseLongOrUseDefault(valString: String?, default: Long): Long {
val value: Long =
try {
valString?.toLong() ?: default
@@ -610,7 +615,7 @@
/** Convert a string to a long, or throws an exception if the string is malformed or null */
@JvmStatic
@Throws(SettingNotFoundException::class)
- fun parseLongOrThrow(name: String, valString: String?): Long {
+ public fun parseLongOrThrow(name: String, valString: String?): Long {
if (valString == null) {
throw SettingNotFoundException(name)
}
@@ -623,7 +628,7 @@
/** Convert a string to a float, or uses a default if the string is malformed or null */
@JvmStatic
- fun parseFloat(v: String?, def: Float): Float {
+ public fun parseFloat(v: String?, def: Float): Float {
return try {
v?.toFloat() ?: def
} catch (e: NumberFormatException) {
@@ -636,7 +641,7 @@
*/
@JvmStatic
@Throws(SettingNotFoundException::class)
- fun parseFloatOrThrow(name: String, v: String?): Float {
+ public fun parseFloatOrThrow(name: String, v: String?): Float {
if (v == null) {
throw SettingNotFoundException(name)
}
@@ -648,7 +653,7 @@
}
}
- fun interface CurrentUserIdProvider {
- @UserIdInt fun getUserId(): Int
+ public fun interface CurrentUserIdProvider {
+ @UserIdInt public fun getUserId(): Int
}
}
diff --git a/packages/SystemUI/pods/com/android/systemui/util/settings/SettingsProxyExt.kt b/packages/SystemUI/pods/com/android/systemui/util/settings/SettingsProxyExt.kt
index 36468144..60feaf1 100644
--- a/packages/SystemUI/pods/com/android/systemui/util/settings/SettingsProxyExt.kt
+++ b/packages/SystemUI/pods/com/android/systemui/util/settings/SettingsProxyExt.kt
@@ -17,6 +17,7 @@
package com.android.systemui.util.settings
+import android.annotation.SuppressLint
import android.annotation.UserIdInt
import android.database.ContentObserver
import com.android.systemui.Flags
@@ -25,10 +26,11 @@
import kotlinx.coroutines.flow.Flow
/** Kotlin extension functions for [SettingsProxy]. */
-object SettingsProxyExt {
+@SuppressLint("RegisterContentObserverSyncWarning")
+public object SettingsProxyExt {
/** Returns a flow of [Unit] that is invoked each time that content is updated. */
- fun UserSettingsProxy.observerFlow(
+ public fun UserSettingsProxy.observerFlow(
@UserIdInt userId: Int,
vararg names: String,
): Flow<Unit> {
@@ -59,9 +61,7 @@
}
/** Returns a flow of [Unit] that is invoked each time that content is updated. */
- fun SettingsProxy.observerFlow(
- vararg names: String,
- ): Flow<Unit> {
+ public fun SettingsProxy.observerFlow(vararg names: String): Flow<Unit> {
return conflatedCallbackFlow {
val observer =
object : ContentObserver(null) {
diff --git a/packages/SystemUI/pods/com/android/systemui/util/settings/UserSettingsProxy.kt b/packages/SystemUI/pods/com/android/systemui/util/settings/UserSettingsProxy.kt
index 3ccac9e3..61c7f73 100644
--- a/packages/SystemUI/pods/com/android/systemui/util/settings/UserSettingsProxy.kt
+++ b/packages/SystemUI/pods/com/android/systemui/util/settings/UserSettingsProxy.kt
@@ -15,6 +15,7 @@
*/
package com.android.systemui.util.settings
+import android.annotation.SuppressLint
import android.annotation.UserIdInt
import android.annotation.WorkerThread
import android.content.ContentResolver
@@ -28,6 +29,7 @@
import com.android.systemui.util.settings.SettingsProxy.Companion.parseFloatOrThrow
import com.android.systemui.util.settings.SettingsProxy.Companion.parseLongOrThrow
import com.android.systemui.util.settings.SettingsProxy.Companion.parseLongOrUseDefault
+import kotlinx.coroutines.Job
/**
* Used to interact with per-user Settings.Secure and Settings.System settings (but not
@@ -42,11 +44,12 @@
* This class also provides [.registerContentObserver] methods, normally found on [ContentResolver]
* instances, unifying setting related actions in one place.
*/
-interface UserSettingsProxy : SettingsProxy {
- val currentUserProvider: SettingsProxy.CurrentUserIdProvider
+@SuppressLint("RegisterContentObserverSyncWarning")
+public interface UserSettingsProxy : SettingsProxy {
+ public val currentUserProvider: SettingsProxy.CurrentUserIdProvider
/** Returns the user id for the associated [ContentResolver]. */
- var userId: Int
+ public var userId: Int
get() = getContentResolver().userId
set(_) {
throw UnsupportedOperationException(
@@ -58,7 +61,7 @@
* Returns the actual current user handle when querying with the current user. Otherwise,
* returns the passed in user id.
*/
- fun getRealUserHandle(userHandle: Int): Int {
+ public fun getRealUserHandle(userHandle: Int): Int {
return if (userHandle != UserHandle.USER_CURRENT) {
userHandle
} else currentUserProvider.getUserId()
@@ -75,7 +78,7 @@
}
}
- override fun registerContentObserverAsync(uri: Uri, settingsObserver: ContentObserver) =
+ override fun registerContentObserverAsync(uri: Uri, settingsObserver: ContentObserver): Job =
settingsScope.launch("registerContentObserverAsync-A") {
registerContentObserverForUserSync(uri, settingsObserver, userId)
}
@@ -109,7 +112,7 @@
uri: Uri,
notifyForDescendants: Boolean,
settingsObserver: ContentObserver,
- ) =
+ ): Job =
settingsScope.launch("registerContentObserverAsync-B") {
registerContentObserverForUserSync(uri, notifyForDescendants, settingsObserver, userId)
}
@@ -120,7 +123,7 @@
* Implicitly calls [getUriFor] on the passed in name.
*/
@WorkerThread
- fun registerContentObserverForUserSync(
+ public fun registerContentObserverForUserSync(
name: String,
settingsObserver: ContentObserver,
userHandle: Int,
@@ -135,7 +138,7 @@
* [ContentObserver] registration happens on a worker thread. Caller may wrap the API in an
* async block if they wish to synchronize execution.
*/
- suspend fun registerContentObserverForUser(
+ public suspend fun registerContentObserverForUser(
name: String,
settingsObserver: ContentObserver,
userHandle: Int,
@@ -150,11 +153,11 @@
*
* API corresponding to [registerContentObserverForUser] for Java usage.
*/
- fun registerContentObserverForUserAsync(
+ public fun registerContentObserverForUserAsync(
name: String,
settingsObserver: ContentObserver,
userHandle: Int,
- ) =
+ ): Job =
settingsScope.launch("registerContentObserverForUserAsync-A") {
try {
registerContentObserverForUserSync(getUriFor(name), settingsObserver, userHandle)
@@ -165,7 +168,7 @@
/** Convenience wrapper around [ContentResolver.registerContentObserver] */
@WorkerThread
- fun registerContentObserverForUserSync(
+ public fun registerContentObserverForUserSync(
uri: Uri,
settingsObserver: ContentObserver,
userHandle: Int,
@@ -180,7 +183,7 @@
* [ContentObserver] registration happens on a worker thread. Caller may wrap the API in an
* async block if they wish to synchronize execution.
*/
- suspend fun registerContentObserverForUser(
+ public suspend fun registerContentObserverForUser(
uri: Uri,
settingsObserver: ContentObserver,
userHandle: Int,
@@ -195,11 +198,11 @@
*
* API corresponding to [registerContentObserverForUser] for Java usage.
*/
- fun registerContentObserverForUserAsync(
+ public fun registerContentObserverForUserAsync(
uri: Uri,
settingsObserver: ContentObserver,
userHandle: Int,
- ) =
+ ): Job =
settingsScope.launch("registerContentObserverForUserAsync-B") {
try {
registerContentObserverForUserSync(uri, settingsObserver, userHandle)
@@ -215,12 +218,12 @@
* complete, the callback block is called on the <b>background thread</b> to allow for update of
* value.
*/
- fun registerContentObserverForUserAsync(
+ public fun registerContentObserverForUserAsync(
uri: Uri,
settingsObserver: ContentObserver,
userHandle: Int,
@WorkerThread registered: Runnable,
- ) =
+ ): Job =
settingsScope.launch("registerContentObserverForUserAsync-C") {
try {
registerContentObserverForUserSync(uri, settingsObserver, userHandle)
@@ -236,7 +239,7 @@
* Implicitly calls [getUriFor] on the passed in name.
*/
@WorkerThread
- fun registerContentObserverForUserSync(
+ public fun registerContentObserverForUserSync(
name: String,
notifyForDescendants: Boolean,
settingsObserver: ContentObserver,
@@ -257,7 +260,7 @@
* [ContentObserver] registration happens on a worker thread. Caller may wrap the API in an
* async block if they wish to synchronize execution.
*/
- suspend fun registerContentObserverForUser(
+ public suspend fun registerContentObserverForUser(
name: String,
notifyForDescendants: Boolean,
settingsObserver: ContentObserver,
@@ -278,7 +281,7 @@
*
* API corresponding to [registerContentObserverForUser] for Java usage.
*/
- fun registerContentObserverForUserAsync(
+ public fun registerContentObserverForUserAsync(
name: String,
notifyForDescendants: Boolean,
settingsObserver: ContentObserver,
@@ -300,7 +303,7 @@
/** Convenience wrapper around [ContentResolver.registerContentObserver] */
@WorkerThread
- fun registerContentObserverForUserSync(
+ public fun registerContentObserverForUserSync(
uri: Uri,
notifyForDescendants: Boolean,
settingsObserver: ContentObserver,
@@ -314,7 +317,6 @@
settingsObserver,
getRealUserHandle(userHandle),
)
- Unit
}
}
@@ -325,7 +327,7 @@
* [ContentObserver] registration happens on a worker thread. Caller may wrap the API in an
* async block if they wish to synchronize execution.
*/
- suspend fun registerContentObserverForUser(
+ public suspend fun registerContentObserverForUser(
uri: Uri,
notifyForDescendants: Boolean,
settingsObserver: ContentObserver,
@@ -346,12 +348,12 @@
*
* API corresponding to [registerContentObserverForUser] for Java usage.
*/
- fun registerContentObserverForUserAsync(
+ public fun registerContentObserverForUserAsync(
uri: Uri,
notifyForDescendants: Boolean,
settingsObserver: ContentObserver,
userHandle: Int,
- ) =
+ ): Job =
settingsScope.launch("registerContentObserverForUserAsync-E") {
try {
registerContentObserverForUserSync(
@@ -376,7 +378,7 @@
}
/** See [getString]. */
- fun getStringForUser(name: String, userHandle: Int): String?
+ public fun getStringForUser(name: String, userHandle: Int): String?
/**
* Store a name/value pair into the database. Values written by this method will be overridden
@@ -386,17 +388,17 @@
* @param value to associate with the name
* @return true if the value was set, false on database errors
*/
- fun putString(name: String, value: String?, overrideableByRestore: Boolean): Boolean
+ public fun putString(name: String, value: String?, overrideableByRestore: Boolean): Boolean
override fun putString(name: String, value: String?): Boolean {
return putStringForUser(name, value, userId)
}
/** Similar implementation to [putString] for the specified [userHandle]. */
- fun putStringForUser(name: String, value: String?, userHandle: Int): Boolean
+ public fun putStringForUser(name: String, value: String?, userHandle: Int): Boolean
/** Similar implementation to [putString] for the specified [userHandle]. */
- fun putStringForUser(
+ public fun putStringForUser(
name: String,
value: String?,
tag: String?,
@@ -410,7 +412,7 @@
}
/** Similar implementation to [getInt] for the specified [userHandle]. */
- fun getIntForUser(name: String, default: Int, userHandle: Int): Int {
+ public fun getIntForUser(name: String, default: Int, userHandle: Int): Int {
val v = getStringForUser(name, userHandle)
return try {
v?.toInt() ?: default
@@ -420,11 +422,11 @@
}
@Throws(SettingNotFoundException::class)
- override fun getInt(name: String) = getIntForUser(name, userId)
+ override fun getInt(name: String): Int = getIntForUser(name, userId)
/** Similar implementation to [getInt] for the specified [userHandle]. */
@Throws(SettingNotFoundException::class)
- fun getIntForUser(name: String, userHandle: Int): Int {
+ public fun getIntForUser(name: String, userHandle: Int): Int {
val v = getStringForUser(name, userHandle) ?: throw SettingNotFoundException(name)
return try {
v.toInt()
@@ -433,24 +435,24 @@
}
}
- override fun putInt(name: String, value: Int) = putIntForUser(name, value, userId)
+ override fun putInt(name: String, value: Int): Boolean = putIntForUser(name, value, userId)
/** Similar implementation to [getInt] for the specified [userHandle]. */
- fun putIntForUser(name: String, value: Int, userHandle: Int) =
+ public fun putIntForUser(name: String, value: Int, userHandle: Int): Boolean =
putStringForUser(name, value.toString(), userHandle)
- override fun getBool(name: String, def: Boolean) = getBoolForUser(name, def, userId)
+ override fun getBool(name: String, def: Boolean): Boolean = getBoolForUser(name, def, userId)
/** Similar implementation to [getBool] for the specified [userHandle]. */
- fun getBoolForUser(name: String, def: Boolean, userHandle: Int) =
+ public fun getBoolForUser(name: String, def: Boolean, userHandle: Int): Boolean =
getIntForUser(name, if (def) 1 else 0, userHandle) != 0
@Throws(SettingNotFoundException::class)
- override fun getBool(name: String) = getBoolForUser(name, userId)
+ override fun getBool(name: String): Boolean = getBoolForUser(name, userId)
/** Similar implementation to [getBool] for the specified [userHandle]. */
@Throws(SettingNotFoundException::class)
- fun getBoolForUser(name: String, userHandle: Int): Boolean {
+ public fun getBoolForUser(name: String, userHandle: Int): Boolean {
return getIntForUser(name, userHandle) != 0
}
@@ -459,40 +461,40 @@
}
/** Similar implementation to [putBool] for the specified [userHandle]. */
- fun putBoolForUser(name: String, value: Boolean, userHandle: Int) =
+ public fun putBoolForUser(name: String, value: Boolean, userHandle: Int): Boolean =
putIntForUser(name, if (value) 1 else 0, userHandle)
/** Similar implementation to [getLong] for the specified [userHandle]. */
- fun getLongForUser(name: String, def: Long, userHandle: Int): Long {
+ public fun getLongForUser(name: String, def: Long, userHandle: Int): Long {
val valString = getStringForUser(name, userHandle)
return parseLongOrUseDefault(valString, def)
}
/** Similar implementation to [getLong] for the specified [userHandle]. */
@Throws(SettingNotFoundException::class)
- fun getLongForUser(name: String, userHandle: Int): Long {
+ public fun getLongForUser(name: String, userHandle: Int): Long {
val valString = getStringForUser(name, userHandle)
return parseLongOrThrow(name, valString)
}
/** Similar implementation to [putLong] for the specified [userHandle]. */
- fun putLongForUser(name: String, value: Long, userHandle: Int) =
+ public fun putLongForUser(name: String, value: Long, userHandle: Int): Boolean =
putStringForUser(name, value.toString(), userHandle)
/** Similar implementation to [getFloat] for the specified [userHandle]. */
- fun getFloatForUser(name: String, def: Float, userHandle: Int): Float {
+ public fun getFloatForUser(name: String, def: Float, userHandle: Int): Float {
val v = getStringForUser(name, userHandle)
return parseFloat(v, def)
}
/** Similar implementation to [getFloat] for the specified [userHandle]. */
@Throws(SettingNotFoundException::class)
- fun getFloatForUser(name: String, userHandle: Int): Float {
+ public fun getFloatForUser(name: String, userHandle: Int): Float {
val v = getStringForUser(name, userHandle)
return parseFloatOrThrow(name, v)
}
/** Similar implementation to [putFloat] for the specified [userHandle]. */
- fun putFloatForUser(name: String, value: Float, userHandle: Int) =
+ public fun putFloatForUser(name: String, value: Float, userHandle: Int): Boolean =
putStringForUser(name, value.toString(), userHandle)
}
diff --git a/packages/SystemUI/res/layout/volume_dialog_top_section.xml b/packages/SystemUI/res/layout/volume_dialog_top_section.xml
index 4fc20e2..29f5248 100644
--- a/packages/SystemUI/res/layout/volume_dialog_top_section.xml
+++ b/packages/SystemUI/res/layout/volume_dialog_top_section.xml
@@ -22,7 +22,6 @@
android:clipChildren="false"
android:clipToPadding="false"
android:gravity="center"
- android:layoutDirection="ltr"
android:paddingEnd="@dimen/volume_dialog_ringer_drawer_diff_end_margin"
app:layoutDescription="@xml/volume_dialog_ringer_drawer_motion_scene">
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 63101d4..bd09e39 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -199,7 +199,7 @@
* to be updated.
*/
@SysUISingleton
-public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpable, CoreStartable {
+public class KeyguardUpdateMonitor implements TrustManager.TrustListener, CoreStartable {
private static final String TAG = "KeyguardUpdateMonitor";
private static final int BIOMETRIC_LOCKOUT_RESET_DELAY_MS = 600;
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index e725353..19da5de 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -112,7 +112,7 @@
*/
@SysUISingleton
public class ScreenDecorations implements
- CoreStartable, ConfigurationController.ConfigurationListener, Dumpable {
+ CoreStartable, ConfigurationController.ConfigurationListener {
private static final boolean DEBUG_LOGGING = false;
private static final String TAG = "ScreenDecorations";
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 85f1880..c78f75a 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -54,6 +54,7 @@
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import com.android.systemui.statusbar.notification.shared.NotificationContentAlphaOptimization;
import com.android.wm.shell.animation.FlingAnimationUtils;
import com.android.wm.shell.shared.animation.PhysicsAnimator;
@@ -890,12 +891,16 @@
if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_DRAG_TO_CONTENTS)) {
if (v instanceof ExpandableNotificationRow) {
ExpandableNotificationRow enr = (ExpandableNotificationRow) v;
- boolean canBubble = enr.getEntry().canBubble();
- Notification notif = enr.getEntry().getSbn().getNotification();
- PendingIntent dragIntent = notif.contentIntent != null ? notif.contentIntent
- : notif.fullScreenIntent;
- if (dragIntent != null && dragIntent.isActivity() && !canBubble) {
- return true;
+ if (NotificationBundleUi.isEnabled()) {
+ return enr.getEntryAdapter().canDragAndDrop();
+ } else {
+ boolean canBubble = enr.getEntry().canBubble();
+ Notification notif = enr.getEntry().getSbn().getNotification();
+ PendingIntent dragIntent = notif.contentIntent != null ? notif.contentIntent
+ : notif.fullScreenIntent;
+ if (dragIntent != null && dragIntent.isActivity() && !canBubble) {
+ return true;
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
index 786d27a..b730c93 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
@@ -289,6 +289,8 @@
List<DeviceItem> hearingDeviceItemList = getHearingDeviceItemList();
CachedBluetoothDevice activeHearingDevice = getActiveHearingDevice(
hearingDeviceItemList);
+ mLocalBluetoothManager.getEventManager().registerCallback(this);
+
mMainExecutor.execute(() -> {
setupDeviceListView(dialog, hearingDeviceItemList);
setupPairNewDeviceButton(dialog);
@@ -302,21 +304,6 @@
}
@Override
- public void onStart(@NonNull SystemUIDialog dialog) {
- mBgExecutor.execute(() -> {
- if (mLocalBluetoothManager != null) {
- mLocalBluetoothManager.getEventManager().registerCallback(this);
- }
- if (mPresetController != null) {
- mPresetController.registerHapCallback();
- }
- if (mAmbientController != null) {
- mAmbientController.start();
- }
- });
- }
-
- @Override
public void onStop(@NonNull SystemUIDialog dialog) {
mBgExecutor.execute(() -> {
if (mLocalBluetoothManager != null) {
@@ -378,6 +365,7 @@
mPresetLayout = dialog.requireViewById(R.id.preset_layout);
mPresetLayout.setVisibility(mPresetController.isPresetControlAvailable() ? VISIBLE : GONE);
+ mBgExecutor.execute(() -> mPresetController.registerHapCallback());
}
private void setupAmbientControls(CachedBluetoothDevice activeHearingDevice) {
@@ -387,6 +375,7 @@
mDialog.getContext(), mLocalBluetoothManager, ambientLayout);
mAmbientController.setShowUiWhenLocalDataExist(false);
mAmbientController.loadDevice(activeHearingDevice);
+ mBgExecutor.execute(() -> mAmbientController.start());
}
private void setupPairNewDeviceButton(SystemUIDialog dialog) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 4dcf268..0902d19 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -34,6 +34,7 @@
import android.util.RotationUtils
import android.view.HapticFeedbackConstants
import android.view.MotionEvent
+import android.view.accessibility.AccessibilityManager
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.keyguard.AuthInteractionProperties
import com.android.launcher3.icons.IconProvider
@@ -85,7 +86,17 @@
private val udfpsUtils: UdfpsUtils,
private val iconProvider: IconProvider,
private val activityTaskManager: ActivityTaskManager,
+ private val accessibilityManager: AccessibilityManager,
) {
+ // When a11y enabled, increase message delay to ensure messages get read
+ private val messageDelay =
+ accessibilityManager
+ .getRecommendedTimeoutMillis(
+ BiometricPrompt.HIDE_DIALOG_DELAY,
+ AccessibilityManager.FLAG_CONTENT_CONTROLS or AccessibilityManager.FLAG_CONTENT_TEXT,
+ )
+ .toLong()
+
/** The set of modalities available for this prompt */
val modalities: Flow<BiometricModalities> =
promptSelectorInteractor.prompt
@@ -692,7 +703,7 @@
messageJob?.cancel()
messageJob = launch {
- delay(BiometricPrompt.HIDE_DIALOG_DELAY.toLong())
+ delay(messageDelay)
if (authenticateAfterError) {
showAuthenticating(messageAfterError)
} else {
@@ -754,7 +765,7 @@
messageJob?.cancel()
messageJob = launch {
- delay(BiometricPrompt.HIDE_DIALOG_DELAY.toLong())
+ delay(messageDelay)
showAuthenticating(messageAfterHelp)
}
}
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 d464200..721d116 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
@@ -26,14 +26,16 @@
import android.os.Handler
import android.util.Log
import android.view.Display
+import android.view.IWindowManager
import com.android.app.tracing.FlowTracing.traceEach
import com.android.app.tracing.traceSection
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
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
@@ -43,6 +45,7 @@
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
@@ -50,12 +53,13 @@
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
-/** Provides a [Flow] of [Display] as returned by [DisplayManager]. */
+/** Repository for providing access to display related information and events. */
interface DisplayRepository {
/** Display change event indicating a change to the given displayId has occurred. */
val displayChangeEvent: Flow<Int>
@@ -66,6 +70,9 @@
/** 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 displays.
*
@@ -124,6 +131,8 @@
@Inject
constructor(
private val displayManager: DisplayManager,
+ private val commandQueue: CommandQueue,
+ private val windowManager: IWindowManager,
@Background backgroundHandler: Handler,
@Background bgApplicationScope: CoroutineScope,
@Background backgroundCoroutineDispatcher: CoroutineDispatcher,
@@ -426,6 +435,56 @@
.map { it.resultSet }
}
+ private val decorationEvents: Flow<Event> = callbackFlow {
+ val callback =
+ object : CommandQueue.Callbacks {
+ override fun onDisplayAddSystemDecorations(displayId: Int) {
+ trySend(Event.Add(displayId))
+ }
+
+ override fun onDisplayRemoveSystemDecorations(displayId: Int) {
+ trySend(Event.Remove(displayId))
+ }
+ }
+ commandQueue.addCallback(callback)
+ awaitClose { commandQueue.removeCallback(callback) }
+ }
+
+ private val initialDisplayIdsWithDecorations: Set<Int> =
+ displayIds.value.filter { windowManager.shouldShowSystemDecors(it) }.toSet()
+
+ /**
+ * A [StateFlow] that maintains a set of display IDs that should have system decorations.
+ *
+ * Updates to the set are triggered by:
+ * - Adding displays via [CommandQueue.Callbacks.onDisplayAddSystemDecorations].
+ * - Removing displays via [CommandQueue.Callbacks.onDisplayRemoveSystemDecorations].
+ * - Removing displays via [displayRemovalEvent] emissions.
+ *
+ * The set is initialized with displays that qualify for system decorations based on
+ * [WindowManager.shouldShowSystemDecors].
+ */
+ override val displayIdsWithSystemDecorations: StateFlow<Set<Int>> =
+ merge(decorationEvents, displayRemovalEvent.map { Event.Remove(it) })
+ .scan(initialDisplayIdsWithDecorations) { displayIds: Set<Int>, event: Event ->
+ when (event) {
+ is Event.Add -> displayIds + event.displayId
+ is Event.Remove -> displayIds - event.displayId
+ }
+ }
+ .distinctUntilChanged()
+ .stateIn(
+ scope = bgApplicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = initialDisplayIdsWithDecorations,
+ )
+
+ private sealed class Event(val displayId: Int) {
+ class Add(displayId: Int) : Event(displayId)
+
+ class Remove(displayId: Int) : Event(displayId)
+ }
+
private companion object {
const val TAG = "DisplayRepository"
val DEBUG = Log.isLoggable(TAG, Log.DEBUG) || Compile.IS_DEBUG
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index efa9c21..cc0efbc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -241,7 +241,7 @@
* directly to the keyguard UI is posted to a {@link android.os.Handler} to ensure it is taken on the UI
* thread of the keyguard.
*/
-public class KeyguardViewMediator implements CoreStartable, Dumpable,
+public class KeyguardViewMediator implements CoreStartable,
StatusBarStateController.StateListener {
private static final boolean ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
index 7a4be1d..fc5914b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
@@ -22,6 +22,7 @@
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.systemui.Flags
import com.android.systemui.customization.R as customR
import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition
@@ -81,7 +82,7 @@
logger.logConstraintSet(cs, clockViewModel)
cs.applyTo(constraintLayout)
- if (com.android.systemui.shared.Flags.clockReactiveVariants()) {
+ if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
manuallySetDateWeatherConstraintsOnConstraintLayout(
cs,
constraintLayout,
@@ -110,7 +111,7 @@
}
logger.logConstraintSet(cs, clockViewModel)
cs.applyTo(constraintLayout)
- if (com.android.systemui.shared.Flags.clockReactiveVariants()) {
+ if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
manuallySetDateWeatherConstraintsOnConstraintLayout(
cs,
constraintLayout,
@@ -168,7 +169,7 @@
str1 = "${cs.getConstraint(smartspaceDateId).propertySet.alpha}"
}
- if (com.android.systemui.shared.Flags.clockReactiveVariants()) {
+ if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
this.i({ "applyCsToSmartspaceWeather: vis=${getVisText(int1)}; alpha=$str1" }) {
val smartspaceDateId = sharedR.id.weather_smartspace_view
int1 = cs.getVisibility(smartspaceDateId)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index b69df68..45801ba 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -89,7 +89,6 @@
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
@@ -189,7 +188,7 @@
viewModel.translationY.collect { y ->
childViews[burnInLayerId]?.translationY = y
childViews[largeClockId]?.translationY = y
- if (com.android.systemui.shared.Flags.clockReactiveVariants()) {
+ if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
childViews[largeClockDateId]?.translationY = y
childViews[largeClockWeatherId]?.translationY = y
}
@@ -381,17 +380,9 @@
if (wallpaperFocalAreaViewModel.hasFocalArea.value) {
launch {
wallpaperFocalAreaViewModel.wallpaperFocalAreaBounds.collect {
- wallpaperFocalAreaBounds ->
- wallpaperFocalAreaViewModel.setFocalAreaBounds(
- wallpaperFocalAreaBounds
- )
+ wallpaperFocalAreaViewModel.setFocalAreaBounds(it)
}
}
- launch {
- wallpaperFocalAreaViewModel.wallpaperFocalAreaBounds
- .filterNotNull()
- .collect { wallpaperFocalAreaViewModel.setFocalAreaBounds(it) }
- }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
index 76ece7d..def1ac8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
@@ -80,7 +80,7 @@
}
}
- if (com.android.systemui.shared.Flags.clockReactiveVariants()) {
+ if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
launch("$TAG#smartspaceViewModel.burnInLayerVisibility") {
keyguardRootViewModel.burnInLayerVisibility.collect { visibility ->
if (clockViewModel.isLargeClockVisible.value) {
@@ -147,7 +147,7 @@
val dateView =
constraintLayout.requireViewById<View>(sharedR.id.date_smartspace_view)
addView(dateView)
- if (com.android.systemui.shared.Flags.clockReactiveVariants()) {
+ if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
val weatherView =
constraintLayout.requireViewById<View>(sharedR.id.weather_smartspace_view)
addView(weatherView)
@@ -169,7 +169,7 @@
val dateView =
constraintLayout.requireViewById<View>(sharedR.id.date_smartspace_view)
removeView(dateView)
- if (com.android.systemui.shared.Flags.clockReactiveVariants()) {
+ if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
val weatherView =
constraintLayout.requireViewById<View>(sharedR.id.weather_smartspace_view)
removeView(weatherView)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
index 3d5dbb6d..8a33c64 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
@@ -121,19 +121,19 @@
setAlpha(getNonTargetClockFace(clock).views, 0F)
if (!keyguardClockViewModel.isLargeClockVisible.value) {
- if (com.android.systemui.shared.Flags.clockReactiveVariants()) {
+ if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
connect(
sharedR.id.bc_smartspace_view,
TOP,
customR.id.lockscreen_clock_view,
- BOTTOM
+ BOTTOM,
)
} else {
connect(
sharedR.id.bc_smartspace_view,
TOP,
sharedR.id.date_smartspace_view,
- BOTTOM
+ BOTTOM,
)
}
} else {
@@ -161,7 +161,7 @@
)
if (
rootViewModel.isNotifIconContainerVisible.value.value &&
- keyguardClockViewModel.hasAodIcons.value
+ keyguardClockViewModel.hasAodIcons.value
) {
createBarrier(
R.id.weather_clock_date_and_icons_barrier_bottom,
@@ -197,13 +197,13 @@
TOP,
)
val largeClockTopMargin =
- if (com.android.systemui.shared.Flags.clockReactiveVariants()) {
+ if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
keyguardClockViewModel.getLargeClockTopMargin() +
- getDimen(ENHANCED_SMARTSPACE_HEIGHT)
+ getDimen(ENHANCED_SMARTSPACE_HEIGHT)
} else {
keyguardClockViewModel.getLargeClockTopMargin() +
- getDimen(DATE_WEATHER_VIEW_HEIGHT) +
- getDimen(ENHANCED_SMARTSPACE_HEIGHT)
+ getDimen(DATE_WEATHER_VIEW_HEIGHT) +
+ getDimen(ENHANCED_SMARTSPACE_HEIGHT)
}
connect(
customR.id.lockscreen_clock_view_large,
@@ -229,9 +229,9 @@
PARENT_ID,
START,
context.resources.getDimensionPixelSize(customR.dimen.clock_padding_start) +
- context.resources.getDimensionPixelSize(
- customR.dimen.status_view_margin_horizontal
- ),
+ context.resources.getDimensionPixelSize(
+ customR.dimen.status_view_margin_horizontal
+ ),
)
val smallClockTopMargin = keyguardClockViewModel.getSmallClockTopMargin()
create(R.id.small_clock_guideline_top, ConstraintSet.HORIZONTAL_GUIDELINE)
@@ -243,18 +243,18 @@
val smallClockBottom =
keyguardClockViewModel.getSmallClockTopMargin() +
- context.resources.getDimensionPixelSize(customR.dimen.small_clock_height)
+ context.resources.getDimensionPixelSize(customR.dimen.small_clock_height)
val marginBetweenSmartspaceAndNotification =
context.resources.getDimensionPixelSize(
R.dimen.keyguard_status_view_bottom_margin
) +
- if (context.resources.getBoolean(R.bool.config_use_large_screen_shade_header)) {
- largeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight()
- } else {
- 0
- }
+ if (context.resources.getBoolean(R.bool.config_use_large_screen_shade_header)) {
+ largeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight()
+ } else {
+ 0
+ }
- if (com.android.systemui.shared.Flags.clockReactiveVariants()) {
+ if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
clockInteractor.setNotificationStackDefaultTop(
(smallClockBottom + marginBetweenSmartspaceAndNotification).toFloat()
)
@@ -263,8 +263,8 @@
getDimen(context, DATE_WEATHER_VIEW_HEIGHT).toFloat()
clockInteractor.setNotificationStackDefaultTop(
smallClockBottom +
- dateWeatherSmartspaceHeight +
- marginBetweenSmartspaceAndNotification
+ dateWeatherSmartspaceHeight +
+ marginBetweenSmartspaceAndNotification
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
index cbd80b4..37cc852f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
@@ -80,7 +80,7 @@
weatherView = smartspaceController.buildAndConnectWeatherView(constraintLayout, false)
dateView =
smartspaceController.buildAndConnectDateView(constraintLayout, false) as? ViewGroup
- if (com.android.systemui.shared.Flags.clockReactiveVariants()) {
+ if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
weatherViewLargeClock =
smartspaceController.buildAndConnectWeatherView(constraintLayout, true)
dateViewLargeClock =
@@ -88,7 +88,7 @@
}
pastVisibility = smartspaceView?.visibility ?: View.GONE
constraintLayout.addView(smartspaceView)
- if (com.android.systemui.shared.Flags.clockReactiveVariants()) {
+ if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
dateView?.visibility = View.GONE
weatherView?.visibility = View.GONE
dateViewLargeClock?.visibility = View.GONE
@@ -139,7 +139,7 @@
constraintSet.apply {
constrainHeight(sharedR.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT)
constrainWidth(sharedR.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT)
- if (!com.android.systemui.shared.Flags.clockReactiveVariants()) {
+ if (!com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
connect(
sharedR.id.date_smartspace_view,
ConstraintSet.START,
@@ -167,7 +167,7 @@
smartspaceHorizontalPadding,
)
if (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) {
- if (!com.android.systemui.shared.Flags.clockReactiveVariants()) {
+ if (!com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
clear(sharedR.id.date_smartspace_view, ConstraintSet.TOP)
connect(
sharedR.id.date_smartspace_view,
@@ -178,7 +178,7 @@
}
} else {
clear(sharedR.id.date_smartspace_view, ConstraintSet.BOTTOM)
- if (com.android.systemui.shared.Flags.clockReactiveVariants()) {
+ if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
connect(
sharedR.id.bc_smartspace_view,
ConstraintSet.TOP,
@@ -201,7 +201,7 @@
}
}
- if (com.android.systemui.shared.Flags.clockReactiveVariants()) {
+ if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
if (keyguardClockViewModel.isLargeClockVisible.value) {
setVisibility(sharedR.id.weather_smartspace_view, GONE)
setVisibility(sharedR.id.date_smartspace_view, GONE)
@@ -335,7 +335,7 @@
}
}
- if (com.android.systemui.shared.Flags.clockReactiveVariants()) {
+ if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
createBarrier(
R.id.smart_space_barrier_bottom,
Barrier.BOTTOM,
@@ -370,7 +370,7 @@
if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) return
val list =
- if (com.android.systemui.shared.Flags.clockReactiveVariants()) {
+ if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
listOf(
smartspaceView,
dateView,
@@ -401,7 +401,7 @@
val weatherId: Int
val dateId: Int
if (
- com.android.systemui.shared.Flags.clockReactiveVariants() &&
+ com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout() &&
keyguardClockViewModel.isLargeClockVisible.value
) {
weatherId = sharedR.id.weather_smartspace_view_large
@@ -420,7 +420,7 @@
setVisibility(dateId, if (showDateView) VISIBLE else GONE)
setAlpha(dateId, if (showDateView) 1f else 0f)
- if (com.android.systemui.shared.Flags.clockReactiveVariants()) {
+ if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
if (keyguardClockViewModel.isLargeClockVisible.value) {
setVisibility(sharedR.id.weather_smartspace_view, GONE)
setVisibility(sharedR.id.date_smartspace_view, GONE)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
index 67158e2..434d7ea 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
@@ -297,14 +297,14 @@
logger.e("No large clock set, falling back")
addTarget(customR.id.lockscreen_clock_view_large)
}
- if (com.android.systemui.shared.Flags.clockReactiveVariants()) {
+ if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
addTarget(sharedR.id.date_smartspace_view_large)
addTarget(sharedR.id.weather_smartspace_view_large)
}
} else {
logger.i("Adding small clock")
addTarget(customR.id.lockscreen_clock_view)
- if (com.android.systemui.shared.Flags.clockReactiveVariants()) {
+ if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
addTarget(sharedR.id.date_smartspace_view)
addTarget(sharedR.id.weather_smartspace_view)
}
@@ -386,7 +386,7 @@
duration =
if (isLargeClock) STATUS_AREA_MOVE_UP_MILLIS else STATUS_AREA_MOVE_DOWN_MILLIS
interpolator = Interpolators.EMPHASIZED
- if (!com.android.systemui.shared.Flags.clockReactiveVariants()) {
+ if (!com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
addTarget(sharedR.id.date_smartspace_view)
}
addTarget(sharedR.id.bc_smartspace_view)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/DefaultClockSteppingTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/DefaultClockSteppingTransition.kt
index 827c61e..0874b6d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/DefaultClockSteppingTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/DefaultClockSteppingTransition.kt
@@ -25,14 +25,12 @@
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.shared.R as sharedR
-class DefaultClockSteppingTransition(
- private val clock: ClockController,
-) : Transition() {
+class DefaultClockSteppingTransition(private val clock: ClockController) : Transition() {
init {
interpolator = Interpolators.LINEAR
duration = KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION_MS
addTarget(clock.largeClock.view)
- if (com.android.systemui.shared.Flags.clockReactiveVariants()) {
+ if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
addTarget(sharedR.id.date_smartspace_view_large)
addTarget(sharedR.id.weather_smartspace_view_large)
}
@@ -56,7 +54,7 @@
override fun createAnimator(
sceneRoot: ViewGroup,
startValues: TransitionValues?,
- endValues: TransitionValues?
+ endValues: TransitionValues?,
): Animator? {
if (startValues == null || endValues == null) {
return null
@@ -72,7 +70,7 @@
clock.largeClock.animations.onPositionUpdated(
fromLeft,
direction,
- animation.animatedFraction
+ animation.animatedFraction,
)
}
return anim
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaProcessingHelper.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaProcessingHelper.kt
index beb4d41..df0e1ad 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaProcessingHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaProcessingHelper.kt
@@ -22,6 +22,7 @@
import android.graphics.drawable.Icon
import android.media.session.MediaController
import android.media.session.PlaybackState
+import android.os.BadParcelableException
import android.util.Log
import com.android.systemui.Flags.mediaControlsPostsOptimization
import com.android.systemui.biometrics.Utils.toBitmap
@@ -109,7 +110,12 @@
}
if (firstAction.extras != null) {
firstAction.extras.keySet().forEach { key ->
- if (firstAction.extras[key] != secondAction.extras[key]) {
+ try {
+ if (firstAction.extras[key] != secondAction.extras[key]) {
+ return false
+ }
+ } catch (e: BadParcelableException) {
+ Log.e(TAG, "Cannot unparcel extras", e)
return false
}
}
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 34f7c4d..cedf661 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,10 +127,9 @@
}
holder.seekBar.setMax(data.duration)
- val totalTimeString =
- DateUtils.formatElapsedTime(data.duration / DateUtils.SECOND_IN_MILLIS)
+ val totalTimeDescription = data.durationDescription
if (data.scrubbing) {
- holder.scrubbingTotalTimeView.text = totalTimeString
+ holder.scrubbingTotalTimeView.text = formatTimeLabel(data.duration)
}
data.elapsedTime?.let {
@@ -148,20 +147,25 @@
}
}
- val elapsedTimeString = DateUtils.formatElapsedTime(it / DateUtils.SECOND_IN_MILLIS)
+ val elapsedTimeDescription = data.elapsedTimeDescription
if (data.scrubbing) {
- holder.scrubbingElapsedTimeView.text = elapsedTimeString
+ holder.scrubbingElapsedTimeView.text = formatTimeLabel(it)
}
holder.seekBar.contentDescription =
holder.seekBar.context.getString(
R.string.controls_media_seekbar_description,
- elapsedTimeString,
- totalTimeString
+ elapsedTimeDescription,
+ totalTimeDescription,
)
}
}
+ /** Returns a time string suitable for display, e.g. "12:34" */
+ private fun formatTimeLabel(milliseconds: Int): CharSequence {
+ return DateUtils.formatElapsedTime(milliseconds / DateUtils.SECOND_IN_MILLIS)
+ }
+
@VisibleForTesting
open fun buildResetAnimator(targetTime: Int): Animator {
val animator =
@@ -169,7 +173,7 @@
holder.seekBar,
"progress",
holder.seekBar.progress,
- targetTime + RESET_ANIMATION_DURATION_MS
+ targetTime + RESET_ANIMATION_DURATION_MS,
)
animator.setAutoCancel(true)
animator.duration = RESET_ANIMATION_DURATION_MS.toLong()
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 1e99697..a1f0cc3 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
@@ -16,11 +16,15 @@
package com.android.systemui.media.controls.ui.viewmodel
+import android.icu.text.MeasureFormat
+import android.icu.util.Measure
+import android.icu.util.MeasureUnit
import android.media.MediaMetadata
import android.media.session.MediaController
import android.media.session.PlaybackState
import android.os.SystemClock
import android.os.Trace
+import android.text.format.DateUtils
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
@@ -38,11 +42,14 @@
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.NotificationMediaManager
import com.android.systemui.util.concurrency.RepeatableExecutor
+import java.util.Locale
import javax.inject.Inject
import kotlin.math.abs
-private const val POSITION_UPDATE_INTERVAL_MILLIS = 100L
+private const val POSITION_UPDATE_INTERVAL_MILLIS = 500L
private const val MIN_FLING_VELOCITY_SCALE_FACTOR = 10
+private const val MIN_IN_SEC = 60
+private const val HOUR_IN_SEC = MIN_IN_SEC * 60
private const val TRACE_POSITION_NAME = "SeekBarPollingPosition"
@@ -97,11 +104,20 @@
)
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)
+ }
}
private val _progress = MutableLiveData<Progress>().apply { postValue(_data) }
@@ -253,7 +269,8 @@
playbackState?.state ?: PlaybackState.STATE_NONE
)
_data = Progress(enabled, seekAvailable, playing, scrubbing, position, duration, listening)
- checkIfPollingNeeded()
+ // No need to update since we just set the progress info
+ checkIfPollingNeeded(requireUpdate = false)
}
/**
@@ -311,8 +328,13 @@
}
}
+ /**
+ * Begin polling if needed given the current seekbar state
+ *
+ * @param requireUpdate If true, update the playback position without beginning polling
+ */
@WorkerThread
- private fun checkIfPollingNeeded() {
+ private fun checkIfPollingNeeded(requireUpdate: Boolean = true) {
val needed = listening && !scrubbing && playbackState?.isInMotion() ?: false
val traceCookie = controller?.sessionToken.hashCode()
if (needed) {
@@ -329,7 +351,7 @@
Trace.endAsyncSection(TRACE_POSITION_NAME, traceCookie)
}
}
- } else {
+ } else if (requireUpdate) {
checkPlaybackPosition()
cancel?.run()
cancel = null
@@ -399,6 +421,43 @@
abs(firstMotionEvent!!.y - lastMotionEvent!!.y)
}
+ /**
+ * Returns a time string suitable for content description, e.g. "12 minutes 34 seconds"
+ *
+ * Follows same logic as Chronometer#formatDuration
+ */
+ private fun formatTimeContentDescription(milliseconds: Int): CharSequence {
+ var seconds = milliseconds / DateUtils.SECOND_IN_MILLIS
+
+ val hours =
+ if (seconds >= HOUR_IN_SEC) {
+ seconds / HOUR_IN_SEC
+ } else {
+ 0
+ }
+ seconds -= hours * HOUR_IN_SEC
+
+ val minutes =
+ if (seconds >= MIN_IN_SEC) {
+ seconds / MIN_IN_SEC
+ } else {
+ 0
+ }
+ seconds -= minutes * MIN_IN_SEC
+
+ val measures = arrayListOf<Measure>()
+ if (hours > 0) {
+ measures.add(Measure(hours, MeasureUnit.HOUR))
+ }
+ if (minutes > 0) {
+ measures.add(Measure(minutes, MeasureUnit.MINUTE))
+ }
+ measures.add(Measure(seconds, MeasureUnit.SECOND))
+
+ return MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.WIDE)
+ .formatMeasures(*measures.toTypedArray())
+ }
+
/** Listener interface to be notified when the user starts or stops scrubbing. */
interface ScrubbingChangeListener {
fun onScrubbingChanged(scrubbing: Boolean)
@@ -580,5 +639,7 @@
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/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
index 6ca0471..469bec7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
@@ -24,7 +24,6 @@
import com.android.internal.logging.UiEventLogger
import com.android.internal.statusbar.IUndoMediaTransferCallback
import com.android.systemui.CoreStartable
-import com.android.systemui.Dumpable
import com.android.systemui.common.shared.model.Text
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
@@ -53,7 +52,7 @@
private val dumpManager: DumpManager,
private val logger: MediaTttSenderLogger,
private val uiEventLogger: MediaTttSenderUiEventLogger,
-) : CoreStartable, Dumpable {
+) : CoreStartable {
// Since the media transfer display is similar to a heads-up notification, use the same timeout.
private val defaultTimeout = context.resources.getInteger(R.integer.heads_up_notification_decay)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index e0b93fb..44c8dc3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -21,6 +21,7 @@
import android.graphics.PointF
import android.graphics.Rect
import android.os.Bundle
+import android.os.Trace
import android.util.IndentingPrintWriter
import android.view.LayoutInflater
import android.view.MotionEvent
@@ -112,6 +113,7 @@
import com.android.systemui.plugins.qs.QSContainerController
import com.android.systemui.qs.composefragment.SceneKeys.QuickQuickSettings
import com.android.systemui.qs.composefragment.SceneKeys.QuickSettings
+import com.android.systemui.qs.composefragment.SceneKeys.debugName
import com.android.systemui.qs.composefragment.SceneKeys.toIdleSceneKey
import com.android.systemui.qs.composefragment.ui.GridAnchor
import com.android.systemui.qs.composefragment.ui.NotificationScrimClipParams
@@ -285,6 +287,12 @@
*/
@Composable
private fun CollapsableQuickSettingsSTL() {
+ val nextCookie = remember {
+ object {
+ var value = 0
+ }
+ }
+ val transitionToCookie = remember { mutableMapOf<TransitionState.Transition, Int>() }
val sceneState =
rememberMutableSceneTransitionLayoutState(
initialScene = remember { viewModel.expansionState.toIdleSceneKey() },
@@ -298,6 +306,20 @@
toEditMode()
}
},
+ onTransitionStart = { transition ->
+ val cookie = nextCookie.value++
+ transitionToCookie[transition] = cookie
+ Trace.beginAsyncSection(
+ "CollapsableQuickSettingsSTL ${transition.debugName}",
+ cookie,
+ )
+ },
+ onTransitionEnd = { transition ->
+ Trace.endAsyncSection(
+ "CollapsableQuickSettingsSTL ${transition.debugName}",
+ transitionToCookie.remove(transition) ?: -1,
+ )
+ },
)
LaunchedEffect(Unit) {
@@ -854,6 +876,9 @@
val QuickSettings = SceneKey("QuickSettingsScene")
val EditMode = SceneKey("EditModeScene")
+ val TransitionState.Transition.debugName: String
+ get() = "[from=${fromContent.debugName}, to=${toContent.debugName}]"
+
fun QSFragmentComposeViewModel.QSExpansionState.toIdleSceneKey(): SceneKey {
return when {
progress < 0.5f -> QuickQuickSettings
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 934404d..05ef164 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -562,12 +562,15 @@
var userId: Int = lockScreenUserManager.getCurrentUserId()
var entry: NotificationEntry? = null
if (expandView is ExpandableNotificationRow) {
- entry = expandView.entry
expandView.setUserExpanded(/* userExpanded= */ true, /* allowChildExpansion= */ true)
// Indicate that the group expansion is changing at this time -- this way the group
// and children backgrounds / divider animations will look correct.
expandView.isGroupExpansionChanging = true
- userId = entry.sbn.userId
+ if (NotificationBundleUi.isEnabled) {
+ userId = expandView.entryAdapter?.sbn?.userId!!
+ } else {
+ userId = expandView.entry.sbn.userId
+ }
}
var fullShadeNeedsBouncer =
(!lockScreenUserManager.shouldShowLockscreenNotifications() ||
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/layout/LetterboxBackgroundProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/layout/LetterboxBackgroundProvider.kt
index 3d8ced1..a1536e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/layout/LetterboxBackgroundProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/layout/LetterboxBackgroundProvider.kt
@@ -23,7 +23,6 @@
import android.os.RemoteException
import android.view.IWindowManager
import com.android.systemui.CoreStartable
-import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -40,7 +39,7 @@
@Background private val backgroundExecutor: Executor,
private val wallpaperManager: WallpaperManager,
@Main private val mainHandler: Handler,
-) : CoreStartable, Dumpable {
+) : CoreStartable {
@ColorInt
var letterboxBackgroundColor: Int = Color.BLACK
private set
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
index f52b924..df8fb5e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
@@ -91,7 +91,7 @@
ImageSpan(it, ImageSpan.ALIGN_CENTER)
}
val decoratedSummary =
- SpannableString("x" + entry.ranking.summarization).apply {
+ SpannableString("x " + entry.ranking.summarization).apply {
setSpan(
/* what = */ imageSpan,
/* start = */ 0,
@@ -100,9 +100,9 @@
)
entry.ranking.summarization?.let {
setSpan(
- /* what = */ StyleSpan(Typeface.BOLD),
- /* start = */ 1,
- /* end = */ it.length,
+ /* what = */ StyleSpan(Typeface.ITALIC),
+ /* start = */ 2,
+ /* end = */ it.length + 2,
/* flags = */ Spanned.SPAN_EXCLUSIVE_INCLUSIVE,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
index ccfb43e..4053d06 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
@@ -25,6 +25,7 @@
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import com.android.wm.shell.bubbles.Bubbles;
import java.util.Optional;
@@ -99,8 +100,14 @@
row.setJustClicked(true);
DejankUtils.postAfterTraversal(() -> row.setJustClicked(false));
- if (!row.getEntry().isBubble() && mBubblesOptional.isPresent()) {
- mBubblesOptional.get().collapseStack();
+ if (NotificationBundleUi.isEnabled()) {
+ if (!row.getEntryAdapter().isBubbleCapable() && mBubblesOptional.isPresent()) {
+ mBubblesOptional.get().collapseStack();
+ }
+ } else {
+ if (!row.getEntry().isBubble() && mBubblesOptional.isPresent()) {
+ mBubblesOptional.get().collapseStack();
+ }
}
mNotificationActivityStarter.onNotificationClicked(entry, row);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java
index c79cae7..6dd44a1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java
@@ -24,6 +24,7 @@
import android.app.Notification;
import android.content.Context;
import android.os.Build;
+import android.service.notification.StatusBarNotification;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -163,6 +164,48 @@
// TODO(b/396446620): implement bundle icons
return null;
}
+
+ @Override
+ public boolean isColorized() {
+ return false;
+ }
+
+ @Override
+ @Nullable
+ public StatusBarNotification getSbn() {
+ return null;
+ }
+
+ @Override
+ public boolean canDragAndDrop() {
+ return false;
+ }
+
+ @Override
+ public boolean isBubbleCapable() {
+ return false;
+ }
+
+ @Override
+ @Nullable
+ public String getStyle() {
+ return null;
+ }
+
+ @Override
+ public int getSectionBucket() {
+ return mBucket;
+ }
+
+ @Override
+ public boolean isAmbient() {
+ return false;
+ }
+
+ @Override
+ public boolean isFullScreenCapable() {
+ return false;
+ }
}
public static final List<BundleEntry> ROOT_BUNDLES = List.of(
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 109ebe6..307a957 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
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.collection;
import android.content.Context;
+import android.service.notification.StatusBarNotification;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -109,4 +110,30 @@
* Returns whether the content of this entry is sensitive
*/
StateFlow<Boolean> isSensitive();
+
+ /**
+ * Returns whether this row has a background color set by an app
+ */
+ boolean isColorized();
+
+ /**
+ * Returns the SBN that backs this row, if present
+ */
+ @Nullable
+ StatusBarNotification getSbn();
+
+ boolean canDragAndDrop();
+
+ boolean isBubbleCapable();
+
+ @Nullable String getStyle();
+
+ int getSectionBucket();
+
+ boolean isAmbient();
+
+ default boolean isFullScreenCapable() {
+ return false;
+ }
}
+
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index fb2a66c..b19ba3a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -32,7 +32,6 @@
import static com.android.systemui.statusbar.notification.collection.BundleEntry.ROOT_BUNDLES;
import static com.android.systemui.statusbar.notification.collection.GroupEntry.ROOT_ENTRY;
import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_NOT_CANCELED;
-import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_ALERTING;
import static java.util.Objects.requireNonNull;
@@ -42,6 +41,7 @@
import android.app.Notification.MessagingStyle.Message;
import android.app.NotificationChannel;
import android.app.NotificationManager.Policy;
+import android.app.PendingIntent;
import android.app.Person;
import android.app.RemoteInput;
import android.app.RemoteInputHistoryItem;
@@ -79,7 +79,6 @@
import com.android.systemui.statusbar.notification.row.shared.NotificationContentModel;
import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor;
import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
-import com.android.systemui.statusbar.notification.stack.PriorityBucket;
import com.android.systemui.util.ListenerSet;
import kotlinx.coroutines.flow.MutableStateFlow;
@@ -183,7 +182,6 @@
new ListenerSet<>();
private boolean mPulseSupressed;
- private int mBucket = BUCKET_ALERTING;
private boolean mIsMarkedForUserTriggeredMovement;
private boolean mIsHeadsUpEntry;
@@ -353,6 +351,56 @@
public IconPack getIcons() {
return NotificationEntry.this.getIcons();
}
+
+ @Override
+ public boolean isColorized() {
+ return getSbn().getNotification().isColorized();
+ }
+
+ @Override
+ @Nullable
+ public StatusBarNotification getSbn() {
+ return NotificationEntry.this.getSbn();
+ }
+
+ @Override
+ public boolean canDragAndDrop() {
+ boolean canBubble = canBubble();
+ Notification notif = getSbn().getNotification();
+ PendingIntent dragIntent = notif.contentIntent != null ? notif.contentIntent
+ : notif.fullScreenIntent;
+ if (dragIntent != null && dragIntent.isActivity() && !canBubble) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isBubbleCapable() {
+ return NotificationEntry.this.isBubble();
+ }
+
+ @Override
+ @Nullable
+ public String getStyle() {
+ return getNotificationStyle();
+ }
+
+ @Override
+ public int getSectionBucket() {
+ return mBucket;
+ }
+
+ @Override
+ public boolean isAmbient() {
+ return mRanking.isAmbient();
+ }
+
+ @Override
+ public boolean isFullScreenCapable() {
+ return getSbn().getNotification().fullScreenIntent != null;
+ }
+
}
public EntryAdapter getEntryAdapter() {
@@ -560,15 +608,6 @@
return wasBubble != isBubble();
}
- @PriorityBucket
- public int getBucket() {
- return mBucket;
- }
-
- public void setBucket(@PriorityBucket int bucket) {
- mBucket = bucket;
- }
-
public ExpandableNotificationRow getRow() {
return row;
}
@@ -589,25 +628,45 @@
/**
* Get the children that are actually attached to this notification's row.
*
- * TODO: Seems like most callers here should probably be using
- * {@link GroupMembershipManager#getChildren(PipelineEntry)}
+ * TODO: Seems like most callers here should be asking a PipelineEntry, not a NotificationEntry
*/
public @Nullable List<NotificationEntry> getAttachedNotifChildren() {
- if (row == null) {
- return null;
+ if (NotificationBundleUi.isEnabled()) {
+ if (isGroupSummary()) {
+ return ((GroupEntry) getParent()).getChildren();
+ }
+ } else {
+ if (row == null) {
+ return null;
+ }
+
+ List<ExpandableNotificationRow> rowChildren = row.getAttachedChildren();
+ if (rowChildren == null) {
+ return null;
+ }
+
+ ArrayList<NotificationEntry> children = new ArrayList<>();
+ for (ExpandableNotificationRow child : rowChildren) {
+ children.add(child.getEntry());
+ }
+
+ return children;
+ }
+ return null;
+ }
+
+ private boolean isGroupSummary() {
+ if (getParent() == null) {
+ // The entry is not attached, so it doesn't count.
+ return false;
+ }
+ PipelineEntry pipelineEntry = getParent();
+ if (!(pipelineEntry instanceof GroupEntry groupEntry)) {
+ return false;
}
- List<ExpandableNotificationRow> rowChildren = row.getAttachedChildren();
- if (rowChildren == null) {
- return null;
- }
-
- ArrayList<NotificationEntry> children = new ArrayList<>();
- for (ExpandableNotificationRow child : rowChildren) {
- children.add(child.getEntry());
- }
-
- return children;
+ // If entry is a summary, its parent is a GroupEntry with summary = entry.
+ return groupEntry.getSummary() == this;
}
public void notifyFullScreenIntentLaunched() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineEntry.java
index 78652cc..84de77b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineEntry.java
@@ -16,10 +16,13 @@
package com.android.systemui.statusbar.notification.collection;
+import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_ALERTING;
+
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection;
+import com.android.systemui.statusbar.notification.stack.PriorityBucket;
/**
* Class to represent a notification, group, or bundle in the pipeline.
@@ -29,6 +32,7 @@
final String mKey;
final ListAttachState mAttachState = ListAttachState.create();
final ListAttachState mPreviousAttachState = ListAttachState.create();
+ protected int mBucket = BUCKET_ALERTING;
public PipelineEntry(String key) {
this.mKey = key;
@@ -86,4 +90,13 @@
final ListAttachState getPreviousAttachState() {
return mPreviousAttachState;
}
+
+ @PriorityBucket
+ public int getBucket() {
+ return mBucket;
+ }
+
+ public void setBucket(@PriorityBucket int bucket) {
+ mBucket = bucket;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpAnimator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpAnimator.kt
new file mode 100644
index 0000000..f9bd805
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpAnimator.kt
@@ -0,0 +1,63 @@
+/*
+ * 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.statusbar.notification.headsup
+
+import android.content.Context
+import com.android.systemui.res.R
+
+/**
+ * A class shared between [StackScrollAlgorithm] and [StackStateAnimator] to ensure all heads up
+ * animations use the same animation values.
+ */
+class HeadsUpAnimator(context: Context) {
+ init {
+ NotificationsHunSharedAnimationValues.assertInNewMode()
+ }
+
+ var headsUpAppearHeightBottom: Int = 0
+ var stackTopMargin: Int = 0
+
+ private var headsUpAppearStartAboveScreen = context.fetchHeadsUpAppearStartAboveScreen()
+
+ /**
+ * Returns the Y translation for a heads-up notification animation.
+ *
+ * For an appear animation, the returned Y translation should be the starting value of the
+ * animation. For a disappear animation, the returned Y translation should be the ending value
+ * of the animation.
+ */
+ fun getHeadsUpYTranslation(isHeadsUpFromBottom: Boolean): Int {
+ NotificationsHunSharedAnimationValues.assertInNewMode()
+
+ if (isHeadsUpFromBottom) {
+ // start from or end at the bottom of the screen
+ return headsUpAppearHeightBottom + headsUpAppearStartAboveScreen
+ }
+
+ // start from or end at the top of the screen
+ return -stackTopMargin - headsUpAppearStartAboveScreen
+ }
+
+ /** Should be invoked when resource values may have changed. */
+ fun updateResources(context: Context) {
+ headsUpAppearStartAboveScreen = context.fetchHeadsUpAppearStartAboveScreen()
+ }
+
+ private fun Context.fetchHeadsUpAppearStartAboveScreen(): Int {
+ return this.resources.getDimensionPixelSize(R.dimen.heads_up_appear_y_above_screen)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/NotificationsHunSharedAnimationValues.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/NotificationsHunSharedAnimationValues.kt
new file mode 100644
index 0000000..ca9d498
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/NotificationsHunSharedAnimationValues.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.statusbar.notification.headsup
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the notifications hun shared animation values flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object NotificationsHunSharedAnimationValues {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_NOTIFICATIONS_HUN_SHARED_ANIMATION_VALUES
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the refactor enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.notificationsHunSharedAnimationValues()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This will throw an exception if
+ * the flag is not enabled to ensure that the refactor author catches issues in testing.
+ * Caution!! Using this check incorrectly will cause crashes in nextfood builds!
+ */
+ @JvmStatic
+ inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
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 185e7fa..179951f 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
@@ -974,6 +974,7 @@
} else if (isAboveShelf() != wasAboveShelf) {
mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);
}
+ updateBackgroundOpacity();
}
/**
@@ -1678,10 +1679,15 @@
view.setBackgroundTintColor(color);
}
if (notificationRowTransparency()
- && (mBackgroundNormal != null)
- && (mEntry != null)) {
- mBackgroundNormal.setBgIsColorized(
- mEntry.getSbn().getNotification().isColorized());
+ && (mBackgroundNormal != null)) {
+ if (NotificationBundleUi.isEnabled()) {
+ mBackgroundNormal.setBgIsColorized(mEntryAdapter.isColorized());
+ } else {
+ if (mEntry != null) {
+ mBackgroundNormal.setBgIsColorized(
+ mEntry.getSbn().getNotification().isColorized());
+ }
+ }
}
}
@@ -2205,7 +2211,7 @@
R.dimen.notification_min_height);
}
mMaxSmallHeightWithSummarization = NotificationUtils.getFontScaledHeight(mContext,
- com.android.internal.R.dimen.notification_min_height);
+ com.android.internal.R.dimen.notification_collapsed_height_with_summarization);
mMaxExpandedHeight = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_max_height);
mMaxExpandedHeightForPromotedOngoing = NotificationUtils.getFontScaledHeight(mContext,
@@ -2373,7 +2379,11 @@
return traceTag;
}
- return traceTag + "(" + getEntry().getNotificationStyle() + ")";
+ if (NotificationBundleUi.isEnabled()) {
+ return traceTag + "(" + getEntryAdapter().getStyle() + ")";
+ } else {
+ return traceTag + "(" + getEntry().getNotificationStyle() + ")";
+ }
}
@Override
@@ -3067,6 +3077,7 @@
mChildrenContainer.setOnKeyguard(onKeyguard);
}
}
+ updateBackgroundOpacity();
}
}
@@ -3696,8 +3707,14 @@
return true;
}
// The colorized background is another layer with which all other elements overlap
- if (getEntry().getSbn().getNotification().isColorized()) {
- return true;
+ if (NotificationBundleUi.isEnabled()) {
+ if (mEntryAdapter.isColorized()) {
+ return true;
+ }
+ } else {
+ if (getEntry().getSbn().getNotification().isColorized()) {
+ return true;
+ }
}
// Check if the showing layout has a need for overlapping rendering.
// NOTE: We could check both public and private layouts here, but becuause these states
@@ -4506,4 +4523,12 @@
return getEntry().isExpandAnimationRunning();
}
}
+
+ private void updateBackgroundOpacity() {
+ if (mBackgroundNormal != null) {
+ // Row background should be opaque when it's displayed as a heads-up notification or
+ // displayed on keyguard.
+ mBackgroundNormal.setForceOpaque(mIsHeadsUp || mOnKeyguard);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index 07711b6..02e8f49 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -127,7 +127,14 @@
@Override
public void onSettingChanged(Uri setting, int userId, String value) {
if (BUBBLES_SETTING_URI.equals(setting)) {
- final int viewUserId = mView.getEntry().getSbn().getUserId();
+ if (NotificationBundleUi.isEnabled()
+ && mView.getEntryAdapter().getSbn() == null) {
+ // only valid for notification rows
+ return;
+ }
+ final int viewUserId = NotificationBundleUi.isEnabled()
+ ? mView.getEntryAdapter().getSbn().getUserId()
+ : mView.getEntry().getSbn().getUserId();
if (viewUserId == UserHandle.USER_ALL || viewUserId == userId) {
mView.getPrivateLayout().setBubblesEnabledForUser(
BUBBLES_SETTING_ENABLED_VALUE.equals(value));
@@ -376,8 +383,12 @@
public void onViewAttachedToWindow(View v) {
if (NotificationBundleUi.isEnabled()) {
mView.setInitializationTime(mClock.elapsedRealtime());
+ if (mView.getEntryAdapter().getSbn() != null) {
+ mSettingsController.addCallback(BUBBLES_SETTING_URI, mSettingsListener);
+ }
} else {
mView.getEntry().setInitializationTime(mClock.elapsedRealtime());
+ mSettingsController.addCallback(BUBBLES_SETTING_URI, mSettingsListener);
}
mPluginManager.addPluginListener(mView,
NotificationMenuRowPlugin.class, false /* Allow multiple */);
@@ -385,7 +396,7 @@
mView.setOnKeyguard(mStatusBarStateController.getState() == KEYGUARD);
mStatusBarStateController.addCallback(mStatusBarStateListener);
}
- mSettingsController.addCallback(BUBBLES_SETTING_URI, mSettingsListener);
+
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
index d5551b1..9ae2eb1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
@@ -54,6 +54,7 @@
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger;
import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import javax.inject.Inject;
@@ -100,7 +101,15 @@
enr = (ExpandableNotificationRow) view;
}
- StatusBarNotification sn = enr.getEntry().getSbn();
+ if (NotificationBundleUi.isEnabled()) {
+ if (!enr.getEntryAdapter().canDragAndDrop()) {
+ return;
+ }
+ }
+
+ StatusBarNotification sn = NotificationBundleUi.isEnabled()
+ ? enr.getEntryAdapter().getSbn()
+ : enr.getEntry().getSbn();
Notification notification = sn.getNotification();
final PendingIntent contentIntent = notification.contentIntent != null
? notification.contentIntent
@@ -115,8 +124,7 @@
.show();
return;
}
- Bitmap iconBitmap = getBitmapFromDrawable(
- getPkgIcon(enr.getEntry().getSbn().getPackageName()));
+ Bitmap iconBitmap = getBitmapFromDrawable(getPkgIcon(sn.getPackageName()));
final ImageView snapshot = new ImageView(mContext);
snapshot.setImageBitmap(iconBitmap);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java
index 344d0f6..ba80f01 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java
@@ -51,6 +51,7 @@
*/
public class HybridConversationNotificationView extends HybridNotificationView {
+ private static final int MAX_SUMMARIZATION_LINES = 2;
private ImageView mConversationIconView;
private TextView mConversationSenderName;
private ViewStub mConversationFacePileStub;
@@ -292,11 +293,14 @@
@Nullable CharSequence summarization
) {
if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) return;
- if (summarization != null) {
+ if (!TextUtils.isEmpty(summarization)) {
mConversationSenderName.setVisibility(GONE);
titleText = null;
contentText = summarization;
+ mTextView.setSingleLine(false);
+ mTextView.setMaxLines(MAX_SUMMARIZATION_LINES);
} else {
+ mTextView.setSingleLine(true);
if (conversationSenderName == null) {
mConversationSenderName.setVisibility(GONE);
} else {
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 c0bc132..4978fa4 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
@@ -53,6 +53,7 @@
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;
@@ -74,6 +75,7 @@
private final ColorStateList mDarkColoredStatefulColors;
private final int mNormalColor;
private boolean mBgIsColorized = false;
+ private boolean mForceOpaque = false;
private final int convexR = 9;
private final int concaveR = 22;
@@ -156,6 +158,14 @@
mBgIsColorized = b;
}
+ /** Sets if the background should be opaque. */
+ public void setForceOpaque(boolean forceOpaque) {
+ mForceOpaque = forceOpaque;
+ if (notificationRowTransparency()) {
+ updateBaseLayerColor();
+ }
+ }
+
private Path calculateDismissButtonCutoutPath(Rect backgroundBounds) {
// TODO(b/365585705): Adapt to RTL after the UX design is finalized.
@@ -317,11 +327,15 @@
// 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))
+ : SurfaceEffectColors.surfaceEffect1(getContext());
+ if (mForceOpaque) {
+ color = ColorUtils.setAlphaComponent(color, MAX_ALPHA);
+ }
getBaseBackgroundLayer().setColorFilter(
new PorterDuffColorFilter(
- isColorized()
- ? ColorUtils.setAlphaComponent(mTintColor, (int) (255 * 0.9f))
- : SurfaceEffectColors.surfaceEffect1(getContext()),
+ color,
PorterDuff.Mode.SRC)); // SRC operator discards the drawable's color+alpha
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 7444679..daa598b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -70,6 +70,7 @@
import com.android.systemui.statusbar.notification.row.ui.viewbinder.SingleLineViewBinder;
import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleLineViewModel;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
@@ -468,7 +469,10 @@
if (LockscreenOtpRedaction.isEnabled()
&& bindParams.redactionType == REDACTION_TYPE_SENSITIVE_CONTENT) {
result.newPublicView = createSensitiveContentMessageNotification(
- row.getEntry().getSbn().getNotification(), builder.getStyle(),
+ NotificationBundleUi.isEnabled()
+ ? row.getEntryAdapter().getSbn().getNotification()
+ : row.getEntry().getSbn().getNotification(),
+ builder.getStyle(),
systemUiContext, packageContext).createContentView();
} else {
result.newPublicView = builder.makePublicContentView(bindParams.isMinimized);
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 0d29981..1932037 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
@@ -64,6 +64,7 @@
import com.android.systemui.statusbar.notification.row.wrapper.NotificationCustomViewWrapper;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationHeaderViewWrapper;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import com.android.systemui.statusbar.phone.ExpandHeadsUpOnInlineReply;
import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
import com.android.systemui.statusbar.policy.InflatedSmartReplyViewHolder;
@@ -594,12 +595,11 @@
if (mContainingNotification == null) {
return null;
}
- final NotificationEntry entry = mContainingNotification.getEntry();
- if (entry == null) {
- return null;
+ if (NotificationBundleUi.isEnabled()) {
+ return mContainingNotification.getEntryAdapter().getSbn();
+ } else {
+ return mContainingNotification.getEntry().getSbn();
}
-
- return entry.getSbn();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
index c930dd8..3586078 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.row
import android.annotation.SuppressLint
+import android.app.Flags
import android.app.Notification
import android.app.Notification.EXTRA_SUMMARIZED_CONTENT
import android.app.Notification.MessagingStyle
@@ -49,10 +50,10 @@
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor
import com.android.systemui.statusbar.notification.InflationException
import com.android.systemui.statusbar.notification.NmSummarizationUiFlag
-import com.android.systemui.statusbar.notification.collection.EntryAdapter
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.logKey
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED
import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_EXPANDED
@@ -1521,6 +1522,10 @@
entry.promotedNotificationContentModel = result.promotedContent
}
+ if (PromotedNotificationUiForceExpanded.isEnabled) {
+ row.setPromotedOngoing(entry.isOngoingPromoted())
+ }
+
result.inflatedSmartReplyState?.let { row.privateLayout.setInflatedSmartReplyState(it) }
setContentViewsFromRemoteViews(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationRowIconViewInflaterFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationRowIconViewInflaterFactory.kt
index bb4aa86..4082a5b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationRowIconViewInflaterFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationRowIconViewInflaterFactory.kt
@@ -26,6 +26,7 @@
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.NotifRemoteViewsFactory
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi
import javax.inject.Inject
/**
@@ -60,7 +61,18 @@
row: ExpandableNotificationRow,
context: Context,
): NotificationIconProvider {
- val sbn = row.entry.sbn
+ val sbn = if (NotificationBundleUi.isEnabled) row.entryAdapter?.sbn else row.entry.sbn
+ if (sbn == null) {
+ return object : NotificationIconProvider {
+ override fun shouldShowAppIcon(): Boolean {
+ return false
+ }
+
+ override fun getAppIcon(): Drawable? {
+ return null
+ }
+ }
+ }
return object : NotificationIconProvider {
override fun shouldShowAppIcon(): Boolean {
val shouldShowAppIcon = iconStyleProvider.shouldShowAppIcon(sbn, context)
@@ -68,7 +80,7 @@
return shouldShowAppIcon
}
- override fun getAppIcon(): Drawable {
+ override fun getAppIcon(): Drawable? {
val withWorkProfileBadge =
iconStyleProvider.shouldShowWorkProfileBadge(sbn, context)
return appIconProvider.getOrFetchAppIcon(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java
index 990adf7..f492b25 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java
@@ -30,6 +30,7 @@
import com.android.systemui.statusbar.notification.ImageTransformState;
import com.android.systemui.statusbar.notification.row.BigPictureIconManager;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
/**
* Wraps a notification containing a big picture template
@@ -47,7 +48,9 @@
public void onContentUpdated(ExpandableNotificationRow row) {
super.onContentUpdated(row);
resolveViews();
- updateImageTag(row.getEntry().getSbn());
+ updateImageTag(NotificationBundleUi.isEnabled()
+ ? row.getEntryAdapter().getSbn()
+ : row.getEntry().getSbn());
}
private void resolveViews() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigTextTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigTextTemplateViewWrapper.java
index d58c183..dec674c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigTextTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigTextTemplateViewWrapper.java
@@ -23,6 +23,7 @@
import com.android.internal.widget.ImageFloatingTextView;
import com.android.systemui.statusbar.TransformableView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
/**
* Wraps a notification containing a big text template
@@ -44,7 +45,9 @@
public void onContentUpdated(ExpandableNotificationRow row) {
// Reinspect the notification. Before the super call, because the super call also updates
// the transformation types and we need to have our values set by then.
- resolveViews(row.getEntry().getSbn());
+ resolveViews(NotificationBundleUi.isEnabled()
+ ? row.getEntryAdapter().getSbn()
+ : row.getEntry().getSbn());
super.onContentUpdated(row);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index e9eecdd8..585051a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -52,6 +52,7 @@
import com.android.systemui.statusbar.notification.TransformState;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.shared.NotificationAddXOnHoverToDismiss;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import java.util.Stack;
@@ -222,7 +223,9 @@
@Override
public void onContentUpdated(ExpandableNotificationRow row) {
super.onContentUpdated(row);
- mIsLowPriority = row.getEntry().isAmbient();
+ mIsLowPriority = NotificationBundleUi.isEnabled()
+ ? row.getEntryAdapter().isAmbient()
+ : row.getEntry().isAmbient();
mTransformLowPriorityTitle = !row.isChildInGroup() && !row.isSummaryWithChildren();
ArraySet<View> previousViews = mTransformationHelper.getAllTransformingViews();
@@ -231,7 +234,9 @@
updateTransformedTypes();
addRemainingTransformTypes();
updateCropToPaddingForImageViews();
- Notification n = row.getEntry().getSbn().getNotification();
+ Notification n = NotificationBundleUi.isEnabled()
+ ? row.getEntryAdapter().getSbn().getNotification()
+ : row.getEntry().getSbn().getNotification();
mIcon.setTag(ImageTransformState.ICON_TAG, n.getSmallIcon());
// We need to reset all views that are no longer transforming in case a view was previously
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java
index b9aa571..4146a94 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java
@@ -54,6 +54,7 @@
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.HybridNotificationView;
import com.android.systemui.util.DimensionKt;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import java.util.function.Consumer;
@@ -310,7 +311,9 @@
public void onContentUpdated(ExpandableNotificationRow row) {
// Reinspect the notification. Before the super call, because the super call also updates
// the transformation types and we need to have our values set by then.
- resolveTemplateViews(row.getEntry().getSbn());
+ resolveTemplateViews(NotificationBundleUi.isEnabled()
+ ? row.getEntryAdapter().getSbn()
+ : row.getEntry().getSbn());
super.onContentUpdated(row);
// With the modern templates, a large icon visually overlaps the header, so we can't
// hide the header, we must show it.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
index 3987ca6..64babb2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
@@ -80,7 +80,10 @@
return new NotificationProgressTemplateViewWrapper(ctx, v, row);
}
- if (row.getEntry().getSbn().getNotification().isStyle(
+ if (NotificationBundleUi.isEnabled()
+ ? row.getEntryAdapter().getSbn().getNotification().isStyle(
+ Notification.DecoratedCustomViewStyle.class)
+ : row.getEntry().getSbn().getNotification().isStyle(
Notification.DecoratedCustomViewStyle.class)) {
return new NotificationDecoratedCustomViewWrapper(ctx, v, row);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 048958e..e830d18 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -60,6 +60,7 @@
import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationHeaderViewWrapper;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import java.util.ArrayList;
import java.util.List;
@@ -421,7 +422,12 @@
Trace.beginSection("NotifChildCont#recreateHeader");
mHeaderClickListener = listener;
mIsConversation = isConversation;
- StatusBarNotification notification = mContainingNotification.getEntry().getSbn();
+ StatusBarNotification notification = NotificationBundleUi.isEnabled()
+ ? mContainingNotification.getEntryAdapter().getSbn()
+ : mContainingNotification.getEntry().getSbn();
+ if (notification == null) {
+ return;
+ }
final Notification.Builder builder = Notification.Builder.recoverBuilder(getContext(),
notification.getNotification());
Trace.beginSection("recreateHeader#makeNotificationGroupHeader");
@@ -565,7 +571,12 @@
void recreateLowPriorityHeader(Notification.Builder builder) {
AsyncGroupHeaderViewInflation.assertInLegacyMode();
RemoteViews header;
- StatusBarNotification notification = mContainingNotification.getEntry().getSbn();
+ StatusBarNotification notification = NotificationBundleUi.isEnabled()
+ ? mContainingNotification.getEntryAdapter().getSbn()
+ : mContainingNotification.getEntry().getSbn();
+ if (notification == null) {
+ return;
+ }
if (mIsMinimized) {
if (builder == null) {
builder = Notification.Builder.recoverBuilder(getContext(),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
index 3d8fe01..96f0e6f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
@@ -35,6 +35,7 @@
import com.android.systemui.statusbar.notification.dagger.SocialHeader
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi
import com.android.systemui.statusbar.notification.stack.PriorityBucket
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.SectionProvider
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -164,7 +165,9 @@
view === socialHeaderView -> BUCKET_SOCIAL
view === recsHeaderView -> BUCKET_RECS
view === promoHeaderView -> BUCKET_PROMO
- view is ExpandableNotificationRow -> view.entry.bucket
+ view is ExpandableNotificationRow ->
+ if (NotificationBundleUi.isEnabled) view.entryAdapter?.sectionBucket
+ else view.entry.bucket
else -> null
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index e7a77eb..4390f1b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -117,8 +117,10 @@
import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix;
import com.android.systemui.statusbar.notification.emptyshade.ui.view.EmptyShadeView;
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpAnimator;
import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper;
import com.android.systemui.statusbar.notification.headsup.HeadsUpUtil;
+import com.android.systemui.statusbar.notification.headsup.NotificationsHunSharedAnimationValues;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -233,6 +235,8 @@
private String mLastInitViewDumpString;
private long mLastInitViewElapsedRealtime;
+ @Nullable
+ private final HeadsUpAnimator mHeadsUpAnimator;
/**
* The algorithm which calculates the properties for our children
*/
@@ -668,8 +672,13 @@
mExpandHelper.setEventSource(this);
mExpandHelper.setScrollAdapter(mScrollAdapter);
- mStackScrollAlgorithm = createStackScrollAlgorithm(context);
- mStateAnimator = new StackStateAnimator(context, this);
+ if (NotificationsHunSharedAnimationValues.isEnabled()) {
+ mHeadsUpAnimator = new HeadsUpAnimator(context);
+ } else {
+ mHeadsUpAnimator = null;
+ }
+ mStackScrollAlgorithm = new StackScrollAlgorithm(context, this, mHeadsUpAnimator);
+ mStateAnimator = new StackStateAnimator(context, this, mHeadsUpAnimator);
setOutlineProvider(mOutlineProvider);
// We could set this whenever we 'requestChildUpdate' much like the viewTreeObserver, but
@@ -1024,12 +1033,18 @@
|| !(view instanceof ExpandableNotificationRow row)) {
continue;
}
+ int bucket = NotificationBundleUi.isEnabled()
+ ? row.getEntryAdapter().getSectionBucket()
+ : row.getEntry().getBucket();
+ boolean isAmbient = NotificationBundleUi.isEnabled()
+ ? row.getEntryAdapter().isAmbient()
+ : row.getEntry().isAmbient();
currentIndex++;
boolean beforeSpeedBump;
if (mHighPriorityBeforeSpeedBump) {
- beforeSpeedBump = row.getEntry().getBucket() < BUCKET_SILENT;
+ beforeSpeedBump = bucket < BUCKET_SILENT;
} else {
- beforeSpeedBump = !row.getEntry().isAmbient();
+ beforeSpeedBump = !isAmbient;
}
if (beforeSpeedBump) {
speedBumpIndex = currentIndex;
@@ -3582,10 +3597,6 @@
mGoToFullShadeNeedsAnimation = false;
}
- protected StackScrollAlgorithm createStackScrollAlgorithm(Context context) {
- return new StackScrollAlgorithm(context, this);
- }
-
/**
* @return Whether a y coordinate is inside the content.
*/
@@ -5111,9 +5122,16 @@
public void setHeadsUpBoundaries(int height, int bottomBarHeight) {
SceneContainerFlag.assertInLegacyMode();
mAmbientState.setMaxHeadsUpTranslation(height - bottomBarHeight);
- mStackScrollAlgorithm.setHeadsUpAppearHeightBottom(height);
- mStateAnimator.setHeadsUpAppearHeightBottom(height);
- mStateAnimator.setStackTopMargin(mAmbientState.getStackTopMargin());
+
+ if (NotificationsHunSharedAnimationValues.isEnabled()) {
+ mHeadsUpAnimator.setHeadsUpAppearHeightBottom(height);
+ mHeadsUpAnimator.setStackTopMargin(mAmbientState.getStackTopMargin());
+ } else {
+ mStackScrollAlgorithm.setHeadsUpAppearHeightBottom(height);
+ mStateAnimator.setHeadsUpAppearHeightBottom(height);
+ mStateAnimator.setStackTopMargin(mAmbientState.getStackTopMargin());
+ }
+
requestChildrenUpdate();
}
@@ -6423,13 +6441,16 @@
static boolean matchesSelection(
ExpandableNotificationRow row,
@SelectedRows int selection) {
+ int bucket = NotificationBundleUi.isEnabled()
+ ? row.getEntryAdapter().getSectionBucket()
+ : row.getEntry().getBucket();
switch (selection) {
case ROWS_ALL:
return true;
case ROWS_HIGH_PRIORITY:
- return row.getEntry().getBucket() < BUCKET_SILENT;
+ return bucket < BUCKET_SILENT;
case ROWS_GENTLE:
- return row.getEntry().getBucket() == BUCKET_SILENT;
+ return bucket == BUCKET_SILENT;
default:
throw new IllegalArgumentException("Unknown selection: " + selection);
}
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 4459430..124e6f5 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
@@ -122,6 +122,7 @@
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.row.NotificationSnooze;
import com.android.systemui.statusbar.notification.shared.GroupHunAnimationFix;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -639,8 +640,10 @@
mView.onSwipeEnd();
if (animView instanceof ExpandableNotificationRow row) {
if (row.isPinned() && !canChildBeDismissed(row)
- && row.getEntry().getSbn().getNotification().fullScreenIntent
- == null) {
+ && NotificationBundleUi.isEnabled()
+ ? !row.getEntryAdapter().isFullScreenCapable()
+ : (row.getEntry().getSbn().getNotification().fullScreenIntent
+ == null)) {
mHeadsUpManager.removeNotification(
row.getKey(),
/* removeImmediately= */ true,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
index 08bc8f5..4e91680 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
@@ -32,6 +32,7 @@
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi
import com.android.systemui.statusbar.notification.shared.NotificationMinimalism
import com.android.systemui.statusbar.policy.SplitShadeStateController
import com.android.systemui.util.Compile
@@ -407,8 +408,14 @@
}
if (counter != null) {
- val entry = (currentNotification as? ExpandableNotificationRow)?.entry
- counter.incrementForBucket(entry?.bucket)
+ if (NotificationBundleUi.isEnabled) {
+ val entry = (currentNotification as? ExpandableNotificationRow)?.entry
+ counter.incrementForBucket(entry?.bucket)
+ } else {
+ val entryAdapter =
+ (currentNotification as? ExpandableNotificationRow)?.entryAdapter
+ counter.incrementForBucket(entryAdapter?.sectionBucket)
+ }
}
log {
@@ -461,12 +468,15 @@
val height = view.heightWithoutLockscreenConstraints.toFloat()
val gapAndDividerHeight =
calculateGapAndDividerHeight(stack, previousView, current = view, visibleIndex)
+ val canPeek = view is ExpandableNotificationRow &&
+ if (NotificationBundleUi.isEnabled) view.entryAdapter?.canPeek() == true
+ else view.entry.isStickyAndNotDemoted
var size =
if (onLockscreen) {
if (
view is ExpandableNotificationRow &&
- (view.entry.isStickyAndNotDemoted ||
+ (canPeek ||
(PromotedNotificationUiForceExpanded.isEnabled &&
view.isPromotedOngoing))
) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 88d3ad8..4effb76 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -36,9 +36,12 @@
import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.emptyshade.ui.view.EmptyShadeView;
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpAnimator;
+import com.android.systemui.statusbar.notification.headsup.NotificationsHunSharedAnimationValues;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import com.android.systemui.statusbar.notification.shared.NotificationHeadsUpCycling;
import java.util.ArrayList;
@@ -56,6 +59,9 @@
private static final String TAG = "StackScrollAlgorithm";
private static final SourceType STACK_SCROLL_ALGO = SourceType.from("StackScrollAlgorithm");
private final ViewGroup mHostView;
+ @Nullable
+ private final HeadsUpAnimator mHeadsUpAnimator;
+
private float mPaddingBetweenElements;
private float mGapHeight;
private float mGapHeightOnLockscreen;
@@ -78,8 +84,12 @@
private int mHeadsUpAppearHeightBottom;
private int mHeadsUpCyclingPadding;
- public StackScrollAlgorithm(Context context, ViewGroup hostView) {
+ public StackScrollAlgorithm(
+ Context context,
+ ViewGroup hostView,
+ @Nullable HeadsUpAnimator headsUpAnimator) {
mHostView = hostView;
+ mHeadsUpAnimator = headsUpAnimator;
initView(context);
}
@@ -111,6 +121,9 @@
mQuickQsOffsetHeight = SystemBarUtils.getQuickQsOffsetHeight(context);
mSmallCornerRadius = res.getDimension(R.dimen.notification_corner_radius_small);
mLargeCornerRadius = res.getDimension(R.dimen.notification_corner_radius);
+ if (NotificationsHunSharedAnimationValues.isEnabled()) {
+ mHeadsUpAnimator.updateResources(context);
+ }
}
/**
@@ -250,6 +263,7 @@
}
public void setHeadsUpAppearHeightBottom(int headsUpAppearHeightBottom) {
+ NotificationsHunSharedAnimationValues.assertInLegacyMode();
mHeadsUpAppearHeightBottom = headsUpAppearHeightBottom;
}
@@ -911,7 +925,9 @@
if (SceneContainerFlag.isEnabled()) {
if (shouldHunBeVisibleWhenScrolled(row.isHeadsUp(),
childState.headsUpIsVisible, row.showingPulsing(),
- ambientState.isOnKeyguard(), row.getEntry().isStickyAndNotDemoted())) {
+ ambientState.isOnKeyguard(), NotificationBundleUi.isEnabled()
+ ? row.getEntryAdapter().canPeek()
+ : row.getEntry().isStickyAndNotDemoted())) {
// the height of this child before clamping it to the top
float unmodifiedChildHeight = childState.height;
clampHunToTop(
@@ -963,7 +979,9 @@
} else {
if (shouldHunBeVisibleWhenScrolled(row.mustStayOnScreen(),
childState.headsUpIsVisible, row.showingPulsing(),
- ambientState.isOnKeyguard(), row.getEntry().isStickyAndNotDemoted())) {
+ ambientState.isOnKeyguard(), NotificationBundleUi.isEnabled()
+ ? row.getEntryAdapter().canPeek()
+ : row.getEntry().isStickyAndNotDemoted())) {
// Ensure that the heads up is always visible even when scrolled off.
// NSSL y starts at top of screen in non-split-shade, but below the qs
// offset
@@ -1037,14 +1055,22 @@
childState.setYTranslation(inSpaceTranslation + extraTranslation);
cyclingInHunHeight = -1;
} else if (!ambientState.isDozing()) {
- if (shouldHunAppearFromBottom(ambientState, childState)) {
- // move to the bottom of the screen
- childState.setYTranslation(
- mHeadsUpAppearHeightBottom + mHeadsUpAppearStartAboveScreen);
+ boolean shouldHunAppearFromBottom =
+ shouldHunAppearFromBottom(ambientState, childState);
+ if (NotificationsHunSharedAnimationValues.isEnabled()) {
+ int yTranslation =
+ mHeadsUpAnimator.getHeadsUpYTranslation(shouldHunAppearFromBottom);
+ childState.setYTranslation(yTranslation);
} else {
- // move to the top of the screen
- childState.setYTranslation(-ambientState.getStackTopMargin()
- - mHeadsUpAppearStartAboveScreen);
+ if (shouldHunAppearFromBottom) {
+ // move to the bottom of the screen
+ childState.setYTranslation(
+ mHeadsUpAppearHeightBottom + mHeadsUpAppearStartAboveScreen);
+ } else {
+ // move to the top of the screen
+ childState.setYTranslation(-ambientState.getStackTopMargin()
+ - mHeadsUpAppearStartAboveScreen);
+ }
}
} else {
// Make sure row yTranslation is at maximum the HUN yTranslation,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index 4da418e..9a5cf9c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -27,6 +27,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
+import android.annotation.Nullable;
import android.content.Context;
import android.util.Property;
import android.view.View;
@@ -39,6 +40,8 @@
import com.android.systemui.shared.clocks.AnimatableClockView;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.notification.PhysicsPropertyAnimator;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpAnimator;
+import com.android.systemui.statusbar.notification.headsup.NotificationsHunSharedAnimationValues;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
@@ -83,6 +86,9 @@
private final ExpandableViewState mTmpState = new ExpandableViewState();
private final AnimationProperties mAnimationProperties;
public NotificationStackScrollLayout mHostLayout;
+ @Nullable
+ private final HeadsUpAnimator mHeadsUpAnimator;
+
private ArrayList<NotificationStackScrollLayout.AnimationEvent> mNewEvents =
new ArrayList<>();
private ArrayList<View> mNewAddChildren = new ArrayList<>();
@@ -104,8 +110,12 @@
private NotificationShelf mShelf;
private StackStateLogger mLogger;
- public StackStateAnimator(Context context, NotificationStackScrollLayout hostLayout) {
+ public StackStateAnimator(
+ Context context,
+ NotificationStackScrollLayout hostLayout,
+ @Nullable HeadsUpAnimator headsUpAnimator) {
mHostLayout = hostLayout;
+ mHeadsUpAnimator = headsUpAnimator;
initView(context);
mAnimationProperties = new AnimationProperties() {
@@ -543,7 +553,6 @@
mHeadsUpAppearChildren.add(changingView);
mTmpState.copyFrom(changingView.getViewState());
- // translate the HUN in from the top, or the bottom of the screen
mTmpState.setYTranslation(getHeadsUpYTranslationStart(event.headsUpFromBottom));
// set the height and the initial position
mTmpState.applyToView(changingView);
@@ -728,6 +737,10 @@
}
private float getHeadsUpYTranslationStart(boolean headsUpFromBottom) {
+ if (NotificationsHunSharedAnimationValues.isEnabled()) {
+ return mHeadsUpAnimator.getHeadsUpYTranslation(headsUpFromBottom);
+ }
+
if (headsUpFromBottom) {
// start from the bottom of the screen
return mHeadsUpAppearHeightBottom + mHeadsUpAppearStartAboveScreen;
@@ -814,10 +827,12 @@
}
public void setHeadsUpAppearHeightBottom(int headsUpAppearHeightBottom) {
+ NotificationsHunSharedAnimationValues.assertInLegacyMode();
mHeadsUpAppearHeightBottom = headsUpAppearHeightBottom;
}
public void setStackTopMargin(int stackTopMargin) {
+ NotificationsHunSharedAnimationValues.assertInLegacyMode();
mStackTopMargin = stackTopMargin;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index 949cb0a..b662892 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -72,7 +72,7 @@
private val swipeStatusBarAwayGestureHandler: SwipeStatusBarAwayGestureHandler,
private val statusBarModeRepository: StatusBarModeRepositoryStore,
@OngoingCallLog private val logger: LogBuffer,
-) : CallbackController<OngoingCallListener>, Dumpable, CoreStartable {
+) : CallbackController<OngoingCallListener>, CoreStartable {
private var isFullscreen: Boolean = false
/** Non-null if there's an active call notification. */
private var callNotificationInfo: CallNotificationInfo? = null
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
index 3c53d2d..6355767 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
@@ -34,7 +34,6 @@
import androidx.annotation.VisibleForTesting
import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.systemui.CoreStartable
-import com.android.systemui.Dumpable
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -81,7 +80,7 @@
private val wakeLockBuilder: WakeLock.Builder,
private val systemClock: SystemClock,
internal val tempViewUiEventLogger: TemporaryViewUiEventLogger,
-) : CoreStartable, Dumpable {
+) : CoreStartable {
/**
* Window layout params that will be used as a starting point for the [windowLayoutParams] of
* all subclasses.
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ui/viewmodel/WallpaperFocalAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/ui/viewmodel/WallpaperFocalAreaViewModel.kt
index 4cd49d0..1e78b12 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ui/viewmodel/WallpaperFocalAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ui/viewmodel/WallpaperFocalAreaViewModel.kt
@@ -26,6 +26,7 @@
import javax.inject.Inject
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
class WallpaperFocalAreaViewModel
@@ -39,25 +40,31 @@
val wallpaperFocalAreaBounds =
combine(
wallpaperFocalAreaInteractor.wallpaperFocalAreaBounds,
+ keyguardTransitionInteractor.startedKeyguardTransitionStep,
+ // Emit transition state when FINISHED instead of STARTED to avoid race with
+ // wakingup command, causing layout change command not be received.
keyguardTransitionInteractor
.transition(
edge = Edge.create(to = Scenes.Lockscreen),
edgeWithoutSceneContainer = Edge.create(to = KeyguardState.LOCKSCREEN),
)
- .filter { transitionStep ->
- // Should not filter by TransitionState.STARTED, it may race with
- // wakingup command, causing layout change command not be received.
- transitionStep.transitionState == TransitionState.FINISHED
- },
- ::Pair,
+ .filter { it.transitionState == TransitionState.FINISHED },
+ ::Triple,
)
- .map { (bounds, _) -> bounds }
+ .map { (bounds, startedStep, _) ->
+ // Avoid sending wrong bounds when transitioning from LOCKSCREEN to GONE
+ if (
+ startedStep.to == KeyguardState.LOCKSCREEN &&
+ startedStep.from != KeyguardState.LOCKSCREEN
+ ) {
+ bounds
+ } else {
+ null
+ }
+ }
+ .filterNotNull()
fun setFocalAreaBounds(bounds: RectF) {
wallpaperFocalAreaInteractor.setFocalAreaBounds(bounds)
}
-
- fun setTapPosition(x: Float, y: Float) {
- wallpaperFocalAreaInteractor.setTapPosition(x, y)
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index b6c6347..0e68fce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -28,6 +28,7 @@
import android.graphics.Rect
import android.graphics.drawable.BitmapDrawable
import android.hardware.biometrics.BiometricFingerprintConstants
+import android.hardware.biometrics.BiometricPrompt
import android.hardware.biometrics.PromptContentItemBulletedText
import android.hardware.biometrics.PromptContentView
import android.hardware.biometrics.PromptContentViewWithMoreOptionsButton
@@ -42,6 +43,7 @@
import android.view.HapticFeedbackConstants
import android.view.MotionEvent
import android.view.Surface
+import android.view.accessibility.accessibilityManager
import androidx.test.filters.SmallTest
import com.android.app.activityTaskManager
import com.android.keyguard.AuthInteractionProperties
@@ -200,6 +202,8 @@
overrideResource(R.dimen.biometric_dialog_face_icon_size, mockFaceIconSize)
kosmos.applicationContext = context
+ whenever(kosmos.accessibilityManager.getRecommendedTimeoutMillis(anyInt(), anyInt()))
+ .thenReturn(BiometricPrompt.HIDE_DIALOG_DELAY)
if (testCase.fingerprint?.isAnyUdfpsType == true) {
kosmos.authController = authController
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 e035a02..f394c80 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
@@ -16,6 +16,9 @@
package com.android.systemui.media.controls.ui.viewmodel
+import android.icu.text.MeasureFormat
+import android.icu.util.Measure
+import android.icu.util.MeasureUnit
import android.media.MediaMetadata
import android.media.session.MediaController
import android.media.session.MediaSession
@@ -34,6 +37,7 @@
import com.android.systemui.util.concurrency.FakeRepeatableExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
+import java.util.Locale
import org.junit.After
import org.junit.Before
import org.junit.Ignore
@@ -155,6 +159,7 @@
whenever(mockController.getPlaybackState()).thenReturn(state)
// WHEN the controller is updated
viewModel.updateController(mockController)
+ fakeExecutor.runNextReady()
// THEN the duration is extracted
assertThat(viewModel.progress.value!!.duration).isEqualTo(duration)
assertThat(viewModel.progress.value!!.enabled).isTrue()
@@ -173,6 +178,7 @@
whenever(mockController.getMetadata()).thenReturn(metadata)
// WHEN the controller is updated
viewModel.updateController(mockController)
+ fakeExecutor.runNextReady()
// THEN the duration is extracted
assertThat(viewModel.progress.value!!.duration).isEqualTo(duration)
assertThat(viewModel.progress.value!!.enabled).isFalse()
@@ -197,6 +203,7 @@
whenever(mockController.getPlaybackState()).thenReturn(state)
// WHEN the controller is updated
viewModel.updateController(mockController)
+ fakeExecutor.runNextReady()
// THEN the seek bar is disabled
assertThat(viewModel.progress.value!!.enabled).isFalse()
}
@@ -220,6 +227,7 @@
whenever(mockController.getPlaybackState()).thenReturn(state)
// WHEN the controller is updated
viewModel.updateController(mockController)
+ fakeExecutor.runNextReady()
// THEN the seek bar is disabled
assertThat(viewModel.progress.value!!.enabled).isFalse()
}
@@ -238,6 +246,7 @@
whenever(mockController.getPlaybackState()).thenReturn(state)
// WHEN the controller is updated
viewModel.updateController(mockController)
+ fakeExecutor.runNextReady()
// THEN the seek bar is disabled
assertThat(viewModel.progress.value!!.enabled).isFalse()
}
@@ -254,6 +263,7 @@
whenever(mockController.getPlaybackState()).thenReturn(state)
// WHEN the controller is updated
viewModel.updateController(mockController)
+ fakeExecutor.runNextReady()
// THEN elapsed time is captured
assertThat(viewModel.progress.value!!.elapsedTime).isEqualTo(200.toInt())
}
@@ -536,6 +546,7 @@
whenever(mockController.getPlaybackState()).thenReturn(state)
// WHEN the controller is updated
viewModel.updateController(mockController)
+ fakeExecutor.runNextReady()
// THEN a task is queued
assertThat(fakeExecutor.numPending()).isEqualTo(1)
}
@@ -551,6 +562,7 @@
whenever(mockController.getPlaybackState()).thenReturn(state)
// WHEN updated
viewModel.updateController(mockController)
+ fakeExecutor.runNextReady()
// THEN an update task is not queued
assertThat(fakeExecutor.numPending()).isEqualTo(0)
}
@@ -572,6 +584,7 @@
whenever(mockController.getPlaybackState()).thenReturn(state)
// WHEN updated
viewModel.updateController(mockController)
+ fakeExecutor.runNextReady()
// THEN an update task is queued
assertThat(fakeExecutor.numPending()).isEqualTo(1)
}
@@ -593,6 +606,7 @@
whenever(mockController.getPlaybackState()).thenReturn(state)
// WHEN updated
viewModel.updateController(mockController)
+ fakeExecutor.runNextReady()
// THEN an update task is not queued
assertThat(fakeExecutor.numPending()).isEqualTo(0)
}
@@ -719,6 +733,7 @@
}
whenever(mockController.getPlaybackState()).thenReturn(state)
viewModel.updateController(mockController)
+ fakeExecutor.runNextReady()
// WHEN start listening
viewModel.listening = true
// THEN an update task is queued
@@ -820,6 +835,7 @@
whenever(mockController.playbackState).thenReturn(state)
val captor = ArgumentCaptor.forClass(MediaController.Callback::class.java)
viewModel.updateController(mockController)
+ fakeExecutor.runNextReady()
verify(mockController).registerCallback(captor.capture())
assertThat(viewModel.progress.value!!.elapsedTime).isEqualTo(firstPosition.toInt())
@@ -831,8 +847,48 @@
build()
}
captor.value.onPlaybackStateChanged(secondState)
+ fakeExecutor.runNextReady()
// THEN then elapsed time should be updated
assertThat(viewModel.progress.value!!.elapsedTime).isEqualTo(secondPosition.toInt())
}
+
+ @Test
+ fun contentDescriptionUpdated() {
+ // When there is a duration and position
+ val duration = (1.5 * 60 * 60 * 1000).toLong()
+ val metadata =
+ MediaMetadata.Builder().run {
+ putLong(MediaMetadata.METADATA_KEY_DURATION, duration)
+ build()
+ }
+ whenever(mockController.getMetadata()).thenReturn(metadata)
+
+ val elapsedTime = 3000L
+ val state =
+ PlaybackState.Builder().run {
+ setState(PlaybackState.STATE_PLAYING, elapsedTime, 1f)
+ build()
+ }
+ whenever(mockController.getPlaybackState()).thenReturn(state)
+
+ viewModel.updateController(mockController)
+ fakeExecutor.runNextReady()
+
+ // Then the content description is set
+ val result = viewModel.progress.value!!
+
+ val expectedProgress =
+ MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.WIDE)
+ .formatMeasures(Measure(3, MeasureUnit.SECOND))
+ val expectedDuration =
+ MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.WIDE)
+ .formatMeasures(
+ Measure(1, MeasureUnit.HOUR),
+ Measure(30, MeasureUnit.MINUTE),
+ Measure(0, MeasureUnit.SECOND),
+ )
+ assertThat(result.durationDescription).isEqualTo(expectedDuration)
+ assertThat(result.elapsedTimeDescription).isEqualTo(expectedProgress)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 7101df1..2a58890 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -82,6 +82,7 @@
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.notification.collection.EntryAdapter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
@@ -732,6 +733,9 @@
NotificationEntry entry = mock(NotificationEntry.class);
when(row.getEntry()).thenReturn(entry);
when(entry.isAmbient()).thenReturn(false);
+ EntryAdapter entryAdapter = mock(EntryAdapter.class);
+ when(entryAdapter.isAmbient()).thenReturn(false);
+ when(row.getEntryAdapter()).thenReturn(entryAdapter);
mStackScroller.addContainerView(row);
// speed bump = 1
@@ -748,6 +752,9 @@
NotificationEntry entry = mock(NotificationEntry.class);
when(row.getEntry()).thenReturn(entry);
when(entry.isAmbient()).thenReturn(true);
+ EntryAdapter entryAdapter = mock(EntryAdapter.class);
+ when(entryAdapter.isAmbient()).thenReturn(true);
+ when(row.getEntryAdapter()).thenReturn(entryAdapter);
mStackScroller.addContainerView(row);
// speed bump is set to 0
@@ -764,6 +771,9 @@
NotificationEntry entry = mock(NotificationEntry.class);
when(row.getEntry()).thenReturn(entry);
when(entry.isAmbient()).thenReturn(false);
+ EntryAdapter entryAdapter = mock(EntryAdapter.class);
+ when(entryAdapter.isAmbient()).thenReturn(false);
+ when(row.getEntryAdapter()).thenReturn(entryAdapter);
mStackScroller.addContainerView(row);
// speed bump is 1
@@ -1377,6 +1387,9 @@
when(row.canViewBeCleared()).thenReturn(true);
when(row.getEntry()).thenReturn(entry);
when(entry.isClearable()).thenReturn(true);
+ EntryAdapter entryAdapter = mock(EntryAdapter.class);
+ when(entryAdapter.isClearable()).thenReturn(true);
+ when(row.getEntryAdapter()).thenReturn(entryAdapter);
return row;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 5e95825..8281132 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -135,6 +135,8 @@
import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
+import com.android.systemui.statusbar.notification.collection.GroupEntry;
+import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -151,6 +153,7 @@
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProviderTestUtil;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.BatteryController;
@@ -1229,8 +1232,36 @@
}
@Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
public void testBubbleSummaryDismissal_suppressesSummaryAndBubbleFromShade() throws Exception {
// GIVEN a group summary with a bubble child
+ NotificationEntry groupedBubble = mNotificationTestHelper.createBubbleEntryInGroup();
+ GroupEntry groupSummary = mNotificationTestHelper.createGroupEntry(
+ 0, List.of(groupedBubble));
+ mEntryListener.onEntryAdded(groupedBubble);
+ when(mCommonNotifCollection.getEntry(groupedBubble.getKey()))
+ .thenReturn(groupedBubble);
+ assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getKey()));
+
+ // WHEN the summary is dismissed
+ mBubblesManager.handleDismissalInterception(groupSummary.getSummary());
+
+ // THEN the summary and bubbled child are suppressed from the shade
+ assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
+ groupedBubble.getKey(),
+ groupedBubble.getSbn().getGroupKey()));
+ assertTrue(mBubbleController.getImplCachedState().isBubbleNotificationSuppressedFromShade(
+ groupedBubble.getKey(),
+ groupedBubble.getSbn().getGroupKey()));
+ assertTrue(mBubbleData.isSummarySuppressed(
+ groupSummary.getSummary().getSbn().getGroupKey()));
+ }
+
+ @Test
+ @DisableFlags(NotificationBundleUi.FLAG_NAME)
+ public void testBubbleSummaryDismissal_suppressesSummaryAndBubbleFromShade_rows()
+ throws Exception {
+ // GIVEN a group summary with a bubble child
ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0);
ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup();
mEntryListener.onEntryAdded(groupedBubble.getEntry());
@@ -1253,8 +1284,33 @@
}
@Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
public void testAppRemovesSummary_removesAllBubbleChildren() throws Exception {
// GIVEN a group summary with a bubble child
+ NotificationEntry groupedBubble = mNotificationTestHelper.createBubbleEntryInGroup();
+ GroupEntry groupSummary = mNotificationTestHelper.createGroupEntry(
+ 0, List.of(groupedBubble));
+ mEntryListener.onEntryAdded(groupedBubble);
+ when(mCommonNotifCollection.getEntry(groupedBubble.getKey()))
+ .thenReturn(groupedBubble);
+ assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getKey()));
+
+ // GIVEN the summary is dismissed
+ mBubblesManager.handleDismissalInterception(groupSummary.getSummary());
+
+ // WHEN the summary is cancelled by the app
+ mEntryListener.onEntryRemoved(groupSummary.getSummary(), REASON_APP_CANCEL);
+
+ // THEN the summary and its children are removed from bubble data
+ assertFalse(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getKey()));
+ assertFalse(mBubbleData.isSummarySuppressed(
+ groupSummary.getSummary().getSbn().getGroupKey()));
+ }
+
+ @Test
+ @DisableFlags(NotificationBundleUi.FLAG_NAME)
+ public void testAppRemovesSummary_removesAllBubbleChildren_rows() throws Exception {
+ // GIVEN a group summary with a bubble child
ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0);
ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup();
mEntryListener.onEntryAdded(groupedBubble.getEntry());
@@ -1276,9 +1332,52 @@
}
@Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
public void testSummaryDismissalMarksBubblesHiddenFromShadeAndDismissesNonBubbledChildren()
throws Exception {
// GIVEN a group summary with two (non-bubble) children and one bubble child
+ NotificationEntry groupedBubble = mNotificationTestHelper.createBubbleEntryInGroup();
+ GroupEntry groupSummary = mNotificationTestHelper.createGroupEntry(
+ 2, List.of(groupedBubble));
+ mEntryListener.onEntryAdded(groupedBubble);
+ when(mCommonNotifCollection.getEntry(groupedBubble.getKey()))
+ .thenReturn(groupedBubble);
+
+ // WHEN the summary is dismissed
+ mBubblesManager.handleDismissalInterception(groupSummary.getSummary());
+
+ // THEN only the NON-bubble children are dismissed
+ List<NotificationEntry> children = groupSummary.getChildren();
+ verify(mNotifCallback, times(1)).removeNotification(
+ eq(children.get(0)), any(), eq(REASON_GROUP_SUMMARY_CANCELED));
+ verify(mNotifCallback, times(1)).removeNotification(
+ eq(children.get(1)), any(), eq(REASON_GROUP_SUMMARY_CANCELED));
+ verify(mNotifCallback, never()).removeNotification(eq(groupedBubble),
+ any(), anyInt());
+
+ // THEN the bubble child still exists as a bubble and is suppressed from the shade
+ assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getKey()));
+ assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
+ groupedBubble.getKey(),
+ groupedBubble.getSbn().getGroupKey()));
+ assertTrue(mBubbleController.getImplCachedState().isBubbleNotificationSuppressedFromShade(
+ groupedBubble.getKey(),
+ groupedBubble.getSbn().getGroupKey()));
+
+ // THEN the summary is also suppressed from the shade
+ assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
+ groupSummary.getSummary().getKey(),
+ groupSummary.getSummary().getSbn().getGroupKey()));
+ assertTrue(mBubbleController.getImplCachedState().isBubbleNotificationSuppressedFromShade(
+ groupSummary.getSummary().getKey(),
+ groupSummary.getSummary().getSbn().getGroupKey()));
+ }
+
+ @Test
+ @DisableFlags(NotificationBundleUi.FLAG_NAME)
+ public void testSummaryDismissalMarksBubblesHiddenFromShadeAndDismissesNonBubbledChildren_row()
+ throws Exception {
+ // GIVEN a group summary with two (non-bubble) children and one bubble child
ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(2);
ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup();
mEntryListener.onEntryAdded(groupedBubble.getEntry());
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt
index 43b57de..d6b625b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.biometrics.ui.viewmodel
import android.content.applicationContext
+import android.view.accessibility.accessibilityManager
import com.android.app.activityTaskManager
import com.android.launcher3.icons.IconProvider
import com.android.systemui.biometrics.domain.interactor.biometricStatusInteractor
@@ -27,7 +28,6 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.util.mockito.mock
-import org.mockito.Mockito.mock
val Kosmos.promptViewModel by Fixture {
PromptViewModel(
@@ -39,6 +39,7 @@
udfpsUtils = udfpsUtils,
iconProvider = iconProvider,
activityTaskManager = activityTaskManager,
+ accessibilityManager = accessibilityManager,
)
}
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 a64fc24..d6f0e06 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
@@ -104,6 +104,8 @@
private val _displayChangeEvent = MutableSharedFlow<Int>(replay = 1)
override val displayChangeEvent: Flow<Int> = _displayChangeEvent
+ override val displayIdsWithSystemDecorations: StateFlow<Set<Int>> = MutableStateFlow(emptySet())
+
suspend fun emitDisplayChangeEvent(displayId: Int) = _displayChangeEvent.emit(displayId)
fun setDefaultDisplayOff(defaultDisplayOff: Boolean) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/VisibilityLocationProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/VisibilityLocationProviderKosmos.kt
new file mode 100644
index 0000000..085d386
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/VisibilityLocationProviderKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.statusbar.notification
+
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
+
+val Kosmos.visibilityLocationProvider: VisibilityLocationProvider by Kosmos.Fixture { mock() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorKosmos.kt
index 358d251..1a5c61a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorKosmos.kt
@@ -16,7 +16,41 @@
package com.android.systemui.statusbar.notification.collection.coordinator
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.wakefulnessLifecycle
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.shade.domain.interactor.shadeAnimationInteractor
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.notification.collection.provider.visualStabilityProvider
+import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
+import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
+import com.android.systemui.statusbar.notification.visibilityLocationProvider
+import com.android.systemui.statusbar.policy.keyguardStateController
+import com.android.systemui.util.kotlin.JavaAdapter
-var Kosmos.visualStabilityCoordinator by Kosmos.Fixture { mock<VisualStabilityCoordinator>() }
+var Kosmos.visualStabilityCoordinator: VisualStabilityCoordinator by
+ Kosmos.Fixture {
+ VisualStabilityCoordinator(
+ fakeExecutor,
+ fakeExecutor,
+ dumpManager,
+ headsUpNotificationRepository,
+ shadeAnimationInteractor,
+ JavaAdapter(testScope.backgroundScope),
+ seenNotificationsInteractor,
+ statusBarStateController,
+ visibilityLocationProvider,
+ visualStabilityProvider,
+ wakefulnessLifecycle,
+ communalSceneInteractor,
+ shadeInteractor,
+ keyguardTransitionInteractor,
+ keyguardStateController,
+ visualStabilityCoordinatorLogger,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorLoggerKosmos.kt
new file mode 100644
index 0000000..6645cdc
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorLoggerKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.statusbar.notification.collection.coordinator
+
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
+
+val Kosmos.visualStabilityCoordinatorLogger: VisualStabilityCoordinatorLogger by
+ Kosmos.Fixture { mock() }
diff --git a/proto/src/metrics_constants/OWNERS b/proto/src/metrics_constants/OWNERS
index 7009282..b032841 100644
--- a/proto/src/metrics_constants/OWNERS
+++ b/proto/src/metrics_constants/OWNERS
@@ -1,4 +1,3 @@
cwren@android.com
-yanglu@google.com
yaochen@google.com
yro@google.com
diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NonNull.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NonNull.java
index db3cd8ed..1153a77 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NonNull.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NonNull.java
@@ -35,14 +35,4 @@
@Retention(SOURCE)
@Target({FIELD, METHOD, PARAMETER, TYPE_USE})
@libcore.api.IntraCoreApi
-public @interface NonNull {
- /**
- * Min Android API level (inclusive) to which this annotation is applied.
- */
- int from() default Integer.MIN_VALUE;
-
- /**
- * Max Android API level to which this annotation is applied.
- */
- int to() default Integer.MAX_VALUE;
-}
+public @interface NonNull {}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/util/Nullable.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/Nullable.java
index 3371978..295f083 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/libcore/util/Nullable.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/Nullable.java
@@ -35,14 +35,4 @@
@Retention(SOURCE)
@Target({FIELD, METHOD, PARAMETER, TYPE_USE})
@libcore.api.IntraCoreApi
-public @interface Nullable {
- /**
- * Min Android API level (inclusive) to which this annotation is applied.
- */
- int from() default Integer.MIN_VALUE;
-
- /**
- * Max Android API level to which this annotation is applied.
- */
- int to() default Integer.MAX_VALUE;
-}
+public @interface Nullable {}
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/FilterRemapper.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/FilterRemapper.kt
index c5a2f9f..bba4681 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/FilterRemapper.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/FilterRemapper.kt
@@ -15,6 +15,7 @@
*/
package com.android.hoststubgen.filters
+import com.android.hoststubgen.log
import org.objectweb.asm.commons.Remapper
/**
@@ -23,19 +24,25 @@
class FilterRemapper(val filter: OutputFilter) : Remapper() {
private val cache = mutableMapOf<String, String>()
- override fun mapType(typeInternalName: String?): String? {
+
+ override fun map(typeInternalName: String?): String? {
if (typeInternalName == null) {
return null
}
cache[typeInternalName]?.let {
+ // log.d("Cached rename from $typeInternalName to $it")
return it
}
- var mapped = filter.remapType(typeInternalName) ?: typeInternalName
+ var mapped = filter.remapType(typeInternalName)
+ if (mapped != null) {
+ log.d("Renaming type $typeInternalName to $mapped")
+ } else {
+ // log.d("Not renaming type $typeInternalName")
+ }
+ mapped = mapped ?: typeInternalName
cache[typeInternalName] = mapped
return mapped
}
-
- // TODO Do we need to implement mapPackage(), etc too?
}
\ No newline at end of file
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyRemapperFilter.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyRemapperFilter.kt
index a78c655..bc90d12 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyRemapperFilter.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyRemapperFilter.kt
@@ -15,7 +15,6 @@
*/
package com.android.hoststubgen.filters
-import com.android.hoststubgen.log
import java.util.regex.Pattern
/**
@@ -34,17 +33,12 @@
val typeInternalNamePrefix: String,
)
- private val cache = mutableMapOf<String, String>()
-
override fun remapType(className: String): String? {
- var mapped: String = className
typeRenameSpecs.forEach {
if (it.typeInternalNamePattern.matcher(className).matches()) {
- mapped = it.typeInternalNamePrefix + className
- log.d("Renaming type $className to $mapped")
+ return it.typeInternalNamePrefix + className
}
}
- cache[className] = mapped
- return mapped
+ return null
}
}
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/01-hoststubgen-test-tiny-framework-orig-dump.txt b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/01-hoststubgen-test-tiny-framework-orig-dump.txt
index 635f66d..e846d6e 100644
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/01-hoststubgen-test-tiny-framework-orig-dump.txt
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/01-hoststubgen-test-tiny-framework-orig-dump.txt
@@ -3198,7 +3198,7 @@
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller
super_class: #x // java/lang/Object
- interfaces: 0, fields: 0, methods: 2, attributes: 2
+ interfaces: 0, fields: 0, methods: 3, attributes: 2
Constant pool:
{
public com.android.hoststubgen.test.tinyframework.TinyFrameworkRenamedClassCaller();
@@ -3229,6 +3229,22 @@
LocalVariableTable:
Start Length Slot Name Signature
0 12 0 value I
+
+ public static int bar(int);
+ descriptor: (I)I
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=2, locals=1, args_size=1
+ x: iload_0
+ x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.getArray:(I)[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
+ x: iconst_0
+ x: aaload
+ x: invokevirtual #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.getValue:()I
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 10 0 value I
}
SourceFile: "TinyFrameworkRenamedClassCaller.java"
RuntimeInvisibleAnnotations:
@@ -3242,7 +3258,7 @@
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
super_class: #x // java/lang/Object
- interfaces: 0, fields: 1, methods: 2, attributes: 2
+ interfaces: 0, fields: 1, methods: 3, attributes: 2
Constant pool:
{
private final int mValue;
@@ -3278,6 +3294,26 @@
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
+
+ public static com.android.hoststubgen.test.tinyframework.TinyFrameworkToBeRenamed[] getArray(int);
+ descriptor: (I)[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=6, locals=1, args_size=1
+ x: iconst_1
+ x: anewarray #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
+ x: dup
+ x: iconst_0
+ x: new #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
+ x: dup
+ x: iload_0
+ x: invokespecial #x // Method "<init>":(I)V
+ x: aastore
+ x: areturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 16 0 value I
}
SourceFile: "TinyFrameworkToBeRenamed.java"
RuntimeInvisibleAnnotations:
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/03-hoststubgen-test-tiny-framework-host-dump.txt b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/03-hoststubgen-test-tiny-framework-host-dump.txt
index 51a3355..be95fe0 100644
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/03-hoststubgen-test-tiny-framework-host-dump.txt
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/03-hoststubgen-test-tiny-framework-host-dump.txt
@@ -3392,7 +3392,7 @@
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller
super_class: #x // java/lang/Object
- interfaces: 0, fields: 0, methods: 2, attributes: 3
+ interfaces: 0, fields: 0, methods: 3, attributes: 3
Constant pool:
{
public com.android.hoststubgen.test.tinyframework.TinyFrameworkRenamedClassCaller();
@@ -3429,6 +3429,25 @@
RuntimeVisibleAnnotations:
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+ public static int bar(int);
+ descriptor: (I)I
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=2, locals=1, args_size=1
+ x: iload_0
+ x: invokestatic #x // Method rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.getArray:(I)[Lrename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
+ x: iconst_0
+ x: aaload
+ x: invokevirtual #x // Method rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.getValue:()I
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 10 0 value I
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
}
SourceFile: "TinyFrameworkRenamedClassCaller.java"
RuntimeVisibleAnnotations:
@@ -3867,7 +3886,7 @@
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
super_class: #x // java/lang/Object
- interfaces: 0, fields: 1, methods: 2, attributes: 3
+ interfaces: 0, fields: 1, methods: 3, attributes: 3
Constant pool:
{
private final int mValue;
@@ -3891,7 +3910,7 @@
LineNumberTable:
LocalVariableTable:
Start Length Slot Name Signature
- 0 10 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
+ 0 10 0 this Lrename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
0 10 1 value I
RuntimeVisibleAnnotations:
x: #x()
@@ -3908,7 +3927,30 @@
LineNumberTable:
LocalVariableTable:
Start Length Slot Name Signature
- 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
+ 0 5 0 this Lrename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+ public static rename_prefix.com.android.hoststubgen.test.tinyframework.TinyFrameworkToBeRenamed[] getArray(int);
+ descriptor: (I)[Lrename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=6, locals=1, args_size=1
+ x: iconst_1
+ x: anewarray #x // class rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
+ x: dup
+ x: iconst_0
+ x: new #x // class rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
+ x: dup
+ x: iload_0
+ x: invokespecial #x // Method "<init>":(I)V
+ x: aastore
+ x: areturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 16 0 value I
RuntimeVisibleAnnotations:
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/13-hoststubgen-test-tiny-framework-host-ext-dump.txt b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/13-hoststubgen-test-tiny-framework-host-ext-dump.txt
index a466a2e..667981f 100644
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/13-hoststubgen-test-tiny-framework-host-ext-dump.txt
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output.RELEASE_TARGET_JAVA_21/13-hoststubgen-test-tiny-framework-host-ext-dump.txt
@@ -4241,7 +4241,7 @@
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller
super_class: #x // java/lang/Object
- interfaces: 0, fields: 0, methods: 3, attributes: 3
+ interfaces: 0, fields: 0, methods: 4, attributes: 3
Constant pool:
{
private static {};
@@ -4298,6 +4298,30 @@
RuntimeVisibleAnnotations:
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+ public static int bar(int);
+ descriptor: (I)I
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=4, locals=1, args_size=1
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller
+ x: ldc #x // String bar
+ x: ldc #x // String (I)I
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ x: iload_0
+ x: invokestatic #x // Method rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.getArray:(I)[Lrename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
+ x: iconst_0
+ x: aaload
+ x: invokevirtual #x // Method rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.getValue:()I
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 11 10 0 value I
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
}
SourceFile: "TinyFrameworkRenamedClassCaller.java"
RuntimeVisibleAnnotations:
@@ -4947,7 +4971,7 @@
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
super_class: #x // java/lang/Object
- interfaces: 0, fields: 1, methods: 3, attributes: 3
+ interfaces: 0, fields: 1, methods: 4, attributes: 3
Constant pool:
{
private final int mValue;
@@ -4962,8 +4986,8 @@
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
Code:
stack=2, locals=0, args_size=0
- x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
- x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+ x: ldc #x // class rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
x: return
@@ -4972,7 +4996,7 @@
flags: (0x0001) ACC_PUBLIC
Code:
stack=4, locals=2, args_size=2
- x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
+ x: ldc #x // class rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
x: ldc #x // String <init>
x: ldc #x // String (I)V
x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
@@ -4986,7 +5010,7 @@
LineNumberTable:
LocalVariableTable:
Start Length Slot Name Signature
- 11 10 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
+ 11 10 0 this Lrename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
11 10 1 value I
RuntimeVisibleAnnotations:
x: #x()
@@ -4997,7 +5021,7 @@
flags: (0x0001) ACC_PUBLIC
Code:
stack=4, locals=1, args_size=1
- x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
+ x: ldc #x // class rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
x: ldc #x // String getValue
x: ldc #x // String ()I
x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
@@ -5008,7 +5032,35 @@
LineNumberTable:
LocalVariableTable:
Start Length Slot Name Signature
- 11 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
+ 11 5 0 this Lrename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+ public static rename_prefix.com.android.hoststubgen.test.tinyframework.TinyFrameworkToBeRenamed[] getArray(int);
+ descriptor: (I)[Lrename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=6, locals=1, args_size=1
+ x: ldc #x // class rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
+ x: ldc #x // String getArray
+ x: ldc #x // String (I)[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ x: iconst_1
+ x: anewarray #x // class rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
+ x: dup
+ x: iconst_0
+ x: new #x // class rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
+ x: dup
+ x: iload_0
+ x: invokespecial #x // Method "<init>":(I)V
+ x: aastore
+ x: areturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 11 16 0 value I
RuntimeVisibleAnnotations:
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
index 78341d7..8f56f0e 100644
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
@@ -3219,7 +3219,7 @@
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller
super_class: #x // java/lang/Object
- interfaces: 0, fields: 0, methods: 2, attributes: 2
+ interfaces: 0, fields: 0, methods: 3, attributes: 2
Constant pool:
{
public com.android.hoststubgen.test.tinyframework.TinyFrameworkRenamedClassCaller();
@@ -3250,6 +3250,22 @@
LocalVariableTable:
Start Length Slot Name Signature
0 12 0 value I
+
+ public static int bar(int);
+ descriptor: (I)I
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=2, locals=1, args_size=1
+ x: iload_0
+ x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.getArray:(I)[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
+ x: iconst_0
+ x: aaload
+ x: invokevirtual #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.getValue:()I
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 10 0 value I
}
SourceFile: "TinyFrameworkRenamedClassCaller.java"
RuntimeInvisibleAnnotations:
@@ -3263,7 +3279,7 @@
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
super_class: #x // java/lang/Object
- interfaces: 0, fields: 1, methods: 2, attributes: 2
+ interfaces: 0, fields: 1, methods: 3, attributes: 2
Constant pool:
{
private final int mValue;
@@ -3299,6 +3315,26 @@
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
+
+ public static com.android.hoststubgen.test.tinyframework.TinyFrameworkToBeRenamed[] getArray(int);
+ descriptor: (I)[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=6, locals=1, args_size=1
+ x: iconst_1
+ x: anewarray #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
+ x: dup
+ x: iconst_0
+ x: new #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
+ x: dup
+ x: iload_0
+ x: invokespecial #x // Method "<init>":(I)V
+ x: aastore
+ x: areturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 16 0 value I
}
SourceFile: "TinyFrameworkToBeRenamed.java"
RuntimeInvisibleAnnotations:
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-dump.txt b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-dump.txt
index 2e0b182..c918bf8 100644
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-dump.txt
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-dump.txt
@@ -3422,7 +3422,7 @@
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller
super_class: #x // java/lang/Object
- interfaces: 0, fields: 0, methods: 2, attributes: 3
+ interfaces: 0, fields: 0, methods: 3, attributes: 3
Constant pool:
{
public com.android.hoststubgen.test.tinyframework.TinyFrameworkRenamedClassCaller();
@@ -3459,6 +3459,25 @@
RuntimeVisibleAnnotations:
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+ public static int bar(int);
+ descriptor: (I)I
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=2, locals=1, args_size=1
+ x: iload_0
+ x: invokestatic #x // Method rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.getArray:(I)[Lrename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
+ x: iconst_0
+ x: aaload
+ x: invokevirtual #x // Method rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.getValue:()I
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 10 0 value I
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
}
SourceFile: "TinyFrameworkRenamedClassCaller.java"
RuntimeVisibleAnnotations:
@@ -3897,7 +3916,7 @@
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
super_class: #x // java/lang/Object
- interfaces: 0, fields: 1, methods: 2, attributes: 3
+ interfaces: 0, fields: 1, methods: 3, attributes: 3
Constant pool:
{
private final int mValue;
@@ -3921,7 +3940,7 @@
LineNumberTable:
LocalVariableTable:
Start Length Slot Name Signature
- 0 10 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
+ 0 10 0 this Lrename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
0 10 1 value I
RuntimeVisibleAnnotations:
x: #x()
@@ -3938,7 +3957,30 @@
LineNumberTable:
LocalVariableTable:
Start Length Slot Name Signature
- 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
+ 0 5 0 this Lrename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+ public static rename_prefix.com.android.hoststubgen.test.tinyframework.TinyFrameworkToBeRenamed[] getArray(int);
+ descriptor: (I)[Lrename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=6, locals=1, args_size=1
+ x: iconst_1
+ x: anewarray #x // class rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
+ x: dup
+ x: iconst_0
+ x: new #x // class rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
+ x: dup
+ x: iload_0
+ x: invokespecial #x // Method "<init>":(I)V
+ x: aastore
+ x: areturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 16 0 value I
RuntimeVisibleAnnotations:
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-dump.txt b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-dump.txt
index 51f7925..28065bf 100644
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-dump.txt
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-dump.txt
@@ -4271,7 +4271,7 @@
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller
super_class: #x // java/lang/Object
- interfaces: 0, fields: 0, methods: 3, attributes: 3
+ interfaces: 0, fields: 0, methods: 4, attributes: 3
Constant pool:
{
private static {};
@@ -4328,6 +4328,30 @@
RuntimeVisibleAnnotations:
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+ public static int bar(int);
+ descriptor: (I)I
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=4, locals=1, args_size=1
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller
+ x: ldc #x // String bar
+ x: ldc #x // String (I)I
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ x: iload_0
+ x: invokestatic #x // Method rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.getArray:(I)[Lrename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
+ x: iconst_0
+ x: aaload
+ x: invokevirtual #x // Method rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.getValue:()I
+ x: ireturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 11 10 0 value I
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
}
SourceFile: "TinyFrameworkRenamedClassCaller.java"
RuntimeVisibleAnnotations:
@@ -4977,7 +5001,7 @@
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #x // rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
super_class: #x // java/lang/Object
- interfaces: 0, fields: 1, methods: 3, attributes: 3
+ interfaces: 0, fields: 1, methods: 4, attributes: 3
Constant pool:
{
private final int mValue;
@@ -4992,8 +5016,8 @@
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
Code:
stack=2, locals=0, args_size=0
- x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
- x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+ x: ldc #x // class rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
x: return
@@ -5002,7 +5026,7 @@
flags: (0x0001) ACC_PUBLIC
Code:
stack=4, locals=2, args_size=2
- x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
+ x: ldc #x // class rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
x: ldc #x // String <init>
x: ldc #x // String (I)V
x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
@@ -5016,7 +5040,7 @@
LineNumberTable:
LocalVariableTable:
Start Length Slot Name Signature
- 11 10 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
+ 11 10 0 this Lrename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
11 10 1 value I
RuntimeVisibleAnnotations:
x: #x()
@@ -5027,7 +5051,7 @@
flags: (0x0001) ACC_PUBLIC
Code:
stack=4, locals=1, args_size=1
- x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
+ x: ldc #x // class rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
x: ldc #x // String getValue
x: ldc #x // String ()I
x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
@@ -5038,7 +5062,35 @@
LineNumberTable:
LocalVariableTable:
Start Length Slot Name Signature
- 11 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
+ 11 5 0 this Lrename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
+ RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+ public static rename_prefix.com.android.hoststubgen.test.tinyframework.TinyFrameworkToBeRenamed[] getArray(int);
+ descriptor: (I)[Lrename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
+ flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=6, locals=1, args_size=1
+ x: ldc #x // class rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
+ x: ldc #x // String getArray
+ x: ldc #x // String (I)[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ x: iconst_1
+ x: anewarray #x // class rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
+ x: dup
+ x: iconst_0
+ x: new #x // class rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
+ x: dup
+ x: iload_0
+ x: invokespecial #x // Method "<init>":(I)V
+ x: aastore
+ x: areturn
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 11 16 0 value I
RuntimeVisibleAnnotations:
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller.java
index 707bc0e..74e4610 100644
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller.java
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller.java
@@ -25,4 +25,9 @@
// so this code should work as-is.
return new TinyFrameworkToBeRenamed(value).getValue();
}
+
+ /** Calls the class that'll be renamed. */
+ public static int bar(int value) {
+ return TinyFrameworkToBeRenamed.getArray(value)[0].getValue();
+ }
}
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.java
index 8319ced..7dcc83e 100644
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.java
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.java
@@ -31,4 +31,8 @@
public int getValue() {
return mValue;
}
+
+ public static TinyFrameworkToBeRenamed[] getArray(int value) {
+ return new TinyFrameworkToBeRenamed[] { new TinyFrameworkToBeRenamed(value) };
+ }
}
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
index 68673dc..b8d6be4 100644
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
@@ -308,6 +308,11 @@
}
@Test
+ public void testTypeRenameArray() {
+ assertThat(TinyFrameworkRenamedClassCaller.bar(2)).isEqualTo(2);
+ }
+
+ @Test
public void testMethodCallReplaceNonStatic() throws Exception {
assertThat(TinyFrameworkMethodCallReplace.nonStaticMethodCallReplaceTester())
.isEqualTo(true);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 1c95184..42834ce 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -2137,9 +2137,6 @@
this, 0, oldUserState.mUserId));
}
- // Announce user changes only if more than one exist.
- final boolean announceNewUser = mUserManager.getUsers().size() > 1;
-
// The user changed.
mCurrentUserId = userId;
AccessibilityUserState userState = getCurrentUserStateLocked();
@@ -2166,13 +2163,6 @@
// As an initialization step, update the shortcuts for the current user.
updateShortcutsForCurrentNavigationMode();
- if (announceNewUser) {
- // Schedule announcement of the current user if needed.
- mMainHandler.sendMessageDelayed(
- obtainMessage(AccessibilityManagerService::announceNewUserIfNeeded, this),
- WAIT_FOR_USER_STATE_FULLY_INITIALIZED_MILLIS);
- }
-
for (IUserInitializationCompleteCallback callback
: mUserInitializationCompleteCallbacks) {
try {
@@ -2186,20 +2176,6 @@
}
}
- private void announceNewUserIfNeeded() {
- synchronized (mLock) {
- AccessibilityUserState userState = getCurrentUserStateLocked();
- if (userState.isHandlingAccessibilityEventsLocked()) {
- String message = mContext.getString(R.string.user_switched,
- mUserManager.getUserInfo(mCurrentUserId).name);
- AccessibilityEvent event = AccessibilityEvent.obtain(
- AccessibilityEvent.TYPE_ANNOUNCEMENT);
- event.getText().add(message);
- sendAccessibilityEventLocked(event, mCurrentUserId);
- }
- }
- }
-
private void unlockUser(int userId) {
synchronized (mLock) {
int parentUserId = mSecurityPolicy.resolveProfileParentLocked(userId);
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 125824c..4187ee2 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -387,6 +387,15 @@
@Overridable
public static final long FGS_SAW_RESTRICTIONS = 319471980L;
+ /**
+ * Allows system to manage foreground state of service with type
+ * <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK}</li>
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = VERSION_CODES.VANILLA_ICE_CREAM)
+ @Overridable
+ public static final long MEDIA_FGS_STATE_TRANSITION = 281762171L;
+
final ActivityManagerService mAm;
// Maximum number of services that we allow to start in the background
@@ -9341,20 +9350,32 @@
== ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE
&& sr.foregroundId == notificationId) {
// check if service is explicitly requested by app to not be in foreground.
- if (sr.systemRequestedFgToBg) {
- Slog.d(TAG,
- "System initiated service transition to foreground "
- + "for package "
- + packageName);
- setServiceForegroundInnerLocked(sr, sr.foregroundId,
- sr.foregroundNoti, /* flags */ 0,
- ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK,
- /* callingUidStart */ 0, /* systemRequestedTransition */ true);
+ if (sr.systemRequestedFgToBg && CompatChanges.isChangeEnabled(
+ MEDIA_FGS_STATE_TRANSITION, sr.appInfo.uid)) {
+ if (DEBUG_FOREGROUND_SERVICE) {
+ Slog.d(TAG,
+ "System initiated service transition to foreground "
+ + "for package "
+ + packageName);
+ }
+ try {
+ setServiceForegroundInnerLocked(sr, sr.foregroundId,
+ sr.foregroundNoti, /* flags */ 0,
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK,
+ /* callingUidStart */ 0, /* systemRequestedTransition */ true);
+ } catch (Exception e) {
+ Slog.w(TAG,
+ "Exception in system initiated foreground service transition "
+ + "for package " + packageName
+ + ":" + e.toString());
+ }
} else {
- Slog.d(TAG,
- "Ignoring system initiated foreground service transition for "
- + "package"
- + packageName);
+ if (DEBUG_FOREGROUND_SERVICE) {
+ Slog.d(TAG,
+ "Ignoring system initiated foreground service transition for "
+ + "package "
+ + packageName);
+ }
}
}
}
@@ -9388,14 +9409,32 @@
if (sr.foregroundServiceType
== ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK
&& sr.foregroundId == notificationId) {
- Slog.d(TAG,
- "System initiated transition of foreground service(type:media) to bg "
- + "for package"
- + packageName);
- setServiceForegroundInnerLocked(sr, /* id */ 0,
- /* notification */ null, /* flags */ 0,
- /* foregroundServiceType */ 0, /* callingUidStart */ 0,
- /* systemRequestedTransition */ true);
+ if (CompatChanges.isChangeEnabled(MEDIA_FGS_STATE_TRANSITION, sr.appInfo.uid)) {
+ if (DEBUG_FOREGROUND_SERVICE) {
+ Slog.d(TAG,
+ "System initiated transition of foreground service"
+ + "(type:media) to"
+ + " bg "
+ + "for package "
+ + packageName);
+ }
+ try {
+ setServiceForegroundInnerLocked(sr, /* id */ 0,
+ /* notification */ null, /* flags */ 0,
+ /* foregroundServiceType */ 0, /* callingUidStart */ 0,
+ /* systemRequestedTransition */ true);
+ } catch (Exception e) {
+ Slog.wtf(TAG,
+ "Exception in system initiated background service transition "
+ + "for package " + packageName
+ + ":" + e.toString());
+ }
+ } else {
+ if (DEBUG_FOREGROUND_SERVICE) {
+ Slog.d(TAG, "Ignoring system initiated transition of foreground"
+ + " service(type:media)to bg for package " + packageName);
+ }
+ }
}
}
}
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index f1007e7..0954c49 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -238,6 +238,8 @@
"pixel_connectivity_gps",
"pixel_continuity",
"pixel_display",
+ "pixel_fingerprint",
+ "pixel_gsc",
"pixel_perf",
"pixel_sensai",
"pixel_sensors",
@@ -245,6 +247,7 @@
"pixel_system_sw_video",
"pixel_video_sw",
"pixel_watch",
+ "pixel_wifi",
"platform_compat",
"platform_security",
"pixel_watch_debug_trace",
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 964b97c..551202c 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -1051,7 +1051,9 @@
void handleHdrSdrNitsChanged(float displayNits, float sdrNits) {
final float newHdrSdrRatio;
- if (displayNits != INVALID_NITS && sdrNits != INVALID_NITS) {
+ if (displayNits != INVALID_NITS && sdrNits != INVALID_NITS
+ && (mBacklightAdapter.mUseSurfaceControlBrightness ||
+ mBacklightAdapter.mForceSurfaceControl)) {
// Ensure the ratio stays >= 1.0f as values below that are nonsensical
newHdrSdrRatio = Math.max(1.f, displayNits / sdrNits);
} else {
diff --git a/services/core/java/com/android/server/display/color/OWNERS b/services/core/java/com/android/server/display/color/OWNERS
index 27adf12..8f5a9a0 100644
--- a/services/core/java/com/android/server/display/color/OWNERS
+++ b/services/core/java/com/android/server/display/color/OWNERS
@@ -1,4 +1,3 @@
christyfranks@google.com
-justinklaassen@google.com
-per-file DisplayTransformManager.java=michaelwr@google.com
\ No newline at end of file
+per-file DisplayTransformManager.java=michaelwr@google.com
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 7e8bb28..144caea 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -1365,6 +1365,21 @@
}
}
+ @Override
+ public void setScreensaverEnabled(boolean enabled) {
+ checkPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS);
+ final UserHandle userHandle = getCallingUserHandle();
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.SCREENSAVER_ENABLED, enabled ? 1 : 0,
+ userHandle.getIdentifier());
+ mPowerManagerInternal.updateSettings();
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
boolean canLaunchDreamActivity(String dreamPackageName, String packageName,
int callingUid) {
if (dreamPackageName == null || packageName == null) {
diff --git a/services/core/java/com/android/server/input/InputDataStore.java b/services/core/java/com/android/server/input/InputDataStore.java
index 834f815..6ed50b6 100644
--- a/services/core/java/com/android/server/input/InputDataStore.java
+++ b/services/core/java/com/android/server/input/InputDataStore.java
@@ -89,6 +89,10 @@
final InputStream inputStream = mInputGestureFileInjector.openRead(userId);
inputGestureDataList = readInputGesturesXml(inputStream, false);
inputStream.close();
+ } catch (FileNotFoundException exception) {
+ // There are valid reasons for the file to be missing, such as shortcuts having not
+ // been registered by the user.
+ return List.of();
} catch (IOException exception) {
// In case we are unable to read from the file on disk or another IO operation error,
// fail gracefully.
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 4f3aa06..280f3b7 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -120,12 +120,21 @@
* Binding flags for establishing connection to the {@link InputMethodService}.
*/
@VisibleForTesting
- static final int IME_CONNECTION_BIND_FLAGS =
- Context.BIND_AUTO_CREATE
+ static final int IME_CONNECTION_BIND_FLAGS;
+ static {
+ if (android.view.inputmethod.Flags.lowerImeOomImportance()) {
+ IME_CONNECTION_BIND_FLAGS = Context.BIND_AUTO_CREATE
+ | Context.BIND_ALMOST_PERCEPTIBLE
+ | Context.BIND_IMPORTANT_BACKGROUND
+ | Context.BIND_SCHEDULE_LIKE_TOP_APP;
+ } else {
+ IME_CONNECTION_BIND_FLAGS = Context.BIND_AUTO_CREATE
| Context.BIND_NOT_VISIBLE
| Context.BIND_NOT_FOREGROUND
| Context.BIND_IMPORTANT_BACKGROUND
| Context.BIND_SCHEDULE_LIKE_TOP_APP;
+ }
+ }
private final int mImeConnectionBindFlags;
diff --git a/services/core/java/com/android/server/inputmethod/OWNERS b/services/core/java/com/android/server/inputmethod/OWNERS
index e507c6b..9d8aef9 100644
--- a/services/core/java/com/android/server/inputmethod/OWNERS
+++ b/services/core/java/com/android/server/inputmethod/OWNERS
@@ -1,7 +1,6 @@
set noparent
roosa@google.com
-yukawa@google.com
tarandeep@google.com
fstern@google.com
cosminbaies@google.com
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
index 463989a..60d028b 100644
--- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
@@ -390,10 +390,11 @@
.max(Comparator.comparingLong(PackageInstaller.SessionInfo::getCreatedMillis));
}
- // ADB sets installerPackageName to null, this creates a loophole to bypass BIC which will be
- // addressed with b/265203007
private boolean installedByAdb(String initiatingPackageName) {
- if(PackageManagerServiceUtils.isInstalledByAdb(initiatingPackageName)) {
+ // GTS tests needs to adopt shell identity to install apps.
+ if(!SystemProperties.get("gts.transparency.bg-install-apps").isEmpty()) {
+ Slog.d(TAG, "handlePackageAdd: is GTS tests, skipping ADB check");
+ } else if(PackageManagerServiceUtils.isInstalledByAdb(initiatingPackageName)) {
Slog.d(TAG, "handlePackageAdd: is installed by ADB, skipping");
return true;
}
diff --git a/services/core/java/com/android/server/pm/OWNERS b/services/core/java/com/android/server/pm/OWNERS
index 62b89f32..f98ec04 100644
--- a/services/core/java/com/android/server/pm/OWNERS
+++ b/services/core/java/com/android/server/pm/OWNERS
@@ -1,7 +1,6 @@
hackbod@android.com
hackbod@google.com
jsharkey@android.com
-jsharkey@google.com
narayan@google.com
include /PACKAGE_MANAGER_OWNERS
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 136cb12..635ef06 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -5189,7 +5189,9 @@
"Session " + sessionId + " is a parent of multi-package session and "
+ "requestUserPreapproval on the parent session isn't supported.");
}
-
+ if (statusReceiver == null) {
+ throw new IllegalArgumentException("Status receiver cannot be null.");
+ }
synchronized (mLock) {
assertPreparedAndNotSealedLocked("request of session " + sessionId);
mPreapprovalDetails = details;
@@ -5542,6 +5544,10 @@
*/
private static void sendOnUserActionRequired(Context context, IntentSender target,
int sessionId, Intent intent) {
+ if (target == null) {
+ Slog.e(TAG, "Missing receiver for pending user action.");
+ return;
+ }
final Intent fillIn = new Intent();
fillIn.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
fillIn.putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_PENDING_USER_ACTION);
diff --git a/services/core/java/com/android/server/pm/dex/OWNERS b/services/core/java/com/android/server/pm/dex/OWNERS
index 5ca8ddd..70af4e7 100644
--- a/services/core/java/com/android/server/pm/dex/OWNERS
+++ b/services/core/java/com/android/server/pm/dex/OWNERS
@@ -1,4 +1,3 @@
-alanstokes@google.com
jiakaiz@google.com
ngeoffray@google.com
mast@google.com
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index e3eced2..dd454cd 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -7445,6 +7445,13 @@
public void setDevicePostured(boolean isPostured) {
setDevicePosturedInternal(isPostured);
}
+
+ @Override
+ public void updateSettings() {
+ synchronized (mLock) {
+ updateSettingsLocked();
+ }
+ }
}
/**
diff --git a/services/core/java/com/android/server/power/stats/BatteryHistoryDirectory.java b/services/core/java/com/android/server/power/stats/BatteryHistoryDirectory.java
index 29cc9ea..5563f98 100644
--- a/services/core/java/com/android/server/power/stats/BatteryHistoryDirectory.java
+++ b/services/core/java/com/android/server/power/stats/BatteryHistoryDirectory.java
@@ -251,6 +251,10 @@
try (FileInputStream stream = file.openRead()) {
byte[] header = new byte[FILE_FORMAT_BYTES];
if (stream.read(header, 0, FILE_FORMAT_BYTES) == -1) {
+ if (file.getBaseFile().length() == 0) {
+ return new byte[0];
+ }
+
Slog.e(TAG, "Invalid battery history file format " + file.getBaseFile());
deleteFragment(fragment);
return null;
diff --git a/services/core/java/com/android/server/uri/OWNERS b/services/core/java/com/android/server/uri/OWNERS
index cdc07ed..6599db7 100644
--- a/services/core/java/com/android/server/uri/OWNERS
+++ b/services/core/java/com/android/server/uri/OWNERS
@@ -1,3 +1,2 @@
jsharkey@android.com
-jsharkey@google.com
varunshah@google.com
diff --git a/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java b/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
index 5eed5470..cb82f48 100644
--- a/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
@@ -18,6 +18,7 @@
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH;
+import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_SIMULATE_REQUESTED_ORIENTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA;
@@ -183,9 +184,10 @@
private boolean shouldEnableCameraCompatFreeformTreatmentForApp() {
if (mCameraCompatAllowOrientationTreatmentOptProp != null) {
+ // OptProp is not-null iff the opt-out flag is on.
return mCameraCompatAllowOrientationTreatmentOptProp
.shouldEnableWithOptOutOverrideAndProperty(isChangeEnabled(mActivityRecord,
- OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT));
+ OVERRIDE_CAMERA_COMPAT_DISABLE_SIMULATE_REQUESTED_ORIENTATION));
} else {
return isChangeEnabled(mActivityRecord,
OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT);
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index d6ae651..c26acec 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -490,7 +490,7 @@
return null;
}
final Task taskToRecord = wc.asTask();
- if (taskToRecord == null) {
+ if (taskToRecord == null || !taskToRecord.isAttached()) {
handleStartRecordingFailed();
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
"Content Recording: Unable to retrieve task to start recording for "
diff --git a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
index b6f74a0..a4eeb68 100644
--- a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
@@ -16,6 +16,10 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.DesktopModeHelper.canEnterDesktopMode;
@@ -23,10 +27,13 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityOptions;
+import android.app.WindowConfiguration;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.util.Slog;
+import android.window.DesktopModeFlags;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.wm.LaunchParamsController.LaunchParamsModifier;
/**
* The class that defines default launch params for tasks in desktop mode
@@ -74,6 +81,13 @@
appendLog("task null, skipping");
return RESULT_SKIP;
}
+
+ if (DesktopModeFlags.DISABLE_DESKTOP_LAUNCH_PARAMS_OUTSIDE_DESKTOP_BUG_FIX.isTrue()
+ && !isEnteringDesktopMode(task, options, currentParams)) {
+ appendLog("not entering desktop mode, skipping");
+ return RESULT_SKIP;
+ }
+
if (com.android.window.flags.Flags.fixLayoutExistingTask()
&& task.getCreatedByOrganizerTask() != null) {
appendLog("has created-by-organizer-task, skipping");
@@ -122,6 +136,62 @@
return RESULT_CONTINUE;
}
+ /**
+ * Returns true if a task is entering desktop mode, due to its windowing mode being freeform or
+ * if there exists other freeform tasks on the display.
+ */
+ @VisibleForTesting
+ boolean isEnteringDesktopMode(
+ @NonNull Task task,
+ @Nullable ActivityOptions options,
+ @NonNull LaunchParamsController.LaunchParams currentParams) {
+ // As freeform tasks cannot exist outside of desktop mode, it is safe to assume if
+ // freeform tasks are visible we are in desktop mode and as a result any launching
+ // activity will also enter desktop mode. On this same relationship, we can also assume
+ // if there are not visible freeform tasks but a freeform activity is now launching, it
+ // will force the device into desktop mode.
+ return (task.getDisplayContent().getTopMostVisibleFreeformActivity() != null
+ && checkSourceWindowModesCompatible(task, options, currentParams))
+ || isRequestingFreeformWindowMode(task, options, currentParams);
+ }
+
+ private boolean isRequestingFreeformWindowMode(
+ @NonNull Task task,
+ @Nullable ActivityOptions options,
+ @NonNull LaunchParamsController.LaunchParams currentParams) {
+ return task.inFreeformWindowingMode()
+ || (options != null && options.getLaunchWindowingMode() == WINDOWING_MODE_FREEFORM)
+ || (currentParams.hasWindowingMode()
+ && currentParams.mWindowingMode == WINDOWING_MODE_FREEFORM);
+ }
+
+ /**
+ * Returns true is all possible source window modes are compatible with desktop mode.
+ */
+ private boolean checkSourceWindowModesCompatible(
+ @NonNull Task task,
+ @Nullable ActivityOptions options,
+ @NonNull LaunchParamsController.LaunchParams currentParams) {
+ return isCompatibleDesktopWindowingMode(task.getWindowingMode())
+ && (options == null
+ || isCompatibleDesktopWindowingMode(options.getLaunchWindowingMode()))
+ && isCompatibleDesktopWindowingMode(currentParams.mWindowingMode);
+ }
+
+ /**
+ * Returns true is the requesting window mode is one that can lead to the activity entering
+ * desktop.
+ */
+ private boolean isCompatibleDesktopWindowingMode(
+ @WindowConfiguration.WindowingMode int windowingMode) {
+ return switch (windowingMode) {
+ case WINDOWING_MODE_UNDEFINED,
+ WINDOWING_MODE_FULLSCREEN,
+ WINDOWING_MODE_FREEFORM -> true;
+ default -> false;
+ };
+ }
+
private void initLogBuilder(Task task, ActivityRecord activity) {
if (DEBUG) {
mLogBuilder = new StringBuilder(
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 50f12c3..64c19ff 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3674,6 +3674,11 @@
pw.println();
super.dump(pw, prefix, dumpAll);
+ pw.print(prefix);
+ if (mHasSetIgnoreOrientationRequest) {
+ pw.print("mHasSetIgnoreOrientationRequest=true ");
+ }
+ pw.print("ignoreOrientationRequest="); pw.println(getIgnoreOrientationRequest());
pw.print(prefix); pw.print("mLayoutSeq="); pw.println(mLayoutSeq);
pw.print(" mCurrentFocus="); pw.println(mCurrentFocus);
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 1a90bcc..9cf792d 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -25,6 +25,7 @@
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ORIENTATION;
+import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ORIENTATION_CHANGE;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_OPEN;
import static com.android.server.wm.DisplayRotationProto.FIXED_TO_USER_ROTATION_MODE;
import static com.android.server.wm.DisplayRotationProto.FROZEN_TO_USER_ROTATION;
@@ -587,11 +588,6 @@
ActivityInfo.screenOrientationToString(lastOrientation), lastOrientation,
Surface.rotationToString(oldRotation), oldRotation);
- ProtoLog.v(WM_DEBUG_ORIENTATION,
- "Display id=%d selected orientation %s (%d), got rotation %s (%d)", displayId,
- ActivityInfo.screenOrientationToString(lastOrientation), lastOrientation,
- Surface.rotationToString(rotation), rotation);
-
if (oldRotation == rotation) {
// No change.
return false;
@@ -601,9 +597,11 @@
mDisplayRotationCoordinator.onDefaultDisplayRotationChanged(rotation);
}
- ProtoLog.v(WM_DEBUG_ORIENTATION,
- "Display id=%d rotation changed to %d from %d, lastOrientation=%d",
- displayId, rotation, oldRotation, lastOrientation);
+ ProtoLog.i(WM_DEBUG_ORIENTATION_CHANGE, "Display id=%d rotation changed to %d from %d,"
+ + " lastOrientation=%d userRotationMode=%d userRotation=%d"
+ + " lastSensorRotation=%d",
+ displayId, rotation, oldRotation, lastOrientation, mUserRotationMode, mUserRotation,
+ mLastSensorRotation);
mRotation = rotation;
diff --git a/services/core/java/com/android/server/wm/OWNERS b/services/core/java/com/android/server/wm/OWNERS
index dede767..243a532 100644
--- a/services/core/java/com/android/server/wm/OWNERS
+++ b/services/core/java/com/android/server/wm/OWNERS
@@ -3,7 +3,6 @@
ogunwale@google.com
jjaggi@google.com
racarr@google.com
-chaviw@google.com
vishnun@google.com
akulian@google.com
roosa@google.com
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index d0d2067..1c3510d 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -2007,6 +2007,11 @@
return getActivity(r -> !r.finishing, true /* traverseTopToBottom */);
}
+ ActivityRecord getTopMostVisibleFreeformActivity() {
+ return getActivity(r -> r.isVisibleRequested() && r.inFreeformWindowingMode(),
+ true /* traverseTopToBottom */);
+ }
+
ActivityRecord getTopActivity(boolean includeFinishing, boolean includeOverlays) {
// Break down into 4 calls to avoid object creation due to capturing input params.
if (includeFinishing) {
diff --git a/services/core/jni/stats/OWNERS b/services/core/jni/stats/OWNERS
index 2611e5b..8d87925 100644
--- a/services/core/jni/stats/OWNERS
+++ b/services/core/jni/stats/OWNERS
@@ -1,8 +1,6 @@
jeffreyhuang@google.com
-jtnguyen@google.com
muhammadq@google.com
sharaieko@google.com
singhtejinder@google.com
tsaichristine@google.com
yaochen@google.com
-yro@google.com
diff --git a/services/musicrecognition/OWNERS b/services/musicrecognition/OWNERS
index 037b048..820be00 100644
--- a/services/musicrecognition/OWNERS
+++ b/services/musicrecognition/OWNERS
@@ -1,5 +1,4 @@
# Bug component: 830636
oni@google.com
-volnov@google.com
diff --git a/services/serial/OWNERS b/services/serial/OWNERS
new file mode 100644
index 0000000..89ce78e
--- /dev/null
+++ b/services/serial/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/hardware/serial/OWNERS
\ No newline at end of file
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml
index 5fe5b23..d6a6853 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml
@@ -18,6 +18,9 @@
<option name="test-suite-tag" value="apct" />
<option name="test-suite-tag" value="apct-instrumentation" />
+ <!-- Needed for reading the app files for the test artifacts -->
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="install-arg" value="-t" />
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
index d4be7f8..51da511 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
@@ -18,6 +18,7 @@
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH;
+import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_SIMULATE_REQUESTED_ORIENTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA;
@@ -231,6 +232,7 @@
@Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT)
public void testShouldApplyCameraCompatFreeformTreatment_notEnabledByOverride_returnsFalse() {
runTestScenario((robot) -> {
robot.activity().createActivityWithComponentInNewTask();
@@ -240,21 +242,19 @@
}
@Test
- @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_SIMULATE_REQUESTED_ORIENTATION})
@EnableFlags({FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING,
FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT})
- public void testShouldApplyCameraCompatFreeformTreatment_propertyFalse_returnsFalse() {
+ public void testShouldApplyCameraCompatFreeformTreatment_disablePropertyOn_returnsFalse() {
runTestScenario((robot) -> {
robot.activity().createActivityWithComponentInNewTask();
- robot.prop().disable(PROPERTY_CAMERA_COMPAT_ALLOW_SIMULATE_REQUESTED_ORIENTATION);
-
robot.checkShouldApplyFreeformTreatmentForCameraCompat(false);
});
}
@Test
- @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
+ @EnableCompatChanges(OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT)
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT)
public void testShouldApplyCameraCompatFreeformTreatment_optOutFlagNotEnabled_optOutIgnored() {
@@ -262,18 +262,50 @@
robot.activity().createActivityWithComponentInNewTask();
robot.prop().disable(PROPERTY_CAMERA_COMPAT_ALLOW_SIMULATE_REQUESTED_ORIENTATION);
+ robot.activity().createActivityWithComponentInNewTask();
robot.checkShouldApplyFreeformTreatmentForCameraCompat(true);
});
}
@Test
- @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
- @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
- public void testShouldApplyCameraCompatFreeformTreatment_overrideAndFlagEnabled_returnsTrue() {
+ @EnableFlags({FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING,
+ FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT})
+ public void testShouldApplyCameraCompatFreeformTreatment_optedOutViaProperty_returnsFalse() {
runTestScenario((robot) -> {
robot.activity().createActivityWithComponentInNewTask();
+ robot.prop().disable(PROPERTY_CAMERA_COMPAT_ALLOW_SIMULATE_REQUESTED_ORIENTATION);
+ robot.activity().createActivityWithComponentInNewTask();
+
+ robot.checkShouldApplyFreeformTreatmentForCameraCompat(false);
+ });
+ }
+
+ @Test
+ @EnableCompatChanges(OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT)
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT)
+ public void testShouldApplyCameraCompatFreeformTreatment_optInAndFlagEnabled_returnsTrue() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponentInNewTask();
+
+ robot.checkShouldApplyFreeformTreatmentForCameraCompat(true);
+ });
+ }
+
+
+ @Test
+ @EnableFlags({FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING,
+ FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT})
+ public void testShouldApplyCameraCompatFreeformTreatment_notOptedOut_flagEnabled_returnsTrue() {
+ runTestScenario((robot) -> {
+ robot.conf().enableCameraCompatTreatment(true);
+ robot.applyOnActivity((a) -> {
+ a.createActivityWithComponentInNewTask();
+ robot.prop().enable(PROPERTY_CAMERA_COMPAT_ALLOW_SIMULATE_REQUESTED_ORIENTATION);
+ });
+
robot.checkShouldApplyFreeformTreatmentForCameraCompat(true);
});
}
@@ -294,6 +326,8 @@
@EnableCompatChanges({OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA,
OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA,
OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT)
public void testShouldRecomputeConfigurationForFreeformTreatment() {
runTestScenario((robot) -> {
robot.conf().enableCameraCompatSplitScreenAspectRatio(true);
@@ -307,6 +341,24 @@
}
@Test
+ @EnableCompatChanges({OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA,
+ OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
+ @EnableFlags({FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING,
+ FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT})
+ public void testShouldRecomputeConfigurationForFreeformTreatmentWithOptOutMechanism() {
+ runTestScenario((robot) -> {
+ robot.conf().enableCameraCompatSplitScreenAspectRatio(true);
+ robot.conf().enableCameraCompatTreatment(true);
+ robot.applyOnActivity((a) -> {
+ a.createActivityWithComponentInNewTask();
+ robot.prop().enable(PROPERTY_CAMERA_COMPAT_ALLOW_SIMULATE_REQUESTED_ORIENTATION);
+ });
+
+ robot.checkShouldApplyFreeformTreatmentForCameraCompat(true);
+ });
+ }
+
+ @Test
@EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
public void shouldOverrideMinAspectRatioForCamera_overrideEnabled_returnsTrue() {
runTestScenario((robot) -> {
diff --git a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
index 5607252..50876c7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
@@ -25,6 +25,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
+import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_SIMULATE_REQUESTED_ORIENTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_FULL_USER;
@@ -42,6 +43,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.server.wm.AppCompatConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
import static com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING;
+import static com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -134,8 +136,10 @@
}
@Test
- @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
- public void testIsCameraRunningAndWindowingModeEligible_overrideDisabled_returnsFalse() {
+ @EnableFlags({FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING,
+ FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT})
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_SIMULATE_REQUESTED_ORIENTATION})
+ public void testIsCameraRunningAndWindowingModeEligible_disabledViaOverride_returnsFalse() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -165,6 +169,7 @@
@Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT)
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testIsCameraRunningAndWindowingModeEligible_optInFreeformCameraRunning_true() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
@@ -175,6 +180,17 @@
}
@Test
+ @EnableFlags({FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING,
+ FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT})
+ public void testIsCameraRunningAndWindowingModeEligible_freeformCameraRunning_true() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertTrue(mCameraCompatFreeformPolicy.isCameraRunningAndWindowingModeEligible(mActivity));
+ }
+
+ @Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
public void testIsFreeformLetterboxingForCameraAllowed_overrideDisabled_returnsFalse() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
index d305c2f..3a06fff 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
@@ -49,6 +49,8 @@
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
import android.app.ActivityOptions;
import android.compat.testing.PlatformCompatChangeRule;
@@ -98,7 +100,8 @@
mResult = new LaunchParamsController.LaunchParams();
mResult.reset();
- mTarget = new DesktopModeLaunchParamsModifier(mContext);
+ mTarget = spy(new DesktopModeLaunchParamsModifier(mContext));
+ doReturn(true).when(mTarget).isEnteringDesktopMode(any(), any(), any());
}
@Test
@@ -137,6 +140,81 @@
}
@Test
+ @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_DISABLE_DESKTOP_LAUNCH_PARAMS_OUTSIDE_DESKTOP_BUG_FIX})
+ public void testReturnsSkipIfIsEnteringDesktopModeFalse() {
+ setupDesktopModeLaunchParamsModifier();
+ when(mTarget.isEnteringDesktopMode(any(), any(), any())).thenReturn(false);
+
+ final Task task = new TaskBuilder(mSupervisor).build();
+
+ assertEquals(RESULT_SKIP, new CalculateRequestBuilder().setTask(task).calculate());
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_DISABLE_DESKTOP_LAUNCH_PARAMS_OUTSIDE_DESKTOP_BUG_FIX})
+ public void testReturnsContinueIfVisibleFreeformTaskExists() {
+ setupDesktopModeLaunchParamsModifier();
+ when(mTarget.isEnteringDesktopMode(any(), any(), any())).thenCallRealMethod();
+
+ final DisplayContent dc = spy(createNewDisplay());
+ final Task existingFreeformTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
+ .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ doReturn(existingFreeformTask.getRootActivity()).when(dc)
+ .getTopMostVisibleFreeformActivity();
+ final Task launchingTask = new TaskBuilder(mSupervisor).build();
+ launchingTask.onDisplayChanged(dc);
+
+ assertEquals(RESULT_CONTINUE,
+ new CalculateRequestBuilder().setTask(launchingTask).calculate());
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_DISABLE_DESKTOP_LAUNCH_PARAMS_OUTSIDE_DESKTOP_BUG_FIX})
+ public void testReturnsContinueIfTaskInFreeform() {
+ setupDesktopModeLaunchParamsModifier();
+ when(mTarget.isEnteringDesktopMode(any(), any(), any())).thenCallRealMethod();
+
+ final Task task = new TaskBuilder(mSupervisor).setWindowingMode(WINDOWING_MODE_FREEFORM)
+ .build();
+
+ assertEquals(RESULT_CONTINUE,
+ new CalculateRequestBuilder().setTask(task).calculate());
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_DISABLE_DESKTOP_LAUNCH_PARAMS_OUTSIDE_DESKTOP_BUG_FIX})
+ public void testReturnsContinueIfFreeformRequestViaActivityOptions() {
+ setupDesktopModeLaunchParamsModifier();
+ when(mTarget.isEnteringDesktopMode(any(), any(), any())).thenCallRealMethod();
+
+ final Task task = new TaskBuilder(mSupervisor).build();
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
+
+ assertEquals(RESULT_CONTINUE,
+ new CalculateRequestBuilder().setTask(task).setOptions(options).calculate());
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_DISABLE_DESKTOP_LAUNCH_PARAMS_OUTSIDE_DESKTOP_BUG_FIX})
+ public void testReturnsContinueIfFreeformRequestViaPreviousModifier() {
+ setupDesktopModeLaunchParamsModifier();
+ when(mTarget.isEnteringDesktopMode(any(), any(), any())).thenCallRealMethod();
+
+ final Task task = new TaskBuilder(mSupervisor).build();
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
+
+ assertEquals(RESULT_CONTINUE,
+ new CalculateRequestBuilder().setTask(task).setOptions(options).calculate());
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
public void testReturnsSkipIfNotBoundsPhase() {
setupDesktopModeLaunchParamsModifier();
diff --git a/telecomm/java/android/telecom/OWNERS b/telecomm/java/android/telecom/OWNERS
index 6656a01..0854c5d 100644
--- a/telecomm/java/android/telecom/OWNERS
+++ b/telecomm/java/android/telecom/OWNERS
@@ -3,4 +3,3 @@
rgreenwalt@google.com
tgunn@google.com
breadley@google.com
-hallliu@google.com
diff --git a/tests/EnforcePermission/OWNERS b/tests/EnforcePermission/OWNERS
index 39550a3..160849e 100644
--- a/tests/EnforcePermission/OWNERS
+++ b/tests/EnforcePermission/OWNERS
@@ -1,3 +1,2 @@
# Bug component: 315013
tweek@google.com
-brufino@google.com
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index f43cf52..43d5b71 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -113,6 +113,7 @@
"io/ZipArchive.cpp",
"link/AutoVersioner.cpp",
"link/FeatureFlagsFilter.cpp",
+ "link/FlaggedXmlVersioner.cpp",
"link/FlagDisabledResourceRemover.cpp",
"link/ManifestFixer.cpp",
"link/NoDefaultResourceRemover.cpp",
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index fb576df..9e2a4c1 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -547,7 +547,8 @@
});
std::string_view resource_type = parser->element_name();
- if (auto flag = ParseFlag(xml::FindAttribute(parser, xml::kSchemaAndroid, "featureFlag"))) {
+ if (auto flag =
+ ParseFlag(xml::FindAttribute(parser, xml::kSchemaAndroid, xml::kAttrFeatureFlag))) {
if (options_.flag) {
diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
<< "Resource flag are not allowed both in the path and in the file");
@@ -1529,7 +1530,7 @@
ResolvePackage(parser, &maybe_key.value());
maybe_key.value().SetSource(source);
- auto flag = ParseFlag(xml::FindAttribute(parser, xml::kSchemaAndroid, "featureFlag"));
+ auto flag = ParseFlag(xml::FindAttribute(parser, xml::kSchemaAndroid, xml::kAttrFeatureFlag));
std::unique_ptr<Item> value = ParseXml(parser, 0, kAllowRawString);
if (!value) {
@@ -1674,7 +1675,7 @@
const std::string& element_namespace = parser->element_namespace();
const std::string& element_name = parser->element_name();
if (element_namespace.empty() && element_name == "item") {
- auto flag = ParseFlag(xml::FindAttribute(parser, xml::kSchemaAndroid, "featureFlag"));
+ auto flag = ParseFlag(xml::FindAttribute(parser, xml::kSchemaAndroid, xml::kAttrFeatureFlag));
std::unique_ptr<Item> item = ParseXml(parser, typeMask, kNoRawString);
if (!item) {
diag_->Error(android::DiagMessage(item_source) << "could not parse array item");
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 0a5cb1f..2a79216 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -58,6 +58,7 @@
#include "java/ProguardRules.h"
#include "link/FeatureFlagsFilter.h"
#include "link/FlagDisabledResourceRemover.h"
+#include "link/FlaggedXmlVersioner.h"
#include "link/Linkers.h"
#include "link/ManifestFixer.h"
#include "link/NoDefaultResourceRemover.h"
@@ -503,10 +504,19 @@
const ConfigDescription& config = file_op->config;
ResourceEntry* entry = file_op->entry;
+ FlaggedXmlVersioner flagged_xml_versioner;
+ auto flag_split_resources = flagged_xml_versioner.Process(context_, doc);
+
+ std::vector<std::unique_ptr<xml::XmlResource>> final_resources;
XmlCompatVersioner xml_compat_versioner(&rules_);
const util::Range<ApiVersion> api_range{config.sdkVersion,
FindNextApiVersionForConfig(entry, config)};
- return xml_compat_versioner.Process(context_, doc, api_range);
+ for (auto& split_res : flag_split_resources) {
+ auto inner_resources = xml_compat_versioner.Process(context_, split_res.get(), api_range);
+ final_resources.insert(final_resources.end(), std::make_move_iterator(inner_resources.begin()),
+ std::make_move_iterator(inner_resources.end()));
+ }
+ return final_resources;
}
ResourceFile::Type XmlFileTypeForOutputFormat(OutputFormat format) {
diff --git a/tools/aapt2/link/FeatureFlagsFilter.cpp b/tools/aapt2/link/FeatureFlagsFilter.cpp
index 23f7838..74066a3 100644
--- a/tools/aapt2/link/FeatureFlagsFilter.cpp
+++ b/tools/aapt2/link/FeatureFlagsFilter.cpp
@@ -51,7 +51,7 @@
private:
bool ShouldRemove(std::unique_ptr<xml::Node>& node) {
if (auto* el = NodeCast<Element>(node.get())) {
- auto* attr = el->FindAttribute(xml::kSchemaAndroid, "featureFlag");
+ auto* attr = el->FindAttribute(xml::kSchemaAndroid, xml::kAttrFeatureFlag);
if (attr == nullptr) {
return false;
}
@@ -76,7 +76,7 @@
// Remove if flag==true && attr=="!flag" (negated) OR flag==false && attr=="flag"
bool remove = *it->second.enabled == negated;
if (!remove) {
- el->RemoveAttribute(xml::kSchemaAndroid, "featureFlag");
+ el->RemoveAttribute(xml::kSchemaAndroid, xml::kAttrFeatureFlag);
}
return remove;
}
diff --git a/tools/aapt2/link/FlaggedResources_test.cpp b/tools/aapt2/link/FlaggedResources_test.cpp
index 7bea96c..dbef776 100644
--- a/tools/aapt2/link/FlaggedResources_test.cpp
+++ b/tools/aapt2/link/FlaggedResources_test.cpp
@@ -163,7 +163,7 @@
auto loaded_apk = LoadedApk::LoadApkFromPath(apk_path, &noop_diag);
std::string output;
- DumpXmlTreeToString(loaded_apk.get(), "res/layout-v22/layout1.xml", &output);
+ DumpXmlTreeToString(loaded_apk.get(), "res/layout-v36/layout1.xml", &output);
ASSERT_FALSE(output.contains("test.package.trueFlag"));
ASSERT_TRUE(output.contains("FIND_ME"));
ASSERT_TRUE(output.contains("test.package.readWriteFlag"));
diff --git a/tools/aapt2/link/FlaggedXmlVersioner.cpp b/tools/aapt2/link/FlaggedXmlVersioner.cpp
new file mode 100644
index 0000000..75c6f17
--- /dev/null
+++ b/tools/aapt2/link/FlaggedXmlVersioner.cpp
@@ -0,0 +1,92 @@
+/*
+ * 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.
+ */
+
+#include "link/FlaggedXmlVersioner.h"
+
+#include "SdkConstants.h"
+#include "androidfw/Util.h"
+
+using ::aapt::xml::Element;
+using ::aapt::xml::NodeCast;
+
+namespace aapt {
+
+// An xml visitor that goes through the a doc and removes any elements that are behind non-negated
+// flags. It also removes the featureFlag attribute from elements behind negated flags.
+class AllDisabledFlagsVisitor : public xml::Visitor {
+ public:
+ void Visit(xml::Element* node) override {
+ std::erase_if(node->children, [this](const std::unique_ptr<xml::Node>& node) {
+ return FixupOrShouldRemove(node);
+ });
+ VisitChildren(node);
+ }
+
+ bool HadFlags() const {
+ return had_flags_;
+ }
+
+ private:
+ bool FixupOrShouldRemove(const std::unique_ptr<xml::Node>& node) {
+ if (auto* el = NodeCast<Element>(node.get())) {
+ auto* attr = el->FindAttribute(xml::kSchemaAndroid, xml::kAttrFeatureFlag);
+ if (attr == nullptr) {
+ return false;
+ }
+
+ had_flags_ = true;
+ // This class assumes all flags are disabled so we want to remove any elements behind flags
+ // unless the flag specification is negated. In the negated case we remove the featureFlag
+ // attribute because we have already determined whether we are keeping the element or not.
+ std::string_view flag_name = util::TrimWhitespace(attr->value);
+ if (flag_name.starts_with('!')) {
+ el->RemoveAttribute(xml::kSchemaAndroid, xml::kAttrFeatureFlag);
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ bool had_flags_ = false;
+};
+
+std::vector<std::unique_ptr<xml::XmlResource>> FlaggedXmlVersioner::Process(IAaptContext* context,
+ xml::XmlResource* doc) {
+ std::vector<std::unique_ptr<xml::XmlResource>> docs;
+ if ((static_cast<ApiVersion>(doc->file.config.sdkVersion) >= SDK_BAKLAVA) ||
+ (static_cast<ApiVersion>(context->GetMinSdkVersion()) >= SDK_BAKLAVA)) {
+ // Support for read/write flags was added in baklava so if the doc will only get used on
+ // baklava or later we can just return the original doc.
+ docs.push_back(doc->Clone());
+ } else {
+ auto preBaklavaVersion = doc->Clone();
+ AllDisabledFlagsVisitor visitor;
+ preBaklavaVersion->root->Accept(&visitor);
+ docs.push_back(std::move(preBaklavaVersion));
+
+ if (visitor.HadFlags()) {
+ auto baklavaVersion = doc->Clone();
+ baklavaVersion->file.config.sdkVersion = SDK_BAKLAVA;
+ docs.push_back(std::move(baklavaVersion));
+ }
+ }
+ return docs;
+}
+
+} // namespace aapt
\ No newline at end of file
diff --git a/tools/aapt2/link/FlaggedXmlVersioner.h b/tools/aapt2/link/FlaggedXmlVersioner.h
new file mode 100644
index 0000000..44ed266
--- /dev/null
+++ b/tools/aapt2/link/FlaggedXmlVersioner.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright 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.
+ */
+
+#pragma once
+
+#include <memory>
+#include <vector>
+
+#include "process/IResourceTableConsumer.h"
+#include "xml/XmlDom.h"
+
+namespace aapt {
+
+// FlaggedXmlVersioner takes an XmlResource and checks if any elements have read write android
+// flags on them. If the doc doesn't refer to any such flags the returned vector only contains
+// the original doc.
+//
+// Read/write flags within xml resources files is only supported in android baklava and later. If
+// the config resource specifies a version that is baklava or later it returns a vector containing
+// the original XmlResource. Otherwise FlaggedXmlVersioner creates a version of the doc where all
+// flags are assumed disabled and the config version is the same as the original doc, if specified.
+// It also creates an XmlResource where the contents are the same as the original doc and the config
+// version is baklava. The returned vector is composed of these two new docs.
+class FlaggedXmlVersioner {
+ public:
+ std::vector<std::unique_ptr<xml::XmlResource>> Process(IAaptContext* context,
+ xml::XmlResource* doc);
+};
+} // namespace aapt
\ No newline at end of file
diff --git a/tools/aapt2/link/FlaggedXmlVersioner_test.cpp b/tools/aapt2/link/FlaggedXmlVersioner_test.cpp
new file mode 100644
index 0000000..0c1314f
--- /dev/null
+++ b/tools/aapt2/link/FlaggedXmlVersioner_test.cpp
@@ -0,0 +1,220 @@
+/*
+ * 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.
+ */
+
+#include "link/FlaggedXmlVersioner.h"
+
+#include "Debug.h"
+#include "SdkConstants.h"
+#include "io/StringStream.h"
+#include "test/Test.h"
+
+using ::aapt::test::ValueEq;
+using ::testing::Eq;
+using ::testing::IsNull;
+using ::testing::NotNull;
+using ::testing::Pointee;
+using ::testing::SizeIs;
+
+namespace aapt {
+
+class FlaggedXmlVersionerTest : public ::testing::Test {
+ public:
+ void SetUp() override {
+ context_ = test::ContextBuilder()
+ .SetCompilationPackage("com.app")
+ .SetPackageId(0x7f)
+ .SetPackageType(PackageType::kApp)
+ .Build();
+ }
+
+ protected:
+ std::unique_ptr<IAaptContext> context_;
+};
+
+static void PrintDocToString(xml::XmlResource* doc, std::string* out) {
+ io::StringOutputStream stream(out, 1024u);
+ text::Printer printer(&stream);
+ Debug::DumpXml(*doc, &printer);
+ stream.Flush();
+}
+
+TEST_F(FlaggedXmlVersionerTest, NoFlagReturnsOriginal) {
+ auto doc = test::BuildXmlDomForPackageName(context_.get(), R"(
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
+ <TextView />
+ <TextView />
+ <TextView />
+ </LinearLayout>)");
+ doc->file.config.sdkVersion = SDK_GINGERBREAD;
+
+ FlaggedXmlVersioner versioner;
+ auto results = versioner.Process(context_.get(), doc.get());
+ EXPECT_THAT(results.size(), Eq(1));
+ EXPECT_THAT(results[0]->file.config.sdkVersion, Eq(SDK_GINGERBREAD));
+
+ std::string expected;
+ PrintDocToString(doc.get(), &expected);
+ std::string actual;
+ PrintDocToString(results[0].get(), &actual);
+
+ EXPECT_THAT(actual, Eq(expected));
+}
+
+TEST_F(FlaggedXmlVersionerTest, AlreadyBaklavaReturnsOriginal) {
+ auto doc = test::BuildXmlDomForPackageName(context_.get(), R"(
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
+ <TextView android:featureFlag="package.flag" />
+ <TextView />
+ <TextView />
+ </LinearLayout>)");
+ doc->file.config.sdkVersion = SDK_BAKLAVA;
+
+ FlaggedXmlVersioner versioner;
+ auto results = versioner.Process(context_.get(), doc.get());
+ EXPECT_THAT(results.size(), Eq(1));
+ EXPECT_THAT(results[0]->file.config.sdkVersion, Eq(SDK_BAKLAVA));
+
+ std::string expected;
+ PrintDocToString(doc.get(), &expected);
+ std::string actual;
+ PrintDocToString(results[0].get(), &actual);
+
+ EXPECT_THAT(actual, Eq(expected));
+}
+
+TEST_F(FlaggedXmlVersionerTest, PreBaklavaGetsSplit) {
+ auto doc = test::BuildXmlDomForPackageName(context_.get(), R"(
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
+ <TextView android:featureFlag="package.flag" /><TextView /><TextView />
+ </LinearLayout>)");
+ doc->file.config.sdkVersion = SDK_GINGERBREAD;
+
+ FlaggedXmlVersioner versioner;
+ auto results = versioner.Process(context_.get(), doc.get());
+ EXPECT_THAT(results.size(), Eq(2));
+ EXPECT_THAT(results[0]->file.config.sdkVersion, Eq(SDK_GINGERBREAD));
+ EXPECT_THAT(results[1]->file.config.sdkVersion, Eq(SDK_BAKLAVA));
+
+ auto gingerbread_doc = test::BuildXmlDomForPackageName(context_.get(), R"(
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
+ <TextView /><TextView />
+ </LinearLayout>)");
+
+ std::string expected0;
+ PrintDocToString(gingerbread_doc.get(), &expected0);
+ std::string actual0;
+ PrintDocToString(results[0].get(), &actual0);
+ EXPECT_THAT(actual0, Eq(expected0));
+
+ std::string expected1;
+ PrintDocToString(doc.get(), &expected1);
+ std::string actual1;
+ PrintDocToString(results[1].get(), &actual1);
+ EXPECT_THAT(actual1, Eq(expected1));
+}
+
+TEST_F(FlaggedXmlVersionerTest, NoVersionGetsSplit) {
+ auto doc = test::BuildXmlDomForPackageName(context_.get(), R"(
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
+ <TextView android:featureFlag="package.flag" /><TextView /><TextView />
+ </LinearLayout>)");
+
+ FlaggedXmlVersioner versioner;
+ auto results = versioner.Process(context_.get(), doc.get());
+ EXPECT_THAT(results.size(), Eq(2));
+ EXPECT_THAT(results[0]->file.config.sdkVersion, Eq(0));
+ EXPECT_THAT(results[1]->file.config.sdkVersion, Eq(SDK_BAKLAVA));
+
+ auto gingerbread_doc = test::BuildXmlDomForPackageName(context_.get(), R"(
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
+ <TextView /><TextView />
+ </LinearLayout>)");
+
+ std::string expected0;
+ PrintDocToString(gingerbread_doc.get(), &expected0);
+ std::string actual0;
+ PrintDocToString(results[0].get(), &actual0);
+ EXPECT_THAT(actual0, Eq(expected0));
+
+ std::string expected1;
+ PrintDocToString(doc.get(), &expected1);
+ std::string actual1;
+ PrintDocToString(results[1].get(), &actual1);
+ EXPECT_THAT(actual1, Eq(expected1));
+}
+
+TEST_F(FlaggedXmlVersionerTest, NegatedFlagAttributeRemoved) {
+ auto doc = test::BuildXmlDomForPackageName(context_.get(), R"(
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
+ <TextView android:featureFlag="!package.flag" /><TextView /><TextView />
+ </LinearLayout>)");
+ doc->file.config.sdkVersion = SDK_GINGERBREAD;
+
+ FlaggedXmlVersioner versioner;
+ auto results = versioner.Process(context_.get(), doc.get());
+ EXPECT_THAT(results.size(), Eq(2));
+ EXPECT_THAT(results[0]->file.config.sdkVersion, Eq(SDK_GINGERBREAD));
+ EXPECT_THAT(results[1]->file.config.sdkVersion, Eq(SDK_BAKLAVA));
+
+ auto gingerbread_doc = test::BuildXmlDomForPackageName(context_.get(), R"(
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
+ <TextView /><TextView /><TextView />
+ </LinearLayout>)");
+
+ std::string expected0;
+ PrintDocToString(gingerbread_doc.get(), &expected0);
+ std::string actual0;
+ PrintDocToString(results[0].get(), &actual0);
+ EXPECT_THAT(actual0, Eq(expected0));
+
+ std::string expected1;
+ PrintDocToString(doc.get(), &expected1);
+ std::string actual1;
+ PrintDocToString(results[1].get(), &actual1);
+ EXPECT_THAT(actual1, Eq(expected1));
+}
+
+TEST_F(FlaggedXmlVersionerTest, NegatedFlagAttributeRemovedNoSpecifiedVersion) {
+ auto doc = test::BuildXmlDomForPackageName(context_.get(), R"(
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
+ <TextView android:featureFlag="!package.flag" /><TextView /><TextView />
+ </LinearLayout>)");
+
+ FlaggedXmlVersioner versioner;
+ auto results = versioner.Process(context_.get(), doc.get());
+ EXPECT_THAT(results.size(), Eq(2));
+ EXPECT_THAT(results[0]->file.config.sdkVersion, Eq(0));
+ EXPECT_THAT(results[1]->file.config.sdkVersion, Eq(SDK_BAKLAVA));
+
+ auto gingerbread_doc = test::BuildXmlDomForPackageName(context_.get(), R"(
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
+ <TextView /><TextView /><TextView />
+ </LinearLayout>)");
+
+ std::string expected0;
+ PrintDocToString(gingerbread_doc.get(), &expected0);
+ std::string actual0;
+ PrintDocToString(results[0].get(), &actual0);
+ EXPECT_THAT(actual0, Eq(expected0));
+
+ std::string expected1;
+ PrintDocToString(doc.get(), &expected1);
+ std::string actual1;
+ PrintDocToString(results[1].get(), &actual1);
+ EXPECT_THAT(actual1, Eq(expected1));
+}
+
+} // namespace aapt
\ No newline at end of file
diff --git a/tools/aapt2/xml/XmlUtil.h b/tools/aapt2/xml/XmlUtil.h
index ad676ca..789f6a0 100644
--- a/tools/aapt2/xml/XmlUtil.h
+++ b/tools/aapt2/xml/XmlUtil.h
@@ -30,6 +30,7 @@
constexpr const char* kSchemaAndroid = "http://schemas.android.com/apk/res/android";
constexpr const char* kSchemaTools = "http://schemas.android.com/tools";
constexpr const char* kSchemaAapt = "http://schemas.android.com/aapt";
+constexpr const char* kAttrFeatureFlag = "featureFlag";
// Result of extracting a package name from a namespace URI declaration.
struct ExtractedPackage {
diff --git a/tools/hiddenapi/OWNERS b/tools/hiddenapi/OWNERS
index dc82aac..d1e36b9 100644
--- a/tools/hiddenapi/OWNERS
+++ b/tools/hiddenapi/OWNERS
@@ -1,6 +1,5 @@
# compat-team@ for changes to hiddenapi files
mathewi@google.com
-satayev@google.com
# soong-team@ as the files these tools protect are tightly coupled with Soong
file:platform/build/soong:/OWNERS
diff --git a/tools/lint/OWNERS b/tools/lint/OWNERS
index 8e4569e..4035e19 100644
--- a/tools/lint/OWNERS
+++ b/tools/lint/OWNERS
@@ -1,6 +1,5 @@
mattgilbride@google.com
azharaa@google.com
-jsharkey@google.com
per-file *CallingSettingsNonUserGetterMethods* = file:/packages/SettingsProvider/OWNERS
per-file *RegisterReceiverFlagDetector* = jacobhobbie@google.com
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/OWNERS b/wifi/java/src/android/net/wifi/sharedconnectivity/OWNERS
index 2a4acc1..abb9aa4 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/OWNERS
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/OWNERS
@@ -1,4 +1,3 @@
# Bug component: 1216021
asapperstein@google.com
-etancohen@google.com