Merge "Add method to get bluetooth key missing count" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 8bfac03..4e05cbc 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -110,6 +110,7 @@
"com.android.window.flags.window-aconfig-java",
"configinfra_framework_flags_java_exported_lib",
"conscrypt_exported_aconfig_flags_lib",
+ "sdk_sandbox_exported_flags_lib",
"device_policy_aconfig_flags_lib",
"display_flags_lib",
"dropbox_flags_lib",
@@ -123,7 +124,6 @@
"libcore_readonly_aconfig_flags_lib",
"libgui_flags_java_lib",
"power_flags_lib",
- "sdk_sandbox_flags_lib",
"surfaceflinger_flags_java_lib",
"telecom_flags_core_java_lib",
"telephony_flags_core_java_lib",
diff --git a/Android.bp b/Android.bp
index 303fa2c..444725e 100644
--- a/Android.bp
+++ b/Android.bp
@@ -103,10 +103,10 @@
":android.hardware.gnss-V2-java-source",
":android.hardware.graphics.common-V3-java-source",
":android.hardware.keymaster-V4-java-source",
- ":android.hardware.radio-V4-java-source",
- ":android.hardware.radio.data-V4-java-source",
- ":android.hardware.radio.network-V4-java-source",
- ":android.hardware.radio.voice-V4-java-source",
+ ":android.hardware.radio-V5-java-source",
+ ":android.hardware.radio.data-V5-java-source",
+ ":android.hardware.radio.network-V5-java-source",
+ ":android.hardware.radio.voice-V5-java-source",
":android.hardware.security.secureclock-V1-java-source",
":android.hardware.thermal-V3-java-source",
":android.hardware.tv.tuner-V3-java-source",
@@ -232,13 +232,13 @@
"android.hardware.gnss-V2.1-java",
"android.hardware.health-V1.0-java-constants",
"android.hardware.radio-V1.6-java",
- "android.hardware.radio.data-V4-java",
- "android.hardware.radio.ims-V3-java",
- "android.hardware.radio.messaging-V4-java",
- "android.hardware.radio.modem-V4-java",
- "android.hardware.radio.network-V4-java",
- "android.hardware.radio.sim-V4-java",
- "android.hardware.radio.voice-V4-java",
+ "android.hardware.radio.data-V5-java",
+ "android.hardware.radio.ims-V4-java",
+ "android.hardware.radio.messaging-V5-java",
+ "android.hardware.radio.modem-V5-java",
+ "android.hardware.radio.network-V5-java",
+ "android.hardware.radio.sim-V5-java",
+ "android.hardware.radio.voice-V5-java",
"android.hardware.thermal-V1.0-java-constants",
"android.hardware.thermal-V1.0-java",
"android.hardware.thermal-V1.1-java",
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 05d6e88..e862cd9 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -6,6 +6,7 @@
[Builtin Hooks Options]
# Only turn on clang-format check for the following subfolders.
clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
+ apct-tests/
cmds/hid/
cmds/input/
cmds/uinput/
@@ -18,7 +19,7 @@
tests/
tools/
bpfmt = -d
-ktfmt = --kotlinlang-style --include-dirs=services/permission,packages/SystemUI,libs/WindowManager/Shell/src/com/android/wm/shell/freeform,libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode,libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode
+ktfmt = --kotlinlang-style --include-dirs=services/permission,packages/SystemUI,libs/WindowManager/Shell/src/com/android/wm/shell/freeform,libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode,libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode,apct-tests
[Hook Scripts]
checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
diff --git a/Ravenwood.bp b/Ravenwood.bp
index 2e038e0..a16b440 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -19,6 +19,7 @@
name: "framework-minus-apex-for-host",
installable: false,
static_libs: ["framework-minus-apex"],
+ srcs: [":framework-ravenwood-sources"],
visibility: ["//frameworks/base/ravenwood"],
}
diff --git a/apct-tests/perftests/core/src/android/input/MotionPredictorBenchmark.kt b/apct-tests/perftests/core/src/android/input/MotionPredictorBenchmark.kt
index add0a08..cf93331 100644
--- a/apct-tests/perftests/core/src/android/input/MotionPredictorBenchmark.kt
+++ b/apct-tests/perftests/core/src/android/input/MotionPredictorBenchmark.kt
@@ -25,11 +25,10 @@
import android.view.MotionEvent.PointerCoords
import android.view.MotionEvent.PointerProperties
import android.view.MotionPredictor
-
-import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.filters.LargeTest
import androidx.test.ext.junit.runners.AndroidJUnit4
-
+import androidx.test.filters.LargeTest
+import androidx.test.platform.app.InstrumentationRegistry
+import java.time.Duration
import org.junit.After
import org.junit.Assert.assertTrue
import org.junit.Before
@@ -37,14 +36,12 @@
import org.junit.Test
import org.junit.runner.RunWith
-import java.time.Duration
-
private fun getStylusMotionEvent(
- eventTime: Duration,
- action: Int,
- x: Float,
- y: Float,
- ): MotionEvent{
+ eventTime: Duration,
+ action: Int,
+ x: Float,
+ y: Float,
+): MotionEvent {
val pointerCount = 1
val properties = arrayOfNulls<MotionEvent.PointerProperties>(pointerCount)
val coords = arrayOfNulls<MotionEvent.PointerCoords>(pointerCount)
@@ -58,37 +55,49 @@
coords[i]!!.y = y
}
- return MotionEvent.obtain(/*downTime=*/0, eventTime.toMillis(), action, properties.size,
- properties, coords, /*metaState=*/0, /*buttonState=*/0,
- /*xPrecision=*/0f, /*yPrecision=*/0f, /*deviceId=*/0, /*edgeFlags=*/0,
- InputDevice.SOURCE_STYLUS, /*flags=*/0)
+ return MotionEvent.obtain(
+ /*downTime=*/ 0,
+ eventTime.toMillis(),
+ action,
+ properties.size,
+ properties,
+ coords,
+ /*metaState=*/ 0,
+ /*buttonState=*/ 0,
+ /*xPrecision=*/ 0f,
+ /*yPrecision=*/ 0f,
+ /*deviceId=*/ 0,
+ /*edgeFlags=*/ 0,
+ InputDevice.SOURCE_STYLUS,
+ /*flags=*/ 0,
+ )
}
@RunWith(AndroidJUnit4::class)
@LargeTest
class MotionPredictorBenchmark {
private val instrumentation = InstrumentationRegistry.getInstrumentation()
- @get:Rule
- val perfStatusReporter = PerfStatusReporter()
+ @get:Rule val perfStatusReporter = PerfStatusReporter()
private val initialPropertyValue =
- SystemProperties.get("persist.input.enable_motion_prediction")
-
+ SystemProperties.get("persist.input.enable_motion_prediction")
@Before
fun setUp() {
instrumentation.uiAutomation.executeShellCommand(
- "setprop persist.input.enable_motion_prediction true")
+ "setprop persist.input.enable_motion_prediction true"
+ )
}
@After
fun tearDown() {
instrumentation.uiAutomation.executeShellCommand(
- "setprop persist.input.enable_motion_prediction $initialPropertyValue")
+ "setprop persist.input.enable_motion_prediction $initialPropertyValue"
+ )
}
/**
- * In a typical usage, app will send the event to the predictor and then call .predict to draw
- * a prediction. In a loop, we keep sending MOVE and then calling .predict to simulate this.
+ * In a typical usage, app will send the event to the predictor and then call .predict to draw a
+ * prediction. In a loop, we keep sending MOVE and then calling .predict to simulate this.
*/
@Test
fun timeRecordAndPredict() {
@@ -99,10 +108,16 @@
var eventPosition = 0f
val positionInterval = 10f
- val predictor = MotionPredictor(/*isPredictionEnabled=*/true, offset.toNanos().toInt())
+ val predictor = MotionPredictor(/* isPredictionEnabled= */ true, offset.toNanos().toInt())
// ACTION_DOWN t=0 x=0 y=0
- predictor.record(getStylusMotionEvent(
- eventTime, ACTION_DOWN, /*x=*/eventPosition, /*y=*/eventPosition))
+ predictor.record(
+ getStylusMotionEvent(
+ eventTime,
+ ACTION_DOWN,
+ /*x=*/ eventPosition,
+ /*y=*/ eventPosition,
+ )
+ )
val state = perfStatusReporter.getBenchmarkState()
while (state.keepRunning()) {
@@ -110,8 +125,13 @@
eventPosition += positionInterval
// Send MOVE event and then call .predict
- val moveEvent = getStylusMotionEvent(
- eventTime, ACTION_MOVE, /*x=*/eventPosition, /*y=*/eventPosition)
+ val moveEvent =
+ getStylusMotionEvent(
+ eventTime,
+ ACTION_MOVE,
+ /*x=*/ eventPosition,
+ /*y=*/ eventPosition,
+ )
predictor.record(moveEvent)
val predictionTime = eventTime + eventInterval
val predicted = checkNotNull(predictor.predict(predictionTime.toNanos()))
@@ -129,7 +149,7 @@
val state = perfStatusReporter.getBenchmarkState()
while (state.keepRunning()) {
- MotionPredictor(/*isPredictionEnabled=*/true, offsetNanos)
+ MotionPredictor(/* isPredictionEnabled= */ true, offsetNanos)
}
}
}
diff --git a/apct-tests/perftests/core/src/android/input/VelocityTrackerBenchmarkTest.kt b/apct-tests/perftests/core/src/android/input/VelocityTrackerBenchmarkTest.kt
index 530ca7b..c6fe324 100644
--- a/apct-tests/perftests/core/src/android/input/VelocityTrackerBenchmarkTest.kt
+++ b/apct-tests/perftests/core/src/android/input/VelocityTrackerBenchmarkTest.kt
@@ -19,12 +19,9 @@
import android.view.InputDevice
import android.view.MotionEvent
import android.view.VelocityTracker
-
import androidx.test.filters.LargeTest
import androidx.test.runner.AndroidJUnit4
-
import java.time.Duration
-
import org.junit.Assert
import org.junit.Before
import org.junit.Rule
@@ -74,22 +71,23 @@
props.id = 0
val coords = MotionEvent.PointerCoords()
coords.setAxisValue(MotionEvent.AXIS_SCROLL, DEFAULT_SCROLL_AMOUNT)
- val motionEvent = MotionEvent.obtain(
- /*downTime=*/0,
- currentTime.toMillis(),
- MotionEvent.ACTION_SCROLL,
- /*pointerCount=*/1,
- arrayOf(props),
- arrayOf(coords),
- /*metaState=*/0,
- /*buttonState=*/0,
- /*xPrecision=*/0f,
- /*yPrecision=*/0f,
- /*deviceId=*/1,
- /*edgeFlags=*/0,
- InputDevice.SOURCE_ROTARY_ENCODER,
- /*flags=*/0
- )
+ val motionEvent =
+ MotionEvent.obtain(
+ /*downTime=*/ 0,
+ currentTime.toMillis(),
+ MotionEvent.ACTION_SCROLL,
+ /*pointerCount=*/ 1,
+ arrayOf(props),
+ arrayOf(coords),
+ /*metaState=*/ 0,
+ /*buttonState=*/ 0,
+ /*xPrecision=*/ 0f,
+ /*yPrecision=*/ 0f,
+ /*deviceId=*/ 1,
+ /*edgeFlags=*/ 0,
+ InputDevice.SOURCE_ROTARY_ENCODER,
+ /*flags=*/ 0,
+ )
currentTime = currentTime.plus(DEFAULT_TIME_JUMP)
@@ -113,13 +111,15 @@
override fun createMotionEvent(): MotionEvent {
val action: Int = if (downEventCreated) MotionEvent.ACTION_MOVE else MotionEvent.ACTION_DOWN
- val motionEvent = MotionEvent.obtain(
- /*downTime=*/START_TIME.toMillis(),
- currentTime.toMillis(),
- action,
- x,
- y,
- /*metaState=*/0)
+ val motionEvent =
+ MotionEvent.obtain(
+ /*downTime=*/ START_TIME.toMillis(),
+ currentTime.toMillis(),
+ action,
+ x,
+ y,
+ /*metaState=*/ 0,
+ )
if (downEventCreated) {
x += INCREMENT
@@ -155,16 +155,15 @@
/**
* Benchmark tests for [VelocityTracker]
*
- * Build/Install/Run:
- * atest VelocityTrackerBenchmarkTest
+ * Build/Install/Run: atest VelocityTrackerBenchmarkTest
*/
@LargeTest
@RunWith(AndroidJUnit4::class)
class VelocityTrackerBenchmarkTest {
- @get:Rule
- val perfStatusReporter: PerfStatusReporter = PerfStatusReporter()
+ @get:Rule val perfStatusReporter: PerfStatusReporter = PerfStatusReporter()
private val velocityTracker = VelocityTracker.obtain()
+
@Before
fun setup() {
velocityTracker.clear()
@@ -255,4 +254,4 @@
companion object {
private const val TEST_NUM_DATAPOINTS = 100
}
-}
\ No newline at end of file
+}
diff --git a/apct-tests/perftests/core/src/android/view/ViewConfigurationPerfTest.java b/apct-tests/perftests/core/src/android/view/ViewConfigurationPerfTest.java
index 7a7250b..8e3ed6d 100644
--- a/apct-tests/perftests/core/src/android/view/ViewConfigurationPerfTest.java
+++ b/apct-tests/perftests/core/src/android/view/ViewConfigurationPerfTest.java
@@ -19,24 +19,27 @@
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import android.content.Context;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
-import androidx.benchmark.BenchmarkState;
-import androidx.benchmark.junit4.BenchmarkRule;
-import androidx.test.filters.SmallTest;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.runner.RunWith;
-@SmallTest
+@LargeTest
+@RunWith(AndroidJUnit4.class)
public class ViewConfigurationPerfTest {
@Rule
- public final BenchmarkRule mBenchmarkRule = new BenchmarkRule();
+ public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
private final Context mContext = getInstrumentation().getTargetContext();
@Test
public void testGet_newViewConfiguration() {
- final BenchmarkState state = mBenchmarkRule.getState();
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
state.pauseTiming();
@@ -50,7 +53,7 @@
@Test
public void testGet_cachedViewConfiguration() {
- final BenchmarkState state = mBenchmarkRule.getState();
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
// Do `get` once to make sure there's something cached.
ViewConfiguration.get(mContext);
@@ -58,4 +61,265 @@
ViewConfiguration.get(mContext);
}
}
+
+ @Test
+ public void testGetPressedStateDuration_unCached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ // Reset any caches.
+ ViewConfiguration.resetCacheForTesting();
+ state.resumeTiming();
+
+ ViewConfiguration.getPressedStateDuration();
+ }
+ }
+
+ @Test
+ public void testGetPressedStateDuration_cached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ // Do `get` once to make sure the value gets cached.
+ ViewConfiguration.getPressedStateDuration();
+
+ while (state.keepRunning()) {
+ ViewConfiguration.getPressedStateDuration();
+ }
+ }
+
+ @Test
+ public void testGetTapTimeout_unCached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ // Reset any caches.
+ ViewConfiguration.resetCacheForTesting();
+ state.resumeTiming();
+
+ ViewConfiguration.getTapTimeout();
+ }
+ }
+
+ @Test
+ public void testGetTapTimeout_cached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ // Do `get` once to make sure the value gets cached.
+ ViewConfiguration.getTapTimeout();
+
+ while (state.keepRunning()) {
+ ViewConfiguration.getTapTimeout();
+ }
+ }
+
+ @Test
+ public void testGetJumpTapTimeout_unCached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ // Reset any caches.
+ ViewConfiguration.resetCacheForTesting();
+ state.resumeTiming();
+
+ ViewConfiguration.getJumpTapTimeout();
+ }
+ }
+
+ @Test
+ public void testGetJumpTapTimeout_cached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ // Do `get` once to make sure the value gets cached.
+ ViewConfiguration.getJumpTapTimeout();
+
+ while (state.keepRunning()) {
+ ViewConfiguration.getJumpTapTimeout();
+ }
+ }
+
+ @Test
+ public void testGetDoubleTapTimeout_unCached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ // Reset any caches.
+ ViewConfiguration.resetCacheForTesting();
+ state.resumeTiming();
+
+ ViewConfiguration.getDoubleTapTimeout();
+ }
+ }
+
+ @Test
+ public void testGetDoubleTapTimeout_cached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ // Do `get` once to make sure the value gets cached.
+ ViewConfiguration.getDoubleTapTimeout();
+
+ while (state.keepRunning()) {
+ ViewConfiguration.getDoubleTapTimeout();
+ }
+ }
+
+ @Test
+ public void testGetDoubleTapMinTime_unCached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ // Reset any caches.
+ ViewConfiguration.resetCacheForTesting();
+ state.resumeTiming();
+
+ ViewConfiguration.getDoubleTapMinTime();
+ }
+ }
+
+ @Test
+ public void testGetDoubleTapMinTime_cached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ // Do `get` once to make sure the value gets cached.
+ ViewConfiguration.getDoubleTapMinTime();
+
+ while (state.keepRunning()) {
+ ViewConfiguration.getDoubleTapMinTime();
+ }
+ }
+
+ @Test
+ public void testGetZoomControlsTimeout_unCached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ // Reset any caches.
+ ViewConfiguration.resetCacheForTesting();
+ state.resumeTiming();
+
+ ViewConfiguration.getZoomControlsTimeout();
+ }
+ }
+
+ @Test
+ public void testGetZoomControlsTimeout_cached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ // Do `get` once to make sure the value gets cached.
+ ViewConfiguration.getZoomControlsTimeout();
+
+ while (state.keepRunning()) {
+ ViewConfiguration.getZoomControlsTimeout();
+ }
+ }
+
+ @Test
+ public void testGetLongPressTimeout() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ ViewConfiguration.getLongPressTimeout();
+ }
+ }
+
+ @Test
+ public void testGetMultiPressTimeout() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ ViewConfiguration.getMultiPressTimeout();
+ }
+ }
+
+ @Test
+ public void testGetKeyRepeatTimeout() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ ViewConfiguration.getKeyRepeatTimeout();
+ }
+ }
+
+ @Test
+ public void testGetKeyRepeatDelay() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ ViewConfiguration.getKeyRepeatDelay();
+ }
+ }
+
+ @Test
+ public void testGetHoverTapSlop_unCached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ // Reset any caches.
+ ViewConfiguration.resetCacheForTesting();
+ state.resumeTiming();
+
+ ViewConfiguration.getHoverTapSlop();
+ }
+ }
+
+ @Test
+ public void testGetHoverTapSlop_cached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ // Do `get` once to make sure the value gets cached.
+ ViewConfiguration.getHoverTapSlop();
+
+ while (state.keepRunning()) {
+ ViewConfiguration.getHoverTapSlop();
+ }
+ }
+
+ @Test
+ public void testGetScrollFriction_unCached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ // Reset any caches.
+ ViewConfiguration.resetCacheForTesting();
+ state.resumeTiming();
+
+ ViewConfiguration.getScrollFriction();
+ }
+ }
+
+ @Test
+ public void testGetScrollFriction_cached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ // Do `get` once to make sure the value gets cached.
+ ViewConfiguration.getScrollFriction();
+
+ while (state.keepRunning()) {
+ ViewConfiguration.getScrollFriction();
+ }
+ }
+
+ @Test
+ public void testGetDefaultActionModeHideDuration_unCached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ // Reset any caches.
+ ViewConfiguration.resetCacheForTesting();
+ state.resumeTiming();
+
+ ViewConfiguration.getDefaultActionModeHideDuration();
+ }
+ }
+
+ @Test
+ public void testGetDefaultActionModeHideDuration_cached() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ // Do `get` once to make sure the value gets cached.
+ ViewConfiguration.getDefaultActionModeHideDuration();
+
+ while (state.keepRunning()) {
+ ViewConfiguration.getDefaultActionModeHideDuration();
+ }
+ }
}
diff --git a/apct-tests/perftests/core/src/android/view/ViewPerfTest.java b/apct-tests/perftests/core/src/android/view/ViewPerfTest.java
index 67b33e5..d2321ec 100644
--- a/apct-tests/perftests/core/src/android/view/ViewPerfTest.java
+++ b/apct-tests/perftests/core/src/android/view/ViewPerfTest.java
@@ -84,12 +84,10 @@
final BenchmarkState state = mBenchmarkRule.getState();
mActivityRule.runOnUiThread(() -> {
- state.pauseTiming();
View view = new View(mContext);
mActivityRule.getActivity().setContentView(view);
assertTrue("View needs to be attached to Window to perform haptic feedback",
view.isAttachedToWindow());
- state.resumeTiming();
// Disable settings so perform will never be ignored.
int flags = HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING
diff --git a/apct-tests/perftests/healthconnect/src/com/android/perftests/healthconnect/HealthConnectReadWritePerfTest.kt b/apct-tests/perftests/healthconnect/src/com/android/perftests/healthconnect/HealthConnectReadWritePerfTest.kt
index 21a4ca0..bdb54c9 100644
--- a/apct-tests/perftests/healthconnect/src/com/android/perftests/healthconnect/HealthConnectReadWritePerfTest.kt
+++ b/apct-tests/perftests/healthconnect/src/com/android/perftests/healthconnect/HealthConnectReadWritePerfTest.kt
@@ -32,8 +32,7 @@
@RunWith(AndroidJUnit4::class)
class HealthConnectReadWritePerfTest {
- @get:Rule
- val perfStatusReporter = PerfStatusReporter()
+ @get:Rule val perfStatusReporter = PerfStatusReporter()
private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
@@ -41,9 +40,7 @@
requireNotNull(context.getSystemService(HealthConnectManager::class.java))
}
- /**
- * A first empty test just to setup the test package and make sure it runs properly.
- */
+ /** A first empty test just to setup the test package and make sure it runs properly. */
@Test
fun placeholder() {
val state = perfStatusReporter.benchmarkState
@@ -51,4 +48,4 @@
SystemClock.sleep(100)
}
}
-}
\ No newline at end of file
+}
diff --git a/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt b/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt
index ea10690..48f2c15 100644
--- a/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt
+++ b/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt
@@ -51,51 +51,52 @@
private const val QUEUE_POLL_TIMEOUT_SECONDS = 5L
// TODO: Replace this with core version of SYSTEM_PARTITIONS
- val FOLDERS_TO_TEST = listOf(
- Environment.getRootDirectory(),
- Environment.getVendorDirectory(),
- Environment.getOdmDirectory(),
- Environment.getOemDirectory(),
- Environment.getOemDirectory(),
- Environment.getSystemExtDirectory()
- )
+ val FOLDERS_TO_TEST =
+ listOf(
+ Environment.getRootDirectory(),
+ Environment.getVendorDirectory(),
+ Environment.getOdmDirectory(),
+ Environment.getOemDirectory(),
+ Environment.getOemDirectory(),
+ Environment.getSystemExtDirectory(),
+ )
@JvmStatic
@Parameterized.Parameters(name = "{0}")
fun parameters(): Array<Params> {
- val apks = FOLDERS_TO_TEST
- .filter(File::exists)
- .map(File::walkTopDown)
- .flatMap(Sequence<File>::asIterable)
- .filter { it.name.endsWith(".apk") }
+ val apks =
+ FOLDERS_TO_TEST.filter(File::exists)
+ .map(File::walkTopDown)
+ .flatMap(Sequence<File>::asIterable)
+ .filter { it.name.endsWith(".apk") }
return arrayOf(
Params(1, apks) { ParallelParser1(it?.let(::PackageCacher1)) },
- Params(2, apks) { ParallelParser2(it?.let(::PackageCacher2)) }
+ Params(2, apks) { ParallelParser2(it?.let(::PackageCacher2)) },
)
}
data class Params(
val version: Int,
val apks: List<File>,
- val cacheDirToParser: (File?) -> ParallelParser<*>
+ val cacheDirToParser: (File?) -> ParallelParser<*>,
) {
// For test name formatting
override fun toString() = "v$version"
}
}
- @get:Rule
- var perfStatusReporter = PerfStatusReporter()
+ @get:Rule var perfStatusReporter = PerfStatusReporter()
- @get:Rule
- var testFolder = TemporaryFolder()
+ @get:Rule var testFolder = TemporaryFolder()
- @Parameterized.Parameter(0)
- lateinit var params: Params
+ @Parameterized.Parameter(0) lateinit var params: Params
- private val state: BenchmarkState get() = perfStatusReporter.benchmarkState
- private val apks: List<File> get() = params.apks
+ private val state: BenchmarkState
+ get() = perfStatusReporter.benchmarkState
+
+ private val apks: List<File>
+ get() = params.apks
private fun safeParse(parser: ParallelParser<*>, file: File) {
try {
@@ -109,9 +110,7 @@
fun sequentialNoCache() {
params.cacheDirToParser(null).use { parser ->
while (state.keepRunning()) {
- apks.forEach {
- safeParse(parser, it)
- }
+ apks.forEach { safeParse(parser, it) }
}
}
}
@@ -155,18 +154,21 @@
private val cacher: PackageCacher<PackageType>? = null
) : AutoCloseable {
private val queue = ArrayBlockingQueue<Any>(PARALLEL_QUEUE_CAPACITY)
- private val service = ConcurrentUtils.newFixedThreadPool(
- PARALLEL_MAX_THREADS, "package-parsing-test",
- Process.THREAD_PRIORITY_FOREGROUND)
+ private val service =
+ ConcurrentUtils.newFixedThreadPool(
+ PARALLEL_MAX_THREADS,
+ "package-parsing-test",
+ Process.THREAD_PRIORITY_FOREGROUND,
+ )
fun submit(file: File) {
- service.submit {
- try {
- queue.put(parse(file))
- } catch (e: Exception) {
- queue.put(e)
- }
+ service.submit {
+ try {
+ queue.put(parse(file))
+ } catch (e: Exception) {
+ queue.put(e)
}
+ }
}
fun take() = queue.poll(QUEUE_POLL_TIMEOUT_SECONDS, TimeUnit.SECONDS)
@@ -175,57 +177,51 @@
service.shutdownNow()
}
- fun parse(file: File) = cacher?.getCachedResult(file)
- ?: parseImpl(file).also { cacher?.cacheResult(file, it) }
+ fun parse(file: File) =
+ cacher?.getCachedResult(file) ?: parseImpl(file).also { cacher?.cacheResult(file, it) }
protected abstract fun parseImpl(file: File): PackageType
}
class ParallelParser1(private val cacher: PackageCacher1? = null) :
ParallelParser<PackageParser.Package>(cacher) {
- val parser = PackageParser().apply {
- setCallback { true }
- }
+ val parser = PackageParser().apply { setCallback { true } }
override fun parseImpl(file: File) = parser.parsePackage(file, 0, cacher != null)
}
- class ParallelParser2(cacher: PackageCacher2? = null) :
- ParallelParser<PackageImpl>(cacher) {
- val input = ThreadLocal.withInitial {
- // For testing, just disable enforcement to avoid hooking up to compat framework
- ParseTypeImpl(ParseInput.Callback { _, _, _ -> false })
- }
- val parser = ParsingPackageUtils(null,
- null,
- emptyList(),
- object :
- ParsingPackageUtils.Callback {
- override fun hasFeature(feature: String) = true
+ class ParallelParser2(cacher: PackageCacher2? = null) : ParallelParser<PackageImpl>(cacher) {
+ val input =
+ ThreadLocal.withInitial {
+ // For testing, just disable enforcement to avoid hooking up to compat framework
+ ParseTypeImpl(ParseInput.Callback { _, _, _ -> false })
+ }
+ val parser =
+ ParsingPackageUtils(
+ null,
+ null,
+ emptyList(),
+ object : ParsingPackageUtils.Callback {
+ override fun hasFeature(feature: String) = true
- override fun startParsingPackage(
- packageName: String,
- baseApkPath: String,
- path: String,
- manifestArray: TypedArray,
- isCoreApp: Boolean
- ) = PackageImpl(
- packageName,
- baseApkPath,
- path,
- manifestArray,
- isCoreApp,
- this,
- )
- override fun getHiddenApiWhitelistedApps() =
+ override fun startParsingPackage(
+ packageName: String,
+ baseApkPath: String,
+ path: String,
+ manifestArray: TypedArray,
+ isCoreApp: Boolean,
+ ) = PackageImpl(packageName, baseApkPath, path, manifestArray, isCoreApp, this)
+
+ override fun getHiddenApiWhitelistedApps() =
SystemConfig.getInstance().hiddenApiWhitelistedApps
- override fun getInstallConstraintsAllowlist() =
+
+ override fun getInstallConstraintsAllowlist() =
SystemConfig.getInstance().installConstraintsAllowlist
- })
+ },
+ )
override fun parseImpl(file: File) =
- parser.parsePackage(input.get()!!.reset(), file, 0).result
- as PackageImpl
+ parser.parsePackage(input.get()!!.reset(), file, 0).result as PackageImpl
}
abstract class PackageCacher<PackageType : Parcelable>(private val cacheDir: File) {
@@ -237,14 +233,13 @@
}
val bytes = IoUtils.readFileAsByteArray(cacheFile.absolutePath)
- val parcel = Parcel.obtain().apply {
- unmarshall(bytes, 0, bytes.size)
- setDataPosition(0)
- }
+ val parcel =
+ Parcel.obtain().apply {
+ unmarshall(bytes, 0, bytes.size)
+ setDataPosition(0)
+ }
ReadHelper(parcel).apply { startAndInstall() }
- return fromParcel(parcel).also {
- parcel.recycle()
- }
+ return fromParcel(parcel).also { parcel.recycle() }
}
fun cacheResult(file: File, parsed: Parcelable) {
@@ -263,26 +258,19 @@
val helper = WriteHelper(parcel)
pkg.writeToParcel(parcel, 0 /* flags */)
helper.finishAndUninstall()
- return parcel.marshall().also {
- parcel.recycle()
- }
+ return parcel.marshall().also { parcel.recycle() }
}
protected abstract fun fromParcel(parcel: Parcel): PackageType
}
- /**
- * Re-implementation of v1's cache, since that's gone in R+.
- */
+ /** Re-implementation of v1's cache, since that's gone in R+. */
class PackageCacher1(cacheDir: File) : PackageCacher<PackageParser.Package>(cacheDir) {
override fun fromParcel(parcel: Parcel) = PackageParser.Package(parcel)
}
- /**
- * Re-implementation of the server side PackageCacher, as it's inaccessible here.
- */
+ /** Re-implementation of the server side PackageCacher, as it's inaccessible here. */
class PackageCacher2(cacheDir: File) : PackageCacher<PackageImpl>(cacheDir) {
- override fun fromParcel(parcel: Parcel) =
- PackageImpl(parcel)
+ override fun fromParcel(parcel: Parcel) = PackageImpl(parcel)
}
}
diff --git a/apct-tests/perftests/permission/src/android/perftests/permission/AppOpsPerfTest.kt b/apct-tests/perftests/permission/src/android/perftests/permission/AppOpsPerfTest.kt
index b6a53bf..7d4061a 100644
--- a/apct-tests/perftests/permission/src/android/perftests/permission/AppOpsPerfTest.kt
+++ b/apct-tests/perftests/permission/src/android/perftests/permission/AppOpsPerfTest.kt
@@ -50,7 +50,7 @@
opPackageUid,
opPackageName,
null,
- null
+ null,
)
}
}
@@ -62,7 +62,7 @@
appOpsManager.unsafeCheckOp(
AppOpsManager.OPSTR_FINE_LOCATION,
opPackageUid,
- opPackageName
+ opPackageName,
)
}
}
diff --git a/apct-tests/perftests/permission/src/android/perftests/permission/PermissionServicePerfTest.kt b/apct-tests/perftests/permission/src/android/perftests/permission/PermissionServicePerfTest.kt
index 1139835..b64d5d9 100644
--- a/apct-tests/perftests/permission/src/android/perftests/permission/PermissionServicePerfTest.kt
+++ b/apct-tests/perftests/permission/src/android/perftests/permission/PermissionServicePerfTest.kt
@@ -28,24 +28,26 @@
import com.android.compatibility.common.util.SystemUtil.eventually
import com.android.compatibility.common.util.SystemUtil.runShellCommand
import com.google.common.truth.Truth.assertThat
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStreamReader
import java.util.concurrent.TimeUnit
import java.util.function.BiConsumer
import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class PermissionServicePerfTest {
@get:Rule val mPerfManualStatusReporter = PerfManualStatusReporter()
- @get:Rule val mAdoptShellPermissionsRule = AdoptShellPermissionsRule(
- InstrumentationRegistry.getInstrumentation().getUiAutomation(),
- Manifest.permission.INSTALL_PACKAGES,
- Manifest.permission.DELETE_PACKAGES
- )
+ @get:Rule
+ val mAdoptShellPermissionsRule =
+ AdoptShellPermissionsRule(
+ InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+ Manifest.permission.INSTALL_PACKAGES,
+ Manifest.permission.DELETE_PACKAGES,
+ )
val mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation()
@Test
@@ -95,13 +97,14 @@
private fun dumpResult(
parser: TraceMarkParser,
- handler: BiConsumer<String, List<TraceMarkParser.TraceMarkSlice>>
+ handler: BiConsumer<String, List<TraceMarkParser.TraceMarkSlice>>,
) {
parser.reset()
try {
- val inputStream = ParcelFileDescriptor.AutoCloseInputStream(
- mUiAutomation.executeShellCommand(COMMAND_TRACE_DUMP)
- )
+ val inputStream =
+ ParcelFileDescriptor.AutoCloseInputStream(
+ mUiAutomation.executeShellCommand(COMMAND_TRACE_DUMP)
+ )
val reader = BufferedReader(InputStreamReader(inputStream))
var line = reader.readLine()
while (line != null) {
diff --git a/apex/jobscheduler/service/aconfig/app_idle.aconfig b/apex/jobscheduler/service/aconfig/app_idle.aconfig
index 74d2a59..8993410 100644
--- a/apex/jobscheduler/service/aconfig/app_idle.aconfig
+++ b/apex/jobscheduler/service/aconfig/app_idle.aconfig
@@ -28,3 +28,14 @@
description: "Adjust the default bucket evaluation parameters"
bug: "379909479"
}
+
+flag {
+ name: "persist_restore_to_rare_apps_list"
+ namespace: "backstage_power"
+ description: "Persist the list of apps which are put in the RARE bucket upon restore."
+ is_fixed_read_only: true
+ bug: "383766428"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
index a8641ae..4acfebc 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
@@ -38,6 +38,7 @@
import android.app.usage.UsageStatsManager;
import android.os.SystemClock;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.IndentingPrintWriter;
import android.util.Slog;
@@ -83,6 +84,9 @@
private SparseArray<ArrayMap<String,AppUsageHistory>> mIdleHistory = new SparseArray<>();
private static final long ONE_MINUTE = 60 * 1000;
+ // Only keep the persisted restore-to-rare apps list for 2 days.
+ static final long RESTORE_TO_RARE_APPS_LIST_EXPIRY = ONE_MINUTE * 60 * 24 * 2;
+
static final int STANDBY_BUCKET_UNKNOWN = -1;
/**
@@ -277,6 +281,58 @@
writeScreenOnTime();
}
+ private File getRestoreToRareAppsListFile(int userId) {
+ return new File(getUserDirectory(userId), "restore_to_rare_apps_list");
+ }
+
+ public ArraySet<String> readRestoreToRareAppsList(int userId) {
+ File restoreToRareAppsListFile = getRestoreToRareAppsListFile(userId);
+ if (!restoreToRareAppsListFile.exists()) {
+ return null;
+ }
+
+ try (BufferedReader reader =
+ new BufferedReader(new FileReader(restoreToRareAppsListFile))) {
+ final ArraySet<String> appsList = new ArraySet<>();
+ final long restoreTime = Long.parseLong(reader.readLine());
+ if (System.currentTimeMillis() - restoreTime > RESTORE_TO_RARE_APPS_LIST_EXPIRY) {
+ // the apps list should only be kept around for 2 days
+ reader.close();
+ restoreToRareAppsListFile.delete();
+ return null;
+ }
+ String pkgName;
+ while ((pkgName = reader.readLine()) != null) {
+ appsList.add(pkgName);
+ }
+ return appsList;
+ } catch (IOException | NumberFormatException e) {
+ return null;
+ }
+ }
+
+ public void writeRestoreToRareAppsList(int userId, ArraySet<String> restoreAppsToRare) {
+ File fileHandle = getRestoreToRareAppsListFile(userId);
+ if (fileHandle.exists()) {
+ // don't update the persisted file - it should only be written once.
+ return;
+ }
+ AtomicFile restoreToRareAppsListFile = new AtomicFile(fileHandle);
+ FileOutputStream fos = null;
+ try {
+ fos = restoreToRareAppsListFile.startWrite();
+ final StringBuilder sb = new StringBuilder();
+ sb.append(System.currentTimeMillis()).append("\n");
+ for (String pkgName : restoreAppsToRare) {
+ sb.append(pkgName).append("\n");
+ }
+ fos.write(sb.toString().getBytes());
+ restoreToRareAppsListFile.finishWrite(fos);
+ } catch (IOException ioe) {
+ restoreToRareAppsListFile.failWrite(fos);
+ }
+ }
+
/**
* Mark the app as used and update the bucket if necessary. If there is a expiry time specified
* that's in the future, then the usage event is temporary and keeps the app in the specified
@@ -694,10 +750,13 @@
return appUsageHistory.bucketExpiryTimesMs.get(bucket, 0);
}
+ private File getUserDirectory(int userId) {
+ return new File(new File(mStorageDir, "users"), Integer.toString(userId));
+ }
+
@VisibleForTesting
File getUserFile(int userId) {
- return new File(new File(new File(mStorageDir, "users"),
- Integer.toString(userId)), APP_IDLE_FILENAME);
+ return new File(getUserDirectory(userId), APP_IDLE_FILENAME);
}
void clearLastUsedTimestamps(String packageName, int userId) {
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index ab8131b..b87b5ce 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -1706,10 +1706,18 @@
restoreAppToRare(packageName, userId, nowElapsed, reason);
}
// Clear out the list of restored apps that need to have their standby buckets adjusted
- // if they still haven't been installed eight hours after restore.
- // Note: if the device reboots within these first 8 hours, this list will be lost since it's
- // not persisted - this is the expected behavior for now and may be updated in the future.
- mHandler.postDelayed(() -> mAppsToRestoreToRare.remove(userId), 8 * ONE_HOUR);
+ // if they still haven't been installed two days after initial restore.
+ final long delayMillis = Flags.persistRestoreToRareAppsList()
+ ? AppIdleHistory.RESTORE_TO_RARE_APPS_LIST_EXPIRY : 8 * ONE_HOUR;
+ mHandler.postDelayed(() -> mAppsToRestoreToRare.remove(userId), delayMillis);
+
+ // Persist the file in case the device reboots within 2 days after the initial restore.
+ if (Flags.persistRestoreToRareAppsList()) {
+ synchronized (mAppIdleLock) {
+ mAppIdleHistory.writeRestoreToRareAppsList(
+ userId, mAppsToRestoreToRare.get(userId));
+ }
+ }
}
/** Adjust the standby bucket of the given package for the user to RARE. */
@@ -2272,6 +2280,22 @@
} else if (!Intent.ACTION_PACKAGE_ADDED.equals(action)) {
clearAppIdleForPackage(pkgName, userId);
} else {
+ // Do a lazy read of the persisted list, if necessary.
+ if (Flags.persistRestoreToRareAppsList()
+ && mAppsToRestoreToRare.get(userId) == null) {
+ synchronized (mAppIdleLock) {
+ final ArraySet<String> restoredApps =
+ mAppIdleHistory.readRestoreToRareAppsList(userId);
+ if (restoredApps != null) {
+ mAppsToRestoreToRare.addAll(userId, restoredApps);
+ // Clear out the list of restored apps if they still haven't been
+ // installed in two days - at worst, we are allowing for up to
+ // 4 days for reinstallation (device reboots just before 2 days)
+ mHandler.postDelayed(() -> mAppsToRestoreToRare.remove(userId),
+ AppIdleHistory.RESTORE_TO_RARE_APPS_LIST_EXPIRY);
+ }
+ }
+ }
// Package was just added and it's not being replaced.
if (mAppsToRestoreToRare.contains(userId, pkgName)) {
restoreAppToRare(pkgName, userId, mInjector.elapsedRealtime(),
@@ -2454,6 +2478,8 @@
+ ": " + Flags.avoidIdleCheck());
pw.println(" " + Flags.FLAG_ADJUST_DEFAULT_BUCKET_ELEVATION_PARAMS
+ ": " + Flags.adjustDefaultBucketElevationParams());
+ pw.println(" " + Flags.FLAG_PERSIST_RESTORE_TO_RARE_APPS_LIST
+ + ": " + Flags.persistRestoreToRareAppsList());
pw.println();
synchronized (mCarrierPrivilegedLock) {
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 844e52c..b0070c5 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -207,7 +207,7 @@
: Thread(false), mLooper(new Looper(false)), mClockEnabled(true), mTimeIsAccurate(false),
mTimeFormat12Hour(false), mTimeCheckThread(nullptr), mCallbacks(callbacks) {
ATRACE_CALL();
- mSession = new SurfaceComposerClient();
+ mSession = sp<SurfaceComposerClient>::make();
std::string powerCtl = android::base::GetProperty("sys.powerctl", "");
if (powerCtl.empty()) {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 00ec48b..d651010 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3031,6 +3031,7 @@
field public static final String DISABLED_PRINT_SERVICES = "disabled_print_services";
field @Deprecated public static final String ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES = "enabled_notification_policy_access_packages";
field public static final String ENABLED_VR_LISTENERS = "enabled_vr_listeners";
+ field public static final String GLANCEABLE_HUB_ENABLED = "glanceable_hub_enabled";
field public static final String IMMERSIVE_MODE_CONFIRMATIONS = "immersive_mode_confirmations";
field public static final String NOTIFICATION_BADGING = "notification_badging";
field public static final String NOTIFICATION_BUBBLES = "notification_bubbles";
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 1ad247e..980d973 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -29,6 +29,15 @@
"android/os/*MessageQueue/**/*.java",
"android/ranging/**/*.java",
":dynamic_instrumentation_manager_aidl_sources",
+ "**/*_ravenwood.java",
+ ],
+ visibility: ["//frameworks/base"],
+}
+
+filegroup {
+ name: "framework-ravenwood-sources",
+ srcs: [
+ "**/*_ravenwood.java",
],
visibility: ["//frameworks/base"],
}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index b4f6533..d5df48a 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -10060,9 +10060,11 @@
}
});
if (mJankTracker == null) {
- // TODO b/377960907 use the Choreographer attached to the ViewRootImpl instead.
- mJankTracker = new JankTracker(Choreographer.getInstance(),
- decorView);
+ if (android.app.jank.Flags.viewrootChoreographer()) {
+ mJankTracker = new JankTracker(decorView);
+ } else {
+ mJankTracker = new JankTracker(Choreographer.getInstance(), decorView);
+ }
}
// TODO b/377674765 confirm this is the string we want logged.
mJankTracker.setActivityName(getComponentName().getClassName());
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index f9ec214..b38f5da 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -4975,6 +4975,25 @@
}
/**
+ * Fully stop the given app's processes without restoring service starts or
+ * bindings, but without the other durable effects of the full-scale
+ * "force stop" intervention.
+ *
+ * @param packageName The name of the package to be stopped.
+ *
+ * @hide This is not available to third party applications due to
+ * it allowing them to break other applications by stopping their
+ * services.
+ */
+ public void stopPackageForUser(String packageName) {
+ try {
+ getService().stopAppForUser(packageName, mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Sets the current locales of the device. Calling app must have the permission
* {@code android.permission.CHANGE_CONFIGURATION} and
* {@code android.permission.WRITE_SETTINGS}.
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index c765298..2c1df73 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1137,12 +1137,19 @@
CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras,
boolean ordered, boolean assumeDelivered, int sendingUser, int processState,
int sendingUid, String sendingPackage) {
+ long debugStoreId = -1;
+ if (DEBUG_STORE_ENABLED) {
+ debugStoreId = DebugStore.recordScheduleReceiver();
+ }
updateProcessState(processState, false);
ReceiverData r = new ReceiverData(intent, resultCode, data, extras,
ordered, false, assumeDelivered, mAppThread.asBinder(), sendingUser,
sendingUid, sendingPackage);
r.info = info;
sendMessage(H.RECEIVER, r);
+ if (DEBUG_STORE_ENABLED) {
+ DebugStore.recordEventEnd(debugStoreId);
+ }
}
public final void scheduleReceiverList(List<ReceiverInfo> info) throws RemoteException {
@@ -1490,6 +1497,10 @@
boolean sticky, boolean assumeDelivered, int sendingUser, int processState,
int sendingUid, String sendingPackage)
throws RemoteException {
+ long debugStoreId = -1;
+ if (DEBUG_STORE_ENABLED) {
+ debugStoreId = DebugStore.recordScheduleRegisteredReceiver();
+ }
updateProcessState(processState, false);
// We can't modify IIntentReceiver due to UnsupportedAppUsage, so
@@ -1514,6 +1525,9 @@
receiver.performReceive(intent, resultCode, dataStr, extras, ordered, sticky,
sendingUser);
}
+ if (DEBUG_STORE_ENABLED) {
+ DebugStore.recordEventEnd(debugStoreId);
+ }
}
@Override
@@ -2505,8 +2519,15 @@
switch (msg.what) {
case BIND_APPLICATION:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
+ if (DEBUG_STORE_ENABLED) {
+ debugStoreId =
+ DebugStore.recordHandleBindApplication();
+ }
AppBindData data = (AppBindData)msg.obj;
handleBindApplication(data);
+ if (DEBUG_STORE_ENABLED) {
+ DebugStore.recordEventEnd(debugStoreId);
+ }
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case EXIT_APPLICATION:
@@ -2529,7 +2550,8 @@
ReceiverData receiverData = (ReceiverData) msg.obj;
if (DEBUG_STORE_ENABLED) {
debugStoreId =
- DebugStore.recordBroadcastHandleReceiver(receiverData.intent);
+ DebugStore.recordBroadcastReceive(
+ receiverData.intent, System.identityHashCode(receiverData));
}
try {
@@ -7340,16 +7362,6 @@
}
WindowManagerGlobal.getInstance().trimMemory(level);
-
- if (SystemProperties.getInt("debug.am.run_gc_trim_level", Integer.MAX_VALUE) <= level) {
- unscheduleGcIdler();
- doGcIfNeeded("tm");
- }
- if (SystemProperties.getInt("debug.am.run_mallopt_trim_level", Integer.MAX_VALUE)
- <= level) {
- unschedulePurgeIdler();
- purgePendingResources();
- }
}
private void setupGraphicsSupport(Context context) {
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index dc5974f..7e5c0fb 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2944,7 +2944,11 @@
private void updateResourceOverlayConstraints() {
if (mResources != null) {
- mResources.getAssets().setOverlayConstraints(getDisplayId(), getDeviceId());
+ // Avoid calling getDisplay() here, as it makes a binder call into
+ // DisplayManagerService if the relevant DisplayInfo is not cached in
+ // DisplayManagerGlobal.
+ int displayId = mDisplay != null ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;
+ mResources.getAssets().setOverlayConstraints(displayId, getDeviceId());
}
}
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index ffd235f..d0949ad 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -61,6 +61,7 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.DebugStore;
import com.android.internal.util.ArrayUtils;
import dalvik.system.BaseDexClassLoader;
@@ -107,6 +108,9 @@
static final String TAG = "LoadedApk";
static final boolean DEBUG = false;
+ private static final boolean DEBUG_STORE_ENABLED =
+ com.android.internal.os.Flags.debugStoreEnabled();
+
@UnsupportedAppUsage
private final ActivityThread mActivityThread;
@UnsupportedAppUsage
@@ -1816,6 +1820,12 @@
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
"broadcastReceiveReg: " + intent.getAction());
}
+ long debugStoreId = -1;
+ if (DEBUG_STORE_ENABLED) {
+ debugStoreId =
+ DebugStore.recordBroadcastReceiveReg(
+ intent, System.identityHashCode(this));
+ }
try {
ClassLoader cl = mReceiver.getClass().getClassLoader();
@@ -1838,6 +1848,10 @@
"Error receiving broadcast " + intent
+ " in " + mReceiver, e);
}
+ } finally {
+ if (DEBUG_STORE_ENABLED) {
+ DebugStore.recordEventEnd(debugStoreId);
+ }
}
if (receiver.getPendingResult() != null) {
diff --git a/core/java/android/app/admin/PolicyUpdateReceiver.java b/core/java/android/app/admin/PolicyUpdateReceiver.java
index be13988..630ab0e 100644
--- a/core/java/android/app/admin/PolicyUpdateReceiver.java
+++ b/core/java/android/app/admin/PolicyUpdateReceiver.java
@@ -20,10 +20,12 @@
import android.annotation.NonNull;
import android.annotation.SdkConstant;
import android.annotation.TestApi;
+import android.app.admin.flags.Flags;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
+import android.text.TextUtils;
import android.util.Log;
import java.util.Objects;
@@ -46,6 +48,10 @@
public abstract class PolicyUpdateReceiver extends BroadcastReceiver {
private static String TAG = "PolicyUpdateReceiver";
+ //TODO(b/378931989): Switch to android.app.admin.DevicePolicyIdentifiers.MEMORY_TAGGING_POLICY
+ //when the appropriate flag is launched.
+ private static final String MEMORY_TAGGING_POLICY = "memoryTagging";
+
/**
* Action for a broadcast sent to admins to communicate back the result of setting a policy in
* {@link DevicePolicyManager}.
@@ -156,15 +162,28 @@
@Override
public final void onReceive(Context context, Intent intent) {
Objects.requireNonNull(intent.getAction());
+ String policyKey;
switch (intent.getAction()) {
case ACTION_DEVICE_POLICY_SET_RESULT:
Log.i(TAG, "Received ACTION_DEVICE_POLICY_SET_RESULT");
- onPolicySetResult(context, getPolicyKey(intent), getPolicyExtraBundle(intent),
+ policyKey = getPolicyKey(intent);
+ if (!shouldPropagatePolicy(policyKey)) {
+ Log.d(TAG, TextUtils.formatSimple(
+ "Skipping propagation of policy %s", policyKey));
+ break;
+ }
+ onPolicySetResult(context, policyKey, getPolicyExtraBundle(intent),
getTargetUser(intent), getPolicyChangedReason(intent));
break;
case ACTION_DEVICE_POLICY_CHANGED:
Log.i(TAG, "Received ACTION_DEVICE_POLICY_CHANGED");
- onPolicyChanged(context, getPolicyKey(intent), getPolicyExtraBundle(intent),
+ policyKey = getPolicyKey(intent);
+ if (!shouldPropagatePolicy(policyKey)) {
+ Log.d(TAG, TextUtils.formatSimple(
+ "Skipping propagation of policy %s", policyKey));
+ break;
+ }
+ onPolicyChanged(context, policyKey, getPolicyExtraBundle(intent),
getTargetUser(intent), getPolicyChangedReason(intent));
break;
default:
@@ -217,6 +236,14 @@
return new TargetUser(targetUserId);
}
+ /**
+ * @hide
+ */
+ private boolean shouldPropagatePolicy(String policyKey) {
+ return !MEMORY_TAGGING_POLICY.equals(policyKey) || Flags.setMtePolicyCoexistence();
+ }
+
+
// TODO(b/260847505): Add javadocs to explain which DPM APIs are supported
/**
* Callback triggered after an admin has set a policy using one of the APIs in
diff --git a/core/java/android/app/jank/JankTracker.java b/core/java/android/app/jank/JankTracker.java
index 9c85b09..e3f6781 100644
--- a/core/java/android/app/jank/JankTracker.java
+++ b/core/java/android/app/jank/JankTracker.java
@@ -25,6 +25,7 @@
import android.view.Choreographer;
import android.view.SurfaceControl;
import android.view.View;
+import android.view.ViewRootImpl;
import android.view.ViewTreeObserver;
import com.android.internal.annotations.VisibleForTesting;
@@ -100,7 +101,7 @@
public void run() {
mDecorView.getViewTreeObserver()
.removeOnWindowAttachListener(mOnWindowAttachListener);
- registerForJankData();
+ initializeJankTrackingComponents();
}
}, REGISTRATION_DELAY_MS);
}
@@ -115,6 +116,7 @@
}
};
+ // TODO remove this once the viewroot_choreographer bugfix has been rolled out. b/399724640
public JankTracker(Choreographer choreographer, View decorView) {
mStateTracker = new StateTracker(choreographer);
mJankDataProcessor = new JankDataProcessor(mStateTracker);
@@ -124,6 +126,19 @@
}
/**
+ * Using this constructor delays the instantiation of the StateTracker and JankDataProcessor
+ * until after the OnWindowAttachListener is fired and the instance of Choreographer attached to
+ * the ViewRootImpl can be passed to StateTracker. This should ensures the vsync ids we are
+ * using to keep track of active states line up with the ids that are being returned by
+ * OnJankDataListener.
+ */
+ public JankTracker(View decorView) {
+ mDecorView = decorView;
+ mHandlerThread.start();
+ registerWindowListeners();
+ }
+
+ /**
* Merges app jank stats reported by components outside the platform to the current pending
* stats
*/
@@ -131,6 +146,9 @@
getHandler().post(new Runnable() {
@Override
public void run() {
+ if (mJankDataProcessor == null) {
+ return;
+ }
mJankDataProcessor.mergeJankStats(appJankStats, mActivityName);
}
});
@@ -192,8 +210,7 @@
public void enableAppJankTracking() {
// Add the activity as a state, this will ensure we track frames to the activity without the
// need for a decorated widget to be used.
- // TODO b/376116199 replace "NONE" with UNSPECIFIED once the API changes are merged.
- mStateTracker.putState("NONE", mActivityName, "NONE");
+ addActivityToStateTracking();
mTrackingEnabled = true;
registerForJankData();
}
@@ -203,27 +220,33 @@
*/
public void disableAppJankTracking() {
mTrackingEnabled = false;
- // TODO b/376116199 replace "NONE" with UNSPECIFIED once the API changes are merged.
- mStateTracker.removeState("NONE", mActivityName, "NONE");
+ removeActivityFromStateTracking();
unregisterForJankData();
}
/**
- * Retrieve all pending widget states, this is intended for testing purposes only.
+ * Retrieve all pending widget states, this is intended for testing purposes only. If
+ * this is called before StateTracker has been created the method will just return without
+ * copying any data to the stateDataList parameter.
*
* @param stateDataList the ArrayList that will be populated with the pending states.
*/
@VisibleForTesting
public void getAllUiStates(@NonNull ArrayList<StateTracker.StateData> stateDataList) {
+ if (mStateTracker == null) return;
mStateTracker.retrieveAllStates(stateDataList);
}
/**
* Retrieve all pending jank stats before they are logged, this is intended for testing
- * purposes only.
+ * purposes only. If this method is called before JankDataProcessor is created it will return
+ * an empty HashMap.
*/
@VisibleForTesting
public HashMap<String, JankDataProcessor.PendingJankStat> getPendingJankStats() {
+ if (mJankDataProcessor == null) {
+ return new HashMap<>();
+ }
return mJankDataProcessor.getPendingJankStats();
}
@@ -233,8 +256,10 @@
*/
@VisibleForTesting
public void forceListenerRegistration() {
+ addActivityToStateTracking();
mSurfaceControl = mDecorView.getRootSurfaceControl();
registerJankDataListener();
+ mListenersRegistered = true;
}
private void unregisterForJankData() {
@@ -270,6 +295,10 @@
*/
@VisibleForTesting
public boolean shouldTrack() {
+ if (DEBUG) {
+ Log.d(DEBUG_KEY, String.format("mTrackingEnabled: %s | mListenersRegistered: %s",
+ mTrackingEnabled, mListenersRegistered));
+ }
return mTrackingEnabled && mListenersRegistered;
}
@@ -313,4 +342,36 @@
}
return mHandler;
}
+
+ private void addActivityToStateTracking() {
+ if (mStateTracker == null) return;
+
+ mStateTracker.putState(AppJankStats.WIDGET_CATEGORY_UNSPECIFIED, mActivityName,
+ AppJankStats.WIDGET_STATE_UNSPECIFIED);
+ }
+
+ private void removeActivityFromStateTracking() {
+ if (mStateTracker == null) return;
+
+ mStateTracker.removeState(AppJankStats.WIDGET_CATEGORY_UNSPECIFIED, mActivityName,
+ AppJankStats.WIDGET_STATE_UNSPECIFIED);
+ }
+
+ private void initializeJankTrackingComponents() {
+ ViewRootImpl viewRoot = mDecorView.getViewRootImpl();
+ if (viewRoot == null || viewRoot.getChoreographer() == null) {
+ return;
+ }
+
+ if (mStateTracker == null) {
+ mStateTracker = new StateTracker(viewRoot.getChoreographer());
+ }
+
+ if (mJankDataProcessor == null) {
+ mJankDataProcessor = new JankDataProcessor(mStateTracker);
+ }
+
+ addActivityToStateTracking();
+ registerForJankData();
+ }
}
diff --git a/core/java/android/app/jank/flags.aconfig b/core/java/android/app/jank/flags.aconfig
index a62df1b..de98b88 100644
--- a/core/java/android/app/jank/flags.aconfig
+++ b/core/java/android/app/jank/flags.aconfig
@@ -14,4 +14,14 @@
namespace: "system_performance"
description: "Controls whether the system will log frame metrics related to app jank"
bug: "366265225"
+}
+
+flag {
+ name: "viewroot_choreographer"
+ namespace: "system_performance"
+ description: "when enabled janktracker will get the instance of choreographer from viewrootimpl"
+ bug: "377960907"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
\ No newline at end of file
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index 8e6b88c..5c267c9 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -255,6 +255,16 @@
}
flag {
+ name: "redaction_on_lockscreen_metrics"
+ namespace: "systemui"
+ description: "enables metrics when redacting notifications on the lockscreen"
+ bug: "343631648"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "api_rich_ongoing"
is_exported: true
namespace: "systemui"
diff --git a/core/java/android/app/supervision/SupervisionManager.java b/core/java/android/app/supervision/SupervisionManager.java
index 0270edf..8217ca1 100644
--- a/core/java/android/app/supervision/SupervisionManager.java
+++ b/core/java/android/app/supervision/SupervisionManager.java
@@ -97,7 +97,7 @@
*
* @hide
*/
- @RequiresPermission(value = android.Manifest.permission.QUERY_USERS)
+ @RequiresPermission(anyOf = {MANAGE_USERS, QUERY_USERS})
@Nullable
public Intent createConfirmSupervisionCredentialsIntent() {
if (mService != null) {
diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java
index cf68487..357baa3 100644
--- a/core/java/android/content/BroadcastReceiver.java
+++ b/core/java/android/content/BroadcastReceiver.java
@@ -263,7 +263,7 @@
1);
}
if (DEBUG_STORE_ENABLED) {
- DebugStore.recordFinish(mReceiverClassName);
+ DebugStore.recordFinish(System.identityHashCode(this));
}
if (mType == TYPE_COMPONENT) {
@@ -444,7 +444,7 @@
PendingResult res = mPendingResult;
mPendingResult = null;
if (DEBUG_STORE_ENABLED) {
- DebugStore.recordGoAsync(getClass().getName());
+ DebugStore.recordGoAsync(System.identityHashCode(res));
}
if (res != null && Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
res.mReceiverClassName = getClass().getName();
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index fd59ea9..f61d695 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -950,6 +950,8 @@
CONFIG_COLOR_MODE,
CONFIG_FONT_SCALE,
CONFIG_GRAMMATICAL_GENDER,
+ CONFIG_FONT_WEIGHT_ADJUSTMENT,
+ CONFIG_WINDOW_CONFIGURATION,
CONFIG_ASSETS_PATHS,
CONFIG_RESOURCES_UNUSED,
})
diff --git a/core/java/android/content/pm/parsing/OWNERS b/core/java/android/content/pm/parsing/OWNERS
index 445a833..b8fa1a9 100644
--- a/core/java/android/content/pm/parsing/OWNERS
+++ b/core/java/android/content/pm/parsing/OWNERS
@@ -1,4 +1,3 @@
# Bug component: 36137
-chiuwinson@google.com
patb@google.com
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index 75c7e26..e43a5fc 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -138,7 +138,7 @@
private static native long nativeOpen(String path, int openFlags, String label,
boolean enableTrace, boolean enableProfile, int lookasideSlotSize,
int lookasideSlotCount);
- private static native void nativeClose(long connectionPtr);
+ private static native void nativeClose(long connectionPtr, boolean fast);
private static native void nativeRegisterCustomScalarFunction(long connectionPtr,
String name, UnaryOperator<String> function);
private static native void nativeRegisterCustomAggregateFunction(long connectionPtr,
@@ -183,6 +183,11 @@
private static native long nativeChanges(long connectionPtr);
private static native long nativeTotalChanges(long connectionPtr);
+ // This method is deprecated and should be removed when it is no longer needed by the
+ // robolectric tests. It should not be called from any frameworks java code.
+ @Deprecated
+ private static native void nativeClose(long connectionPtr);
+
private SQLiteConnection(SQLiteConnectionPool pool,
SQLiteDatabaseConfiguration configuration,
int connectionId, boolean primaryConnection) {
@@ -300,7 +305,7 @@
final int cookie = mRecentOperations.beginOperation("close", null, null);
try {
mPreparedStatementCache.evictAll();
- nativeClose(mConnectionPtr);
+ nativeClose(mConnectionPtr, finalized && Flags.noCheckpointOnFinalize());
mConnectionPtr = 0;
} finally {
mRecentOperations.endOperation(cookie);
diff --git a/core/java/android/database/sqlite/flags.aconfig b/core/java/android/database/sqlite/flags.aconfig
index 1d17a51..9f4f1a1 100644
--- a/core/java/android/database/sqlite/flags.aconfig
+++ b/core/java/android/database/sqlite/flags.aconfig
@@ -5,7 +5,7 @@
name: "oneway_finalizer_close_fixed"
namespace: "system_performance"
is_fixed_read_only: true
- description: "Make BuildCursorNative.close oneway if in the the finalizer"
+ description: "Make BuildCursorNative.close oneway if in the finalizer"
bug: "368221351"
}
@@ -26,3 +26,10 @@
description: "Make SQLiteOpenHelper thread-safe"
bug: "335904370"
}
+
+flag {
+ name: "no_checkpoint_on_finalize"
+ namespace: "system_performance"
+ description: "Do not checkpoint WAL if closing in the finalizer"
+ bug: "397982577"
+}
diff --git a/core/java/android/gesture/OWNERS b/core/java/android/gesture/OWNERS
index 168630a..ffa753a 100644
--- a/core/java/android/gesture/OWNERS
+++ b/core/java/android/gesture/OWNERS
@@ -3,5 +3,4 @@
romainguy@google.com
adamp@google.com
aurimas@google.com
-nduca@google.com
sumir@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt b/core/java/android/hardware/usb/IUsbManagerInternal.aidl
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt
copy to core/java/android/hardware/usb/IUsbManagerInternal.aidl
index 231fb2d..32479d4 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt
+++ b/core/java/android/hardware/usb/IUsbManagerInternal.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2024 The Android Open Source Project
+ * Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,11 +14,13 @@
* limitations under the License.
*/
-package com.android.systemui.dagger.qualifiers
+package android.hardware.usb;
-import javax.inject.Qualifier
+import android.hardware.usb.IUsbOperationInternal;
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class NotifInflation
+/** @hide */
+interface IUsbManagerInternal {
+
+ /* Disable/enable USB data on a port for System Service callers. */
+ boolean enableUsbDataSignal(boolean enable, int disableReason);
+}
diff --git a/core/java/android/metrics/OWNERS b/core/java/android/metrics/OWNERS
index ba867e0..98aaf3f 100644
--- a/core/java/android/metrics/OWNERS
+++ b/core/java/android/metrics/OWNERS
@@ -1,4 +1,3 @@
# Bug component: 26805
cwren@android.com
-cwren@google.com
diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
index ce1717b..0964cde 100644
--- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java
+++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
@@ -76,8 +76,13 @@
@SuppressWarnings("unused")
private long mPtr; // used by native code
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(
+ maxTargetSdk = Build.VERSION_CODES.BAKLAVA,
+ publicAlternatives =
+ "To manipulate the queue in Instrumentation tests, use {@link"
+ + " android.os.TestLooperManager}")
Message mMessages;
+
private Message mLast;
@UnsupportedAppUsage
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
@@ -139,8 +144,8 @@
return;
}
- if (RavenwoodEnvironment.getInstance().isRunningOnRavenwood()) {
- sIsProcessAllowedToUseConcurrent = false;
+ if (Flags.forceConcurrentMessageQueue()) {
+ sIsProcessAllowedToUseConcurrent = true;
return;
}
@@ -995,7 +1000,11 @@
}
}
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(
+ maxTargetSdk = Build.VERSION_CODES.BAKLAVA,
+ publicAlternatives =
+ "To manipulate the queue in Instrumentation tests, use {@link"
+ + " android.os.TestLooperManager}")
Message next() {
if (mUseConcurrent) {
return nextConcurrent();
diff --git a/core/java/android/os/ExternalVibration.java b/core/java/android/os/ExternalVibration.java
index 3aad0fd..70a17ab 100644
--- a/core/java/android/os/ExternalVibration.java
+++ b/core/java/android/os/ExternalVibration.java
@@ -87,8 +87,12 @@
int capturePreset = in.readInt();
int flags = in.readInt();
AudioAttributes.Builder builder = new AudioAttributes.Builder();
- return builder.setUsage(usage)
- .setContentType(contentType)
+ if (AudioAttributes.isSystemUsage(usage)) {
+ builder.setSystemUsage(usage);
+ } else {
+ builder.setUsage(usage);
+ }
+ return builder.setContentType(contentType)
.setCapturePreset(capturePreset)
.setFlags(flags)
.build();
@@ -196,7 +200,9 @@
}
private static void writeAudioAttributes(AudioAttributes attrs, Parcel out) {
- out.writeInt(attrs.getUsage());
+ // Since we allow audio system usages, must use getSystemUsage() instead of getUsage() for
+ // all usages.
+ out.writeInt(attrs.getSystemUsage());
out.writeInt(attrs.getContentType());
out.writeInt(attrs.getCapturePreset());
out.writeInt(attrs.getAllFlags());
diff --git a/core/java/android/os/PerfettoTrackEventExtra.java b/core/java/android/os/PerfettoTrackEventExtra.java
index 2848bcb..07b44a8 100644
--- a/core/java/android/os/PerfettoTrackEventExtra.java
+++ b/core/java/android/os/PerfettoTrackEventExtra.java
@@ -214,9 +214,6 @@
* Initialize the builder for a new trace event.
*/
public Builder init(int traceType, PerfettoTrace.Category category) {
- if (!category.isEnabled()) {
- return this;
- }
mTraceType = traceType;
mCategory = category;
mEventName = "";
@@ -228,7 +225,7 @@
mExtra.reset();
// Reset after on init in case the thread created builders without calling emit
- return initInternal(this, null, true);
+ return initInternal(this, null, category.isEnabled());
}
/**
@@ -677,6 +674,7 @@
/**
* Resets the track event extra.
*/
+ @android.ravenwood.annotation.RavenwoodReplace
public void reset() {
native_clear_args(mPtr);
mPendingPointers.clear();
@@ -1306,4 +1304,8 @@
// Tracing currently completely disabled under Ravenwood
return null;
}
+
+ private void reset$ravenwood() {
+ // Tracing currently completely disabled under Ravenwood
+ }
}
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 0c5d9e97..b68b9a7 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -1347,6 +1347,7 @@
* Return the name of this process. By default, the process name is the same as the app's
* package name, but this can be changed using {@code android:process}.
*/
+ @RavenwoodReplace
@NonNull
public static String myProcessName() {
// Note this could be different from the actual process name if someone changes the
@@ -1355,6 +1356,12 @@
return sArgV0;
}
+ /** @hide */
+ @NonNull
+ public static String myProcessName$ravenwood() {
+ return "ravenwood";
+ }
+
/**
* Kill the process with the given PID.
* Note that, though this API allows us to request to
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index d3c677b..86acb2b 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -211,6 +211,14 @@
}
flag {
+ name: "force_concurrent_message_queue"
+ namespace: "system_performance"
+ is_exported: true
+ description: "Whether MessageQueue uses the new concurrent implementation"
+ bug: "336880969"
+}
+
+flag {
name: "get_private_space_settings"
namespace: "profile_experiences"
description: "Guards a new Private Profile API in LauncherApps"
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index ca24c0c..0476f62 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -358,7 +358,16 @@
is_fixed_read_only: true
is_exported: true
namespace: "permissions"
- description: "Enables SQlite for recording discrete and historical AppOp accesses"
+ description: "Enables SQlite for recording individual/discrete AppOp accesses"
+ bug: "377584611"
+}
+
+flag {
+ name: "enable_all_sqlite_appops_accesses"
+ is_fixed_read_only: true
+ is_exported: true
+ namespace: "permissions"
+ description: "Enables SQlite for storing aggregated & individual/discrete AppOp accesses"
bug: "377584611"
}
diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java
index 25e8a4d..3fd9418 100644
--- a/core/java/android/preference/PreferenceActivity.java
+++ b/core/java/android/preference/PreferenceActivity.java
@@ -749,6 +749,10 @@
getListView().clearChoices();
} else if (!WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(this)) {
super.onBackPressed();
+ } else if (!mIsBackCallbackRegistered) {
+ // If predictive back is enabled and no callback is registered, finish the activity.
+ // This ensures correct back navigation behaviour when onBackPressed is called manually.
+ finish();
}
updateBackCallbackRegistrationState();
}
diff --git a/core/java/android/preference/PreferenceScreen.java b/core/java/android/preference/PreferenceScreen.java
index 6b813b0..9654851 100644
--- a/core/java/android/preference/PreferenceScreen.java
+++ b/core/java/android/preference/PreferenceScreen.java
@@ -109,6 +109,7 @@
private int mLayoutResId = com.android.internal.R.layout.preference_list_fragment;
private Drawable mDividerDrawable;
private boolean mDividerSpecified;
+ private boolean mDialogFitsSystemWindows = false;
/**
* Do NOT use this constructor, use {@link PreferenceManager#createPreferenceScreen(Context)}.
@@ -136,6 +137,18 @@
}
/**
+ * Used in {@link #onClick()} to override the {@link View#setFitsSystemWindows(boolean)} for
+ * the dialog that shows. This is set separately to limit the scope of this change to just
+ * the {@link PreferenceScreen} instances which have demonstrated an issue with edge to edge.
+ *
+ * @param dialogFitsSystemWindows value passed to {@link View#setFitsSystemWindows(boolean)}.
+ * @hide
+ */
+ public void setDialogFitsSystemWindows(boolean dialogFitsSystemWindows) {
+ mDialogFitsSystemWindows = dialogFitsSystemWindows;
+ }
+
+ /**
* Returns an adapter that can be attached to a {@link PreferenceActivity}
* or {@link PreferenceFragment} to show the preferences contained in this
* {@link PreferenceScreen}.
@@ -201,6 +214,11 @@
View childPrefScreen = inflater.inflate(mLayoutResId, null);
View titleView = childPrefScreen.findViewById(android.R.id.title);
mListView = (ListView) childPrefScreen.findViewById(android.R.id.list);
+ // Don't override any potential state that may exist on mListView. If it was already marked
+ // as "setFitsSystemWindows(true)" somewhere else don't change to "false" here.
+ if (mDialogFitsSystemWindows) {
+ mListView.setFitsSystemWindows(true);
+ }
if (mDividerSpecified) {
mListView.setDivider(mDividerDrawable);
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 89f66c0..b97c9b5 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9357,6 +9357,16 @@
"accessibility_autoclick_panel_position";
/**
+ * Setting that specifies whether autoclick type reverts to left click after performing
+ * an action when {@link #ACCESSIBILITY_AUTOCLICK_ENABLED} is set.
+ *
+ * @see #ACCESSIBILITY_AUTOCLICK_ENABLED
+ * @hide
+ */
+ public static final String ACCESSIBILITY_AUTOCLICK_REVERT_TO_LEFT_CLICK =
+ "accessibility_autoclick_revert_to_left_click";
+
+ /**
* Whether or not larger size icons are used for the pointer of mouse/trackpad for
* accessibility.
* (0 = false, 1 = true)
@@ -9455,24 +9465,6 @@
"reduce_bright_colors_persist_across_reboots";
/**
- * Setting that specifies whether Even Dimmer - a feature that allows the brightness
- * slider to go below what the display can conventionally do, should be enabled.
- *
- * @hide
- */
- public static final String EVEN_DIMMER_ACTIVATED =
- "even_dimmer_activated";
-
- /**
- * Setting that specifies which nits level Even Dimmer should allow the screen brightness
- * to go down to.
- *
- * @hide
- */
- public static final String EVEN_DIMMER_MIN_NITS =
- "even_dimmer_min_nits";
-
- /**
* Setting that holds EM_VALUE (proprietary)
*
* @hide
@@ -10591,6 +10583,9 @@
*
* @hide
*/
+ @TestApi
+ @Readable
+ @SuppressLint({"UnflaggedApi", "NoSettingsProvider"}) // @TestApi purely for CTS support.
public static final String GLANCEABLE_HUB_ENABLED = "glanceable_hub_enabled";
/**
diff --git a/core/java/android/security/advancedprotection/AdvancedProtectionManager.java b/core/java/android/security/advancedprotection/AdvancedProtectionManager.java
index ea01fc9..770e234 100644
--- a/core/java/android/security/advancedprotection/AdvancedProtectionManager.java
+++ b/core/java/android/security/advancedprotection/AdvancedProtectionManager.java
@@ -16,7 +16,6 @@
package android.security.advancedprotection;
-import static android.app.admin.DevicePolicyIdentifiers.MEMORY_TAGGING_POLICY;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.os.UserManager.DISALLOW_CELLULAR_2G;
import static android.os.UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY;
@@ -59,6 +58,10 @@
public final class AdvancedProtectionManager {
private static final String TAG = "AdvancedProtectionMgr";
+ //TODO(b/378931989): Switch to android.app.admin.DevicePolicyIdentifiers.MEMORY_TAGGING_POLICY
+ //when the appropriate flag is launched.
+ private static final String MEMORY_TAGGING_POLICY = "memoryTagging";
+
/**
* Advanced Protection's identifier for setting policies or restrictions in
* {@link DevicePolicyManager}.
@@ -359,8 +362,7 @@
featureId = FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES;
} else if (DISALLOW_CELLULAR_2G.equals(identifier)) {
featureId = FEATURE_ID_DISALLOW_CELLULAR_2G;
- } else if (android.app.admin.flags.Flags.setMtePolicyCoexistence() && MEMORY_TAGGING_POLICY
- .equals(identifier)) {
+ } else if (MEMORY_TAGGING_POLICY.equals(identifier)) {
featureId = FEATURE_ID_ENABLE_MTE;
} else {
throw new UnsupportedOperationException("Unsupported identifier: " + identifier);
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 3a3ea18..7013f7d 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -52,13 +52,6 @@
}
flag {
- name: "deprecate_fsv_sig"
- namespace: "hardware_backed_security"
- description: "Feature flag for deprecating .fsv_sig"
- bug: "277916185"
-}
-
-flag {
name: "extend_vb_chain_to_updated_apk"
namespace: "hardware_backed_security"
description: "Use v4 signature and fs-verity to chain verification of allowlisted APKs to Verified Boot"
diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java
index 505db30..772fd96 100644
--- a/core/java/android/service/notification/Adjustment.java
+++ b/core/java/android/service/notification/Adjustment.java
@@ -225,7 +225,7 @@
public static final int TYPE_CONTENT_RECOMMENDATION = 4;
/**
- * Data type: String, a summarization of the text of the notification, or, if provided for
+ * Data type: CharSequence, a summarization of the text of the notification, or, if provided for
* a group summary, a summarization of the text of all of the notificatrions in the group.
* Send this key with a null value to remove an existing summarization.
*/
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 41a64e2..744cdf6 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -1323,14 +1323,13 @@
redrawNeeded ? 1 : 0));
return;
}
-
- final int transformHint = SurfaceControl.rotationToBufferTransform(
- (mDisplay.getInstallOrientation() + mDisplay.getRotation()) % 4);
- mSurfaceControl.setTransformHint(transformHint);
WindowLayout.computeSurfaceSize(mLayout, maxBounds, mWidth, mHeight,
mWinFrames.frame, false /* dragResizing */, mSurfaceSize);
if (mSurfaceControl.isValid()) {
+ final int transformHint = SurfaceControl.rotationToBufferTransform(
+ (mDisplay.getInstallOrientation() + mDisplay.getRotation()) % 4);
+ mSurfaceControl.setTransformHint(transformHint);
if (mBbqSurfaceControl == null) {
mBbqSurfaceControl = new SurfaceControl.Builder()
.setName("Wallpaper BBQ wrapper")
diff --git a/core/java/android/view/ImeBackAnimationController.java b/core/java/android/view/ImeBackAnimationController.java
index 19e0913..c39456a 100644
--- a/core/java/android/view/ImeBackAnimationController.java
+++ b/core/java/android/view/ImeBackAnimationController.java
@@ -16,7 +16,11 @@
package android.view;
+import static android.view.InsetsController.ANIMATION_DURATION_SYNC_IME_MS;
+import static android.view.InsetsController.ANIMATION_DURATION_UNSYNC_IME_MS;
import static android.view.InsetsController.ANIMATION_TYPE_USER;
+import static android.view.InsetsController.FAST_OUT_LINEAR_IN_INTERPOLATOR;
+import static android.view.InsetsController.SYNC_IME_INTERPOLATOR;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
@@ -56,8 +60,6 @@
private static final Interpolator BACK_GESTURE = new BackGestureInterpolator();
private static final Interpolator EMPHASIZED_DECELERATE = new PathInterpolator(
0.05f, 0.7f, 0.1f, 1f);
- private static final Interpolator STANDARD_ACCELERATE = new PathInterpolator(0.3f, 0f, 1f, 1f);
-
private final InsetsController mInsetsController;
private final ViewRootImpl mViewRoot;
private WindowInsetsAnimationController mWindowInsetsAnimationController = null;
@@ -183,8 +185,21 @@
float targetProgress = triggerBack ? 1f : 0f;
mPostCommitAnimator = ValueAnimator.ofFloat(
BACK_GESTURE.getInterpolation(mLastProgress) * PEEK_FRACTION, targetProgress);
- mPostCommitAnimator.setInterpolator(
- triggerBack ? STANDARD_ACCELERATE : EMPHASIZED_DECELERATE);
+ Interpolator interpolator;
+ long duration;
+ if (triggerBack && mViewRoot.mView.hasWindowInsetsAnimationCallback()
+ && mWindowInsetsAnimationController.getShownStateInsets().bottom != 0) {
+ interpolator = SYNC_IME_INTERPOLATOR;
+ duration = ANIMATION_DURATION_SYNC_IME_MS;
+ } else if (triggerBack) {
+ interpolator = FAST_OUT_LINEAR_IN_INTERPOLATOR;
+ duration = ANIMATION_DURATION_UNSYNC_IME_MS;
+ } else {
+ interpolator = EMPHASIZED_DECELERATE;
+ duration = POST_COMMIT_CANCEL_DURATION_MS;
+ }
+ mPostCommitAnimator.setInterpolator(interpolator);
+ mPostCommitAnimator.setDuration(duration);
mPostCommitAnimator.addUpdateListener(animation -> {
if (mWindowInsetsAnimationController != null) {
setInterpolatedProgress((float) animation.getAnimatedValue());
@@ -207,8 +222,6 @@
reset();
}
});
- mPostCommitAnimator.setDuration(
- triggerBack ? POST_COMMIT_DURATION_MS : POST_COMMIT_CANCEL_DURATION_MS);
mPostCommitAnimator.start();
if (triggerBack) {
mInsetsController.setPredictiveBackImeHideAnimInProgress(true);
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 462c5c6..6f346bd 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -235,8 +235,8 @@
private static final int ANIMATION_DELAY_DIM_MS = 500;
- private static final int ANIMATION_DURATION_SYNC_IME_MS = 285;
- private static final int ANIMATION_DURATION_UNSYNC_IME_MS = 200;
+ static final int ANIMATION_DURATION_SYNC_IME_MS = 285;
+ static final int ANIMATION_DURATION_UNSYNC_IME_MS = 200;
private static final int PENDING_CONTROL_TIMEOUT_MS = 2000;
@@ -256,11 +256,11 @@
return 1f - SYSTEM_BARS_ALPHA_INTERPOLATOR.getInterpolation(innerFraction);
}
};
- private static final Interpolator SYNC_IME_INTERPOLATOR =
+ static final Interpolator SYNC_IME_INTERPOLATOR =
new PathInterpolator(0.2f, 0f, 0f, 1f);
private static final Interpolator LINEAR_OUT_SLOW_IN_INTERPOLATOR =
new PathInterpolator(0, 0, 0.2f, 1f);
- private static final Interpolator FAST_OUT_LINEAR_IN_INTERPOLATOR =
+ static final Interpolator FAST_OUT_LINEAR_IN_INTERPOLATOR =
new PathInterpolator(0.4f, 0f, 1f, 1f);
/** Visible for WindowManagerWrapper */
diff --git a/core/java/android/view/NotificationTopLineView.java b/core/java/android/view/NotificationTopLineView.java
index e567414..beaf211 100644
--- a/core/java/android/view/NotificationTopLineView.java
+++ b/core/java/android/view/NotificationTopLineView.java
@@ -385,6 +385,13 @@
}
/**
+ * Returns whether the title is present.
+ */
+ public boolean isTitlePresent() {
+ return mTitle != null;
+ }
+
+ /**
* Determine if the given point is touching an active part of the top line.
*/
public boolean isInTouchRect(float x, float y) {
diff --git a/core/java/android/view/OWNERS b/core/java/android/view/OWNERS
index 80484a6..3353923 100644
--- a/core/java/android/view/OWNERS
+++ b/core/java/android/view/OWNERS
@@ -3,7 +3,6 @@
romainguy@google.com
adamp@google.com
aurimas@google.com
-nduca@google.com
sumir@google.com
ogunwale@google.com
jjaggi@google.com
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 9e97a8e..2895bf3 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -21,7 +21,9 @@
import android.annotation.TestApi;
import android.annotation.UiContext;
import android.app.Activity;
+import android.app.ActivityThread;
import android.app.AppGlobals;
+import android.app.Application;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Configuration;
@@ -39,14 +41,13 @@
import android.util.TypedValue;
import android.view.flags.Flags;
+import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
/**
* Contains methods to standard constants used in the UI for timeouts, sizes, and distances.
*/
public class ViewConfiguration {
- private static final String TAG = "ViewConfiguration";
-
/**
* Defines the width of the horizontal scrollbar and the height of the vertical scrollbar in
* dips
@@ -349,6 +350,8 @@
*/
private static final int SMART_SELECTION_INITIALIZING_TIMEOUT_IN_MILLISECOND = 500;
+ private static ResourceCache sResourceCache = new ResourceCache();
+
private final boolean mConstructedWithContext;
private final int mEdgeSlop;
private final int mFadingEdgeLength;
@@ -374,7 +377,6 @@
private final int mOverscrollDistance;
private final int mOverflingDistance;
private final boolean mViewTouchScreenHapticScrollFeedbackEnabled;
- @UnsupportedAppUsage
private final boolean mFadingMarqueeEnabled;
private final long mGlobalActionsKeyTimeout;
private final float mVerticalScrollFactor;
@@ -468,14 +470,12 @@
mEdgeSlop = (int) (sizeAndDensity * EDGE_SLOP + 0.5f);
mFadingEdgeLength = (int) (sizeAndDensity * FADING_EDGE_LENGTH + 0.5f);
- mScrollbarSize = res.getDimensionPixelSize(
- com.android.internal.R.dimen.config_scrollbarSize);
+ mScrollbarSize = res.getDimensionPixelSize(R.dimen.config_scrollbarSize);
mDoubleTapSlop = (int) (sizeAndDensity * DOUBLE_TAP_SLOP + 0.5f);
mWindowTouchSlop = (int) (sizeAndDensity * WINDOW_TOUCH_SLOP + 0.5f);
final TypedValue multiplierValue = new TypedValue();
- res.getValue(
- com.android.internal.R.dimen.config_ambiguousGestureMultiplier,
+ res.getValue(R.dimen.config_ambiguousGestureMultiplier,
multiplierValue,
true /*resolveRefs*/);
mAmbiguousGestureMultiplier = Math.max(1.0f, multiplierValue.getFloat());
@@ -488,8 +488,7 @@
mOverflingDistance = (int) (sizeAndDensity * OVERFLING_DISTANCE + 0.5f);
if (!sHasPermanentMenuKeySet) {
- final int configVal = res.getInteger(
- com.android.internal.R.integer.config_overrideHasPermanentMenuKey);
+ final int configVal = res.getInteger(R.integer.config_overrideHasPermanentMenuKey);
switch (configVal) {
default:
@@ -516,32 +515,27 @@
}
}
- mFadingMarqueeEnabled = res.getBoolean(
- com.android.internal.R.bool.config_ui_enableFadingMarquee);
- mTouchSlop = res.getDimensionPixelSize(
- com.android.internal.R.dimen.config_viewConfigurationTouchSlop);
+ mFadingMarqueeEnabled = res.getBoolean(R.bool.config_ui_enableFadingMarquee);
+ mTouchSlop = res.getDimensionPixelSize(R.dimen.config_viewConfigurationTouchSlop);
mHandwritingSlop = res.getDimensionPixelSize(
- com.android.internal.R.dimen.config_viewConfigurationHandwritingSlop);
- mHoverSlop = res.getDimensionPixelSize(
- com.android.internal.R.dimen.config_viewConfigurationHoverSlop);
+ R.dimen.config_viewConfigurationHandwritingSlop);
+ mHoverSlop = res.getDimensionPixelSize(R.dimen.config_viewConfigurationHoverSlop);
mMinScrollbarTouchTarget = res.getDimensionPixelSize(
- com.android.internal.R.dimen.config_minScrollbarTouchTarget);
+ R.dimen.config_minScrollbarTouchTarget);
mPagingTouchSlop = mTouchSlop * 2;
mDoubleTapTouchSlop = mTouchSlop;
mHandwritingGestureLineMargin = res.getDimensionPixelSize(
- com.android.internal.R.dimen.config_viewConfigurationHandwritingGestureLineMargin);
+ R.dimen.config_viewConfigurationHandwritingGestureLineMargin);
- mMinimumFlingVelocity = res.getDimensionPixelSize(
- com.android.internal.R.dimen.config_viewMinFlingVelocity);
- mMaximumFlingVelocity = res.getDimensionPixelSize(
- com.android.internal.R.dimen.config_viewMaxFlingVelocity);
+ mMinimumFlingVelocity = res.getDimensionPixelSize(R.dimen.config_viewMinFlingVelocity);
+ mMaximumFlingVelocity = res.getDimensionPixelSize(R.dimen.config_viewMaxFlingVelocity);
int configMinRotaryEncoderFlingVelocity = res.getDimensionPixelSize(
- com.android.internal.R.dimen.config_viewMinRotaryEncoderFlingVelocity);
+ R.dimen.config_viewMinRotaryEncoderFlingVelocity);
int configMaxRotaryEncoderFlingVelocity = res.getDimensionPixelSize(
- com.android.internal.R.dimen.config_viewMaxRotaryEncoderFlingVelocity);
+ R.dimen.config_viewMaxRotaryEncoderFlingVelocity);
if (configMinRotaryEncoderFlingVelocity < 0 || configMaxRotaryEncoderFlingVelocity < 0) {
mMinimumRotaryEncoderFlingVelocity = NO_FLING_MIN_VELOCITY;
mMaximumRotaryEncoderFlingVelocity = NO_FLING_MAX_VELOCITY;
@@ -551,8 +545,7 @@
}
int configRotaryEncoderHapticScrollFeedbackTickIntervalPixels =
- res.getDimensionPixelSize(
- com.android.internal.R.dimen
+ res.getDimensionPixelSize(R.dimen
.config_rotaryEncoderAxisScrollTickInterval);
mRotaryEncoderHapticScrollFeedbackTickIntervalPixels =
configRotaryEncoderHapticScrollFeedbackTickIntervalPixels > 0
@@ -560,41 +553,31 @@
: NO_HAPTIC_SCROLL_TICK_INTERVAL;
mRotaryEncoderHapticScrollFeedbackEnabled =
- res.getBoolean(
- com.android.internal.R.bool
+ res.getBoolean(R.bool
.config_viewRotaryEncoderHapticScrollFedbackEnabled);
- mGlobalActionsKeyTimeout = res.getInteger(
- com.android.internal.R.integer.config_globalActionsKeyTimeout);
+ mGlobalActionsKeyTimeout = res.getInteger(R.integer.config_globalActionsKeyTimeout);
- mHorizontalScrollFactor = res.getDimensionPixelSize(
- com.android.internal.R.dimen.config_horizontalScrollFactor);
- mVerticalScrollFactor = res.getDimensionPixelSize(
- com.android.internal.R.dimen.config_verticalScrollFactor);
+ mHorizontalScrollFactor = res.getDimensionPixelSize(R.dimen.config_horizontalScrollFactor);
+ mVerticalScrollFactor = res.getDimensionPixelSize(R.dimen.config_verticalScrollFactor);
mShowMenuShortcutsWhenKeyboardPresent = res.getBoolean(
- com.android.internal.R.bool.config_showMenuShortcutsWhenKeyboardPresent);
+ R.bool.config_showMenuShortcutsWhenKeyboardPresent);
- mMinScalingSpan = res.getDimensionPixelSize(
- com.android.internal.R.dimen.config_minScalingSpan);
+ mMinScalingSpan = res.getDimensionPixelSize(R.dimen.config_minScalingSpan);
- mScreenshotChordKeyTimeout = res.getInteger(
- com.android.internal.R.integer.config_screenshotChordKeyTimeout);
+ mScreenshotChordKeyTimeout = res.getInteger(R.integer.config_screenshotChordKeyTimeout);
mSmartSelectionInitializedTimeout = res.getInteger(
- com.android.internal.R.integer.config_smartSelectionInitializedTimeoutMillis);
+ R.integer.config_smartSelectionInitializedTimeoutMillis);
mSmartSelectionInitializingTimeout = res.getInteger(
- com.android.internal.R.integer.config_smartSelectionInitializingTimeoutMillis);
- mPreferKeepClearForFocusEnabled = res.getBoolean(
- com.android.internal.R.bool.config_preferKeepClearForFocus);
+ R.integer.config_smartSelectionInitializingTimeoutMillis);
+ mPreferKeepClearForFocusEnabled = res.getBoolean(R.bool.config_preferKeepClearForFocus);
mViewBasedRotaryEncoderScrollHapticsEnabledConfig =
- res.getBoolean(
- com.android.internal.R.bool.config_viewBasedRotaryEncoderHapticsEnabled);
+ res.getBoolean(R.bool.config_viewBasedRotaryEncoderHapticsEnabled);
mViewTouchScreenHapticScrollFeedbackEnabled =
Flags.enableScrollFeedbackForTouch()
- ? res.getBoolean(
- com.android.internal.R.bool
- .config_viewTouchScreenHapticScrollFeedbackEnabled)
+ ? res.getBoolean(R.bool.config_viewTouchScreenHapticScrollFeedbackEnabled)
: false;
}
@@ -632,6 +615,7 @@
@VisibleForTesting
public static void resetCacheForTesting() {
sConfigurations.clear();
+ sResourceCache = new ResourceCache();
}
/**
@@ -707,7 +691,7 @@
* components.
*/
public static int getPressedStateDuration() {
- return PRESSED_STATE_DURATION;
+ return sResourceCache.getPressedStateDuration();
}
/**
@@ -752,7 +736,7 @@
* considered to be a tap.
*/
public static int getTapTimeout() {
- return TAP_TIMEOUT;
+ return sResourceCache.getTapTimeout();
}
/**
@@ -761,7 +745,7 @@
* considered to be a tap.
*/
public static int getJumpTapTimeout() {
- return JUMP_TAP_TIMEOUT;
+ return sResourceCache.getJumpTapTimeout();
}
/**
@@ -770,7 +754,7 @@
* double-tap.
*/
public static int getDoubleTapTimeout() {
- return DOUBLE_TAP_TIMEOUT;
+ return sResourceCache.getDoubleTapTimeout();
}
/**
@@ -782,7 +766,7 @@
*/
@UnsupportedAppUsage
public static int getDoubleTapMinTime() {
- return DOUBLE_TAP_MIN_TIME;
+ return sResourceCache.getDoubleTapMinTime();
}
/**
@@ -792,7 +776,7 @@
* @hide
*/
public static int getHoverTapTimeout() {
- return HOVER_TAP_TIMEOUT;
+ return sResourceCache.getHoverTapTimeout();
}
/**
@@ -803,7 +787,7 @@
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static int getHoverTapSlop() {
- return HOVER_TAP_SLOP;
+ return sResourceCache.getHoverTapSlop();
}
/**
@@ -1044,7 +1028,7 @@
* in milliseconds.
*/
public static long getZoomControlsTimeout() {
- return ZOOM_CONTROLS_TIMEOUT;
+ return sResourceCache.getZoomControlsTimeout();
}
/**
@@ -1113,14 +1097,14 @@
* friction.
*/
public static float getScrollFriction() {
- return SCROLL_FRICTION;
+ return sResourceCache.getScrollFriction();
}
/**
* @return the default duration in milliseconds for {@link ActionMode#hide(long)}.
*/
public static long getDefaultActionModeHideDuration() {
- return ACTION_MODE_HIDE_DURATION_DEFAULT;
+ return sResourceCache.getDefaultActionModeHideDuration();
}
/**
@@ -1471,8 +1455,137 @@
return HOVER_TOOLTIP_HIDE_SHORT_TIMEOUT;
}
- private static final int getDisplayDensity(Context context) {
+ private static int getDisplayDensity(Context context) {
final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
return (int) (100.0f * metrics.density);
}
+
+ /**
+ * Fetches resource values statically and caches them locally for fast lookup. Note that these
+ * values will not be updated during the lifetime of a process, even if resource overlays are
+ * applied.
+ */
+ private static final class ResourceCache {
+
+ private int mPressedStateDuration = -1;
+ private int mTapTimeout = -1;
+ private int mJumpTapTimeout = -1;
+ private int mDoubleTapTimeout = -1;
+ private int mDoubleTapMinTime = -1;
+ private int mHoverTapTimeout = -1;
+ private int mHoverTapSlop = -1;
+ private long mZoomControlsTimeout = -1L;
+ private float mScrollFriction = -1f;
+ private long mDefaultActionModeHideDuration = -1L;
+
+ public int getPressedStateDuration() {
+ if (mPressedStateDuration < 0) {
+ Resources resources = getCurrentResources();
+ mPressedStateDuration = resources != null
+ ? resources.getInteger(R.integer.config_pressedStateDurationMillis)
+ : PRESSED_STATE_DURATION;
+ }
+ return mPressedStateDuration;
+ }
+
+ public int getTapTimeout() {
+ if (mTapTimeout < 0) {
+ Resources resources = getCurrentResources();
+ mTapTimeout = resources != null
+ ? resources.getInteger(R.integer.config_tapTimeoutMillis)
+ : TAP_TIMEOUT;
+ }
+ return mTapTimeout;
+ }
+
+ public int getJumpTapTimeout() {
+ if (mJumpTapTimeout < 0) {
+ Resources resources = getCurrentResources();
+ mJumpTapTimeout = resources != null
+ ? resources.getInteger(R.integer.config_jumpTapTimeoutMillis)
+ : JUMP_TAP_TIMEOUT;
+ }
+ return mJumpTapTimeout;
+ }
+
+ public int getDoubleTapTimeout() {
+ if (mDoubleTapTimeout < 0) {
+ Resources resources = getCurrentResources();
+ mDoubleTapTimeout = resources != null
+ ? resources.getInteger(R.integer.config_doubleTapTimeoutMillis)
+ : DOUBLE_TAP_TIMEOUT;
+ }
+ return mDoubleTapTimeout;
+ }
+
+ public int getDoubleTapMinTime() {
+ if (mDoubleTapMinTime < 0) {
+ Resources resources = getCurrentResources();
+ mDoubleTapMinTime = resources != null
+ ? resources.getInteger(R.integer.config_doubleTapMinTimeMillis)
+ : DOUBLE_TAP_MIN_TIME;
+ }
+ return mDoubleTapMinTime;
+ }
+
+ public int getHoverTapTimeout() {
+ if (mHoverTapTimeout < 0) {
+ Resources resources = getCurrentResources();
+ mHoverTapTimeout = resources != null
+ ? resources.getInteger(R.integer.config_hoverTapTimeoutMillis)
+ : HOVER_TAP_TIMEOUT;
+ }
+ return mHoverTapTimeout;
+ }
+
+ public int getHoverTapSlop() {
+ if (mHoverTapSlop < 0) {
+ Resources resources = getCurrentResources();
+ mHoverTapSlop = resources != null
+ ? resources.getDimensionPixelSize(R.dimen.config_hoverTapSlop)
+ : HOVER_TAP_SLOP;
+ }
+ return mHoverTapSlop;
+ }
+
+ public long getZoomControlsTimeout() {
+ if (mZoomControlsTimeout < 0) {
+ Resources resources = getCurrentResources();
+ mZoomControlsTimeout = resources != null
+ ? resources.getInteger(R.integer.config_zoomControlsTimeoutMillis)
+ : ZOOM_CONTROLS_TIMEOUT;
+ }
+ return mZoomControlsTimeout;
+ }
+
+ public float getScrollFriction() {
+ if (mScrollFriction < 0) {
+ Resources resources = getCurrentResources();
+ mScrollFriction = resources != null
+ ? resources.getFloat(R.dimen.config_scrollFriction)
+ : SCROLL_FRICTION;
+ }
+ return mScrollFriction;
+ }
+
+ public long getDefaultActionModeHideDuration() {
+ if (mDefaultActionModeHideDuration < 0) {
+ Resources resources = getCurrentResources();
+ mDefaultActionModeHideDuration = resources != null
+ ? resources.getInteger(R.integer.config_defaultActionModeHideDurationMillis)
+ : ACTION_MODE_HIDE_DURATION_DEFAULT;
+ }
+ return mDefaultActionModeHideDuration;
+ }
+
+ private static Resources getCurrentResources() {
+ if (!android.companion.virtualdevice.flags.Flags
+ .migrateViewconfigurationConstantsToResources()) {
+ return null;
+ }
+ Application application = ActivityThread.currentApplication();
+ Context context = application != null ? application.getApplicationContext() : null;
+ return context != null ? context.getResources() : null;
+ }
+ }
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e157da7..98e6cdd 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2071,26 +2071,40 @@
*/
@VisibleForTesting
public @ForceDarkType.ForceDarkTypeDef int determineForceDarkType() {
- if (forceInvertColor()) {
- // Force invert ignores all developer opt-outs.
- // We also ignore dark theme, since the app developer can override the user's preference
- // for dark mode in configuration.uiMode. Instead, we assume that both force invert and
- // the system's dark theme are enabled.
- if (getUiModeManager().getForceInvertState() == UiModeManager.FORCE_INVERT_TYPE_DARK) {
- return ForceDarkType.FORCE_INVERT_COLOR_DARK;
+ TypedArray a = mContext.obtainStyledAttributes(R.styleable.Theme);
+ try {
+ if (forceInvertColor()) {
+ // Force invert ignores all developer opt-outs.
+ // We also ignore dark theme, since the app developer can override the user's
+ // preference for dark mode in configuration.uiMode. Instead, we assume that both
+ // force invert and the system's dark theme are enabled.
+ if (getUiModeManager().getForceInvertState() ==
+ UiModeManager.FORCE_INVERT_TYPE_DARK) {
+ final boolean isLightTheme =
+ a.getBoolean(R.styleable.Theme_isLightTheme, false);
+ // TODO: b/372558459 - Also check the background ColorDrawable color lightness
+ // TODO: b/368725782 - Use hwui color area detection instead of / in
+ // addition to these heuristics.
+ if (isLightTheme) {
+ return ForceDarkType.FORCE_INVERT_COLOR_DARK;
+ } else {
+ return ForceDarkType.NONE;
+ }
+ }
}
- }
- boolean useAutoDark = getNightMode() == Configuration.UI_MODE_NIGHT_YES;
- if (useAutoDark) {
- boolean forceDarkAllowedDefault =
- SystemProperties.getBoolean(ThreadedRenderer.DEBUG_FORCE_DARK, false);
- TypedArray a = mContext.obtainStyledAttributes(R.styleable.Theme);
- useAutoDark = a.getBoolean(R.styleable.Theme_isLightTheme, true)
- && a.getBoolean(R.styleable.Theme_forceDarkAllowed, forceDarkAllowedDefault);
+ boolean useAutoDark = getNightMode() == Configuration.UI_MODE_NIGHT_YES;
+ if (useAutoDark) {
+ boolean forceDarkAllowedDefault =
+ SystemProperties.getBoolean(ThreadedRenderer.DEBUG_FORCE_DARK, false);
+ useAutoDark = a.getBoolean(R.styleable.Theme_isLightTheme, true)
+ && a.getBoolean(R.styleable.Theme_forceDarkAllowed,
+ forceDarkAllowedDefault);
+ }
+ return useAutoDark ? ForceDarkType.FORCE_DARK : ForceDarkType.NONE;
+ } finally {
a.recycle();
}
- return useAutoDark ? ForceDarkType.FORCE_DARK : ForceDarkType.NONE;
}
private void updateForceDarkMode() {
@@ -13640,4 +13654,11 @@
ThreadedRenderer.preInitBufferAllocator();
}
}
+
+ /**
+ * @hide
+ */
+ public Choreographer getChoreographer() {
+ return mChoreographer;
+ }
}
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index d267c94..e43fb48 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -163,6 +163,9 @@
/** @hide */
public static final boolean AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT_DEFAULT = false;
+ /** @hide */
+ public static final boolean AUTOCLICK_REVERT_TO_LEFT_CLICK_DEFAULT = true;
+
/**
* Activity action: Launch UI to manage which accessibility service or feature is assigned
* to the navigation bar Accessibility button.
diff --git a/core/java/android/view/inspector/OWNERS b/core/java/android/view/inspector/OWNERS
index 705f4b3..f345034 100644
--- a/core/java/android/view/inspector/OWNERS
+++ b/core/java/android/view/inspector/OWNERS
@@ -2,5 +2,4 @@
alanv@google.com
adamp@google.com
aurimas@google.com
-nduca@google.com
sumir@google.com
diff --git a/core/java/android/webkit/LegacyErrorStrings.java b/core/java/android/webkit/LegacyErrorStrings.java
index 60a6ee1..f52214c 100644
--- a/core/java/android/webkit/LegacyErrorStrings.java
+++ b/core/java/android/webkit/LegacyErrorStrings.java
@@ -22,7 +22,7 @@
/**
* Localized strings for the error codes defined in EventHandler.
*
- * {@hide}
+ * @hide
*/
class LegacyErrorStrings {
private LegacyErrorStrings() { /* Utility class, don't instantiate. */ }
diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index 1baf3b8..c5e558f 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -531,8 +531,9 @@
* the chosen file(s). WebView can access all files that your app can access.
* In case the file(s) are chosen through an untrusted source such as a third-party
* app, it is your own app's responsibility to check what the returned Uris
- * refer to before calling the <code>filePathCallback</code>. See
- * {@link #createIntent} and {@link #parseResult} for more details.</p>
+ * refer to before calling the {@code filePathCallback}. See
+ * {@link FileChooserParams#createIntent} and {@link FileChooserParams#parseResult} for more
+ * details.
*
* @param webView The WebView instance that is initiating the request.
* @param filePathCallback Invoke this callback to supply the list of paths to files to upload,
diff --git a/core/java/android/webkit/WebIconDatabase.java b/core/java/android/webkit/WebIconDatabase.java
index b705658..76d2e4c 100644
--- a/core/java/android/webkit/WebIconDatabase.java
+++ b/core/java/android/webkit/WebIconDatabase.java
@@ -73,7 +73,7 @@
*/
public abstract void requestIconForPageUrl(String url, IconListener listener);
- /** {@hide}
+ /** @hide
*/
@SuppressWarnings("HiddenAbstractMethod")
@SystemApi
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 5e828ba..99fe0cb 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -5211,11 +5211,7 @@
*/
@Nullable
public String getFontVariationSettings() {
- if (Flags.typefaceRedesignReadonly()) {
- return mTextPaint.getFontVariationOverride();
- } else {
- return mTextPaint.getFontVariationSettings();
- }
+ return mTextPaint.getFontVariationSettings();
}
/**
@@ -5571,10 +5567,10 @@
Math.clamp(400 + mFontWeightAdjustment,
FontStyle.FONT_WEIGHT_MIN, FontStyle.FONT_WEIGHT_MAX)));
}
- mTextPaint.setFontVariationOverride(
+ mTextPaint.setFontVariationSettings(
FontVariationAxis.toFontVariationSettings(axes));
} else {
- mTextPaint.setFontVariationOverride(fontVariationSettings);
+ mTextPaint.setFontVariationSettings(fontVariationSettings);
}
effective = true;
} else {
diff --git a/core/java/android/window/DesktopExperienceFlags.java b/core/java/android/window/DesktopExperienceFlags.java
index e0c48b0..cf58217 100644
--- a/core/java/android/window/DesktopExperienceFlags.java
+++ b/core/java/android/window/DesktopExperienceFlags.java
@@ -60,6 +60,8 @@
ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT(Flags::enableMoveToNextDisplayShortcut, false),
ENABLE_MULTIPLE_DESKTOPS_BACKEND(Flags::enableMultipleDesktopsBackend, false),
ENABLE_MULTIPLE_DESKTOPS_FRONTEND(Flags::enableMultipleDesktopsFrontend, false),
+ ENABLE_PERSISTING_DISPLAY_SIZE_FOR_CONNECTED_DISPLAYS(
+ Flags::enablePersistingDisplaySizeForConnectedDisplays, false),
ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY(Flags::enablePerDisplayDesktopWallpaperActivity,
false),
ENABLE_PER_DISPLAY_PACKAGE_CONTEXT_CACHE_IN_STATUSBAR_NOTIF(
diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index 696b7b8..888d7e7 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -53,12 +53,14 @@
ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS(
Flags::enableCaptionCompatInsetForceConsumptionAlways, true),
ENABLE_CASCADING_WINDOWS(Flags::enableCascadingWindows, true),
+ ENABLE_DESKTOP_APP_HANDLE_ANIMATION(Flags::enableDesktopAppHandleAnimation, false),
ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS_BUGFIX(
Flags::enableDesktopAppLaunchAlttabTransitionsBugfix, true),
ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX(Flags::enableDesktopAppLaunchTransitionsBugfix,
true),
ENABLE_DESKTOP_CLOSE_SHORTCUT_BUGFIX(Flags::enableDesktopCloseShortcutBugfix, false),
ENABLE_DESKTOP_COMPAT_UI_VISIBILITY_STATUS(Flags::enableCompatUiVisibilityStatus, true),
+ ENABLE_DESKTOP_IMMERSIVE_DRAG_BUGFIX(Flags::enableDesktopImmersiveDragBugfix, false),
ENABLE_DESKTOP_INDICATOR_IN_SEPARATE_THREAD_BUGFIX(
Flags::enableDesktopIndicatorInSeparateThreadBugfix, false),
ENABLE_DESKTOP_OPENING_DEEPLINK_MINIMIZE_ANIMATION_BUGFIX(
@@ -106,6 +108,7 @@
ENABLE_FULLY_IMMERSIVE_IN_DESKTOP(Flags::enableFullyImmersiveInDesktop, true),
ENABLE_HANDLE_INPUT_FIX(Flags::enableHandleInputFix, true),
ENABLE_HOLD_TO_DRAG_APP_HANDLE(Flags::enableHoldToDragAppHandle, true),
+ ENABLE_INPUT_LAYER_TRANSITION_FIX(Flags::enableInputLayerTransitionFix, false),
ENABLE_MINIMIZE_BUTTON(Flags::enableMinimizeButton, true),
ENABLE_MODALS_FULLSCREEN_WITH_PERMISSIONS(Flags::enableModalsFullscreenWithPermission, false),
ENABLE_OPAQUE_BACKGROUND_FOR_TRANSPARENT_WINDOWS(
@@ -126,6 +129,8 @@
ENABLE_TILE_RESIZING(Flags::enableTileResizing, true),
ENABLE_TOP_VISIBLE_ROOT_TASK_PER_USER_TRACKING(Flags::enableTopVisibleRootTaskPerUserTracking,
true),
+ ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX(
+ Flags::enableVisualIndicatorInTransitionBugfix, false),
ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS(Flags::enableWindowingDynamicInitialBounds, true),
ENABLE_WINDOWING_EDGE_DRAG_RESIZE(Flags::enableWindowingEdgeDragResize, true),
ENABLE_WINDOWING_SCALED_RESIZING(Flags::enableWindowingScaledResizing, true),
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 63c55ad..be4edc3 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -29,6 +29,7 @@
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_FLAG_AOD_APPEARING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_NONE;
@@ -375,7 +376,8 @@
*/
public boolean hasChangesOrSideEffects() {
return !mChanges.isEmpty() || isKeyguardGoingAway()
- || (mFlags & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0;
+ || (mFlags & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0
+ || (mFlags & TRANSIT_FLAG_AOD_APPEARING) != 0;
}
/**
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 09f458b..1f710c1 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -154,6 +154,16 @@
}
flag {
+ name: "enable_input_layer_transition_fix"
+ namespace: "lse_desktop_experience"
+ description: "Enables a bugfix for input layer disposal during certain transitions."
+ bug: "371473978"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "enable_accessible_custom_headers"
namespace: "lse_desktop_experience"
description: "Enables a11y-friendly custom header input handling"
@@ -803,6 +813,26 @@
}
flag {
+ name: "enable_desktop_app_handle_animation"
+ namespace: "lse_desktop_experience"
+ description: "Enables the animation that occurs when the app handle of a task in immersive mode is shown or hidden."
+ bug: "375252977"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "enable_desktop_immersive_drag_bugfix"
+ namespace: "lse_desktop_experience"
+ description: "Keeps the app handle visible during a drag."
+ bug: "381280828"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "enable_desktop_indicator_in_separate_thread_bugfix"
namespace: "lse_desktop_experience"
description: "Enables running visual indicator view operations in ShellDesktopThread."
@@ -816,7 +846,7 @@
name: "enable_taskbar_overflow"
namespace: "lse_desktop_experience"
description: "Show recent apps in the taskbar overflow."
- bug: "368119679"
+ bug: "375627272"
}
flag {
@@ -827,13 +857,10 @@
}
flag {
- name: "enable_persisting_density_scale_for_connected_displays"
+ name: "enable_persisting_display_size_for_connected_displays"
namespace: "lse_desktop_experience"
- description: "Enables persisting density scale on resolution change for connected displays."
+ description: "Enables persisting display size on resolution change for connected displays."
bug: "392855657"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
}
flag {
@@ -864,4 +891,28 @@
metadata {
purpose: PURPOSE_BUGFIX
}
-}
\ No newline at end of file
+}
+
+flag {
+ name: "enable_visual_indicator_in_transition_bugfix"
+ namespace: "lse_desktop_experience"
+ description: "Enables bugfix to move visual drop-zone indicator to transition root, so it can't be shown after the transition."
+ bug: "392826275"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "enable_size_compat_mode_improvements_for_connected_displays"
+ namespace: "lse_desktop_experience"
+ description: "Enable some improvements in size compat mode for connected displays."
+ bug: "399752440"
+}
+
+flag {
+ name: "form_factor_based_desktop_first_switch"
+ namespace: "lse_desktop_experience"
+ description: "Enables the desktop-first mode switching logic based on its form factor."
+ bug: "394736817"
+}
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 0e19eb2..b4fec41 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -51,6 +51,17 @@
}
flag {
+ name: "use_cached_insets_for_display_switch"
+ namespace: "windowing_frontend"
+ description: "Reduce intermediate insets changes for display switch"
+ bug: "266197298"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "edge_to_edge_by_default"
namespace: "windowing_frontend"
description: "Make app go edge-to-edge by default when targeting SDK 35 or greater"
@@ -482,4 +493,15 @@
metadata {
purpose: PURPOSE_BUGFIX
}
+}
+
+flag {
+ name: "early_launch_hint"
+ namespace: "windowing_frontend"
+ description: "Sets Launch powermode for activity launches earlier"
+ bug: "399380676"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
\ No newline at end of file
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index f2efa20..cbeaeda 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -81,6 +81,16 @@
flag {
namespace: "windowing_sdk"
+ name: "activity_embedding_delay_task_fragment_finish_for_activity_launch"
+ description: "Fixes a race condition that we finish the TaskFragment too early when there is a pending activity launch."
+ bug: "390452023"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ namespace: "windowing_sdk"
name: "wlinfo_oncreate"
description: "Makes WindowLayoutInfo accessible without racing in the Activity#onCreate()"
bug: "337820752"
@@ -127,17 +137,6 @@
flag {
namespace: "windowing_sdk"
- name: "use_self_sync_transaction_for_layer"
- description: "Always use this.getSyncTransaction for assignLayer"
- bug: "388127825"
- is_fixed_read_only: true
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
- namespace: "windowing_sdk"
name: "safe_region_letterboxing"
description: "Enables letterboxing for a safe region"
bug: "380132497"
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
index 6498c53..a2d6c2b 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
@@ -215,7 +215,6 @@
Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);
targets.add(colorInversion);
- // TODO(b/394683600): Update Icon with the autoclick asset.
final ToggleAllowListingFeatureTarget autoclick =
new ToggleAllowListingFeatureTarget(context,
shortcutType,
@@ -224,7 +223,7 @@
AUTOCLICK_COMPONENT_NAME.flattenToString(),
uid,
context.getString(R.string.autoclick_feature_name),
- context.getDrawable(R.drawable.ic_accessibility_generic),
+ context.getDrawable(R.drawable.ic_accessibility_autoclick),
Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED);
targets.add(autoclick);
diff --git a/core/java/com/android/internal/config/appcloning/OWNERS b/core/java/com/android/internal/config/appcloning/OWNERS
index 0645a8c5..9369438 100644
--- a/core/java/com/android/internal/config/appcloning/OWNERS
+++ b/core/java/com/android/internal/config/appcloning/OWNERS
@@ -1,3 +1,2 @@
# Bug component: 1207885
jigarthakkar@google.com
-saumyap@google.com
\ No newline at end of file
diff --git a/core/java/com/android/internal/os/DEBUG_STORE_OWNERS b/core/java/com/android/internal/os/DEBUG_STORE_OWNERS
new file mode 100644
index 0000000..c8e22b7
--- /dev/null
+++ b/core/java/com/android/internal/os/DEBUG_STORE_OWNERS
@@ -0,0 +1,2 @@
+benmiles@google.com
+mohamadmahmoud@google.com
diff --git a/core/java/com/android/internal/os/DebugStore.java b/core/java/com/android/internal/os/DebugStore.java
index 4c45fee..3dca786 100644
--- a/core/java/com/android/internal/os/DebugStore.java
+++ b/core/java/com/android/internal/os/DebugStore.java
@@ -20,6 +20,7 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Intent;
import android.content.pm.ServiceInfo;
+import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -43,6 +44,9 @@
* @hide
*/
public class DebugStore {
+ private static final boolean DEBUG_EVENTS = false;
+ private static final String TAG = "DebugStore";
+
private static DebugStoreNative sDebugStoreNative = new DebugStoreNativeImpl();
@UnsupportedAppUsage
@@ -120,7 +124,7 @@
* @param receiverClassName The class name of the broadcast receiver.
*/
@UnsupportedAppUsage
- public static void recordGoAsync(String receiverClassName) {
+ public static void recordGoAsync(int pendingResultId) {
sDebugStoreNative.recordEvent(
"GoAsync",
List.of(
@@ -128,8 +132,8 @@
Thread.currentThread().getName(),
"tid",
String.valueOf(Thread.currentThread().getId()),
- "rcv",
- Objects.toString(receiverClassName)));
+ "prid",
+ Integer.toHexString(pendingResultId)));
}
/**
@@ -138,7 +142,7 @@
* @param receiverClassName The class of the broadcast receiver that completed the operation.
*/
@UnsupportedAppUsage
- public static void recordFinish(String receiverClassName) {
+ public static void recordFinish(int pendingResultId) {
sDebugStoreNative.recordEvent(
"Finish",
List.of(
@@ -146,9 +150,10 @@
Thread.currentThread().getName(),
"tid",
String.valueOf(Thread.currentThread().getId()),
- "rcv",
- Objects.toString(receiverClassName)));
+ "prid",
+ Integer.toHexString(pendingResultId)));
}
+
/**
* Records the completion of a long-running looper message.
*
@@ -172,21 +177,92 @@
/**
- * Records the reception of a broadcast.
+ * Records the reception of a broadcast by a manifest-declared receiver.
*
* @param intent The Intent associated with the broadcast.
* @return A unique ID for the recorded event.
*/
@UnsupportedAppUsage
- public static long recordBroadcastHandleReceiver(@Nullable Intent intent) {
+ public static long recordBroadcastReceive(@Nullable Intent intent, int pendingResultId) {
return sDebugStoreNative.beginEvent(
- "HandleReceiver",
+ "BcRcv",
+ List.of(
+ "tname",
+ Thread.currentThread().getName(),
+ "tid",
+ String.valueOf(Thread.currentThread().getId()),
+ "act",
+ Objects.toString(intent != null ? intent.getAction() : null),
+ "cmp",
+ Objects.toString(intent != null ? intent.getComponent() : null),
+ "pkg",
+ Objects.toString(intent != null ? intent.getPackage() : null),
+ "prid",
+ Integer.toHexString(pendingResultId)));
+ }
+
+ /**
+ * Records the reception of a broadcast by a context-registered receiver.
+ *
+ * @param intent The Intent associated with the broadcast.
+ * @param pendingResultId The object ID of the PendingResult associated with the broadcast.
+ * @return A unique ID for the recorded event.
+ */
+ @UnsupportedAppUsage
+ public static long recordBroadcastReceiveReg(@Nullable Intent intent, int pendingResultId) {
+ return sDebugStoreNative.beginEvent(
+ "BcRcvReg",
+ List.of(
+ "tname",
+ Thread.currentThread().getName(),
+ "tid",
+ String.valueOf(Thread.currentThread().getId()),
+ "act",
+ Objects.toString(intent != null ? intent.getAction() : null),
+ "cmp",
+ Objects.toString(intent != null ? intent.getComponent() : null),
+ "pkg",
+ Objects.toString(intent != null ? intent.getPackage() : null),
+ "prid",
+ Integer.toHexString(pendingResultId)));
+ }
+
+ /**
+ * Records the binding of an application.
+ *
+ * @return A unique ID for the recorded event.
+ */
+ @UnsupportedAppUsage
+ public static long recordHandleBindApplication() {
+ return sDebugStoreNative.beginEvent("BindApp", List.of());
+ }
+
+ /**
+ * Records the scheduling of a receiver.
+ *
+ * @return A unique ID for the recorded event.
+ */
+ @UnsupportedAppUsage
+ public static long recordScheduleReceiver() {
+ return sDebugStoreNative.beginEvent(
+ "SchRcv",
List.of(
"tname", Thread.currentThread().getName(),
- "tid", String.valueOf(Thread.currentThread().getId()),
- "act", Objects.toString(intent != null ? intent.getAction() : null),
- "cmp", Objects.toString(intent != null ? intent.getComponent() : null),
- "pkg", Objects.toString(intent != null ? intent.getPackage() : null)));
+ "tid", String.valueOf(Thread.currentThread().getId())));
+ }
+
+ /**
+ * Records the scheduling of a registered receiver.
+ *
+ * @return A unique ID for the recorded event.
+ */
+ @UnsupportedAppUsage
+ public static long recordScheduleRegisteredReceiver() {
+ return sDebugStoreNative.beginEvent(
+ "SchRcvReg",
+ List.of(
+ "tname", Thread.currentThread().getName(),
+ "tid", String.valueOf(Thread.currentThread().getId())));
}
/**
@@ -225,18 +301,48 @@
private static class DebugStoreNativeImpl implements DebugStoreNative {
@Override
public long beginEvent(String eventName, List<String> attributes) {
- return DebugStore.beginEventNative(eventName, attributes);
+ long id = DebugStore.beginEventNative(eventName, attributes);
+ if (DEBUG_EVENTS) {
+ Log.i(
+ TAG,
+ "beginEvent: " + id + " " + eventName + " " + attributeString(attributes));
+ }
+ return id;
}
@Override
public void endEvent(long id, List<String> attributes) {
+ if (DEBUG_EVENTS) {
+ Log.i(TAG, "endEvent: " + id + " " + attributeString(attributes));
+ }
DebugStore.endEventNative(id, attributes);
}
@Override
public void recordEvent(String eventName, List<String> attributes) {
+ if (DEBUG_EVENTS) {
+ Log.i(TAG, "recordEvent: " + eventName + " " + attributeString(attributes));
+ }
DebugStore.recordEventNative(eventName, attributes);
}
+
+ /**
+ * Returns a string like "[key1=foo, key2=bar]"
+ */
+ private String attributeString(List<String> attributes) {
+ StringBuilder sb = new StringBuilder().append("[");
+
+ for (int i = 0; i < attributes.size(); i++) {
+ sb.append(attributes.get(i));
+
+ if (i % 2 == 0) {
+ sb.append("=");
+ } else if (i < attributes.size() - 1) {
+ sb.append(", ");
+ }
+ }
+ return sb.append("]").toString();
+ }
}
private static native long beginEventNative(String eventName, List<String> attributes);
diff --git a/core/java/com/android/internal/os/OWNERS b/core/java/com/android/internal/os/OWNERS
index ffd4499..a5aa9618 100644
--- a/core/java/com/android/internal/os/OWNERS
+++ b/core/java/com/android/internal/os/OWNERS
@@ -2,6 +2,7 @@
per-file *Zygote* = file:/ZYGOTE_OWNERS
per-file *Cpu* = file:CPU_OWNERS
per-file *Binder* = file:BINDER_OWNERS
+per-file *DebugStore* = file:DEBUG_STORE_OWNERS
per-file *BinaryTransparency* = file:/core/java/android/transparency/OWNERS
# BatteryStats
diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java
index 1f90760..dd31c38 100644
--- a/core/java/com/android/internal/widget/LockPatternView.java
+++ b/core/java/com/android/internal/widget/LockPatternView.java
@@ -634,7 +634,6 @@
}
private void notifyPatternStarted() {
- sendAccessEvent(R.string.lockscreen_access_pattern_start);
if (mOnPatternListener != null) {
mOnPatternListener.onPatternStart();
}
@@ -642,14 +641,12 @@
@UnsupportedAppUsage
private void notifyPatternDetected() {
- sendAccessEvent(R.string.lockscreen_access_pattern_detected);
if (mOnPatternListener != null) {
mOnPatternListener.onPatternDetected(mPattern);
}
}
private void notifyPatternCleared() {
- sendAccessEvent(R.string.lockscreen_access_pattern_cleared);
if (mOnPatternListener != null) {
mOnPatternListener.onPatternCleared();
}
@@ -1240,10 +1237,6 @@
}
}
- private void sendAccessEvent(int resId) {
- announceForAccessibility(mContext.getString(resId));
- }
-
private void handleActionUp() {
// report pattern detected
if (!mPattern.isEmpty()) {
diff --git a/core/java/com/android/internal/widget/remotecompose/OWNERS b/core/java/com/android/internal/widget/remotecompose/OWNERS
index e163474..c606744 100644
--- a/core/java/com/android/internal/widget/remotecompose/OWNERS
+++ b/core/java/com/android/internal/widget/remotecompose/OWNERS
@@ -1,6 +1,5 @@
nicolasroard@google.com
hoford@google.com
-jnichol@google.com
sihua@google.com
sunnygoyal@google.com
oscarad@google.com
diff --git a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
index b8503da..796ea8d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
@@ -65,7 +65,7 @@
// We also keep a more fine-grained BUILD number, exposed as
// ID_API_LEVEL = DOCUMENT_API_LEVEL + BUILD
- static final float BUILD = 0.2f;
+ static final float BUILD = 0.3f;
@NonNull ArrayList<Operation> mOperations = new ArrayList<>();
@@ -411,7 +411,7 @@
@Override
public void serialize(MapSerializer serializer) {
serializer
- .add("type", "CoreDocument")
+ .addType("CoreDocument")
.add("width", mWidth)
.add("height", mHeight)
.add("operations", mOperations);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/Operations.java b/core/java/com/android/internal/widget/remotecompose/core/Operations.java
index 09ec402..9cbafab 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/Operations.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/Operations.java
@@ -100,6 +100,7 @@
import com.android.internal.widget.remotecompose.core.operations.layout.managers.CollapsibleColumnLayout;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.CollapsibleRowLayout;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.ColumnLayout;
+import com.android.internal.widget.remotecompose.core.operations.layout.managers.FitBoxLayout;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.RowLayout;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.StateLayout;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.TextLayout;
@@ -231,6 +232,7 @@
public static final int LAYOUT_ROOT = 200;
public static final int LAYOUT_CONTENT = 201;
public static final int LAYOUT_BOX = 202;
+ public static final int LAYOUT_FIT_BOX = 176;
public static final int LAYOUT_ROW = 203;
public static final int LAYOUT_COLLAPSIBLE_ROW = 230;
public static final int LAYOUT_COLUMN = 204;
@@ -391,6 +393,7 @@
map.put(LAYOUT_ROOT, RootLayoutComponent::read);
map.put(LAYOUT_CONTENT, LayoutComponentContent::read);
map.put(LAYOUT_BOX, BoxLayout::read);
+ map.put(LAYOUT_FIT_BOX, FitBoxLayout::read);
map.put(LAYOUT_COLUMN, ColumnLayout::read);
map.put(LAYOUT_COLLAPSIBLE_COLUMN, CollapsibleColumnLayout::read);
map.put(LAYOUT_ROW, RowLayout::read);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java b/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java
index f355676..c27ee32 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java
@@ -17,11 +17,13 @@
import android.annotation.NonNull;
+import com.android.internal.widget.remotecompose.core.serialize.Serializable;
+
/**
* PaintOperation interface, used for operations aimed at painting (while any operation _can_ paint,
* this make it a little more explicit)
*/
-public abstract class PaintOperation extends Operation {
+public abstract class PaintOperation extends Operation implements Serializable {
@Override
public void apply(@NonNull RemoteContext context) {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/Platform.java b/core/java/com/android/internal/widget/remotecompose/core/Platform.java
index 6073de6..3fff86c 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/Platform.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/Platform.java
@@ -47,6 +47,14 @@
int getImageHeight(@NonNull Object image);
/**
+ * Returns true if the platform-specific image object has format ALPHA_8
+ *
+ * @param image platform-specific image object
+ * @return whether or not the platform-specific image object has format ALPHA_8
+ */
+ boolean isAlpha8Image(@NonNull Object image);
+
+ /**
* Converts a platform-specific path object into a platform-independent float buffer
*
* @param path
@@ -110,6 +118,11 @@
}
@Override
+ public boolean isAlpha8Image(@NonNull Object image) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public float[] pathToFloatArray(@NonNull Object path) {
throw new UnsupportedOperationException();
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
index e75bd30..c249adf 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
@@ -98,6 +98,7 @@
import com.android.internal.widget.remotecompose.core.operations.layout.managers.CollapsibleColumnLayout;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.CollapsibleRowLayout;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.ColumnLayout;
+import com.android.internal.widget.remotecompose.core.operations.layout.managers.FitBoxLayout;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.RowLayout;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.StateLayout;
import com.android.internal.widget.remotecompose.core.operations.layout.managers.TextLayout;
@@ -281,13 +282,7 @@
int dstRight,
int dstBottom,
@Nullable String contentDescription) {
- int imageId = mRemoteComposeState.dataGetId(image);
- if (imageId == -1) {
- imageId = mRemoteComposeState.cacheData(image);
- byte[] data = mPlatform.imageToByteArray(image); // todo: potential npe
- BitmapData.apply(
- mBuffer, imageId, imageWidth, imageHeight, data); // todo: potential npe
- }
+ int imageId = storeBitmap(image);
int contentDescriptionId = 0;
if (contentDescription != null) {
contentDescriptionId = addText(contentDescription);
@@ -443,16 +438,7 @@
float right,
float bottom,
@Nullable String contentDescription) {
- int imageId = mRemoteComposeState.dataGetId(image);
- if (imageId == -1) {
- imageId = mRemoteComposeState.cacheData(image);
- byte[] data = mPlatform.imageToByteArray(image); // todo: potential npe
- int imageWidth = mPlatform.getImageWidth(image);
- int imageHeight = mPlatform.getImageHeight(image);
-
- BitmapData.apply(
- mBuffer, imageId, imageWidth, imageHeight, data); // todo: potential npe
- }
+ int imageId = storeBitmap(image);
addDrawBitmap(imageId, left, top, right, bottom, contentDescription);
}
@@ -523,15 +509,7 @@
int scaleType,
float scaleFactor,
@Nullable String contentDescription) {
- int imageId = mRemoteComposeState.dataGetId(image);
- if (imageId == -1) {
- imageId = mRemoteComposeState.cacheData(image);
- byte[] data = mPlatform.imageToByteArray(image); // todo: potential npe
- int imageWidth = mPlatform.getImageWidth(image);
- int imageHeight = mPlatform.getImageHeight(image);
-
- BitmapData.apply(mBuffer, imageId, imageWidth, imageHeight, data); // todo: potential pe
- }
+ int imageId = storeBitmap(image);
int contentDescriptionId = 0;
if (contentDescription != null) {
contentDescriptionId = addText(contentDescription);
@@ -559,16 +537,7 @@
* @return id of the image useful with
*/
public int addBitmap(@NonNull Object image) {
- int imageId = mRemoteComposeState.dataGetId(image);
- if (imageId == -1) {
- imageId = mRemoteComposeState.cacheData(image);
- byte[] data = mPlatform.imageToByteArray(image); // tODO: potential npe
- int imageWidth = mPlatform.getImageWidth(image);
- int imageHeight = mPlatform.getImageHeight(image);
-
- BitmapData.apply(mBuffer, imageId, imageWidth, imageHeight, data);
- }
- return imageId;
+ return storeBitmap(image);
}
/**
@@ -578,18 +547,7 @@
* @return id of the image useful with
*/
public int addBitmap(@NonNull Object image, @NonNull String name) {
- int imageId = mRemoteComposeState.dataGetId(image);
- if (imageId == -1) {
- imageId = mRemoteComposeState.cacheData(image);
- byte[] data = mPlatform.imageToByteArray(image); // todo: potential npe
- int imageWidth = mPlatform.getImageWidth(image);
- int imageHeight = mPlatform.getImageHeight(image);
-
- BitmapData.apply(mBuffer, imageId, imageWidth, imageHeight, data);
- setBitmapName(imageId, name);
- }
-
- return imageId;
+ return storeBitmap(image);
}
/**
@@ -1393,7 +1351,7 @@
* @return the id of the command representing long
*/
public int addLong(long value) {
- int id = mRemoteComposeState.cacheData(value);
+ int id = mRemoteComposeState.nextId();
LongConstant.apply(mBuffer, id, value);
return id;
}
@@ -1405,7 +1363,7 @@
* @return the id
*/
public int addBoolean(boolean value) {
- int id = mRemoteComposeState.cacheData(value);
+ int id = mRemoteComposeState.nextId();
BooleanConstant.apply(mBuffer, id, value);
return id;
}
@@ -1821,33 +1779,14 @@
}
/**
- * This defines the name of the color given the id.
- *
- * @param id of the color
- * @param name Name of the color
- */
- public void setColorName(int id, @NonNull String name) {
- NamedVariable.apply(mBuffer, id, NamedVariable.COLOR_TYPE, name);
- }
-
- /**
- * This defines the name of the string given the id
- *
- * @param id of the string
- * @param name name of the string
- */
- public void setStringName(int id, @NonNull String name) {
- NamedVariable.apply(mBuffer, id, NamedVariable.STRING_TYPE, name);
- }
-
- /**
- * This defines the name of the float given the id
+ * This defines the name of a type of given object
*
* @param id of the float
* @param name name of the float
+ * @param type the type of variable NamedVariable.COLOR_TYPE, STRING_TYPE, etc
*/
- public void setFloatName(int id, String name) {
- NamedVariable.apply(mBuffer, id, NamedVariable.FLOAT_TYPE, name);
+ public void setNamedVariable(int id, @NonNull String name, int type) {
+ NamedVariable.apply(mBuffer, id, type, name);
}
/**
@@ -2139,6 +2078,19 @@
}
/**
+ * Add a fitbox start tag
+ *
+ * @param componentId component id
+ * @param animationId animation id
+ * @param horizontal horizontal alignment
+ * @param vertical vertical alignment
+ */
+ public void addFitBoxStart(int componentId, int animationId, int horizontal, int vertical) {
+ mLastComponentId = getComponentId(componentId);
+ FitBoxLayout.apply(mBuffer, mLastComponentId, animationId, horizontal, vertical);
+ }
+
+ /**
* Add a row start tag
*
* @param componentId component id
@@ -2439,4 +2391,35 @@
public void drawComponentContent() {
DrawContent.apply(mBuffer);
}
+
+ /**
+ * Ensures the bitmap is stored.
+ *
+ * @param image the bitbap to store
+ * @return the id of the bitmap
+ */
+ private int storeBitmap(Object image) {
+ int imageId = mRemoteComposeState.dataGetId(image);
+ if (imageId == -1) {
+ imageId = mRemoteComposeState.cacheData(image);
+ byte[] data = mPlatform.imageToByteArray(image); // todo: potential npe
+ short imageWidth = (short) mPlatform.getImageWidth(image);
+ short imageHeight = (short) mPlatform.getImageHeight(image);
+ if (mPlatform.isAlpha8Image(image)) {
+ BitmapData.apply(
+ mBuffer,
+ imageId,
+ BitmapData.TYPE_PNG_ALPHA_8,
+ imageWidth,
+ BitmapData.ENCODING_INLINE,
+ imageHeight,
+ data); // todo: potential npe
+ } else {
+ BitmapData.apply(
+ mBuffer, imageId, imageWidth, imageHeight, data); // todo: potential npe
+ }
+ }
+
+ return imageId;
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
index 363b82b..83c0619 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
@@ -27,6 +27,7 @@
import com.android.internal.widget.remotecompose.core.operations.utilities.NanMap;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
/**
@@ -36,7 +37,7 @@
public class RemoteComposeState implements CollectionsAccess {
public static final int START_ID = 42;
// private static final int MAX_FLOATS = 500;
- private static final int MAX_COLORS = 200;
+ private static int sMaxColors = 200;
private static final int MAX_DATA = 1000;
private final IntMap<Object> mIntDataMap = new IntMap<>();
@@ -52,7 +53,7 @@
private final IntMap<Object> mPathMap = new IntMap<>();
private final IntMap<float[]> mPathData = new IntMap<>();
- private final boolean[] mColorOverride = new boolean[MAX_COLORS];
+ private boolean[] mColorOverride = new boolean[sMaxColors];
@NonNull private final IntMap<ArrayAccess> mCollectionMap = new IntMap<>();
private final boolean[] mDataOverride = new boolean[MAX_DATA];
@@ -318,7 +319,7 @@
* @param color
*/
public void updateColor(int id, int color) {
- if (mColorOverride[id]) {
+ if (id < sMaxColors && mColorOverride[id]) {
return;
}
mColorMap.put(id, color);
@@ -342,6 +343,10 @@
* @param color
*/
public void overrideColor(int id, int color) {
+ if (id >= sMaxColors) {
+ sMaxColors *= 2;
+ mColorOverride = Arrays.copyOf(mColorOverride, sMaxColors);
+ }
mColorOverride[id] = true;
mColorMap.put(id, color);
updateListeners(id);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
index 622f0c8..c6b17e4 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
@@ -211,6 +211,14 @@
public abstract void clearNamedFloatOverride(String floatName);
/**
+ * Set the value of a named long. This modifies the content of a LongConstant
+ *
+ * @param name the name of the float to override
+ * @param value Override the default float
+ */
+ public abstract void setNamedLong(String name, long value);
+
+ /**
* Set the value of a named Object. This overrides the Object in the document
*
* @param dataName the name of the Object to override
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java
index 13e6f38..255d7a4 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java
@@ -72,6 +72,9 @@
/** The data is encoded as RAW 8888 bit */
public static final short TYPE_RAW8888 = 3;
+ /** The data is encoded as PNG_8888 but decoded as ALPHA_8 */
+ public static final short TYPE_PNG_ALPHA_8 = 4;
+
/**
* create a bitmap structure
*
@@ -136,6 +139,15 @@
}
/**
+ * The type of the image
+ *
+ * @return the type of the image
+ */
+ public int getType() {
+ return mType;
+ }
+
+ /**
* Add the image to the document
*
* @param buffer document to write to
@@ -195,6 +207,21 @@
int imageId = buffer.readInt();
int width = buffer.readInt();
int height = buffer.readInt();
+ int type;
+ if (width > 0xffff) {
+ type = width >> 16;
+ width = width & 0xffff;
+ } else {
+ type = TYPE_PNG_8888;
+ }
+
+ int encoding;
+ if (height > 0xffff) {
+ encoding = height >> 16;
+ height = height & 0xffff;
+ } else {
+ encoding = ENCODING_INLINE;
+ }
if (width < 1
|| height < 1
|| height > MAX_IMAGE_DIMENSION
@@ -202,7 +229,10 @@
throw new RuntimeException("Dimension of image is invalid " + width + "x" + height);
}
byte[] bitmap = buffer.readBuffer();
- operations.add(new BitmapData(imageId, width, height, bitmap));
+ BitmapData bitmapData = new BitmapData(imageId, width, height, bitmap);
+ bitmapData.mType = (short) type;
+ bitmapData.mEncoding = (short) encoding;
+ operations.add(bitmapData);
}
/**
@@ -244,7 +274,7 @@
@Override
public void serialize(MapSerializer serializer) {
serializer
- .add("type", CLASS_NAME)
+ .addType(CLASS_NAME)
.add("imageId", mImageId)
.add("imageWidth", mImageWidth)
.add("imageHeight", mImageHeight)
@@ -275,6 +305,8 @@
return "TYPE_RAW8";
case TYPE_RAW8888:
return "TYPE_RAW8888";
+ case TYPE_PNG_ALPHA_8:
+ return "TYPE_PNG_ALPHA_8";
default:
return "TYPE_INVALID";
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapFontData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapFontData.java
index 078ce98..70bda6d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapFontData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapFontData.java
@@ -221,6 +221,6 @@
@Override
public void serialize(MapSerializer serializer) {
- serializer.add("type", CLASS_NAME).add("id", mId);
+ serializer.addType(CLASS_NAME).add("id", mId);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ClickArea.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ClickArea.java
index 00ac9c2..7a8373b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ClickArea.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ClickArea.java
@@ -237,7 +237,7 @@
@Override
public void serialize(MapSerializer serializer) {
serializer
- .add("type", CLASS_NAME)
+ .addType(CLASS_NAME)
.add("id", mId)
.add("contentDescriptionId", mContentDescription)
.add("left", mLeft, mOutLeft)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java
index e7dc405..03bdb70 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java
@@ -133,7 +133,7 @@
@Override
public void serialize(MapSerializer serializer) {
- serializer.add("type", CLASS_NAME).add("id", mId).add("regionOp", regionOpToString());
+ serializer.addType(CLASS_NAME).add("id", mId).add("regionOp", regionOpToString());
}
String regionOpToString() {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java
index 1646146..8b1e96a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java
@@ -118,6 +118,6 @@
@Override
public void serialize(MapSerializer serializer) {
- serialize(serializer, "left", "top", "right", "bottom").add("type", CLASS_NAME);
+ serialize(serializer, "left", "top", "right", "bottom").addType(CLASS_NAME);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ColorConstant.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorConstant.java
index 333ffaa..7b12f4a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ColorConstant.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorConstant.java
@@ -133,7 +133,7 @@
@Override
public void serialize(MapSerializer serializer) {
serializer
- .add("type", CLASS_NAME)
+ .addType(CLASS_NAME)
.add("color", Utils.colorInt(mColor))
.add("colorId", mColorId);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java
index d5af791..25323a8 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java
@@ -507,7 +507,7 @@
@Override
public void serialize(MapSerializer serializer) {
- serializer.add("type", CLASS_NAME).add("id", mId);
+ serializer.addType(CLASS_NAME).add("id", mId);
switch (mMode) {
case COLOR_COLOR_INTERPOLATE:
case ID_COLOR_INTERPOLATE:
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ComponentValue.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ComponentValue.java
index cd13d25..5335e4f 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ComponentValue.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ComponentValue.java
@@ -171,7 +171,7 @@
@Override
public void serialize(MapSerializer serializer) {
serializer
- .add("type", CLASS_NAME)
+ .addType(CLASS_NAME)
.add("valueId", mValueId)
.add("componentValueType", typeToString(mType))
.add("componentId", mComponentID);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java
index fb3abdb..20bebaa 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java
@@ -148,8 +148,9 @@
return mValues.length;
}
+ @SuppressWarnings("JdkImmutableCollections")
@Override
public void serialize(MapSerializer serializer) {
- serializer.add("type", CLASS_NAME).add("id", mId).add("values", List.of(mValues));
+ serializer.addType(CLASS_NAME).add("id", mId).add("values", List.of(mValues));
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java
index 58fd74f..af660f3 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java
@@ -150,8 +150,9 @@
return 0;
}
+ @SuppressWarnings("JdkImmutableCollections")
@Override
public void serialize(MapSerializer serializer) {
- serializer.add("type", CLASS_NAME).add("id", mId).add("ids", List.of(mIds));
+ serializer.addType(CLASS_NAME).add("id", mId).add("ids", List.of(mIds));
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java
index a427836..5024164 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java
@@ -141,6 +141,6 @@
@Override
public void serialize(MapSerializer serializer) {
serialize(serializer, "left", "top", "right", "bottom", "startAngle", "sweepAngle")
- .add("type", CLASS_NAME);
+ .addType(CLASS_NAME);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java
index 40d3bed..e3b53a1 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java
@@ -28,6 +28,7 @@
import com.android.internal.widget.remotecompose.core.VariableSupport;
import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
import java.util.List;
@@ -185,4 +186,16 @@
public void paint(@NonNull PaintContext context) {
context.drawBitmap(mId, mOutputLeft, mOutputTop, mOutputRight, mOutputBottom);
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer
+ .addType(CLASS_NAME)
+ .add("imageId", mId)
+ .add("contentDescriptionId", mDescriptionId)
+ .add("left", mLeft, mOutputLeft)
+ .add("top", mTop, mOutputTop)
+ .add("right", mRight, mOutputRight)
+ .add("bottom", mBottom, mOutputBottom);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapFontText.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapFontText.java
index 258988e..bff87fd 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapFontText.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapFontText.java
@@ -28,6 +28,7 @@
import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
import java.util.List;
@@ -229,4 +230,16 @@
xPos = xPos2 + glyph.mMarginRight;
}
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer
+ .addType(CLASS_NAME)
+ .add("textId", mTextID)
+ .add("bitmapFontId", mBitmapFontID)
+ .add("start", mStart)
+ .add("end", mEnd)
+ .add("x", mX, mOutX)
+ .add("y", mY, mOutY);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java
index 013dd1a..39d85af 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java
@@ -25,6 +25,7 @@
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
import com.android.internal.widget.remotecompose.core.semantics.AccessibleComponent;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
import java.util.List;
@@ -230,4 +231,20 @@
mDstBottom,
mContentDescId);
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer
+ .addType(CLASS_NAME)
+ .add("imageId", mImageId)
+ .add("contentDescriptionId", mContentDescId)
+ .add("srcLeft", mSrcLeft)
+ .add("srcTop", mSrcTop)
+ .add("srcRight", mSrcRight)
+ .add("srcBottom", mSrcBottom)
+ .add("dstLeft", mDstLeft)
+ .add("dstTop", mDstTop)
+ .add("dstRight", mDstRight)
+ .add("dstBottom", mDstBottom);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java
index e1070f9..827e569 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java
@@ -28,6 +28,7 @@
import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
import com.android.internal.widget.remotecompose.core.operations.utilities.ImageScaling;
import com.android.internal.widget.remotecompose.core.semantics.AccessibleComponent;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
import java.util.List;
@@ -374,4 +375,46 @@
mContentDescId);
context.restore();
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer
+ .addType(CLASS_NAME)
+ .add("imageId", mImageId)
+ .add("contentDescriptionId", mContentDescId)
+ .add("scaleType", getScaleTypeString())
+ .add("mode", mMode)
+ .add("scaleFactor", mScaleFactor, mOutScaleFactor)
+ .add("srcLeft", mSrcLeft, mOutSrcLeft)
+ .add("srcTop", mSrcTop, mOutSrcTop)
+ .add("srcRight", mSrcRight, mOutSrcRight)
+ .add("srcBottom", mSrcBottom, mOutSrcBottom)
+ .add("dstLeft", mDstLeft, mOutDstLeft)
+ .add("dstTop", mDstTop, mOutDstTop)
+ .add("dstRight", mDstRight, mOutDstRight)
+ .add("dstBottom", mDstBottom, mOutDstBottom);
+ }
+
+ private String getScaleTypeString() {
+ switch (mScaleType) {
+ case SCALE_NONE:
+ return "SCALE_NONE";
+ case SCALE_INSIDE:
+ return "SCALE_INSIDE";
+ case SCALE_FILL_WIDTH:
+ return "SCALE_FILL_WIDTH";
+ case SCALE_FILL_HEIGHT:
+ return "SCALE_FILL_HEIGHT";
+ case SCALE_FIT:
+ return "SCALE_FIT";
+ case SCALE_CROP:
+ return "SCALE_CROP";
+ case SCALE_FILL_BOUNDS:
+ return "SCALE_FILL_BOUNDS";
+ case SCALE_FIXED_SCALE:
+ return "SCALE_FIXED_SCALE";
+ default:
+ return "INVALID_SCALE_TYPE";
+ }
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java
index dfc89b1..538cbaf 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java
@@ -112,6 +112,6 @@
@Override
public void serialize(MapSerializer serializer) {
- serialize(serializer, "cx", "cy", "radius").add("type", CLASS_NAME);
+ serialize(serializer, "cx", "cy", "radius").addType(CLASS_NAME);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawContent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawContent.java
index e2e22ac..4d2a939 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawContent.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawContent.java
@@ -114,6 +114,6 @@
@Override
public void serialize(MapSerializer serializer) {
- serializer.add("type", CLASS_NAME);
+ serializer.addType(CLASS_NAME);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java
index 461dece..97e5057 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java
@@ -140,6 +140,6 @@
@Override
public void serialize(MapSerializer serializer) {
- serialize(serializer, "startX", "startY", "endX", "endY").add("type", CLASS_NAME);
+ serialize(serializer, "startX", "startY", "endX", "endY").addType(CLASS_NAME);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java
index d0a5adc..5d619ba 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java
@@ -110,6 +110,6 @@
@Override
public void serialize(MapSerializer serializer) {
- serialize(serializer, "left", "top", "right", "bottom").add("type", CLASS_NAME);
+ serialize(serializer, "left", "top", "right", "bottom").addType(CLASS_NAME);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java
index 3fd8bb4..6e29927 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java
@@ -112,6 +112,6 @@
@Override
public void serialize(MapSerializer serializer) {
- serializer.add("type", CLASS_NAME).add("id", mId).add("start", mStart).add("end", mEnd);
+ serializer.addType(CLASS_NAME).add("id", mId).add("start", mStart).add("end", mEnd);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java
index f6aa30f..15f3ced 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java
@@ -106,6 +106,6 @@
@Override
public void serialize(MapSerializer serializer) {
- serialize(serializer, "left", "top", "right", "bottom").add("type", CLASS_NAME);
+ serialize(serializer, "left", "top", "right", "bottom").addType(CLASS_NAME);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java
index 67338c1..31d9b6a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java
@@ -127,6 +127,6 @@
@Override
public void serialize(MapSerializer serializer) {
serialize(serializer, "left", "top", "right", "bottom", "rx", "sweepAngle")
- .add("type", CLASS_NAME);
+ .addType(CLASS_NAME);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawSector.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawSector.java
index 78f64a5..19f1623 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawSector.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawSector.java
@@ -128,6 +128,6 @@
@Override
public void serialize(MapSerializer serializer) {
serialize(serializer, "left", "top", "right", "bottom", "startAngle", "sweepAngle")
- .add("type", CLASS_NAME);
+ .addType(CLASS_NAME);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawText.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawText.java
index 8adba1d..ee1689c 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawText.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawText.java
@@ -28,6 +28,7 @@
import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
import java.util.List;
@@ -205,4 +206,17 @@
public void paint(@NonNull PaintContext context) {
context.drawTextRun(mTextID, mStart, mEnd, mContextStart, mContextEnd, mOutX, mOutY, mRtl);
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer
+ .addType(CLASS_NAME)
+ .add("start", mStart)
+ .add("end", mEnd)
+ .add("contextStart", mContextStart)
+ .add("contextEnd", mContextEnd)
+ .add("x", mX, mOutX)
+ .add("y", mY, mOutY)
+ .add("rtl", mRtl);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java
index 92469d1..1d917d5 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java
@@ -244,7 +244,7 @@
@Override
public void serialize(MapSerializer serializer) {
serializer
- .add("type", CLASS_NAME)
+ .addType(CLASS_NAME)
.add("textId", mTextID)
.add("x", mX, mOutX)
.add("y", mY, mOutY)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java
index 1f7910e..f382440 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java
@@ -159,7 +159,7 @@
@Override
public void serialize(MapSerializer serializer) {
serializer
- .add("type", CLASS_NAME)
+ .addType(CLASS_NAME)
.add("pathId", mPathId)
.add("textId", mTextId)
.add("vOffset", mVOffset, mOutVOffset)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java
index e288394..4ca995b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java
@@ -28,6 +28,7 @@
import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
import java.util.List;
@@ -171,4 +172,15 @@
public void paint(@NonNull PaintContext context) {
context.drawTweenPath(mPath1Id, mPath2Id, mOutTween, mOutStart, mOutStop);
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer
+ .addType(CLASS_NAME)
+ .add("path1Id", mPath1Id)
+ .add("path2Id", mPath2Id)
+ .add("tween", mTween, mOutTween)
+ .add("start", mStart, mOutStart)
+ .add("stop", mStop, mOutStop);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java
index 7f3c3ed..233e246 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java
@@ -123,6 +123,6 @@
@Override
public void serialize(MapSerializer serializer) {
- serializer.add("type", CLASS_NAME).add("id", mId).add("value", mValue);
+ serializer.addType(CLASS_NAME).add("id", mId).add("value", mValue);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java
index c1fa898..eba201b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java
@@ -160,6 +160,9 @@
mLastAnimatedValue = lastComputedValue;
context.loadFloat(mId, lastComputedValue);
context.needsRepaint();
+ if (mFloatAnimation.isPropagate()) {
+ markDirty();
+ }
}
} else if (mSpring != null) {
float lastComputedValue = mSpring.get(t - mLastChange);
@@ -169,8 +172,12 @@
context.needsRepaint();
}
} else {
- float v =
- mExp.eval(context.getCollectionsAccess(), mPreCalcValue, mPreCalcValue.length);
+ float v = 0;
+ try {
+ v = mExp.eval(context.getCollectionsAccess(), mPreCalcValue, mPreCalcValue.length);
+ } catch (Exception e) {
+ throw new RuntimeException(this.toString() + " len = " + mPreCalcValue.length, e);
+ }
if (mFloatAnimation != null) {
mFloatAnimation.setTargetValue(v);
}
@@ -344,7 +351,7 @@
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.EXPRESSION)
- .add("type", CLASS_NAME)
+ .addType(CLASS_NAME)
.add("id", mId)
.addFloatExpressionSrc("srcValues", mSrcValue)
.add("animation", mFloatAnimation);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatFunctionCall.java b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatFunctionCall.java
index eccc00a..8559294 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatFunctionCall.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatFunctionCall.java
@@ -32,8 +32,10 @@
import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
import com.android.internal.widget.remotecompose.core.operations.utilities.AnimatedFloatExpression;
import com.android.internal.widget.remotecompose.core.operations.utilities.NanMap;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
/** This provides the command to call a floatfunction defined in floatfunction */
@@ -182,4 +184,13 @@
}
mFunction.execute(remoteContext);
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer
+ .addType(CLASS_NAME)
+ .add("id", mId)
+ .add("args", Collections.singletonList(mArgs))
+ .add("outArgs", Collections.singletonList(mOutArgs));
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ImageAttribute.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ImageAttribute.java
index fb4a5e4..9ef1b3b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ImageAttribute.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ImageAttribute.java
@@ -26,7 +26,9 @@
import com.android.internal.widget.remotecompose.core.PaintOperation;
import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
+import java.util.Collections;
import java.util.List;
/** Operation to extract meta Attributes from image data objects */
@@ -162,4 +164,25 @@
break;
}
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer
+ .addType(CLASS_NAME)
+ .add("id", mId)
+ .add("imageId", mImageId)
+ .add("args", Collections.singletonList(mArgs))
+ .addType(typeToString());
+ }
+
+ private String typeToString() {
+ switch (mType) {
+ case IMAGE_WIDTH:
+ return "IMAGE_WIDTH";
+ case IMAGE_HEIGHT:
+ return "IMAGE_HEIGHT";
+ default:
+ return "INVALID_TYPE";
+ }
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/IntegerExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/IntegerExpression.java
index 2a5260c..53b3c89 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/IntegerExpression.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/IntegerExpression.java
@@ -233,7 +233,7 @@
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.EXPRESSION)
- .add("type", CLASS_NAME)
+ .addType(CLASS_NAME)
.add("id", mId)
.add("mask", mId)
.addIntExpressionSrc("srcValues", mSrcValue, mMask);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java
index 64df19d..d2b38f42 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java
@@ -102,6 +102,6 @@
@Override
public void serialize(MapSerializer serializer) {
- serializer.add("type", CLASS_NAME);
+ serializer.addType(CLASS_NAME);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java
index 9c4df0b..5990b4b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java
@@ -114,6 +114,6 @@
@Override
public void serialize(MapSerializer serializer) {
- serialize(serializer, "rotate", "pivotX", "pivotY").add("type", CLASS_NAME);
+ serialize(serializer, "rotate", "pivotX", "pivotY").addType(CLASS_NAME);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java
index 0e6de0d..06da156 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java
@@ -100,6 +100,6 @@
@Override
public void serialize(MapSerializer serializer) {
- serializer.add("type", CLASS_NAME);
+ serializer.addType(CLASS_NAME);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java
index b6e5cbc..20d4065 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java
@@ -106,6 +106,6 @@
@Override
public void serialize(MapSerializer serializer) {
- serialize(serializer, "scaleX", "scaleY", "pivotX", "pivotY").add("type", CLASS_NAME);
+ serialize(serializer, "scaleX", "scaleY", "pivotX", "pivotY").addType(CLASS_NAME);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java
index f9a589c..6d1c503 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java
@@ -103,6 +103,6 @@
@Override
public void serialize(MapSerializer serializer) {
- serialize(serializer, "skewX", "skewY").add("type", CLASS_NAME);
+ serialize(serializer, "skewX", "skewY").addType(CLASS_NAME);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java
index de783bf..e21f133 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java
@@ -102,6 +102,6 @@
@Override
public void serialize(MapSerializer serializer) {
- serialize(serializer, "dx", "dy").add("type", CLASS_NAME);
+ serialize(serializer, "dx", "dy").addType(CLASS_NAME);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java b/core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java
index 96628fd..9a88085 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java
@@ -25,7 +25,6 @@
import com.android.internal.widget.remotecompose.core.RemoteContext;
import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
-import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
import com.android.internal.widget.remotecompose.core.serialize.Serializable;
@@ -43,6 +42,8 @@
public static final int FLOAT_TYPE = 1;
public static final int STRING_TYPE = 0;
public static final int IMAGE_TYPE = 3;
+ public static final int INT_TYPE = 4;
+ public static final int LONG_TYPE = 5;
public NamedVariable(int varId, int varType, @NonNull String name) {
this.mVarId = varId;
@@ -122,7 +123,7 @@
public static void documentation(@NonNull DocumentationBuilder doc) {
doc.operation("Data Operations", OP_CODE, CLASS_NAME)
.description("Add a string name for an ID")
- .field(DocumentedOperation.INT, "varId", "id to label")
+ .field(INT, "varId", "id to label")
.field(INT, "varType", "The type of variable")
.field(UTF8, "name", "String");
}
@@ -141,7 +142,7 @@
@Override
public void serialize(MapSerializer serializer) {
serializer
- .add("type", CLASS_NAME)
+ .addType(CLASS_NAME)
.add("varId", mVarId)
.add("varName", mVarName)
.add("varType", typeToString());
@@ -157,6 +158,8 @@
return "STRING_TYPE";
case IMAGE_TYPE:
return "IMAGE_TYPE";
+ case INT_TYPE:
+ return "INT_TYPE";
default:
return "INVALID_TYPE";
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java
index 8389aa7..70197c6 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java
@@ -131,6 +131,6 @@
@Override
public void serialize(MapSerializer serializer) {
- serializer.add("type", CLASS_NAME).add("paintBundle", mPaintData);
+ serializer.addType(CLASS_NAME).add("paintBundle", mPaintData);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ParticlesLoop.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ParticlesLoop.java
index 8d19c94..f9fdfdf 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ParticlesLoop.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ParticlesLoop.java
@@ -33,6 +33,7 @@
import com.android.internal.widget.remotecompose.core.operations.layout.Container;
import com.android.internal.widget.remotecompose.core.operations.utilities.AnimatedFloatExpression;
import com.android.internal.widget.remotecompose.core.operations.utilities.NanMap;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
import java.util.ArrayList;
import java.util.List;
@@ -292,4 +293,9 @@
}
}
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer.addType(CLASS_NAME).add("id", mId);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java
index 8f353ce..8a747e1 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java
@@ -258,9 +258,6 @@
@Override
public void serialize(MapSerializer serializer) {
- serializer
- .add("type", CLASS_NAME)
- .add("id", mInstanceId)
- .add("path", pathString(mFloatPath));
+ serializer.addType(CLASS_NAME).add("id", mInstanceId).add("path", pathString(mFloatPath));
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java
index 7aa3390..78e3b9e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java
@@ -242,9 +242,6 @@
@Override
public void serialize(MapSerializer serializer) {
- serializer
- .add("type", CLASS_NAME)
- .add("id", mInstanceId)
- .add("path", pathString(mFloatPath));
+ serializer.addType(CLASS_NAME).add("id", mInstanceId).add("path", pathString(mFloatPath));
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java
index 9564f15..cedc4f3b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java
@@ -243,9 +243,6 @@
@Override
public void serialize(MapSerializer serializer) {
- serializer
- .add("type", CLASS_NAME)
- .add("id", mInstanceId)
- .add("path", pathString(mFloatPath));
+ serializer.addType(CLASS_NAME).add("id", mInstanceId).add("path", pathString(mFloatPath));
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathTween.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathTween.java
index c5add57..09b29e8 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/PathTween.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathTween.java
@@ -161,7 +161,7 @@
@Override
public void serialize(MapSerializer serializer) {
serializer
- .add("type", CLASS_NAME)
+ .addType(CLASS_NAME)
.add("outId", mOutId)
.add("pathId1", mPathId1)
.add("pathId2", mPathId2)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java
index a6a8a81..214d240 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java
@@ -133,6 +133,6 @@
@Override
public void serialize(MapSerializer serializer) {
- serializer.add("type", CLASS_NAME).add("contentDescriptionId", mContentDescription);
+ serializer.addType(CLASS_NAME).add("contentDescriptionId", mContentDescription);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ShaderData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ShaderData.java
index 5f6162b..013b6f6 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ShaderData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ShaderData.java
@@ -390,7 +390,7 @@
@Override
public void serialize(MapSerializer serializer) {
serializer
- .add("type", CLASS_NAME)
+ .addType(CLASS_NAME)
.add("shaderTextId", mShaderTextId)
.add("shaderID", mShaderID)
.add("uniformRawFloatMap", mUniformRawFloatMap)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextAttribute.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextAttribute.java
index 3e72995d..36f25c6 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextAttribute.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextAttribute.java
@@ -173,7 +173,7 @@
@Override
public void serialize(MapSerializer serializer) {
serializer
- .add("type", CLASS_NAME)
+ .addType(CLASS_NAME)
.add("id", mId)
.add("textId", mTextId)
.add("measureType", typeToString());
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java
index 419e6d0..67773d1 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java
@@ -136,6 +136,6 @@
@Override
public void serialize(MapSerializer serializer) {
- serializer.add("type", CLASS_NAME).add("textId", mTextId).add("text", mText);
+ serializer.addType(CLASS_NAME).add("textId", mTextId).add("text", mText);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java
index 6b2f49b..f22369f 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java
@@ -215,7 +215,7 @@
@Override
public void serialize(MapSerializer serializer) {
serializer
- .add("type", CLASS_NAME)
+ .addType(CLASS_NAME)
.add("textId", mTextId)
.add("value", mValue, mOutValue)
.add("digitsBefore", mDigitsBefore)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookup.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookup.java
index e8865c2..fa44bf1 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookup.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookup.java
@@ -156,7 +156,7 @@
@Override
public void serialize(MapSerializer serializer) {
serializer
- .add("type", CLASS_NAME)
+ .addType(CLASS_NAME)
.add("textId", mTextId)
.add("dataSetId", mDataSetId)
.add("indexId", mIndex, mOutIndex);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookupInt.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookupInt.java
index de20255..5ec3290 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookupInt.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookupInt.java
@@ -149,7 +149,7 @@
@Override
public void serialize(MapSerializer serializer) {
serializer
- .add("type", CLASS_NAME)
+ .addType(CLASS_NAME)
.add("textId", mTextId)
.add("dataSetId", mDataSetId)
.add("indexId", mIndex, mOutIndex);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextMeasure.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextMeasure.java
index 58cd68e..3559d1db 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextMeasure.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextMeasure.java
@@ -27,6 +27,7 @@
import com.android.internal.widget.remotecompose.core.PaintOperation;
import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
import java.util.List;
@@ -161,4 +162,33 @@
break;
}
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer
+ .addType(CLASS_NAME)
+ .add("id", mId)
+ .add("textId", mTextId)
+ .add("measureType", typeToString());
+ }
+
+ private String typeToString() {
+ int val = mType & 255;
+ switch (val) {
+ case MEASURE_WIDTH:
+ return "MEASURE_WIDTH";
+ case MEASURE_HEIGHT:
+ return "MEASURE_HEIGHT";
+ case MEASURE_LEFT:
+ return "MEASURE_LEFT";
+ case MEASURE_TOP:
+ return "MEASURE_TOP";
+ case MEASURE_RIGHT:
+ return "MEASURE_RIGHT";
+ case MEASURE_BOTTOM:
+ return "MEASURE_BOTTOM";
+ default:
+ return "INVALID_TYPE";
+ }
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java
index 262916d..1239b56 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java
@@ -132,7 +132,7 @@
@Override
public void serialize(MapSerializer serializer) {
serializer
- .add("type", CLASS_NAME)
+ .addType(CLASS_NAME)
.add("id", mTextId)
.add("leftId", mSrcId1)
.add("rightId", mSrcId2);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TimeAttribute.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TimeAttribute.java
index afb84b5..fd9a2bf 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TimeAttribute.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TimeAttribute.java
@@ -27,12 +27,14 @@
import com.android.internal.widget.remotecompose.core.RemoteContext;
import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
import com.android.internal.widget.remotecompose.core.types.LongConstant;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
/** Operation to perform time related calculation */
@@ -292,4 +294,48 @@
break;
}
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer
+ .addType(CLASS_NAME)
+ .add("id", mId)
+ .add("timeId", mTimeId)
+ .addType(getTypeString())
+ .add("args", Collections.singletonList(mArgs));
+ }
+
+ private String getTypeString() {
+ int val = mType & 255;
+ switch (val) {
+ case TIME_FROM_NOW_SEC:
+ return "TIME_FROM_NOW_SEC";
+ case TIME_FROM_NOW_MIN:
+ return "TIME_FROM_NOW_MIN";
+ case TIME_FROM_NOW_HR:
+ return "TIME_FROM_NOW_HR";
+ case TIME_FROM_ARG_SEC:
+ return "TIME_FROM_ARG_SEC";
+ case TIME_FROM_ARG_MIN:
+ return "TIME_FROM_ARG_MIN";
+ case TIME_FROM_ARG_HR:
+ return "TIME_FROM_ARG_HR";
+ case TIME_IN_SEC:
+ return "TIME_IN_SEC";
+ case TIME_IN_MIN:
+ return "TIME_IN_MIN";
+ case TIME_IN_HR:
+ return "TIME_IN_HR";
+ case TIME_DAY_OF_MONTH:
+ return "TIME_DAY_OF_MONTH";
+ case TIME_MONTH_VALUE:
+ return "TIME_MONTH_VALUE";
+ case TIME_DAY_OF_WEEK:
+ return "TIME_DAY_OF_WEEK";
+ case TIME_YEAR:
+ return "TIME_YEAR";
+ default:
+ return "INVALID_TIME_TYPE";
+ }
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java
index 2591a4c..f246729 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java
@@ -716,9 +716,9 @@
@Override
public void serialize(MapSerializer serializer) {
serializer
- .add("type", CLASS_NAME)
+ .addType(CLASS_NAME)
.add("id", mId)
- .add("mDefValue", mDefValue, mOutDefValue)
+ .add("defValue", mDefValue, mOutDefValue)
.add("min", mMin, mOutMin)
.add("max", mMax, mOutMax)
.add("mode", mMode)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/AnimatableValue.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/AnimatableValue.java
index 9dc2a49..b98a017 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/AnimatableValue.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/AnimatableValue.java
@@ -129,6 +129,6 @@
@Override
public void serialize(MapSerializer serializer) {
- serializer.add("type", "AnimatableValue").add("id", mId);
+ serializer.addType("AnimatableValue").add("id", mId);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasOperations.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasOperations.java
index 3e7f1d3..25a10ab 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasOperations.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasOperations.java
@@ -156,7 +156,7 @@
@Override
public void serialize(MapSerializer serializer) {
- serializer.add("type", CLASS_NAME).add("list", mList);
+ serializer.addType(CLASS_NAME).add("list", mList);
}
/**
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierOperation.java
index 8b13c13..7ee1490 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierOperation.java
@@ -253,6 +253,6 @@
@Override
public void serialize(MapSerializer serializer) {
- serializer.addTags(SerializeTags.MODIFIER).add("type", "ClickModifierOperation");
+ serializer.addTags(SerializeTags.MODIFIER).addType("ClickModifierOperation");
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java
index c736436..b30dade 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java
@@ -57,8 +57,8 @@
protected float mHeight;
@Nullable protected Component mParent;
protected int mAnimationId = -1;
- @NonNull public Visibility mVisibility = Visibility.VISIBLE;
- @NonNull public Visibility mScheduledVisibility = Visibility.VISIBLE;
+ public int mVisibility = Visibility.VISIBLE;
+ public int mScheduledVisibility = Visibility.VISIBLE;
@NonNull public ArrayList<Operation> mList = new ArrayList<>();
public PaintOperation mPreTranslate; // todo, can we initialize this here and make it NonNull?
public boolean mNeedsMeasure = true;
@@ -288,22 +288,42 @@
}
/**
- * Returns the intrinsic width of the layout
+ * Returns the min intrinsic width of the layout
*
* @param context
* @return the width in pixels
*/
- public float intrinsicWidth(@Nullable RemoteContext context) {
+ public float minIntrinsicWidth(@Nullable RemoteContext context) {
return getWidth();
}
/**
- * Returns the intrinsic height of the layout
+ * Returns the max intrinsic width of the layout
+ *
+ * @param context
+ * @return the width in pixels
+ */
+ public float maxIntrinsicWidth(@Nullable RemoteContext context) {
+ return getWidth();
+ }
+
+ /**
+ * Returns the min intrinsic height of the layout
*
* @param context
* @return the height in pixels
*/
- public float intrinsicHeight(@Nullable RemoteContext context) {
+ public float minIntrinsicHeight(@Nullable RemoteContext context) {
+ return getHeight();
+ }
+
+ /**
+ * Returns the max intrinsic height of the layout
+ *
+ * @param context
+ * @return the height in pixels
+ */
+ public float maxIntrinsicHeight(@Nullable RemoteContext context) {
return getHeight();
}
@@ -338,10 +358,119 @@
// Nothing here
}
- public enum Visibility {
- GONE,
- VISIBLE,
- INVISIBLE
+ public static class Visibility {
+
+ public static final int GONE = 0;
+ public static final int VISIBLE = 1;
+ public static final int INVISIBLE = 2;
+ public static final int OVERRIDE_GONE = 16;
+ public static final int OVERRIDE_VISIBLE = 32;
+ public static final int OVERRIDE_INVISIBLE = 64;
+ public static final int CLEAR_OVERRIDE = 128;
+
+ /**
+ * Returns a string representation of the field
+ *
+ * @param value
+ * @return
+ */
+ public static String toString(int value) {
+ switch (value) {
+ case GONE:
+ return "GONE";
+ case VISIBLE:
+ return "VISIBLE";
+ case INVISIBLE:
+ return "INVISIBLE";
+ }
+ if ((value >> 4) > 0) {
+ if ((value & OVERRIDE_GONE) == OVERRIDE_GONE) {
+ return "OVERRIDE_GONE";
+ }
+ if ((value & OVERRIDE_VISIBLE) == OVERRIDE_VISIBLE) {
+ return "OVERRIDE_VISIBLE";
+ }
+ if ((value & OVERRIDE_INVISIBLE) == OVERRIDE_INVISIBLE) {
+ return "OVERRIDE_INVISIBLE";
+ }
+ }
+ return "" + value;
+ }
+
+ /**
+ * Returns true if gone
+ *
+ * @param value
+ * @return
+ */
+ public static boolean isGone(int value) {
+ if ((value >> 4) > 0) {
+ return (value & OVERRIDE_GONE) == OVERRIDE_GONE;
+ }
+ return value == GONE;
+ }
+
+ /**
+ * Returns true if visible
+ *
+ * @param value
+ * @return
+ */
+ public static boolean isVisible(int value) {
+ if ((value >> 4) > 0) {
+ return (value & OVERRIDE_VISIBLE) == OVERRIDE_VISIBLE;
+ }
+ return value == VISIBLE;
+ }
+
+ /**
+ * Returns true if invisible
+ *
+ * @param value
+ * @return
+ */
+ public static boolean isInvisible(int value) {
+ if ((value >> 4) > 0) {
+ return (value & OVERRIDE_INVISIBLE) == OVERRIDE_INVISIBLE;
+ }
+ return value == INVISIBLE;
+ }
+
+ /**
+ * Returns true if the field has an override
+ *
+ * @param value
+ * @return
+ */
+ public static boolean hasOverride(int value) {
+ return (value >> 4) > 0;
+ }
+
+ /**
+ * Clear the override values
+ *
+ * @param value
+ * @return
+ */
+ public static int clearOverride(int value) {
+ return value & 15;
+ }
+
+ /**
+ * Add an override value
+ *
+ * @param value
+ * @param visibility
+ * @return
+ */
+ public static int add(int value, int visibility) {
+ int v = value & 15;
+ v += visibility;
+ if ((v & CLEAR_OVERRIDE) == CLEAR_OVERRIDE) {
+ v = v & 15;
+ }
+ return v;
+ }
}
/**
@@ -350,13 +479,28 @@
* @return
*/
public boolean isVisible() {
- if (mVisibility != Visibility.VISIBLE || mParent == null) {
- return mVisibility == Visibility.VISIBLE;
+ if (mParent == null || !Visibility.isVisible(mVisibility)) {
+ return Visibility.isVisible(mVisibility);
}
- if (mParent != null) { // TODO: this is always true -- bbade@
- return mParent.isVisible();
- }
- return true;
+ return mParent.isVisible();
+ }
+
+ /**
+ * Returns true if the component is gone
+ *
+ * @return
+ */
+ public boolean isGone() {
+ return Visibility.isGone(mVisibility);
+ }
+
+ /**
+ * Returns true if the component is invisible
+ *
+ * @return
+ */
+ public boolean isInvisible() {
+ return Visibility.isInvisible(mVisibility);
}
/**
@@ -364,7 +508,7 @@
*
* @param visibility can be VISIBLE, INVISIBLE or GONE
*/
- public void setVisibility(@NonNull Visibility visibility) {
+ public void setVisibility(int visibility) {
if (visibility != mVisibility || visibility != mScheduledVisibility) {
mScheduledVisibility = visibility;
invalidateMeasure();
@@ -705,7 +849,7 @@
+ "] "
+ textContent()
+ " Visibility ("
- + mVisibility
+ + Visibility.toString(mVisibility)
+ ") ";
}
@@ -732,7 +876,7 @@
+ ", "
+ mHeight
+ "] "
- + mVisibility;
+ + Visibility.toString(mVisibility);
// + " [" + mNeedsMeasure + ", " + mNeedsRepaint + "]"
serializer.append(indent, content);
}
@@ -966,7 +1110,7 @@
if (applyAnimationAsNeeded(context)) {
return;
}
- if (mVisibility == Visibility.GONE || mVisibility == Visibility.INVISIBLE) {
+ if (isGone() || isInvisible()) {
return;
}
paintingComponent(context);
@@ -1071,13 +1215,13 @@
@Override
public void serialize(MapSerializer serializer) {
serializer.addTags(SerializeTags.COMPONENT);
- serializer.add("type", getSerializedName());
+ serializer.addType(getSerializedName());
serializer.add("id", mComponentId);
serializer.add("x", mX);
serializer.add("y", mY);
serializer.add("width", mWidth);
serializer.add("height", mHeight);
- serializer.add("visibility", mVisibility);
+ serializer.add("visibility", Visibility.toString(mVisibility));
serializer.add("list", mList);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseOperation.java
index e277d49..0e629c5 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseOperation.java
@@ -27,6 +27,7 @@
import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
import com.android.internal.widget.remotecompose.core.operations.Utils;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
import java.util.ArrayList;
import java.util.List;
@@ -228,4 +229,12 @@
public void setProcess(ImpulseProcess impulseProcess) {
mProcess = impulseProcess;
}
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ serializer
+ .addType(CLASS_NAME)
+ .add("duration", mDuration, mOutDuration)
+ .add("startAt", mStartAt, mOutStartAt);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseProcess.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseProcess.java
index 8c9dd76..83d4d38 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseProcess.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseProcess.java
@@ -157,6 +157,6 @@
@Override
public void serialize(MapSerializer serializer) {
- serializer.add("type", CLASS_NAME).add("list", mList);
+ serializer.addType(CLASS_NAME).add("list", mList);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java
index 2b63cf2..dda328f 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java
@@ -206,12 +206,12 @@
@Override
public void serialize(MapSerializer serializer) {
serializer
- .add("type", CLASS_NAME)
+ .addType(CLASS_NAME)
.add("indexVariableId", mIndexVariableId)
.add("until", mUntil, mUntilOut)
.add("from", mFrom, mFromOut)
.add("step", mStep, mStepOut)
- .add("mUntilOut", mUntilOut)
+ .add("untilOut", mUntilOut)
.add("list", mList);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java
index f2503b2..77d3dae 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java
@@ -77,7 +77,7 @@
+ " x "
+ mHeight
+ ") "
- + mVisibility;
+ + Visibility.toString(mVisibility);
}
@Override
@@ -97,7 +97,7 @@
+ ", "
+ mHeight
+ "] "
- + mVisibility);
+ + Visibility.toString(mVisibility));
}
/**
@@ -282,6 +282,6 @@
public void serialize(MapSerializer serializer) {
super.serialize(serializer);
serializer.addTags(SerializeTags.COMPONENT);
- serializer.add("type", "RootLayoutComponent");
+ serializer.addType("RootLayoutComponent");
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchCancelModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchCancelModifierOperation.java
index 62b1b6c..283bc7a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchCancelModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchCancelModifierOperation.java
@@ -129,6 +129,6 @@
@Override
public void serialize(MapSerializer serializer) {
super.serialize(serializer);
- serializer.add("type", "TouchCancelModifierOperation");
+ serializer.addType("TouchCancelModifierOperation");
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchDownModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchDownModifierOperation.java
index 5289fda..b010c14 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchDownModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchDownModifierOperation.java
@@ -131,6 +131,6 @@
@Override
public void serialize(MapSerializer serializer) {
super.serialize(serializer);
- serializer.add("type", "TouchDownModifierOperation");
+ serializer.addType("TouchDownModifierOperation");
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchUpModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchUpModifierOperation.java
index 576c5e9..bc5c10b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchUpModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchUpModifierOperation.java
@@ -129,6 +129,6 @@
@Override
public void serialize(MapSerializer serializer) {
super.serialize(serializer);
- serializer.add("type", "TouchUpModifierOperation");
+ serializer.addType("TouchUpModifierOperation");
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java
index e5cd485..1a60451d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java
@@ -143,7 +143,7 @@
*/
public void paint(@NonNull PaintContext context) {
if (mOriginal.getVisibility() != mTarget.getVisibility()) {
- if (mTarget.getVisibility() == Component.Visibility.GONE) {
+ if (mTarget.isGone()) {
switch (mExitAnimation) {
case PARTICLE:
// particleAnimation(context, component, original, target, vp)
@@ -229,8 +229,7 @@
mParticleAnimation.animate(context, mComponent, mOriginal, mTarget, mVp);
break;
}
- } else if (mOriginal.getVisibility() == Component.Visibility.GONE
- && mTarget.getVisibility() == Component.Visibility.VISIBLE) {
+ } else if (mOriginal.isGone() && mTarget.isVisible()) {
switch (mEnterAnimation) {
case ROTATE:
float px = mTarget.getX() + mTarget.getW() / 2f;
@@ -323,7 +322,7 @@
} else {
mComponent.paintingComponent(context);
}
- } else if (mTarget.getVisibility() == Component.Visibility.VISIBLE) {
+ } else if (mTarget.isVisible()) {
mComponent.paintingComponent(context);
}
@@ -360,7 +359,7 @@
public float getVisibility() {
if (mOriginal.getVisibility() == mTarget.getVisibility()) {
return 1f;
- } else if (mTarget.getVisibility() == Component.Visibility.VISIBLE) {
+ } else if (mTarget.isVisible()) {
return mVp;
} else {
return 1 - mVp;
@@ -382,7 +381,7 @@
float targetY = mTarget.getY();
float targetW = mTarget.getW();
float targetH = mTarget.getH();
- Component.Visibility targetVisibility = mTarget.getVisibility();
+ int targetVisibility = mTarget.getVisibility();
if (targetX != measure.getX()
|| targetY != measure.getY()
|| targetW != measure.getW()
@@ -393,7 +392,13 @@
mTarget.setW(measure.getW());
mTarget.setH(measure.getH());
mTarget.setVisibility(measure.getVisibility());
- mStartTime = currentTime;
+ // We shouldn't reset the leftover animation time here
+ // 1/ if we are eg fading out a component, and an updateTarget comes on, we don't want
+ // to restart the full animation time
+ // 2/ if no visibility change but quick updates come in (eg live resize) it seems
+ // better as well to not restart the animation time and only allows the original
+ // time to wrap up
+ // mStartTime = currentTime;
}
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java
index 91348f5..c87bbdc 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java
@@ -129,7 +129,7 @@
@Override
public void serialize(MapSerializer serializer) {
serializer
- .add("type", "AnimationSpec")
+ .addType("AnimationSpec")
.add("motionDuration", getMotionDuration())
.add("motionEasingType", Easing.getString(getMotionEasingType()))
.add("visibilityDuration", getVisibilityDuration())
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java
index 35d639e..6ee18bb 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java
@@ -115,8 +115,10 @@
for (Component c : mChildrenComponents) {
c.measure(context, 0f, maxWidth, 0f, maxHeight, measure);
ComponentMeasure m = measure.get(c);
- size.setWidth(Math.max(size.getWidth(), m.getW()));
- size.setHeight(Math.max(size.getHeight(), m.getH()));
+ if (!m.isGone()) {
+ size.setWidth(Math.max(size.getWidth(), m.getW()));
+ size.setHeight(Math.max(size.getHeight(), m.getH()));
+ }
}
// add padding
size.setWidth(Math.max(size.getWidth(), computeModifierDefinedWidth(context.getContext())));
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java
index 508b685..f9111df 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java
@@ -154,7 +154,7 @@
@Override
public void serialize(MapSerializer serializer) {
super.serialize(serializer);
- serializer.add("type", getSerializedName());
+ serializer.addType(getSerializedName());
serializer.add("horizontalPositioning", mHorizontalPositioning);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleColumnLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleColumnLayout.java
index afc41b1..b008952 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleColumnLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleColumnLayout.java
@@ -21,6 +21,7 @@
import com.android.internal.widget.remotecompose.core.Operation;
import com.android.internal.widget.remotecompose.core.Operations;
import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.operations.layout.Component;
import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
@@ -134,6 +135,24 @@
}
@Override
+ public float minIntrinsicHeight(@NonNull RemoteContext context) {
+ float height = computeModifierDefinedHeight(context);
+ if (!mChildrenComponents.isEmpty()) {
+ height += mChildrenComponents.get(0).minIntrinsicHeight(context);
+ }
+ return height;
+ }
+
+ @Override
+ public float minIntrinsicWidth(@NonNull RemoteContext context) {
+ float width = computeModifierDefinedWidth(context);
+ if (!mChildrenComponents.isEmpty()) {
+ width += mChildrenComponents.get(0).minIntrinsicWidth(context);
+ }
+ return width;
+ }
+
+ @Override
protected boolean hasVerticalIntrinsicDimension() {
return true;
}
@@ -147,29 +166,54 @@
boolean verticalWrap,
@NonNull MeasurePass measure,
@NonNull Size size) {
- super.computeWrapSize(
- context, maxWidth, Float.MAX_VALUE, horizontalWrap, verticalWrap, measure, size);
- }
-
- @Override
- public boolean applyVisibility(
- float selfWidth, float selfHeight, @NonNull MeasurePass measure) {
- float childrenWidth = 0f;
- float childrenHeight = 0f;
- boolean changedVisibility = false;
- for (Component child : mChildrenComponents) {
- ComponentMeasure childMeasure = measure.get(child);
- if (childMeasure.getVisibility() == Visibility.GONE) {
- continue;
- }
- if (childrenHeight + childMeasure.getH() > selfHeight) {
- childMeasure.setVisibility(Visibility.GONE);
- changedVisibility = true;
+ int visibleChildren = 0;
+ ComponentMeasure self = measure.get(this);
+ self.addVisibilityOverride(Visibility.OVERRIDE_VISIBLE);
+ float currentMaxHeight = maxHeight;
+ for (Component c : mChildrenComponents) {
+ if (c instanceof CollapsibleColumnLayout) {
+ c.measure(context, 0f, maxWidth, 0f, currentMaxHeight, measure);
} else {
- childrenHeight += childMeasure.getH();
- childrenWidth = Math.max(childrenWidth, childMeasure.getW());
+ c.measure(context, 0f, maxWidth, 0f, Float.MAX_VALUE, measure);
+ }
+ ComponentMeasure m = measure.get(c);
+ if (!m.isGone()) {
+ size.setWidth(Math.max(size.getWidth(), m.getW()));
+ size.setHeight(size.getHeight() + m.getH());
+ visibleChildren++;
+ currentMaxHeight -= m.getH();
}
}
- return changedVisibility;
+ if (!mChildrenComponents.isEmpty()) {
+ size.setHeight(size.getHeight() + (mSpacedBy * (visibleChildren - 1)));
+ }
+
+ float childrenWidth = 0f;
+ float childrenHeight = 0f;
+
+ boolean overflow = false;
+ for (Component child : mChildrenComponents) {
+ ComponentMeasure childMeasure = measure.get(child);
+ if (overflow || childMeasure.isGone()) {
+ childMeasure.addVisibilityOverride(Visibility.OVERRIDE_GONE);
+ continue;
+ }
+ float childHeight = childMeasure.getH();
+ boolean childDoesNotFits = childrenHeight + childHeight > maxHeight;
+ if (childDoesNotFits) {
+ childMeasure.addVisibilityOverride(Visibility.OVERRIDE_GONE);
+ overflow = true;
+ } else {
+ childrenHeight += childHeight;
+ childrenWidth = Math.max(childrenWidth, childMeasure.getW());
+ visibleChildren++;
+ }
+ }
+ if (verticalWrap) {
+ size.setHeight(Math.min(maxHeight, childrenHeight));
+ }
+ if (visibleChildren == 0 || size.getHeight() <= 0f) {
+ self.addVisibilityOverride(Visibility.OVERRIDE_GONE);
+ }
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleRowLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleRowLayout.java
index 0e7eb86..05f3329 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleRowLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleRowLayout.java
@@ -21,6 +21,7 @@
import com.android.internal.widget.remotecompose.core.Operation;
import com.android.internal.widget.remotecompose.core.Operations;
import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
import com.android.internal.widget.remotecompose.core.WireBuffer;
import com.android.internal.widget.remotecompose.core.operations.layout.Component;
import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
@@ -139,6 +140,24 @@
}
@Override
+ public float minIntrinsicWidth(@NonNull RemoteContext context) {
+ float width = computeModifierDefinedWidth(context);
+ if (!mChildrenComponents.isEmpty()) {
+ width += mChildrenComponents.get(0).minIntrinsicWidth(context);
+ }
+ return width;
+ }
+
+ @Override
+ public float minIntrinsicHeight(@NonNull RemoteContext context) {
+ float height = computeModifierDefinedHeight(context);
+ if (!mChildrenComponents.isEmpty()) {
+ height += mChildrenComponents.get(0).minIntrinsicHeight(context);
+ }
+ return height;
+ }
+
+ @Override
public void computeWrapSize(
@NonNull PaintContext context,
float maxWidth,
@@ -157,19 +176,35 @@
float childrenWidth = 0f;
float childrenHeight = 0f;
boolean changedVisibility = false;
+ int visibleChildren = 0;
+ ComponentMeasure self = measure.get(this);
+ self.clearVisibilityOverride();
+ if (selfWidth <= 0 || selfHeight <= 0) {
+ self.addVisibilityOverride(Visibility.OVERRIDE_GONE);
+ return true;
+ }
for (Component child : mChildrenComponents) {
ComponentMeasure childMeasure = measure.get(child);
- if (childMeasure.getVisibility() == Visibility.GONE) {
+ int visibility = childMeasure.getVisibility();
+ childMeasure.clearVisibilityOverride();
+ if (!childMeasure.isVisible()) {
continue;
}
- if (childrenWidth + childMeasure.getW() > selfWidth) {
- childMeasure.setVisibility(Visibility.GONE);
- changedVisibility = true;
+ if (childrenWidth + childMeasure.getW() > selfWidth
+ && childrenHeight + childMeasure.getH() > selfHeight) {
+ childMeasure.addVisibilityOverride(Visibility.OVERRIDE_GONE);
+ if (visibility != childMeasure.getVisibility()) {
+ changedVisibility = true;
+ }
} else {
childrenWidth += childMeasure.getW();
childrenHeight = Math.max(childrenHeight, childMeasure.getH());
+ visibleChildren++;
}
}
+ if (visibleChildren == 0) {
+ self.addVisibilityOverride(Visibility.OVERRIDE_GONE);
+ }
return changedVisibility;
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java
index 47a55b6..cda90c2 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java
@@ -138,7 +138,7 @@
for (Component c : mChildrenComponents) {
c.measure(context, 0f, maxWidth, 0f, currentMaxHeight, measure);
ComponentMeasure m = measure.get(c);
- if (m.getVisibility() != Visibility.GONE) {
+ if (!m.isGone()) {
size.setWidth(Math.max(size.getWidth(), m.getW()));
size.setHeight(size.getHeight() + m.getH());
visibleChildrens++;
@@ -164,7 +164,7 @@
for (Component child : mChildrenComponents) {
child.measure(context, minWidth, maxWidth, minHeight, mh, measure);
ComponentMeasure m = measure.get(child);
- if (m.getVisibility() != Visibility.GONE) {
+ if (!m.isGone()) {
mh -= m.getH();
}
}
@@ -172,11 +172,11 @@
}
@Override
- public float intrinsicHeight(@NonNull RemoteContext context) {
+ public float minIntrinsicHeight(@NonNull RemoteContext context) {
float height = computeModifierDefinedHeight(context);
float componentHeights = 0f;
for (Component c : mChildrenComponents) {
- componentHeights += c.intrinsicHeight(context);
+ componentHeights += c.minIntrinsicHeight(context);
}
return Math.max(height, componentHeights);
}
@@ -225,7 +225,7 @@
float totalWeights = 0f;
for (Component child : mChildrenComponents) {
ComponentMeasure childMeasure = measure.get(child);
- if (childMeasure.getVisibility() == Visibility.GONE) {
+ if (childMeasure.isGone()) {
continue;
}
if (child instanceof LayoutComponent
@@ -242,7 +242,7 @@
if (child instanceof LayoutComponent
&& ((LayoutComponent) child).getHeightModifier().hasWeight()) {
ComponentMeasure childMeasure = measure.get(child);
- if (childMeasure.getVisibility() == Visibility.GONE) {
+ if (childMeasure.isGone()) {
continue;
}
float weight = ((LayoutComponent) child).getHeightModifier().getValue();
@@ -280,7 +280,7 @@
int visibleChildrens = 0;
for (Component child : mChildrenComponents) {
ComponentMeasure childMeasure = measure.get(child);
- if (childMeasure.getVisibility() == Visibility.GONE) {
+ if (childMeasure.isGone()) {
continue;
}
childrenWidth = Math.max(childrenWidth, childMeasure.getW());
@@ -307,17 +307,22 @@
case SPACE_BETWEEN:
for (Component child : mChildrenComponents) {
ComponentMeasure childMeasure = measure.get(child);
- if (childMeasure.getVisibility() == Visibility.GONE) {
+ if (childMeasure.isGone()) {
continue;
}
total += childMeasure.getH();
}
- verticalGap = (selfHeight - total) / (visibleChildrens - 1);
+ if (visibleChildrens > 1) {
+ verticalGap = (selfHeight - total) / (visibleChildrens - 1);
+ } else {
+ // we center the element
+ ty = (selfHeight - childrenHeight) / 2f;
+ }
break;
case SPACE_EVENLY:
for (Component child : mChildrenComponents) {
ComponentMeasure childMeasure = measure.get(child);
- if (childMeasure.getVisibility() == Visibility.GONE) {
+ if (childMeasure.isGone()) {
continue;
}
total += childMeasure.getH();
@@ -328,7 +333,7 @@
case SPACE_AROUND:
for (Component child : mChildrenComponents) {
ComponentMeasure childMeasure = measure.get(child);
- if (childMeasure.getVisibility() == Visibility.GONE) {
+ if (childMeasure.isGone()) {
continue;
}
total += childMeasure.getH();
@@ -353,7 +358,7 @@
}
childMeasure.setX(tx);
childMeasure.setY(ty);
- if (childMeasure.getVisibility() == Visibility.GONE) {
+ if (childMeasure.isGone()) {
continue;
}
ty += childMeasure.getH();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/FitBoxLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/FitBoxLayout.java
new file mode 100644
index 0000000..ff7a3af
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/FitBoxLayout.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2023 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.internal.widget.remotecompose.core.operations.layout.managers;
+
+import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponent;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightInModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthInModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthModifierOperation;
+import com.android.internal.widget.remotecompose.core.serialize.MapSerializer;
+
+import java.util.List;
+
+/** FitBox layout implementation -- only display the child that fits in the available space */
+public class FitBoxLayout extends LayoutManager {
+
+ public static final int START = 1;
+ public static final int CENTER = 2;
+ public static final int END = 3;
+ public static final int TOP = 4;
+ public static final int BOTTOM = 5;
+
+ int mHorizontalPositioning;
+ int mVerticalPositioning;
+
+ public FitBoxLayout(
+ @Nullable Component parent,
+ int componentId,
+ int animationId,
+ float x,
+ float y,
+ float width,
+ float height,
+ int horizontalPositioning,
+ int verticalPositioning) {
+ super(parent, componentId, animationId, x, y, width, height);
+ mHorizontalPositioning = horizontalPositioning;
+ mVerticalPositioning = verticalPositioning;
+ }
+
+ public FitBoxLayout(
+ @Nullable Component parent,
+ int componentId,
+ int animationId,
+ int horizontalPositioning,
+ int verticalPositioning) {
+ this(
+ parent,
+ componentId,
+ animationId,
+ 0,
+ 0,
+ 0,
+ 0,
+ horizontalPositioning,
+ verticalPositioning);
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "BOX ["
+ + mComponentId
+ + ":"
+ + mAnimationId
+ + "] ("
+ + mX
+ + ", "
+ + mY
+ + " - "
+ + mWidth
+ + " x "
+ + mHeight
+ + ") "
+ + mVisibility;
+ }
+
+ @NonNull
+ @Override
+ protected String getSerializedName() {
+ return "FITBOX";
+ }
+
+ @Override
+ public void computeWrapSize(
+ @NonNull PaintContext context,
+ float maxWidth,
+ float maxHeight,
+ boolean horizontalWrap,
+ boolean verticalWrap,
+ @NonNull MeasurePass measure,
+ @NonNull Size size) {
+
+ boolean found = false;
+ ComponentMeasure self = measure.get(this);
+ for (Component c : mChildrenComponents) {
+ float cw = 0f; // c.intrinsicWidth(context.getContext());
+ float ch = 0f; // c.intrinsicHeight(context.getContext());
+ if (c instanceof LayoutComponent) {
+ LayoutComponent lc = (LayoutComponent) c;
+ WidthModifierOperation widthModifier = lc.getWidthModifier();
+ if (widthModifier != null) {
+ WidthInModifierOperation widthIn = lc.getWidthModifier().getWidthIn();
+ if (widthIn != null) {
+ cw = widthIn.getMin();
+ }
+ }
+ HeightModifierOperation heightModifier = lc.getHeightModifier();
+ if (heightModifier != null) {
+ HeightInModifierOperation heightIn = lc.getHeightModifier().getHeightIn();
+ if (heightIn != null) {
+ ch = heightIn.getMin();
+ }
+ }
+ }
+ c.measure(context, 0f, maxWidth, 0f, maxHeight, measure);
+ ComponentMeasure m = measure.get(c);
+ if (!found && cw <= maxWidth && ch <= maxHeight) {
+ found = true;
+ m.addVisibilityOverride(Visibility.OVERRIDE_VISIBLE);
+ size.setWidth(m.getW());
+ size.setHeight(m.getH());
+ } else {
+ m.addVisibilityOverride(Visibility.OVERRIDE_GONE);
+ }
+ }
+ if (!found) {
+ self.setVisibility(Visibility.GONE);
+ } else {
+ self.setVisibility(Visibility.VISIBLE);
+ }
+
+ // add padding
+ size.setWidth(Math.max(size.getWidth(), computeModifierDefinedWidth(context.getContext())));
+ size.setHeight(
+ Math.max(size.getHeight(), computeModifierDefinedHeight(context.getContext())));
+ }
+
+ @Override
+ public void computeSize(
+ @NonNull PaintContext context,
+ float minWidth,
+ float maxWidth,
+ float minHeight,
+ float maxHeight,
+ @NonNull MeasurePass measure) {
+
+ ComponentMeasure self = measure.get(this);
+ boolean found = false;
+ for (Component c : mChildrenComponents) {
+ float cw = 0f;
+ float ch = 0f;
+ if (c instanceof LayoutComponent) {
+ LayoutComponent lc = (LayoutComponent) c;
+ WidthModifierOperation widthModifier = lc.getWidthModifier();
+ if (widthModifier != null) {
+ WidthInModifierOperation widthIn = lc.getWidthModifier().getWidthIn();
+ if (widthIn != null) {
+ cw = widthIn.getMin();
+ }
+ }
+ HeightModifierOperation heightModifier = lc.getHeightModifier();
+ if (heightModifier != null) {
+ HeightInModifierOperation heightIn = lc.getHeightModifier().getHeightIn();
+ if (heightIn != null) {
+ ch = heightIn.getMin();
+ }
+ }
+ }
+ c.measure(context, minWidth, maxWidth, minHeight, maxHeight, measure);
+ // child.measure(context, minWidth, Float.MAX_VALUE, minHeight,
+ // Float.MAX_VALUE, measure);
+ // m.getVisibility().clearOverride();
+ ComponentMeasure m = measure.get(c);
+ // m.setVisibility(Visibility.GONE);
+ // m.getVisibility().add(Visibility.OVERRIDE_GONE);
+ // m.getVisibility().add(Visibility.OVERRIDE_GONE);
+ m.clearVisibilityOverride();
+ if (!found && cw <= maxWidth && ch <= maxHeight) {
+ found = true;
+ m.addVisibilityOverride(Visibility.OVERRIDE_VISIBLE);
+ } else {
+ m.addVisibilityOverride(Visibility.OVERRIDE_GONE);
+ }
+ }
+ }
+
+ @Override
+ public void internalLayoutMeasure(@NonNull PaintContext context, @NonNull MeasurePass measure) {
+ ComponentMeasure selfMeasure = measure.get(this);
+ float selfWidth = selfMeasure.getW() - mPaddingLeft - mPaddingRight;
+ float selfHeight = selfMeasure.getH() - mPaddingTop - mPaddingBottom;
+ applyVisibility(selfWidth, selfHeight, measure);
+ for (Component child : mChildrenComponents) {
+ ComponentMeasure m = measure.get(child);
+ float tx = 0f;
+ float ty = 0f;
+ switch (mVerticalPositioning) {
+ case TOP:
+ ty = 0f;
+ break;
+ case CENTER:
+ ty = (selfHeight - m.getH()) / 2f;
+ break;
+ case BOTTOM:
+ ty = selfHeight - m.getH();
+ break;
+ }
+ switch (mHorizontalPositioning) {
+ case START:
+ tx = 0f;
+ break;
+ case CENTER:
+ tx = (selfWidth - m.getW()) / 2f;
+ break;
+ case END:
+ tx = selfWidth - m.getW();
+ break;
+ }
+ m.setX(tx);
+ m.setY(ty);
+ }
+ }
+
+ /**
+ * The name of the class
+ *
+ * @return the name
+ */
+ @NonNull
+ public static String name() {
+ return "BoxLayout";
+ }
+
+ /**
+ * The OP_CODE for this command
+ *
+ * @return the opcode
+ */
+ public static int id() {
+ return Operations.LAYOUT_FIT_BOX;
+ }
+
+ /**
+ * Write the operation to the buffer
+ *
+ * @param buffer a WireBuffer
+ * @param componentId the component id
+ * @param animationId the component animation id
+ * @param horizontalPositioning the horizontal positioning rules
+ * @param verticalPositioning the vertical positioning rules
+ */
+ public static void apply(
+ @NonNull WireBuffer buffer,
+ int componentId,
+ int animationId,
+ int horizontalPositioning,
+ int verticalPositioning) {
+ buffer.start(id());
+ buffer.writeInt(componentId);
+ buffer.writeInt(animationId);
+ buffer.writeInt(horizontalPositioning);
+ buffer.writeInt(verticalPositioning);
+ }
+
+ /**
+ * Read this operation and add it to the list of operations
+ *
+ * @param buffer the buffer to read
+ * @param operations the list of operations that will be added to
+ */
+ public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
+ int componentId = buffer.readInt();
+ int animationId = buffer.readInt();
+ int horizontalPositioning = buffer.readInt();
+ int verticalPositioning = buffer.readInt();
+ operations.add(
+ new FitBoxLayout(
+ null,
+ componentId,
+ animationId,
+ horizontalPositioning,
+ verticalPositioning));
+ }
+
+ /**
+ * Populate the documentation with a description of this operation
+ *
+ * @param doc to append the description to.
+ */
+ public static void documentation(@NonNull DocumentationBuilder doc) {
+ doc.operation("Layout Operations", id(), name())
+ .description(
+ "FitBox layout implementation.\n\n"
+ + "Only display the first child component that fits in the available"
+ + " space")
+ .examplesDimension(150, 100)
+ .exampleImage("Top", "layout-BoxLayout-start-top.png")
+ .exampleImage("Center", "layout-BoxLayout-center-center.png")
+ .exampleImage("Bottom", "layout-BoxLayout-end-bottom.png")
+ .field(INT, "COMPONENT_ID", "unique id for this component")
+ .field(
+ INT,
+ "ANIMATION_ID",
+ "id used to match components," + " for animation purposes")
+ .field(INT, "HORIZONTAL_POSITIONING", "horizontal positioning value")
+ .possibleValues("START", FitBoxLayout.START)
+ .possibleValues("CENTER", FitBoxLayout.CENTER)
+ .possibleValues("END", FitBoxLayout.END)
+ .field(INT, "VERTICAL_POSITIONING", "vertical positioning value")
+ .possibleValues("TOP", FitBoxLayout.TOP)
+ .possibleValues("CENTER", FitBoxLayout.CENTER)
+ .possibleValues("BOTTOM", FitBoxLayout.BOTTOM);
+ }
+
+ @Override
+ public void write(@NonNull WireBuffer buffer) {
+ apply(buffer, mComponentId, mAnimationId, mHorizontalPositioning, mVerticalPositioning);
+ }
+
+ @Override
+ public void serialize(MapSerializer serializer) {
+ super.serialize(serializer);
+ serializer.add("verticalPositioning", getPositioningString(mVerticalPositioning));
+ serializer.add("horizontalPositioning", getPositioningString(mHorizontalPositioning));
+ }
+
+ private String getPositioningString(int pos) {
+ switch (pos) {
+ case START:
+ return "START";
+ case CENTER:
+ return "CENTER";
+ case END:
+ return "END";
+ case TOP:
+ return "TOP";
+ case BOTTOM:
+ return "BOTTOM";
+ default:
+ return "NONE";
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java
index 8b52bbe..5b66b95 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java
@@ -73,19 +73,19 @@
}
@Override
- public float intrinsicHeight(@Nullable RemoteContext context) {
+ public float minIntrinsicHeight(@Nullable RemoteContext context) {
float height = computeModifierDefinedHeight(context);
for (Component c : mChildrenComponents) {
- height = Math.max(c.intrinsicHeight(context), height);
+ height = Math.max(c.minIntrinsicHeight(context), height);
}
return height;
}
@Override
- public float intrinsicWidth(@Nullable RemoteContext context) {
+ public float minIntrinsicWidth(@Nullable RemoteContext context) {
float width = computeModifierDefinedWidth(context);
for (Component c : mChildrenComponents) {
- width = Math.max(c.intrinsicWidth(context), width);
+ width = Math.max(c.minIntrinsicWidth(context), width);
}
return width;
}
@@ -149,10 +149,10 @@
Math.min(maxHeight, computeModifierDefinedHeight(context.getContext()));
if (mWidthModifier.isIntrinsicMin()) {
- maxWidth = intrinsicWidth(context.getContext()) + mPaddingLeft + mPaddingRight;
+ maxWidth = minIntrinsicWidth(context.getContext()) + mPaddingLeft + mPaddingRight;
}
if (mHeightModifier.isIntrinsicMin()) {
- maxHeight = intrinsicHeight(context.getContext()) + mPaddingTop + mPaddingBottom;
+ maxHeight = minIntrinsicHeight(context.getContext()) + mPaddingTop + mPaddingBottom;
}
float insetMaxWidth = maxWidth - mPaddingLeft - mPaddingRight;
@@ -171,6 +171,11 @@
mHeightModifier.isWrap(),
measure,
mCachedWrapSize);
+ int selfVisibilityAfterMeasure = measure.get(this).getVisibility();
+ if (Visibility.hasOverride(selfVisibilityAfterMeasure)
+ && mScheduledVisibility != selfVisibilityAfterMeasure) {
+ mScheduledVisibility = selfVisibilityAfterMeasure;
+ }
measuredWidth = mCachedWrapSize.getWidth();
if (hasHorizontalWrap) {
measuredWidth += mPaddingLeft + mPaddingRight;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java
index e93cbd7..d5d2e03 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java
@@ -136,7 +136,7 @@
for (Component c : mChildrenComponents) {
c.measure(context, 0f, currentMaxWidth, 0f, maxHeight, measure);
ComponentMeasure m = measure.get(c);
- if (m.getVisibility() != Visibility.GONE) {
+ if (!m.isGone()) {
size.setWidth(size.getWidth() + m.getW());
size.setHeight(Math.max(size.getHeight(), m.getH()));
visibleChildrens++;
@@ -162,7 +162,7 @@
for (Component child : mChildrenComponents) {
child.measure(context, minWidth, mw, minHeight, maxHeight, measure);
ComponentMeasure m = measure.get(child);
- if (m.getVisibility() != Visibility.GONE) {
+ if (!m.isGone()) {
mw -= m.getW();
}
}
@@ -170,16 +170,26 @@
}
@Override
- public float intrinsicWidth(@Nullable RemoteContext context) {
+ public float minIntrinsicWidth(@Nullable RemoteContext context) {
float width = computeModifierDefinedWidth(context);
float componentWidths = 0f;
for (Component c : mChildrenComponents) {
- componentWidths += c.intrinsicWidth(context);
+ componentWidths += c.minIntrinsicWidth(context);
}
return Math.max(width, componentWidths);
}
@Override
+ public float minIntrinsicHeight(@Nullable RemoteContext context) {
+ float height = computeModifierDefinedHeight(context);
+ float componentHeights = 0f;
+ for (Component c : mChildrenComponents) {
+ componentHeights = Math.max(componentHeights, c.minIntrinsicHeight(context));
+ }
+ return Math.max(height, componentHeights);
+ }
+
+ @Override
public void internalLayoutMeasure(@NonNull PaintContext context, @NonNull MeasurePass measure) {
ComponentMeasure selfMeasure = measure.get(this);
DebugLog.s(
@@ -225,7 +235,7 @@
float totalWeights = 0f;
for (Component child : mChildrenComponents) {
ComponentMeasure childMeasure = measure.get(child);
- if (childMeasure.getVisibility() == Visibility.GONE) {
+ if (childMeasure.isGone()) {
continue;
}
if (child instanceof LayoutComponent
@@ -245,7 +255,7 @@
if (child instanceof LayoutComponent
&& ((LayoutComponent) child).getWidthModifier().hasWeight()) {
ComponentMeasure childMeasure = measure.get(child);
- if (childMeasure.getVisibility() == Visibility.GONE) {
+ if (childMeasure.isGone()) {
continue;
}
float weight = ((LayoutComponent) child).getWidthModifier().getValue();
@@ -283,7 +293,7 @@
int visibleChildrens = 0;
for (Component child : mChildrenComponents) {
ComponentMeasure childMeasure = measure.get(child);
- if (childMeasure.getVisibility() == Visibility.GONE) {
+ if (childMeasure.isGone()) {
continue;
}
childrenWidth += childMeasure.getW();
@@ -311,17 +321,22 @@
case SPACE_BETWEEN:
for (Component child : mChildrenComponents) {
ComponentMeasure childMeasure = measure.get(child);
- if (childMeasure.getVisibility() == Visibility.GONE) {
+ if (childMeasure.isGone()) {
continue;
}
total += childMeasure.getW();
}
- horizontalGap = (selfWidth - total) / (visibleChildrens - 1);
+ if (visibleChildrens > 1) {
+ horizontalGap = (selfWidth - total) / (visibleChildrens - 1);
+ } else {
+ // we center the element
+ tx = (selfWidth - childrenWidth) / 2f;
+ }
break;
case SPACE_EVENLY:
for (Component child : mChildrenComponents) {
ComponentMeasure childMeasure = measure.get(child);
- if (childMeasure.getVisibility() == Visibility.GONE) {
+ if (childMeasure.isGone()) {
continue;
}
total += childMeasure.getW();
@@ -332,7 +347,7 @@
case SPACE_AROUND:
for (Component child : mChildrenComponents) {
ComponentMeasure childMeasure = measure.get(child);
- if (childMeasure.getVisibility() == Visibility.GONE) {
+ if (childMeasure.isGone()) {
continue;
}
total += childMeasure.getW();
@@ -357,7 +372,7 @@
}
childMeasure.setX(tx);
childMeasure.setY(ty);
- if (childMeasure.getVisibility() == Visibility.GONE) {
+ if (childMeasure.isGone()) {
continue;
}
tx += childMeasure.getW();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java
index ee16bc2..0192d84 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java
@@ -413,7 +413,7 @@
if (index != currentLayoutIndex && index != previousLayoutIndex) {
pane.mVisibility = Visibility.GONE;
}
- if (index == currentLayoutIndex && pane.mVisibility != Visibility.VISIBLE) {
+ if (index == currentLayoutIndex && !pane.isVisible()) {
pane.mVisibility = Visibility.VISIBLE;
}
index++;
@@ -511,7 +511,7 @@
&& previousLayout.mAnimateMeasure == null) {
inTransition = false;
LayoutManager previous = getLayout(previousLayoutIndex);
- if (previous != currentLayout && previous.mVisibility != Visibility.GONE) {
+ if (previous != currentLayout && !previous.isGone()) {
previous.mVisibility = Visibility.GONE;
previous.needsRepaint();
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java
index d5db74b..2595a71 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java
@@ -280,7 +280,7 @@
+ " x "
+ mHeight
+ ") "
- + mVisibility;
+ + Visibility.toString(mVisibility);
}
@NonNull
@@ -308,7 +308,7 @@
+ ", "
+ mHeight
+ "] "
- + mVisibility
+ + Visibility.toString(mVisibility)
+ " ("
+ mTextId
+ ":\""
@@ -343,7 +343,7 @@
flags |= PaintContext.TEXT_COMPLEX;
}
context.getTextBounds(mTextId, 0, mCachedString.length(), flags, bounds);
- if (bounds[2] - bounds[1] > maxWidth && mMaxLines > 1) {
+ if (bounds[2] - bounds[1] > maxWidth && mMaxLines > 1 && maxWidth > 0f) {
mComputedTextLayout =
context.layoutComplexText(
mTextId,
@@ -375,12 +375,12 @@
}
@Override
- public float intrinsicHeight(@Nullable RemoteContext context) {
+ public float minIntrinsicHeight(@Nullable RemoteContext context) {
return mTextH;
}
@Override
- public float intrinsicWidth(@Nullable RemoteContext context) {
+ public float minIntrinsicWidth(@Nullable RemoteContext context) {
return mTextW;
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java
index 11ed9f4..9934419 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java
@@ -26,7 +26,7 @@
float mY;
float mW;
float mH;
- @NonNull Component.Visibility mVisibility = Component.Visibility.VISIBLE;
+ int mVisibility = Component.Visibility.VISIBLE;
public void setX(float value) {
mX = value;
@@ -60,16 +60,15 @@
return mH;
}
- public @NonNull Component.Visibility getVisibility() {
+ public int getVisibility() {
return mVisibility;
}
- public void setVisibility(@NonNull Component.Visibility visibility) {
+ public void setVisibility(int visibility) {
mVisibility = visibility;
}
- public ComponentMeasure(
- int id, float x, float y, float w, float h, @NonNull Component.Visibility visibility) {
+ public ComponentMeasure(int id, float x, float y, float w, float h, int visibility) {
this.mId = id;
this.mX = x;
this.mY = y;
@@ -114,4 +113,42 @@
public boolean same(@NonNull ComponentMeasure m) {
return mX == m.mX && mY == m.mY && mW == m.mW && mH == m.mH && mVisibility == m.mVisibility;
}
+
+ /**
+ * Returns true if the component will be gone
+ *
+ * @return true if gone
+ */
+ public boolean isGone() {
+ return Component.Visibility.isGone(mVisibility);
+ }
+
+ /**
+ * Returns true if the component will be visible
+ *
+ * @return true if visible
+ */
+ public boolean isVisible() {
+ return Component.Visibility.isVisible(mVisibility);
+ }
+
+ /**
+ * Returns true if the component will be invisible
+ *
+ * @return true if invisible
+ */
+ public boolean isInvisible() {
+ return Component.Visibility.isInvisible(mVisibility);
+ }
+
+ /** Clear any override on the visibility */
+ public void clearVisibilityOverride() {
+ mVisibility = Component.Visibility.clearOverride(mVisibility);
+ }
+
+ /** Add a visibility override */
+ public void addVisibilityOverride(int value) {
+ mVisibility = Component.Visibility.clearOverride(mVisibility);
+ mVisibility = Component.Visibility.add(mVisibility, value);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java
index fd5f8c9..1ab0c3d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java
@@ -228,7 +228,7 @@
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER)
- .add("type", "BackgroundModifierOperation")
+ .addType("BackgroundModifierOperation")
.add("x", mX)
.add("y", mY)
.add("width", mWidth)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java
index e5f3183..656a3c0 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java
@@ -290,7 +290,7 @@
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER)
- .add("type", "BorderModifierOperation")
+ .addType("BorderModifierOperation")
.add("x", mX)
.add("y", mY)
.add("width", mWidth)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java
index 00a5317..e96dc83 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java
@@ -111,7 +111,7 @@
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER)
- .add("type", "ClipRectModifierOperation")
+ .addType("ClipRectModifierOperation")
.add("width", mWidth)
.add("height", mHeight);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java
index a9e3421..14b2fad 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java
@@ -327,7 +327,7 @@
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER)
- .add("type", "ComponentModifiers")
+ .addType("ComponentModifiers")
.add("modifiers", mList);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java
index fbf8a95..88b28c3 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java
@@ -41,7 +41,7 @@
private static final int OP_CODE = Operations.MODIFIER_VISIBILITY;
int mVisibilityId;
- @NonNull Component.Visibility mVisibility = Component.Visibility.VISIBLE;
+ int mVisibility = Component.Visibility.VISIBLE;
private LayoutComponent mParent;
public ComponentVisibilityOperation(int id) {
@@ -124,11 +124,11 @@
@Override
public void updateVariables(@NonNull RemoteContext context) {
int visibility = context.getInteger(mVisibilityId);
- if (visibility == Component.Visibility.VISIBLE.ordinal()) {
+ if (Component.Visibility.isVisible(visibility)) {
mVisibility = Component.Visibility.VISIBLE;
- } else if (visibility == Component.Visibility.GONE.ordinal()) {
+ } else if (Component.Visibility.isGone(visibility)) {
mVisibility = Component.Visibility.GONE;
- } else if (visibility == Component.Visibility.INVISIBLE.ordinal()) {
+ } else if (Component.Visibility.isInvisible(visibility)) {
mVisibility = Component.Visibility.INVISIBLE;
} else {
mVisibility = Component.Visibility.GONE;
@@ -150,8 +150,8 @@
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER)
- .add("type", "ComponentVisibilityOperation")
+ .addType("ComponentVisibilityOperation")
.add("visibilityId", mVisibilityId)
- .add("visibility", mVisibility);
+ .add("visibility", Component.Visibility.toString(mVisibility));
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DrawContentOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DrawContentOperation.java
index d7abdba..6beb135 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DrawContentOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DrawContentOperation.java
@@ -120,6 +120,6 @@
@Override
public void serialize(MapSerializer serializer) {
- serializer.addTags(SerializeTags.MODIFIER).add("type", "DrawContentOperation");
+ serializer.addTags(SerializeTags.MODIFIER).addType("DrawContentOperation");
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java
index c1c1f95..361438b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java
@@ -352,7 +352,7 @@
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER)
- .add("type", "GraphicsLayerModifierOperation")
+ .addType("GraphicsLayerModifierOperation")
.add("scaleX", mScaleX)
.add("scaleX", mScaleX)
.add("rotationX", mRotationX)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightInModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightInModifierOperation.java
index 7f0dd8d..9b63c03 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightInModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightInModifierOperation.java
@@ -107,7 +107,7 @@
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER)
- .add("type", "HeightInModifierOperation")
+ .addType("HeightInModifierOperation")
.add("min", mV1, mValue1)
.add("max", mV2, mValue2);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java
index 1df8425..5fbaafc 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java
@@ -143,7 +143,7 @@
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER)
- .add("type", "HeightModifierOperation")
+ .addType("HeightModifierOperation")
.add("height", mValue, mOutValue)
.add("dimensionModifierType", mType);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java
index 67714ef..4d8acb4 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java
@@ -130,7 +130,7 @@
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER)
- .add("type", "HostActionOperation")
+ .addType("HostActionOperation")
.add("id", mActionId);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java
index 40c13f14..807ff68 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java
@@ -157,7 +157,7 @@
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER)
- .add("type", "HostNamedActionOperation")
+ .addType("HostNamedActionOperation")
.add("textId", mTextId)
.add("actionType", getActionType(mType))
.add("valueId", mValueId);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/MarqueeModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/MarqueeModifierOperation.java
index d2a1684..c493e25 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/MarqueeModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/MarqueeModifierOperation.java
@@ -247,8 +247,8 @@
mComponentHeight = height;
if (component instanceof LayoutComponent) {
LayoutComponent layoutComponent = (LayoutComponent) component;
- setContentWidth(layoutComponent.intrinsicWidth(context));
- setContentHeight(layoutComponent.intrinsicHeight(context));
+ setContentWidth(layoutComponent.minIntrinsicWidth(context));
+ setContentHeight(layoutComponent.minIntrinsicHeight(context));
}
}
@@ -256,7 +256,7 @@
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER)
- .add("type", "MarqueeModifierOperation")
+ .addType("MarqueeModifierOperation")
.add("iterations", mIterations)
.add("animationMode", mAnimationMode)
.add("repeatDelayMillis", mRepeatDelayMillis)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/OffsetModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/OffsetModifierOperation.java
index b7fe97b..37f56cb 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/OffsetModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/OffsetModifierOperation.java
@@ -162,7 +162,7 @@
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER)
- .add("type", "OffsetModifierOperation")
+ .addType("OffsetModifierOperation")
.add("x", mX)
.add("y", mY);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java
index d5b3a0b..0156992 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java
@@ -184,7 +184,7 @@
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER)
- .add("type", "PaddingModifierOperation")
+ .addType("PaddingModifierOperation")
.add("left", mLeft)
.add("top", mTop)
.add("right", mRight)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RippleModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RippleModifierOperation.java
index 69ace84..eb5bfcf 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RippleModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RippleModifierOperation.java
@@ -217,7 +217,7 @@
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER)
- .add("type", "RippleModifierOperation")
+ .addType("RippleModifierOperation")
.add("animateRippleStart", mAnimateRippleStart)
.add("animateRippleX", mAnimateRippleX)
.add("animateRippleY", mAnimateRippleY)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RoundedClipRectModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RoundedClipRectModifierOperation.java
index 8442e05..f0ed905 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RoundedClipRectModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RoundedClipRectModifierOperation.java
@@ -167,7 +167,7 @@
public void serialize(MapSerializer serializer) {
serialize(serializer, "topStart", "topEnd", "bottomStart", "bottomEnd")
.addTags(SerializeTags.MODIFIER)
- .add("type", CLASS_NAME)
+ .addType(CLASS_NAME)
.add("width", mWidth)
.add("height", mHeight);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java
index a57365e..466e435e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java
@@ -397,7 +397,7 @@
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER)
- .add("type", "ScrollModifierOperation")
+ .addType("ScrollModifierOperation")
.add("direction", mDirection)
.add("max", mMax)
.add("notchMax", mNotchMax)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatChangeActionOperation.java
index bd91734..171e2be 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatChangeActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatChangeActionOperation.java
@@ -126,7 +126,7 @@
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER, SerializeTags.ACTION)
- .add("type", "ValueFloatChangeActionOperation")
+ .addType("ValueFloatChangeActionOperation")
.add("targetValueId", mTargetValueId)
.add("value", mValue);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatExpressionChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatExpressionChangeActionOperation.java
index 4b18d0a..d8133f6 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatExpressionChangeActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatExpressionChangeActionOperation.java
@@ -133,7 +133,7 @@
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER, SerializeTags.ACTION)
- .add("type", "ValueFloatExpressionChangeActionOperation")
+ .addType("ValueFloatExpressionChangeActionOperation")
.add("targetValueId", mTargetValueId)
.add("valueExpressionId", mValueExpressionId);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java
index d86c4a6..05a6fd0 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java
@@ -131,7 +131,7 @@
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER, SerializeTags.ACTION)
- .add("type", "ValueIntegerChangeActionOperation")
+ .addType("ValueIntegerChangeActionOperation")
.add("targetValueId", mTargetValueId)
.add("value", mValue);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java
index e253460..8994feb 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java
@@ -133,7 +133,7 @@
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER, SerializeTags.ACTION)
- .add("type", "ValueIntegerExpressionChangeActionOperation")
+ .addType("ValueIntegerExpressionChangeActionOperation")
.add("targetValueId", mTargetValueId)
.add("valueExpressionId", mValueExpressionId);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java
index e84b299..08960d3 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java
@@ -139,7 +139,7 @@
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER, SerializeTags.ACTION)
- .add("type", "ValueIntegerExpressionChangeActionOperation")
+ .addType("ValueIntegerExpressionChangeActionOperation")
.add("targetValueId", mTargetValueId)
.add("valueId", mValueId);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthInModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthInModifierOperation.java
index 3282a9c..93074c7 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthInModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthInModifierOperation.java
@@ -107,7 +107,7 @@
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER)
- .add("type", "WidthInModifierOperation")
+ .addType("WidthInModifierOperation")
.add("min", mV1, mValue1)
.add("max", mV2, mValue2);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java
index 6fe5a70..ebdafa2 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java
@@ -143,7 +143,7 @@
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER)
- .add("type", "WidthModifierOperation")
+ .addType("WidthModifierOperation")
.add("width", mValue, mOutValue)
.add("dimensionModifierType", mType);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java
index f250951..ddb34b5 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java
@@ -147,7 +147,7 @@
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER)
- .add("type", "ZIndexModifierOperation")
+ .addType("ZIndexModifierOperation")
.add("value", mValue);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java
index 0f17b11..55b6436 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java
@@ -1248,7 +1248,7 @@
@Override
public void serialize(MapSerializer serializer) {
- serializer.add("type", "PaintBundle");
+ serializer.addType("PaintBundle");
List<Map<String, Object>> list = new ArrayList<>();
int i = 0;
while (i < mPos) {
@@ -1336,6 +1336,7 @@
serializer.add("operations", list);
}
+ @SuppressWarnings("JdkImmutableCollections")
private static Map<String, Object> getVariable(int value) {
float fValue = Float.intBitsToFloat(value);
if (Float.isNaN(fValue)) {
@@ -1344,6 +1345,7 @@
return orderedOf("type", "Value", "value", fValue);
}
+ @SuppressWarnings("JdkImmutableCollections")
private static int serializeGradient(
int cmd, int[] array, int i, List<Map<String, Object>> list) {
int ret = i;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java
index cad7605..349ab61 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java
@@ -34,8 +34,10 @@
private float mWrap = Float.NaN;
private float mInitialValue = Float.NaN;
private float mTargetValue = Float.NaN;
+ private int mDirectionalSnap = 0;
// private float mScale = 1;
float mOffset = 0;
+ private boolean mPropagate = false;
@NonNull
@Override
@@ -161,11 +163,15 @@
int type = 0;
float wrapValue = Float.NaN;
float initialValue = Float.NaN;
+ int directionalSnap = 0;
+ boolean propagate = false;
if (mSpec.length > 1) {
int num_type = Float.floatToRawIntBits(mSpec[1]);
type = num_type & 0xFF;
boolean wrap = ((num_type >> 8) & 0x1) > 0;
boolean init = ((num_type >> 8) & 0x2) > 0;
+ directionalSnap = (num_type >> 10) & 0x3;
+ propagate = ((num_type >> 12) & 0x1) > 0;
len = (num_type >> 16) & 0xFFFF;
int off = 2 + len;
if (init) {
@@ -229,6 +235,12 @@
if (!Float.isNaN(wrapValue)) {
str += " wrap =" + wrapValue;
}
+ if (directionalSnap != 0) {
+ str += " directionalSnap=" + directionalSnap;
+ }
+ if (propagate) {
+ str += " propagate";
+ }
return str;
}
@@ -246,6 +258,8 @@
mType = num_type & 0xFF;
boolean wrap = ((num_type >> 8) & 0x1) > 0;
boolean init = ((num_type >> 8) & 0x2) > 0;
+ int directional = (num_type >> 10) & 0x3;
+ boolean propagate = ((num_type >> 12) & 0x1) > 0;
len = (num_type >> 16) & 0xFFFF;
int off = 2 + len;
if (init) {
@@ -254,6 +268,8 @@
if (wrap) {
mWrap = mSpec[off];
}
+ mDirectionalSnap = directional;
+ mPropagate = propagate;
}
create(mType, description, 2, len);
}
@@ -347,7 +363,13 @@
float dist = wrapDistance(mWrap, mInitialValue, mTargetValue);
if ((dist > 0) && (mTargetValue < mInitialValue)) {
mTargetValue += mWrap;
- } else if ((dist < 0) && (mTargetValue > mInitialValue)) {
+ } else if ((dist < 0) && mDirectionalSnap != 0) {
+ if (mDirectionalSnap == 1 && mTargetValue > mInitialValue) {
+ mInitialValue = mTargetValue;
+ }
+ if (mDirectionalSnap == 2 && mTargetValue < mInitialValue) {
+ mInitialValue = mTargetValue;
+ }
mTargetValue -= mWrap;
}
}
@@ -377,6 +399,14 @@
/** get the value at time t in seconds since start */
@Override
public float get(float t) {
+ if (mDirectionalSnap == 1 && mTargetValue < mInitialValue) {
+ mInitialValue = mTargetValue;
+ return mTargetValue;
+ }
+ if (mDirectionalSnap == 2 && mTargetValue > mInitialValue) {
+ mInitialValue = mTargetValue;
+ return mTargetValue;
+ }
return mEasingCurve.get(t / mDuration) * (mTargetValue - mInitialValue) + mInitialValue;
}
@@ -387,6 +417,13 @@
}
/**
+ * @return if you should propagate the animation
+ */
+ public boolean isPropagate() {
+ return mPropagate;
+ }
+
+ /**
* Get the initial value
*
* @return the initial value
@@ -398,7 +435,7 @@
@Override
public void serialize(MapSerializer serializer) {
serializer
- .add("type", "FloatAnimation")
+ .addType("FloatAnimation")
.add("initialValue", mInitialValue)
.add("targetValue", mInitialValue)
.add("duration", mInitialValue)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/semantics/CoreSemantics.java b/core/java/com/android/internal/widget/remotecompose/core/semantics/CoreSemantics.java
index 08559fc..424894a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/semantics/CoreSemantics.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/semantics/CoreSemantics.java
@@ -161,7 +161,7 @@
public void serialize(MapSerializer serializer) {
serializer
.addTags(SerializeTags.MODIFIER, SerializeTags.A11Y)
- .add("type", "CoreSemantics")
+ .addType("CoreSemantics")
.add("contentDescriptionId", mContentDescriptionId)
.add("role", mRole)
.add("textId", mTextId)
diff --git a/core/java/com/android/internal/widget/remotecompose/core/serialize/MapSerializer.java b/core/java/com/android/internal/widget/remotecompose/core/serialize/MapSerializer.java
index f9ecf0f..20e94ab 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/serialize/MapSerializer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/serialize/MapSerializer.java
@@ -25,11 +25,17 @@
public interface MapSerializer {
/**
+ * Adds a "type" field with this value
+ *
+ * @param type The name of the type
+ */
+ MapSerializer addType(String type);
+
+ /**
* Add a float expression
*
- * @param key
- * @param value
- * @return
+ * @param key The key
+ * @param value The float src
*/
MapSerializer addFloatExpressionSrc(String key, float[] value);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.java b/core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.java
index cb759a6..0da543f 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.java
@@ -131,6 +131,6 @@
@Override
public void serialize(MapSerializer serializer) {
- serializer.add("type", CLASS_NAME).add("id", mId).add("value", mValue);
+ serializer.addType(CLASS_NAME).add("id", mId).add("value", mValue);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java b/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java
index c734f81..bdc7659 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java
@@ -123,6 +123,6 @@
@Override
public void serialize(MapSerializer serializer) {
- serializer.add("type", CLASS_NAME).add("id", mId).add("value", mValue);
+ serializer.addType(CLASS_NAME).add("id", mId).add("value", mValue);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java b/core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java
index 50509f3..d071e0a2 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java
@@ -35,9 +35,13 @@
private static final String CLASS_NAME = "LongConstant";
private static final int OP_CODE = Operations.DATA_LONG;
- private final long mValue;
+ private long mValue;
private final int mId;
+ /**
+ * @param id the id of the constant
+ * @param value the value of the constant
+ */
public LongConstant(int id, long value) {
mId = id;
mValue = value;
@@ -52,6 +56,15 @@
return mValue;
}
+ /**
+ * Set the value of the long constant
+ *
+ * @param value the value to set it to
+ */
+ public void setValue(long value) {
+ mValue = value;
+ }
+
@Override
public void write(@NonNull WireBuffer buffer) {
apply(buffer, mId, mValue);
@@ -114,6 +127,6 @@
@Override
public void serialize(MapSerializer serializer) {
- serializer.add("type", CLASS_NAME).add("id", mId).add("value", mValue);
+ serializer.addType(CLASS_NAME).add("id", mId).add("value", mValue);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
index 1d1e579..1f9a274 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
@@ -388,6 +388,16 @@
mInner.setColor(colorName, colorValue);
}
+ /**
+ * This sets long based on its name.
+ *
+ * @param name Name of the color
+ * @param value The new long value
+ */
+ public void setLong(String name, long value) {
+ mInner.setLong(name, value);
+ }
+
private void mapColors() {
String[] name = getNamedColors();
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java
index ac4a294..b5aedd8 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java
@@ -290,8 +290,8 @@
}
if ((flags & PaintContext.TEXT_MEASURE_FONT_HEIGHT) != 0) {
- bounds[1] = Math.round(mCachedFontMetrics.top);
- bounds[3] = Math.round(mCachedFontMetrics.bottom);
+ bounds[1] = Math.round(mCachedFontMetrics.ascent);
+ bounds[3] = Math.round(mCachedFontMetrics.descent);
} else {
bounds[1] = mTmpRect.top;
bounds[3] = mTmpRect.bottom;
@@ -344,6 +344,7 @@
default:
}
staticLayoutBuilder.setMaxLines(maxLines);
+ staticLayoutBuilder.setIncludePad(false);
StaticLayout staticLayout = staticLayoutBuilder.build();
return new AndroidComputedTextLayout(
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPlatformServices.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPlatformServices.java
index ba8d83b..51c42fe 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPlatformServices.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPlatformServices.java
@@ -60,6 +60,14 @@
}
@Override
+ public boolean isAlpha8Image(@NonNull Object image) {
+ if (image instanceof Bitmap) {
+ return ((Bitmap) image).getConfig().equals(Bitmap.Config.ALPHA_8);
+ }
+ return false;
+ }
+
+ @Override
@Nullable
public float[] pathToFloatArray(@NonNull Object path) {
// if (path is RemotePath) {
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java
index 14349b0..b31c760 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java
@@ -20,6 +20,7 @@
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
+import android.graphics.Paint;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -30,6 +31,7 @@
import com.android.internal.widget.remotecompose.core.operations.ShaderData;
import com.android.internal.widget.remotecompose.core.operations.utilities.ArrayAccess;
import com.android.internal.widget.remotecompose.core.operations.utilities.DataMap;
+import com.android.internal.widget.remotecompose.core.types.LongConstant;
import java.io.IOException;
import java.net.MalformedURLException;
@@ -141,6 +143,16 @@
}
@Override
+ public void setNamedLong(String name, long value) {
+ VarName entry = mVarNameHashMap.get(name);
+ if (entry != null) {
+ int id = entry.mId;
+ LongConstant longConstant = (LongConstant) mRemoteComposeState.getObject(id);
+ longConstant.setValue(value);
+ }
+ }
+
+ @Override
public void setNamedDataOverride(String dataName, Object value) {
if (mVarNameHashMap.get(dataName) != null) {
int id = mVarNameHashMap.get(dataName).mId;
@@ -215,6 +227,27 @@
case BitmapData.TYPE_PNG_8888:
image = BitmapFactory.decodeByteArray(data, 0, data.length);
break;
+ case BitmapData.TYPE_PNG_ALPHA_8:
+ image = decodePreferringAlpha8(data);
+
+ // If needed convert to ALPHA_8.
+ if (!image.getConfig().equals(Bitmap.Config.ALPHA_8)) {
+ Bitmap alpha8Bitmap =
+ Bitmap.createBitmap(
+ image.getWidth(),
+ image.getHeight(),
+ Bitmap.Config.ALPHA_8);
+ Canvas canvas = new Canvas(alpha8Bitmap);
+ Paint paint = new Paint();
+ paint.setXfermode(
+ new android.graphics.PorterDuffXfermode(
+ android.graphics.PorterDuff.Mode.SRC));
+ canvas.drawBitmap(image, 0, 0, paint);
+ image.recycle(); // Release resources
+
+ image = alpha8Bitmap;
+ }
+ break;
case BitmapData.TYPE_RAW8888:
image = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
int[] idata = new int[data.length / 4];
@@ -255,6 +288,12 @@
}
}
+ private Bitmap decodePreferringAlpha8(@NonNull byte[] data) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inPreferredConfig = Bitmap.Config.ALPHA_8;
+ return BitmapFactory.decodeByteArray(data, 0, data.length, options);
+ }
+
@Override
public void loadText(int id, @NonNull String text) {
if (!mRemoteComposeState.containsId(id)) {
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java b/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java
index 4d2dd05..29cd40d 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java
@@ -200,6 +200,16 @@
mARContext.setNamedColorOverride(colorName, colorValue);
}
+ /**
+ * set the value of a long associated with this name.
+ *
+ * @param name Name of color typically "android.xxx"
+ * @param value the long value
+ */
+ public void setLong(String name, long value) {
+ mARContext.setNamedLong(name, value);
+ }
+
public RemoteComposeDocument getDocument() {
return mDocument;
}
diff --git a/core/jni/android_database_SQLiteConnection.cpp b/core/jni/android_database_SQLiteConnection.cpp
index ba7e705..36c08a5 100644
--- a/core/jni/android_database_SQLiteConnection.cpp
+++ b/core/jni/android_database_SQLiteConnection.cpp
@@ -204,7 +204,7 @@
return reinterpret_cast<jlong>(connection);
}
-static void nativeClose(JNIEnv* env, jclass clazz, jlong connectionPtr) {
+static void nativeClose(JNIEnv* env, jclass clazz, jlong connectionPtr, jboolean fast) {
SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
if (connection) {
@@ -212,6 +212,13 @@
if (connection->tableQuery != nullptr) {
sqlite3_finalize(connection->tableQuery);
}
+ if (fast) {
+ // The caller requested a fast close, so do not checkpoint even if this is the last
+ // connection to the database. Note that the change is only to this connection.
+ // Any other connections to the same database are unaffected.
+ int _unused = 0;
+ sqlite3_db_config(connection->db, SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE, 1, &_unused);
+ }
int err = sqlite3_close(connection->db);
if (err != SQLITE_OK) {
// This can happen if sub-objects aren't closed first. Make sure the caller knows.
@@ -224,6 +231,12 @@
}
}
+// This method is deprecated and should be removed when it is no longer needed by the
+// robolectric tests.
+static void nativeClose(JNIEnv* env, jclass clazz, jlong connectionPtr) {
+ nativeClose(env, clazz, connectionPtr, false);
+}
+
static void sqliteCustomScalarFunctionCallback(sqlite3_context *context,
int argc, sqlite3_value **argv) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
@@ -959,8 +972,10 @@
/* name, signature, funcPtr */
{ "nativeOpen", "(Ljava/lang/String;ILjava/lang/String;ZZII)J",
(void*)nativeOpen },
+ { "nativeClose", "(JZ)V",
+ (void*) static_cast<void(*)(JNIEnv*,jclass,jlong,jboolean)>(nativeClose) },
{ "nativeClose", "(J)V",
- (void*)nativeClose },
+ (void*) static_cast<void(*)(JNIEnv*,jclass,jlong)>(nativeClose) },
{ "nativeRegisterCustomScalarFunction", "(JLjava/lang/String;Ljava/util/function/UnaryOperator;)V",
(void*)nativeRegisterCustomScalarFunction },
{ "nativeRegisterCustomAggregateFunction", "(JLjava/lang/String;Ljava/util/function/BinaryOperator;)V",
diff --git a/core/jni/android_media_ImageWriter.cpp b/core/jni/android_media_ImageWriter.cpp
index 1357dd8..8e58922 100644
--- a/core/jni/android_media_ImageWriter.cpp
+++ b/core/jni/android_media_ImageWriter.cpp
@@ -399,7 +399,7 @@
}
sp<JNIImageWriterContext> ctx(new JNIImageWriterContext(env, weakThiz, clazz));
- sp<Surface> producer = new Surface(bufferProducer, /*controlledByApp*/false);
+ sp<Surface> producer = sp<Surface>::make(bufferProducer, /*controlledByApp*/ false);
ctx->setProducer(producer);
/**
* NATIVE_WINDOW_API_CPU isn't a good choice here, as it makes the bufferQueue not connectable
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 312c206..783daec 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -139,7 +139,7 @@
return NULL;
}
- sp<Surface> surface(new Surface(bufferProducer, true));
+ sp<Surface> surface = sp<Surface>::make(bufferProducer, true);
return android_view_Surface_createFromSurface(env, surface);
}
@@ -161,7 +161,7 @@
return 0;
}
- sp<Surface> surface(new Surface(producer, true));
+ sp<Surface> surface = sp<Surface>::make(producer, true);
if (surface == NULL) {
jniThrowException(env, OutOfResourcesException, NULL);
return 0;
@@ -358,8 +358,8 @@
sp<Surface> sur;
if (surfaceShim.graphicBufferProducer != nullptr) {
// we have a new IGraphicBufferProducer, create a new Surface for it
- sur = new Surface(surfaceShim.graphicBufferProducer, true,
- surfaceShim.surfaceControlHandle);
+ sur = sp<Surface>::make(surfaceShim.graphicBufferProducer, true,
+ surfaceShim.surfaceControlHandle);
// and keep a reference before passing to java
sur->incStrong(&sRefBaseOwner);
}
diff --git a/core/jni/android_view_SurfaceSession.cpp b/core/jni/android_view_SurfaceSession.cpp
index 6ad109e..4f2ab09 100644
--- a/core/jni/android_view_SurfaceSession.cpp
+++ b/core/jni/android_view_SurfaceSession.cpp
@@ -42,14 +42,14 @@
static jlong nativeCreate(JNIEnv* env, jclass clazz) {
- SurfaceComposerClient* client = new SurfaceComposerClient();
- client->incStrong((void*)nativeCreate);
- return reinterpret_cast<jlong>(client);
+ // Will be deleted via decStrong() in nativeDestroy.
+ auto client = sp<SurfaceComposerClient>::make();
+ return reinterpret_cast<jlong>(client.release());
}
static void nativeDestroy(JNIEnv* env, jclass clazz, jlong ptr) {
SurfaceComposerClient* client = reinterpret_cast<SurfaceComposerClient*>(ptr);
- client->decStrong((void*)nativeCreate);
+ client->decStrong((void*)client);
}
static const JNINativeMethod gMethods[] = {
diff --git a/core/jni/android_view_TextureView.cpp b/core/jni/android_view_TextureView.cpp
index 21fe1f0..f71878c 100644
--- a/core/jni/android_view_TextureView.cpp
+++ b/core/jni/android_view_TextureView.cpp
@@ -85,7 +85,7 @@
jobject surface) {
sp<IGraphicBufferProducer> producer(SurfaceTexture_getProducer(env, surface));
- sp<ANativeWindow> window = new Surface(producer, true);
+ sp<ANativeWindow> window = sp<Surface>::make(producer, true);
window->incStrong((void*)android_view_TextureView_createNativeWindow);
SET_LONG(textureView, gTextureViewClassInfo.nativeWindow, jlong(window.get()));
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 8c7b335..42f4444 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -1833,6 +1833,15 @@
ReloadBuildJavaConstants(env);
}
+static void MountInitOverride(fail_fn_t fail_fn, JNIEnv* env) {
+ const char* init_etc_dir = "/system/etc/init";
+
+ if (TEMP_FAILURE_RETRY(mount("tmpfs", init_etc_dir, "tmpfs", MS_NOSUID | MS_NODEV | MS_NOEXEC,
+ "uid=0,gid=0,mode=0751")) == -1) {
+ fail_fn(CREATE_ERROR("Failed to mount tmpfs %s: %s", init_etc_dir, strerror(errno)));
+ }
+}
+
static void BindMountStorageToLowerFs(const userid_t user_id, const uid_t uid,
const char* dir_name, const char* package, fail_fn_t fail_fn) {
bool hasSdcardFs = IsSdcardfsUsed();
@@ -1954,6 +1963,7 @@
if (mount_sysprop_overrides) {
BindMountSyspropOverride(fail_fn, env);
+ MountInitOverride(fail_fn, env);
}
// If this zygote isn't root, it won't be able to create a process group,
diff --git a/core/jni/com_google_android_gles_jni_EGLImpl.cpp b/core/jni/com_google_android_gles_jni_EGLImpl.cpp
index 75330be..1b3b14d 100644
--- a/core/jni/com_google_android_gles_jni_EGLImpl.cpp
+++ b/core/jni/com_google_android_gles_jni_EGLImpl.cpp
@@ -300,7 +300,7 @@
}
sp<IGraphicBufferProducer> producer(SurfaceTexture_getProducer(_env, native_window));
- window = new Surface(producer, true);
+ window = sp<Surface>::make(producer, true);
if (window == NULL)
goto not_valid_surface;
diff --git a/core/proto/android/net/OWNERS b/core/proto/android/net/OWNERS
index 509699b..a6627fe 100644
--- a/core/proto/android/net/OWNERS
+++ b/core/proto/android/net/OWNERS
@@ -1,3 +1,2 @@
-ek@google.com
lorenzo@google.com
satk@google.com
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 34ec148..ac4bac6 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -111,6 +111,7 @@
optional SettingProto autoclick_cursor_area_size = 62 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto autoclick_ignore_minor_cursor_movement = 63 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto autoclick_panel_position = 64 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto autoclick_revert_to_left_click = 65 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional Accessibility accessibility = 2;
@@ -294,13 +295,6 @@
optional SettingProto enhanced_voice_privacy_enabled = 23 [ (android.privacy).dest = DEST_AUTOMATIC ];
- message EvenDimmer {
- optional SettingProto even_dimmer_activated = 1 [ (android.privacy).dest = DEST_AUTOMATIC ];
- optional SettingProto even_dimmer_min_nits = 2 [ (android.privacy).dest = DEST_AUTOMATIC ];
- }
-
- optional EvenDimmer even_dimmer = 98;
-
optional SettingProto font_weight_adjustment = 85 [ (android.privacy).dest = DEST_AUTOMATIC ];
message Gesture {
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index a673ad7..5820c8e 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -216,10 +216,10 @@
optional .com.android.server.wm.IdentifierProto resumed_activity = 24;
repeated TaskProto tasks = 25 [deprecated=true];
optional bool display_ready = 26;
- optional WindowStateProto input_method_target = 27;
- optional WindowStateProto input_method_input_target = 28;
- optional WindowStateProto input_method_control_target = 29;
- optional WindowStateProto current_focus = 30;
+ optional WindowStateProto input_method_target = 27 [deprecated = true];
+ optional WindowStateProto input_method_input_target = 28 [deprecated = true];
+ optional WindowStateProto input_method_control_target = 29 [deprecated = true];
+ optional WindowStateProto current_focus = 30 [deprecated = true];
optional ImeInsetsSourceProviderProto ime_insets_source_provider = 31;
optional bool can_show_ime = 32 [deprecated=true];
@@ -231,6 +231,10 @@
repeated string sleep_tokens = 37;
repeated .android.graphics.RectProto keep_clear_areas = 38;
optional int32 min_size_of_resizeable_task_dp = 39;
+ optional IdentifierProto input_method_layering_target_identifier = 40;
+ optional IdentifierProto input_method_input_target_identifier = 41;
+ optional IdentifierProto input_method_control_target_identifier = 42;
+ optional IdentifierProto current_focus_identifier = 43;
}
/* represents DisplayArea object */
@@ -590,9 +594,9 @@
optional .android.graphics.RectProto frame = 2;
optional .android.view.InsetsSourceControlProto fake_control = 3;
optional .android.view.InsetsSourceControlProto control = 4;
- optional WindowStateProto control_target = 5;
- optional WindowStateProto pending_control_target = 6;
- optional WindowStateProto fake_control_target = 7;
+ optional WindowStateProto control_target = 5 [deprecated = true];
+ optional WindowStateProto pending_control_target = 6 [deprecated = true];
+ optional WindowStateProto fake_control_target = 7 [deprecated = true];
optional .android.view.SurfaceControlProto captured_leash = 8;
optional .android.graphics.RectProto ime_overridden_frame = 9 [deprecated=true];
optional bool is_leash_ready_for_dispatching = 10;
@@ -601,15 +605,21 @@
optional bool seamless_rotating = 13;
optional int64 finish_seamless_rotate_frame_number = 14;
optional bool controllable = 15;
- optional WindowStateProto source_window_state = 16;
+ optional WindowStateProto source_window_state = 16 [deprecated = true];
+
+ optional IdentifierProto control_target_identifier = 17;
+ optional IdentifierProto pending_control_target_identifier = 18;
+ optional IdentifierProto fake_control_target_identifier = 19;
+ optional IdentifierProto source_window_state_identifier = 20;
}
message ImeInsetsSourceProviderProto {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
optional InsetsSourceProviderProto insets_source_provider = 1;
- optional WindowStateProto ime_target_from_ime = 2;
+ optional WindowStateProto ime_target_from_ime = 2 [deprecated = true];
optional bool is_ime_layout_drawn = 3 [deprecated=true];
+ optional IdentifierProto ime_target_from_ime_identifier = 4;
}
message BackNavigationProto {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ee6899c..36b65ba 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8995,13 +8995,13 @@
<!-- @SystemApi
@FlaggedApi("android.permission.flags.text_classifier_choice_api_enabled")
- This permission is required to access the specific text classifier you need from the
+ This permission is required to access the specific text classifier from the
TextClassificationManager.
- <p>Protection level: signature|role
+ <p>Protection level: signature|role|privileged
@hide
-->
<permission android:name="android.permission.ACCESS_TEXT_CLASSIFIER_BY_TYPE"
- android:protectionLevel="signature|role"
+ android:protectionLevel="signature|role|privileged"
android:featureFlag="android.permission.flags.text_classifier_choice_api_enabled"/>
<!-- Attribution for Geofencing service. -->
@@ -9292,6 +9292,9 @@
<action android:name="android.intent.action.UPDATE_PINS" />
<data android:scheme="content" android:host="*" android:mimeType="*/*" />
</intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.BOOT_COMPLETED" />
+ </intent-filter>
</receiver>
<receiver android:name="com.android.server.updates.IntentFirewallInstallReceiver"
diff --git a/core/res/res/drawable/ic_accessibility_autoclick.xml b/core/res/res/drawable/ic_accessibility_autoclick.xml
new file mode 100644
index 0000000..44d34d3
--- /dev/null
+++ b/core/res/res/drawable/ic_accessibility_autoclick.xml
@@ -0,0 +1,8 @@
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@color/accessibility_autoclick_background" />
+ <foreground>
+ <inset
+ android:drawable="@drawable/ic_accessibility_autoclick_foreground"
+ android:inset="@dimen/accessibility_icon_foreground_padding_ratio" />
+ </foreground>
+</adaptive-icon>
diff --git a/core/res/res/drawable/ic_accessibility_autoclick_foreground.xml b/core/res/res/drawable/ic_accessibility_autoclick_foreground.xml
new file mode 100644
index 0000000..0a76a1b
--- /dev/null
+++ b/core/res/res/drawable/ic_accessibility_autoclick_foreground.xml
@@ -0,0 +1,17 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="40dp"
+ android:height="41dp"
+ android:viewportWidth="40"
+ android:viewportHeight="41">
+
+ <path
+ android:fillColor="#67D4FF"
+ android:pathData="M0 20.3789C0 9.33321 8.95431 0.378906 20 0.378906C31.0457 0.378906 40 9.33321 40 20.3789C40 31.4246 31.0457 40.3789 20 40.3789C8.95431 40.3789 0 31.4246 0 20.3789Z" />
+ <path
+ android:fillColor="#04409F"
+ android:pathData="M28.9564 32.0587L24.0973 27.1996L22.6765 31.4904L19.2666 20.124L30.633 23.5339L26.3422 24.9547L31.2013 29.8138L28.9564 32.0587Z" />
+ <path
+ android:fillColor="#04409F"
+ android:fillType="evenOdd"
+ android:pathData="M8.01596 20.8918C8.10753 22.8222 8.63239 24.6433 9.60868 26.3343C10.9014 28.5733 12.8274 30.221 15.1666 31.2713C16.9169 32.0453 18.7173 32.3866 20.5679 32.2953L19.9158 30.1502C18.5615 30.1389 17.2456 29.8364 15.9682 29.2427C14.0941 28.3832 12.6373 27.0532 11.5977 25.2526C10.7841 23.8434 10.3491 22.3305 10.2925 20.7139C10.2607 19.104 10.2783 17.806 10.9916 16.3295C10.9916 16.3295 11.4971 15.2881 11.6974 14.9932C11.8978 14.6983 12.7644 13.5882 12.7644 13.5882L11.3665 11.8494C11.3665 11.8494 10.6288 12.6605 10.291 13.1593C10.1116 13.4242 9.64185 14.1332 9.64185 14.1332L9.07295 15.2304C8.24895 17.0214 7.92073 18.8842 8.01596 20.8918ZM31.9633 21.2755L29.8026 20.6995C29.8208 20.1549 29.797 19.6129 29.7312 19.0733C29.6285 18.1723 29.3858 17.2999 29.0031 16.4562L30.8819 15.3714C31.3937 16.4747 31.7295 17.6169 31.8895 18.798C32.0056 19.6202 32.0302 20.4461 31.9633 21.2755ZM18.4931 8.55776C17.312 8.71775 16.1653 9.04578 15.053 9.54184L16.1513 11.4442C16.995 11.0615 17.8674 10.8188 18.7684 10.7161C19.6851 10.6043 20.6091 10.6137 21.5403 10.7441L22.1061 8.63249C20.8942 8.41364 19.6899 8.38872 18.4931 8.55776ZM24.1807 9.18837L23.6149 11.3C24.4865 11.6526 25.2869 12.0987 26.0158 12.6382C26.7448 13.1776 27.379 13.824 27.9183 14.5773L29.7972 13.4925C29.1223 12.5043 28.3055 11.6502 27.347 10.9302C26.4042 10.2011 25.3487 9.62046 24.1807 9.18837Z" />
+</vector>
diff --git a/core/res/res/drawable/ic_notification_summarization.xml b/core/res/res/drawable/ic_notification_summarization.xml
index d476872..acfd90e 100644
--- a/core/res/res/drawable/ic_notification_summarization.xml
+++ b/core/res/res/drawable/ic_notification_summarization.xml
@@ -16,9 +16,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
- android:tint="?android:attr/colorControlNormal"
- android:viewportHeight="960"
- android:viewportWidth="960">
- <path android:fillColor="#ffffff"
- android:pathData="M120,840L120,760L600,760L600,840L120,840ZM120,640L120,560L840,560L840,640L120,640ZM120,440L120,360L560,360L560,440L120,440ZM700,480Q700,388 636,324Q572,260 480,260Q572,260 636,196Q700,132 700,40Q700,132 764,196Q828,260 920,260Q828,260 764,324Q700,388 700,480Z"/>
+ android:tint="@color/materialColorPrimary"
+ android:viewportHeight="14"
+ android:viewportWidth="14">
+ <path
+ android:pathData="M10,9C9.986,9 9.979,8.993 9.979,8.979C9.979,8.431 9.875,7.917 9.667,7.438C9.465,6.958 9.184,6.538 8.823,6.177C8.462,5.816 8.042,5.535 7.563,5.333C7.083,5.125 6.569,5.021 6.021,5.021C6.007,5.021 6,5.014 6,5C6,4.986 6.007,4.979 6.021,4.979C6.569,4.979 7.083,4.878 7.563,4.677C8.042,4.469 8.462,4.184 8.823,3.823C9.184,3.462 9.465,3.042 9.667,2.563C9.875,2.083 9.979,1.569 9.979,1.021C9.979,1.007 9.986,1 10,1C10.014,1 10.021,1.007 10.021,1.021C10.021,1.569 10.122,2.083 10.323,2.563C10.531,3.042 10.816,3.462 11.177,3.823C11.538,4.184 11.958,4.469 12.438,4.677C12.917,4.878 13.431,4.979 13.979,4.979C13.993,4.979 14,4.986 14,5C14,5.014 13.993,5.021 13.979,5.021C13.431,5.021 12.917,5.125 12.438,5.333C11.958,5.535 11.538,5.816 11.177,6.177C10.816,6.538 10.531,6.958 10.323,7.438C10.122,7.917 10.021,8.431 10.021,8.979C10.021,8.993 10.014,9 10,9Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M1,10.456C1,10.204 1.204,10 1.456,10H12.544C12.796,10 13,10.204 13,10.456V11.544C13,11.796 12.796,12 12.544,12H1.456C1.204,12 1,11.796 1,11.544V10.456Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M1,7.456C1,7.204 1.204,7 1.456,7H6.544C6.796,7 7,7.204 7,7.456V8.544C7,8.796 6.796,9 6.544,9H1.456C1.204,9 1,8.796 1,8.544V7.456Z"
+ android:fillColor="#ffffff"/>
</vector>
\ No newline at end of file
diff --git a/core/res/res/layout/accessibility_autoclick_type_panel.xml b/core/res/res/layout/accessibility_autoclick_type_panel.xml
index cedbdc1..902ef7f 100644
--- a/core/res/res/layout/accessibility_autoclick_type_panel.xml
+++ b/core/res/res/layout/accessibility_autoclick_type_panel.xml
@@ -17,7 +17,7 @@
*/
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.server.accessibility.autoclick.AutoclickLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/accessibility_autoclick_type_panel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -130,4 +130,4 @@
</LinearLayout>
-</LinearLayout>
+</com.android.server.accessibility.autoclick.AutoclickLinearLayout>
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index b6b5d8b..ae05666 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Jy kan jou gehoortoestel en mikrofoon vir handvrye oproepe gebruik. Dit skakel net jou mikrofoon tydens die oproep oor."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Skakel oor"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Instellings"</string>
- <string name="user_switched" msgid="7249833311585228097">"Huidige gebruiker <xliff:g id="NAME">%1$s</xliff:g> ."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Skakel tans oor na <xliff:g id="NAME">%1$s</xliff:g> …"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Meld <xliff:g id="NAME">%1$s</xliff:g> tans af …"</string>
<string name="owner_name" msgid="8713560351570795743">"Eienaar"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 29b153b..19be04e 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"የእርስዎን መስሚያ አጋዥ መሣሪያ ማይክሮፎን ለነጻ እጅ መደወል መጠቀም ይችላሉ። ይህ በጥሪው ወቅት ማይክሮፎንዎን ብቻ ይቀይራል።"</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"ቀይር"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"ቅንብሮች"</string>
- <string name="user_switched" msgid="7249833311585228097">"የአሁኑ ተጠቃሚ <xliff:g id="NAME">%1$s</xliff:g>።"</string>
<string name="user_switching_message" msgid="1912993630661332336">"ወደ <xliff:g id="NAME">%1$s</xliff:g> በመቀየር ላይ…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> በማውጣት ላይ…"</string>
<string name="owner_name" msgid="8713560351570795743">"ባለቤት"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 08d0af5..66192cb 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -1829,7 +1829,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"يمكنك استخدام ميكروفون سماعة الأذن الطبية لإجراء مكالمات بدون لمس الجهاز. يؤدي هذا الإجراء إلى تبديل الميكروفون فقط أثناء المكالمة."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"تبديل"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"الإعدادات"</string>
- <string name="user_switched" msgid="7249833311585228097">"المستخدم الحالي <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"جارٍ التبديل إلى <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"جارٍ الخروج <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"المالك"</string>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index 9eab313..b85be47 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"আপুনি হেণ্ডছ্-ফ্ৰী কলিঙৰ বাবে আপোনাৰ শ্ৰৱণ যন্ত্ৰৰ মাইক্ৰ’ফ’ন ব্যৱহাৰ কৰিব পাৰে। এইটোৱে কল চলি থাকোঁতে কেৱল আপোনাৰ মাইকটো সলনি কৰে।"</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"সলনি কৰক"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"ছেটিং"</string>
- <string name="user_switched" msgid="7249833311585228097">"বৰ্তমানৰ ব্যৱহাৰকাৰী <xliff:g id="NAME">%1$s</xliff:g>।"</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g>লৈ সলনি কৰি থকা হৈছে…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g>ৰ পৰা লগ আউট কৰি থকা হৈছে…"</string>
<string name="owner_name" msgid="8713560351570795743">"গৰাকী"</string>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index 3aec62a..b9bd543 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Səsli idarəetmə ilə zəng etmək üçün eşitmə aparatı mikrofonunuzdan istifadə edə bilərsiniz. Bu, yalnız zəng zamanı mikrofonu dəyişdirir."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Dəyişin"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Ayarlar"</string>
- <string name="user_switched" msgid="7249833311585228097">"Cari istifadəçi <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> adına keçirilir…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> çıxır..."</string>
<string name="owner_name" msgid="8713560351570795743">"Sahib"</string>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index 47203a9..a558de3 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -1804,8 +1804,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Režim jednom rukom"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Dodatno zatamni"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Slušni aparati"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Automatski klik"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Veza je prekinuta"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Povezano"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Aktivno"</string>
@@ -1826,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Možete da koristite mikrofon slušnog aparata za hendsfri pozivanje. Time se mikrofon menja samo tokom poziva."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Promeni"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Podešavanja"</string>
- <string name="user_switched" msgid="7249833311585228097">"Aktuelni korisnik <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Prebacivanje na <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Odjavljuje se <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Vlasnik"</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index 8230f0c..113be9d 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -159,7 +159,7 @@
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> праз <xliff:g id="TIME_DELAY">{2}</xliff:g> с."</string>
<string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: не пераадрасоўваецца"</string>
<string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: не пераадрасоўваецца"</string>
- <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Сеткавая бяспека"</string>
+ <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Бяспека мабільнай сеткі"</string>
<string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Шыфраванне, апавяшчэнні для незашыфраваных сетак"</string>
<string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Атрыманы доступ да ідэнтыфікатара прылады"</string>
<string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"У <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> у сетцы паблізу быў запісаны ўнікальны ідэнтыфікатар вашай прылады (IMSI або IMEI) пры выкарыстанні SIM-карты <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>"</string>
@@ -1827,7 +1827,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Вы можаце выкарыстоўваць мікрафон слыхавога апарата, каб размаўляць падчас званка без дапамогі рук. Будзе пераключаны толькі ваш мікрафон."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Пераключыцца"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Налады"</string>
- <string name="user_switched" msgid="7249833311585228097">"Бягучы карыстальнік <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Пераключэнне на карыстальніка \"<xliff:g id="NAME">%1$s</xliff:g>\"…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> выходзіць з сістэмы…"</string>
<string name="owner_name" msgid="8713560351570795743">"Уладальнік"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index d9471e6..c36940f 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Можете да използвате микрофона на слуховия си апарат за обаждания в режим „свободни ръце“. Микрофонът ще бъде включен само по време на обаждането."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Превключване"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Настройки"</string>
- <string name="user_switched" msgid="7249833311585228097">"Текущ потребител <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Превключва се към: <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> излиза…"</string>
<string name="owner_name" msgid="8713560351570795743">"Собственик"</string>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index cc05d28..b79c1d7 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -327,7 +327,7 @@
<string name="permgroupdesc_contacts" msgid="9163927941244182567">"আপনার পরিচিতিগুলিতে অ্যাক্সেস"</string>
<string name="permgrouplab_location" msgid="1858277002233964394">"লোকেশন"</string>
<string name="permgroupdesc_location" msgid="1995955142118450685">"এই ডিভাইসের লোকেশন অ্যাক্সেস"</string>
- <string name="permgrouplab_calendar" msgid="6426860926123033230">"ক্যালেন্ডার"</string>
+ <string name="permgrouplab_calendar" msgid="6426860926123033230">"Calendar"</string>
<string name="permgroupdesc_calendar" msgid="6762751063361489379">"আপনার ক্যালেন্ডারে অ্যাক্সেস"</string>
<string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
<string name="permgroupdesc_sms" msgid="5726462398070064542">"এসএমএসগুলি পাঠাতে এবং দেখতে"</string>
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"হ্যান্ডস-ফ্রি কলিংয়ের জন্য আপনার হিয়ারিং এডের মাইক্রোফোন ব্যবহার করতে পারবেন। এটি শুধুমাত্র কল চলাকালীন আপনার মাইক পরিবর্তন করে।"</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"পরিবর্তন করুন"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"সেটিংস"</string>
- <string name="user_switched" msgid="7249833311585228097">"বর্তমান ব্যবহারকারী <xliff:g id="NAME">%1$s</xliff:g>৷"</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> প্রোফাইলে পাল্টানো হচ্ছে…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g>কে লগ-আউট করা হচ্ছে..."</string>
<string name="owner_name" msgid="8713560351570795743">"মালিক"</string>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index 9c553ce..a995746 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -1826,7 +1826,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Možete koristiti mikrofon slušnog aparata za pozivanje bez dodira. Ovo mijenja mikrofon samo tokom poziva."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Promijeni"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Postavke"</string>
- <string name="user_switched" msgid="7249833311585228097">"Trenutni korisnik <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Prebacivanje na korisnika <xliff:g id="NAME">%1$s</xliff:g>..."</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Odjava korisnika <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Vlasnik"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 3fa177e..62f59f2 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -1826,7 +1826,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Pots fer servir el micròfon del teu audiòfon per fer trucades amb mans lliures. Aquesta opció només canvia el micròfon durant la trucada."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Canvia"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Configuració"</string>
- <string name="user_switched" msgid="7249833311585228097">"Usuari actual: <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"S\'està canviant a <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"S\'està tancant la sessió de l\'usuari <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Propietari"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 670ca33..5e7a8aa 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -1827,7 +1827,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"K handsfree telefonování můžete použít mikrofon naslouchátka. Mikrofon se přepne jen během hovoru."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Přepnout"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Nastavení"</string>
- <string name="user_switched" msgid="7249833311585228097">"Aktuální uživatel je <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Přepínání na uživatele <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Odhlašování uživatele <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Vlastník"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 47eb357..ab73394 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Du kan bruge mikrofonen i dit høreapparat til at foretage håndfrie opkald. Der skiftes kun mikrofon under opkaldet."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Skift"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Indstillinger"</string>
- <string name="user_switched" msgid="7249833311585228097">"Nuværende bruger <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Skifter til <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> logges ud…"</string>
<string name="owner_name" msgid="8713560351570795743">"Ejer"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 06a35e6..8bff350 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Du kannst das Mikrofon deines Hörgeräts für Anrufe per Sprachbefehl verwenden. Das Mikrofon wird nur für die Dauer des Anrufs gewechselt."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Wechseln"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Einstellungen"</string>
- <string name="user_switched" msgid="7249833311585228097">"Aktueller Nutzer <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="user_switching_message" msgid="1912993630661332336">"Wechseln zu <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> wird abgemeldet…"</string>
<string name="owner_name" msgid="8713560351570795743">"Eigentümer"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 8a12e0e..0e968bd 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Μπορείτε να χρησιμοποιήσετε το μικρόφωνο του βοηθήματος ακοής σας για κλήσεις handsfree. Με αυτή την ενέργεια, το μικρόφωνό σας αλλάζει μόνο κατά τη διάρκεια της κλήσης."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Εναλλαγή"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Ρυθμίσεις"</string>
- <string name="user_switched" msgid="7249833311585228097">"Τρέχων χρήστης <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Εναλλαγή σε <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Αποσύνδεση <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Κάτοχος"</string>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index 3881f1d..6c43f9a 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"You can use your hearing aid microphone for hands-free calling. This only switches your mic during the call."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Switch"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Settings"</string>
- <string name="user_switched" msgid="7249833311585228097">"Current user <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Switching to <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Logging out <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Owner"</string>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index e6c4047..99abe8b 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -1824,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"You can use your hearing aid microphone for hands-free calling. This only switches your mic during the call."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Switch"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Settings"</string>
- <string name="user_switched" msgid="7249833311585228097">"Current user <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Switching to <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Logging out <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Owner"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index 9680b83..99040f3 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"You can use your hearing aid microphone for hands-free calling. This only switches your mic during the call."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Switch"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Settings"</string>
- <string name="user_switched" msgid="7249833311585228097">"Current user <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Switching to <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Logging out <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Owner"</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index fbe1c0a..cb1e92ed 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"You can use your hearing aid microphone for hands-free calling. This only switches your mic during the call."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Switch"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Settings"</string>
- <string name="user_switched" msgid="7249833311585228097">"Current user <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Switching to <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Logging out <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Owner"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 54297a5..18cbfaa 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -1826,7 +1826,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Puedes usar el micrófono de tu audífono para realizar llamadas sin usar las manos. Esto solo cambia el micrófono durante la llamada."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Cambiar"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Configuración"</string>
- <string name="user_switched" msgid="7249833311585228097">"Usuario actual: <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="user_switching_message" msgid="1912993630661332336">"Cambiando a <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Saliendo de <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Propietario"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 5bd2bf5..8523473 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -1826,7 +1826,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Puedes usar el micrófono de tu audífono para hacer llamadas en manos libres. Esta opción solo cambia el micrófono durante la llamada."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Cambiar"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Ajustes"</string>
- <string name="user_switched" msgid="7249833311585228097">"Usuario actual: <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="user_switching_message" msgid="1912993630661332336">"Cambiando a <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Cerrando la sesión de <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Propietario"</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index 0c928a6..a1ddf81 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -652,7 +652,7 @@
<string name="permdesc_imagesWrite" msgid="5195054463269193317">"Võimaldab rakendusel muuta teie fotokogu."</string>
<string name="permlab_mediaLocation" msgid="7368098373378598066">"Lugeda teie meediakogus olevaid asukohti"</string>
<string name="permdesc_mediaLocation" msgid="597912899423578138">"Võimaldab rakendusel lugeda teie meediakogus olevaid asukohti."</string>
- <string name="permlab_eye_tracking_coarse" msgid="7989596289790269059">"ligikaudse pilgu jälgimine"</string>
+ <string name="permlab_eye_tracking_coarse" msgid="7989596289790269059">"pilgu ligikaudne jälgimine"</string>
<string name="permdesc_eye_tracking_coarse" msgid="870510233930553355">"Võimaldab rakendusel teie pilku ligikaudselt jälgida."</string>
<string name="permlab_eye_tracking_fine" msgid="6914457357027049512">"pilgu suuna jälgimine"</string>
<string name="permdesc_eye_tracking_fine" msgid="5788889152304524730">"Võimaldab rakendusel juurde pääseda täpsetele pilguandmetele."</string>
@@ -665,9 +665,9 @@
<string name="permlab_scene_understanding_coarse" msgid="6518646430502858641">"vahetu keskkonna mõistmine"</string>
<string name="permdesc_scene_understanding_coarse" msgid="4508880777646198656">"Võimaldab rakendusel juurde pääseda teie vahetu keskkonna jälgimisandmetele."</string>
<string name="permlab_scene_understanding_fine" msgid="409126403264393251">"vahetu keskkonna väga üksikasjalik mõistmine"</string>
- <string name="permdesc_scene_understanding_fine" msgid="6223368011593524179">"Võimaldab rakendusel pääseda väga üksikasjalikult juurde teie vahetu keskkonna jälgimisandmetele."</string>
- <string name="permlab_xr_tracking_in_background" msgid="7117098718465619023">"juurdepääs XR-i andmetele, kui see pole esiplaanil"</string>
- <string name="permdesc_xr_tracking_in_background" msgid="939504041387836853">"Võimaldab rakendusel pääseda juurde XR-i andmetele, kui see pole esiplaanil."</string>
+ <string name="permdesc_scene_understanding_fine" msgid="6223368011593524179">"Võimaldab rakendusel pääseda juurde teie ümbruskonna üksikasjalikele jälgimisandmetele."</string>
+ <string name="permlab_xr_tracking_in_background" msgid="7117098718465619023">"juurdepääs XR-i andmetele, kui rakendus pole esiplaanil"</string>
+ <string name="permdesc_xr_tracking_in_background" msgid="939504041387836853">"Võimaldab rakendusel pääseda juurde XR-i andmetele, kui rakendus pole esiplaanil."</string>
<string name="biometric_app_setting_name" msgid="3339209978734534457">"Biomeetria kasutamine"</string>
<string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Biomeetria või ekraaniluku kasutamine"</string>
<string name="biometric_dialog_default_title" msgid="55026799173208210">"Kinnitage oma isik"</string>
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Saate kasutada oma kuuldeaparaadi mikrofon vabakäerežiimis helistamiseks. See vahetab teie mikrofoni ainult kõne ajal."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Vaheta"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Seaded"</string>
- <string name="user_switched" msgid="7249833311585228097">"Praegune kasutaja <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Üleminek kasutajale <xliff:g id="NAME">%1$s</xliff:g> ..."</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Kasutaja <xliff:g id="NAME">%1$s</xliff:g> väljalogimine …"</string>
<string name="owner_name" msgid="8713560351570795743">"Omanik"</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index 13f0452..c63cfe6 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Audifonoaren mikrofonoa erabil dezakezu esku libreko deiak egiteko. Deirako soilik aldatzen da mikrofonoa."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Aldatu"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Ezarpenak"</string>
- <string name="user_switched" msgid="7249833311585228097">"Erabiltzailea: <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"\"<xliff:g id="NAME">%1$s</xliff:g>\" erabiltzailera aldatzen…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> erabiltzailearen saioa amaitzen…"</string>
<string name="owner_name" msgid="8713560351570795743">"Jabea"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 2eaf4c0..5e1caad 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"برای تماس دستآزاد میتوانید از میکروفون سمعک خود استفاده کنید. این کار فقط درطول تماس میکروفون شما را عوض میکند."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"عوض کردن"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"تنظیمات"</string>
- <string name="user_switched" msgid="7249833311585228097">"کاربر کنونی <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"در حالت تغییر به <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"در حال خروج از سیستم <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"مالک"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 280658c..f7115eb 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Voit soittaa ääniohjatusti kuulolaitteen mikrofonin avulla. Tämä vaihtaa mikrofonia vain puhelun ajaksi."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Vaihda"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Asetukset"</string>
- <string name="user_switched" msgid="7249833311585228097">"Nykyinen käyttäjä: <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Vaihdetaan käyttäjään <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> kirjautuu ulos…"</string>
<string name="owner_name" msgid="8713560351570795743">"Omistaja"</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index 1740ce5..56623a2 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -1826,7 +1826,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Vous pouvez utiliser votre microphone pour prothèse auditive pour faire des appels en mode mains libres. Cette option ne change votre micro que pendant l\'appel."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Changer"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Paramètres"</string>
- <string name="user_switched" msgid="7249833311585228097">"Utilisateur actuel : <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="user_switching_message" msgid="1912993630661332336">"Passage au profil : <xliff:g id="NAME">%1$s</xliff:g> en cours…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Déconnexion de <xliff:g id="NAME">%1$s</xliff:g> en cours..."</string>
<string name="owner_name" msgid="8713560351570795743">"Propriétaire"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 0564661..90a0f44 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -1826,7 +1826,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Vous pouvez utiliser le micro de votre appareil auditif pour passer des appels en mode mains-libres. Cette option change uniquement le micro pendant l\'appel."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Changer"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Paramètres"</string>
- <string name="user_switched" msgid="7249833311585228097">"Utilisateur actuel : <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="user_switching_message" msgid="1912993630661332336">"Passage à <xliff:g id="NAME">%1$s</xliff:g>..."</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Déconnexion de <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Propriétaire"</string>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index 73bed7f..a2b5381 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Podes usar o micrófono do audiófono para facer chamadas coas mans libres. Esta acción só cambia o micrófono durante a chamada."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Cambiar"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Configuración"</string>
- <string name="user_switched" msgid="7249833311585228097">"Usuario actual <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Cambiando a <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Pechando sesión de <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Propietario"</string>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index 0c99096..9d57bde 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"તમે હાથના ઉપયોગ વિના કૉલ કરવા માટે સાંભળવામાં મદદ આપતા તમારા યંત્રના માઇક્રોફોનનો ઉપયોગ કરી શકો છો. કૉલ દરમિયાન આ ફક્ત તમારા માઇકને સ્વિચ કરે છે."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"સ્વિચ કરો"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"સેટિંગ"</string>
- <string name="user_switched" msgid="7249833311585228097">"વર્તમાન વપરાશકર્તા <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> પર સ્વિચ કરી રહ્યાં છીએ…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> લોગ આઉટ થઈ રહ્યાં છે…"</string>
<string name="owner_name" msgid="8713560351570795743">"માલિક"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index df211dc..563b724 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"बोलकर कॉल का जवाब देने के लिए, \'कान की मशीन का माइक्रोफ़ोन\' इस्तेमाल किया जा सकता है. इससे, कॉल के दौरान सिर्फ़ आपका माइक चालू या बंद होता है."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"स्विच करें"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"सेटिंग"</string>
- <string name="user_switched" msgid="7249833311585228097">"मौजूदा उपयोगकर्ता <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> पर स्विच किया जा रहा है…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> द्वारा प्रस्थान किया जा रहा है…"</string>
<string name="owner_name" msgid="8713560351570795743">"मालिक"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index f701483..fec624d 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -354,9 +354,9 @@
<string name="permgroupdesc_sensors" msgid="2610631290633747752">"pristupiti podacima senzora o vašim vitalnim znakovima"</string>
<string name="permgrouplab_notifications" msgid="5472972361980668884">"Obavijesti"</string>
<string name="permgroupdesc_notifications" msgid="4608679556801506580">"prikazati obavijesti"</string>
- <string name="permgrouplab_xr_tracking" msgid="7418994009794287471">"XR podaci o praćenju"</string>
- <string name="permgroupdesc_xr_tracking" msgid="6777198859446500821">"pristup XR podacima o vama i vašem okruženju"</string>
- <string name="permgrouplab_xr_tracking_sensitive" msgid="1194833982988144536">"osjetljivi XR podaci o praćenju"</string>
+ <string name="permgrouplab_xr_tracking" msgid="7418994009794287471">"podaci o praćenju za proširenu stvarnost"</string>
+ <string name="permgroupdesc_xr_tracking" msgid="6777198859446500821">"pristup podacima proširene stvarnosti o vama i vašem okruženju"</string>
+ <string name="permgrouplab_xr_tracking_sensitive" msgid="1194833982988144536">"osjetljivi podaci o praćenju za XR"</string>
<string name="permgroupdesc_xr_tracking_sensitive" msgid="9178027369004805829">"pristup osjetljivim podacima o praćenju, kao što je pogled"</string>
<string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"Dohvaćati sadržaj prozora"</string>
<string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"Pregledat će sadržaj prozora koji upotrebljavate."</string>
@@ -655,7 +655,7 @@
<string name="permdesc_mediaLocation" msgid="597912899423578138">"Omogućuje aplikaciji čitanje lokacija iz vaše medijske zbirke."</string>
<string name="permlab_eye_tracking_coarse" msgid="7989596289790269059">"praćenje približnog pogleda"</string>
<string name="permdesc_eye_tracking_coarse" msgid="870510233930553355">"Aplikaciji omogućuje praćenje približnih podataka o pogledu."</string>
- <string name="permlab_eye_tracking_fine" msgid="6914457357027049512">"praćenje toga gdje gledate"</string>
+ <string name="permlab_eye_tracking_fine" msgid="6914457357027049512">"praćenje mjesta u koje gledate"</string>
<string name="permdesc_eye_tracking_fine" msgid="5788889152304524730">"Aplikaciji omogućuje pristup preciznim podacima o pogledu."</string>
<string name="permlab_face_tracking" msgid="2272048395128283324">"praćenje lica"</string>
<string name="permdesc_face_tracking" msgid="2622783922311211866">"Aplikaciji omogućuje pristup podacima o praćenju lica."</string>
@@ -664,10 +664,10 @@
<string name="permlab_head_tracking" msgid="1309731456372087270">"praćenje pokreta glave"</string>
<string name="permdesc_head_tracking" msgid="231597390513699188">"Aplikaciji omogućuje pristup podacima o praćenju pokreta glave."</string>
<string name="permlab_scene_understanding_coarse" msgid="6518646430502858641">"stjecanje uvida u neposredno okruženje"</string>
- <string name="permdesc_scene_understanding_coarse" msgid="4508880777646198656">"Aplikaciji omogućuje pristup podacima o praćenju o vašem neposrednom okruženju."</string>
+ <string name="permdesc_scene_understanding_coarse" msgid="4508880777646198656">"Aplikaciji omogućuje pristup podacima o praćenju vašeg neposrednog okruženja."</string>
<string name="permlab_scene_understanding_fine" msgid="409126403264393251">"stjecanje detaljnih uvida u neposredno okruženje"</string>
- <string name="permdesc_scene_understanding_fine" msgid="6223368011593524179">"Aplikaciji omogućuje pristup detaljnim podacima o praćenju o vašem neposrednom okruženju."</string>
- <string name="permlab_xr_tracking_in_background" msgid="7117098718465619023">"pristup XR podacima dok nije u prednjem planu"</string>
+ <string name="permdesc_scene_understanding_fine" msgid="6223368011593524179">"Aplikaciji omogućuje pristup detaljnim podacima o praćenju vašeg neposrednog okruženja."</string>
+ <string name="permlab_xr_tracking_in_background" msgid="7117098718465619023">"pristup podacima proširene stvarnosti dok nije u prednjem planu"</string>
<string name="permdesc_xr_tracking_in_background" msgid="939504041387836853">"Aplikaciji omogućuje pristup XR podacima dok nije u prednjem planu."</string>
<string name="biometric_app_setting_name" msgid="3339209978734534457">"Upotreba biometrije"</string>
<string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Upotreba biometrijske autentifikacije ili zaključavanja zaslona"</string>
@@ -1714,7 +1714,7 @@
<string name="wireless_display_route_description" msgid="8297563323032966831">"Bežični prikaz"</string>
<string name="media_route_button_content_description" msgid="2299223698196869956">"Emitiranje"</string>
<string name="media_route_chooser_title" msgid="6646594924991269208">"Povezivanje s uređajem"</string>
- <string name="media_route_chooser_title_for_remote_display" msgid="3105906508794326446">"Emitiranje zaslona na uređaj"</string>
+ <string name="media_route_chooser_title_for_remote_display" msgid="3105906508794326446">"Emitirajte zaslon na uređaj"</string>
<string name="media_route_chooser_searching" msgid="6119673534251329535">"Traženje uređaja…"</string>
<string name="media_route_chooser_extended_settings" msgid="2506352159381327741">"Postavke"</string>
<string name="media_route_controller_disconnect" msgid="7362617572732576959">"Prekini vezu"</string>
@@ -1826,7 +1826,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Mikrofon slušnog pomagala možete koristiti za pozivanje bez upotrebe ruku. Time se mikrofon prebacuje samo tijekom poziva."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Prebaci"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Postavke"</string>
- <string name="user_switched" msgid="7249833311585228097">"Trenutačni korisnik <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Prebacivanje na korisnika <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Odjavljivanje korisnika <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Vlasnik"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index e3d261e..7871aba 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Használhatja hallókészüléke mikrofonját a szabadkezes hívásokhoz. Csak a mikrofont kapcsolja át hívás közben."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Váltás"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Beállítások"</string>
- <string name="user_switched" msgid="7249833311585228097">"<xliff:g id="NAME">%1$s</xliff:g> az aktuális felhasználó."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Átváltás erre: <xliff:g id="NAME">%1$s</xliff:g>..."</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> kijelentkeztetése folyamatban van…"</string>
<string name="owner_name" msgid="8713560351570795743">"Tulajdonos"</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index de62af5..01e5110 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Լսողական սարքի խոսափողը կարող եք օգտագործել ձայնային կառավարման համար։ Դուք կանցնեք խոսափողին միայն զանգի ընթացքում։"</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Անցնել"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Կարգավորումներ"</string>
- <string name="user_switched" msgid="7249833311585228097">"Ներկայիս օգտատերը <xliff:g id="NAME">%1$s</xliff:g>:"</string>
<string name="user_switching_message" msgid="1912993630661332336">"Անցում <xliff:g id="NAME">%1$s</xliff:g> պրոֆիլին..."</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Ելք <xliff:g id="NAME">%1$s</xliff:g>-ից…"</string>
<string name="owner_name" msgid="8713560351570795743">"Սեփականատեր"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 18ae5e9..df42e47 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Anda dapat menggunakan mikrofon alat bantu dengar untuk melakukan panggilan handsfree. Tindakan ini hanya akan mengalihkan mikrofon Anda selama panggilan."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Alihkan"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Setelan"</string>
- <string name="user_switched" msgid="7249833311585228097">"Pengguna saat ini <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Beralih ke <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Mengeluarkan <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Pemilik"</string>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index a74ff1c..40d642d 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -1713,7 +1713,7 @@
<string name="wireless_display_route_description" msgid="8297563323032966831">"Þráðlaus skjábirting"</string>
<string name="media_route_button_content_description" msgid="2299223698196869956">"Senda út"</string>
<string name="media_route_chooser_title" msgid="6646594924991269208">"Tengjast tæki"</string>
- <string name="media_route_chooser_title_for_remote_display" msgid="3105906508794326446">"Senda skjá út í tæki"</string>
+ <string name="media_route_chooser_title_for_remote_display" msgid="3105906508794326446">"Varpa skjá í tæki"</string>
<string name="media_route_chooser_searching" msgid="6119673534251329535">"Leitar að tækjum…"</string>
<string name="media_route_chooser_extended_settings" msgid="2506352159381327741">"Stillingar"</string>
<string name="media_route_controller_disconnect" msgid="7362617572732576959">"Aftengja"</string>
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Þú getur notað hljóðnema heyrnartækisins þíns til að hringja eða svara símtölum handfrjálst. Aðeins verður skipt um hljóðnema á meðan á símtali stendur."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Skipta"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Stillingar"</string>
- <string name="user_switched" msgid="7249833311585228097">"Núverandi notandi <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Skiptir yfir á <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Skráir <xliff:g id="NAME">%1$s</xliff:g> út…"</string>
<string name="owner_name" msgid="8713560351570795743">"Eigandi"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index befd2b2..e501c57 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -277,7 +277,7 @@
<string name="bugreport_option_full_title" msgid="7681035745950045690">"Report completo"</string>
<string name="bugreport_option_full_summary" msgid="1975130009258435885">"Utilizza questa opzione per ridurre al minimo l\'interferenza di sistema quando il dispositivo non risponde, è troppo lento oppure quando ti servono tutte le sezioni della segnalazione. Non puoi inserire altri dettagli o acquisire altri screenshot."</string>
<string name="bugreport_countdown" msgid="6418620521782120755">"{count,plural, =1{Lo screenshot per la segnalazione di bug verrà acquisito tra # secondo.}many{Lo screenshot per la segnalazione di bug verrà acquisito tra # secondi.}other{Lo screenshot per la segnalazione di bug verrà acquisito tra # secondi.}}"</string>
- <string name="bugreport_screenshot_success_toast" msgid="7986095104151473745">"Screenshot con segnalazione di bug effettuato correttamente"</string>
+ <string name="bugreport_screenshot_success_toast" msgid="7986095104151473745">"Screenshot con segnalazione di bug effettuato"</string>
<string name="bugreport_screenshot_failure_toast" msgid="6736320861311294294">"Impossibile acquisire screenshot con segnalazione di bug"</string>
<string name="global_action_toggle_silent_mode" msgid="8464352592860372188">"Modalità silenziosa"</string>
<string name="global_action_silent_mode_on_status" msgid="2371892537738632013">"Audio non attivo"</string>
@@ -1804,8 +1804,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Modalità a una mano"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Attenuazione extra"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Protesi uditive"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Clic automatico"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Disconnesso"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Connesso"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Attivo"</string>
@@ -1826,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Puoi usare il microfono dell\'apparecchio acustico per le chiamate in vivavoce. In questo modo, il microfono viene attivato solo durante la chiamata."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Cambia"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Impostazioni"</string>
- <string name="user_switched" msgid="7249833311585228097">"Utente corrente <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Passaggio a <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Disconnessione di <xliff:g id="NAME">%1$s</xliff:g> in corso…"</string>
<string name="owner_name" msgid="8713560351570795743">"Proprietario"</string>
@@ -2282,7 +2280,7 @@
<string name="conversation_title_fallback_one_to_one" msgid="1980753619726908614">"Conversazione"</string>
<string name="conversation_title_fallback_group_chat" msgid="456073374993104303">"Conversazione di gruppo"</string>
<string name="unread_convo_overflow" msgid="920517615597353833">"<xliff:g id="MAX_UNREAD_COUNT">%1$d</xliff:g>+"</string>
- <string name="resolver_personal_tab" msgid="2051260504014442073">"Personale"</string>
+ <string name="resolver_personal_tab" msgid="2051260504014442073">"Personali"</string>
<string name="resolver_work_tab" msgid="2690019516263167035">"Lavoro"</string>
<string name="resolver_personal_tab_accessibility" msgid="5739524949153091224">"Visualizzazione personale"</string>
<string name="resolver_work_tab_accessibility" msgid="4753168230363802734">"Visualizzazione di lavoro"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 0b6c80a..c9984e2 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -1826,7 +1826,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"אפשר לשוחח במצב דיבורית באמצעות המיקרופון של מכשיר השמיעה. במצב הזה המיקרופון מוחלף רק במהלך השיחה."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"החלפה"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"הגדרות"</string>
- <string name="user_switched" msgid="7249833311585228097">"המשתמש הנוכחי <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"מעבר אל <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"מתבצע ניתוק של <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"בעלים"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 2f8133a..7ab896e 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"片手モード"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"さらに輝度を下げる"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"補聴器"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"自動クリック"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"未接続"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"接続済み"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"有効"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"補聴器のマイクを使ってハンズフリー通話を行えます。通話時のみマイクが切り替わります。"</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"切り替える"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"設定"</string>
- <string name="user_switched" msgid="7249833311585228097">"現在のユーザーは<xliff:g id="NAME">%1$s</xliff:g>です。"</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g>に切り替えています…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> をログアウトしています…"</string>
<string name="owner_name" msgid="8713560351570795743">"所有者"</string>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index ef15405..4d634d4 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"შეგიძლიათ სმენის მოწყობილობის მიკროფონის გამოყენება უკონტაქტოდ დასარეკად. ეს გადართავს თქვენს მიკროფონს მხოლოდ ზარის დროს."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"გადართვა"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"პარამეტრები"</string>
- <string name="user_switched" msgid="7249833311585228097">"ამჟამინდელი მომხმარებელი <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g>-ზე გადართვა…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g>-ის ანგარიშიდან გასვლა…"</string>
<string name="owner_name" msgid="8713560351570795743">"მფლობელი"</string>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index b575630..8d54a32 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Дауыспен басқару арқылы қоңырау шалу үшін есту аппаратының микрофонын пайдалана аласыз. Микрофонға тек қоңырау кезінде ауысады."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Ауысу"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Параметрлер"</string>
- <string name="user_switched" msgid="7249833311585228097">"Ағымдағы пайдаланушы <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> профиліне ауысу…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> ішінен шығу…"</string>
<string name="owner_name" msgid="8713560351570795743">"Құрылғы иесі"</string>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index eeae49b..d7e0299 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"អ្នកអាចប្រើមីក្រូហ្វូនឧបករណ៍ជំនួយការស្ដាប់របស់អ្នកសម្រាប់ការហៅទូរសព្ទដោយមិនប្រើដៃ។ ការធ្វើបែបនេះប្ដូរមីក្រូហ្វូនរបស់អ្នក ក្នុងអំឡុងការហៅទូរសព្ទតែប៉ុណ្ណោះ។"</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"ប្ដូរ"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"ការកំណត់"</string>
- <string name="user_switched" msgid="7249833311585228097">"អ្នកប្រើបច្ចុប្បន្ន <xliff:g id="NAME">%1$s</xliff:g> ។"</string>
<string name="user_switching_message" msgid="1912993630661332336">"កំពុងប្ដូរទៅ <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"កំពុងចេញ <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"ម្ចាស់"</string>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index fd468b4..9771def 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"ಹ್ಯಾಂಡ್ಸ್-ಫ್ರೀ ಕರೆ ಮಾಡುವಿಕೆಗಾಗಿ ನಿಮ್ಮ ಶ್ರವಣ ಸಾಧನದ ಮೈಕ್ರೊಫೋನ್ ಅನ್ನು ನೀವು ಬಳಸಬಹುದು. ಇದು ಕರೆಯ ಸಮಯದಲ್ಲಿ ಮಾತ್ರ ನಿಮ್ಮ ಮೈಕ್ ಅನ್ನು ಬದಲಾಯಿಸುತ್ತದೆ."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"ಬದಲಿಸಿ"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"ಸೆಟ್ಟಿಂಗ್ಗಳು"</string>
- <string name="user_switched" msgid="7249833311585228097">"ಪ್ರಸ್ತುತ ಬಳಕೆದಾರರು <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g>ಗೆ ಬದಲಾಯಿಸಲಾಗುತ್ತಿದೆ…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> ಅವರನ್ನು ಲಾಗ್ ಔಟ್ ಮಾಡಲಾಗುತ್ತಿದೆ…"</string>
<string name="owner_name" msgid="8713560351570795743">"ಮಾಲೀಕರು"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 0a88670..a39db4b 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"보청기 마이크로 핸즈프리 통화 기능을 이용할 수 있습니다. 통화 중에만 마이크가 전환됩니다."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"전환"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"설정"</string>
- <string name="user_switched" msgid="7249833311585228097">"현재 사용자는 <xliff:g id="NAME">%1$s</xliff:g>님입니다."</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g>로 전환하는 중…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g>님을 로그아웃하는 중…"</string>
<string name="owner_name" msgid="8713560351570795743">"소유자"</string>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index 83a1c69..fbc3afd 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Угуу аппаратыңыздын микрофонун үн режиминде чалуу үчүн колдоно аласыз. Микрофонуңуз чалуу учурунда гана которулат."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Которулуу"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Параметрлер"</string>
- <string name="user_switched" msgid="7249833311585228097">"Учурдагы колдонуучу <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> дегенге которулууда…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> чыгууда…"</string>
<string name="owner_name" msgid="8713560351570795743">"Ээси"</string>
@@ -2502,8 +2501,8 @@
<string name="satellite_manual_selection_state_popup_ok" msgid="2459664752624985095">"Күйгүзүү"</string>
<string name="satellite_manual_selection_state_popup_cancel" msgid="973605633339469252">"Артка кайтуу"</string>
<string name="unarchival_session_app_label" msgid="6811856981546348205">"Кезекте турат..."</string>
- <string name="satellite_sos_available_notification_title" msgid="5396708154268096124">"Спутник SOS эми жеткиликтүү"</string>
- <string name="satellite_sos_available_notification_summary" msgid="1727088812951848330">"Мобилдик Интернет же Wi-Fi тармагы жок болсо, кырсыктаганда жардамга келчү кызматтарга билдирүү жөнөтө аласыз. Google Messages демейки жазышуу колдонмоңуз болушу керек."</string>
+ <string name="satellite_sos_available_notification_title" msgid="5396708154268096124">"Спутник SOS кызматы иштеп баштады"</string>
+ <string name="satellite_sos_available_notification_summary" msgid="1727088812951848330">"Мобилдик Интернет же Wi-Fi жок болгон учурда, кырсыктаганда жардамга келчү кызматтарга билдирүү жөнөтө аласыз. Google Жазышуу демейки колдонмоңуз болушу керек."</string>
<string name="satellite_sos_not_supported_notification_title" msgid="2659100983227637285">"Спутник SOS колдоого алынбайт"</string>
<string name="satellite_sos_not_supported_notification_summary" msgid="1071762454665310549">"Бул түзмөктө спутник SOS колдоого алынбайт"</string>
<string name="satellite_sos_not_provisioned_notification_title" msgid="8564738683795406715">"Спутник SOS туураланган жок"</string>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index f32ede6..a0a6b38 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"ທ່ານສາມາດໃຊ້ໄມໂຄຣໂຟນຂອງເຄື່ອງຊ່ວຍຟັງເພື່ອການໂທແບບແຮນຟຣີໄດ້. ການດຳເນີນການນີ້ພຽງແຕ່ປ່ຽນໄມໂຄຣໂຟນຂອງທ່ານໃນລະຫວ່າງການໂທເທົ່ານັ້ນ."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"ປ່ຽນ"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"ການຕັ້ງຄ່າ"</string>
- <string name="user_switched" msgid="7249833311585228097">"ຜູ່ໃຊ້ປັດຈຸບັນ <xliff:g id="NAME">%1$s</xliff:g> ."</string>
<string name="user_switching_message" msgid="1912993630661332336">"ກຳລັງສະຫຼັບໄປຫາ <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"ກຳລັງອອກຈາກລະບົບ <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"ເຈົ້າຂອງ"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 51311b37..0b4967a 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -1827,7 +1827,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Galite naudoti klausos aparato mikrofoną skambučiams laisvų rankų režimu. Mikrofonas įjungiamas tik per skambutį."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Perjungti"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Nustatymai"</string>
- <string name="user_switched" msgid="7249833311585228097">"Dabartinis naudotojas: <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Perjungiama į <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Atsijungiama (<xliff:g id="NAME">%1$s</xliff:g>)…"</string>
<string name="owner_name" msgid="8713560351570795743">"Savininkas"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 3b3609e..0d19dc2 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -1826,7 +1826,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Varat izmantot dzirdes aparāta mikrofonu zvaniem brīvroku režīmā. Mikrofons tiek pārslēgts tikai zvana laikā."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Pārslēgt"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Iestatījumi"</string>
- <string name="user_switched" msgid="7249833311585228097">"Pašreizējais lietotājs: <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Notiek pāriešana uz: <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Notiek lietotāja <xliff:g id="NAME">%1$s</xliff:g> atteikšanās…"</string>
<string name="owner_name" msgid="8713560351570795743">"Īpašnieks"</string>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index 0c92fd5..9889ea9 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Може да го користите вашиот микрофон на слушното помагало за повикување без користење раце. Ова го префрла вашиот микрофон само за време на повикот."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Префрли"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Поставки"</string>
- <string name="user_switched" msgid="7249833311585228097">"Тековен корисник <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Се префрла на <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> се одјавува…"</string>
<string name="owner_name" msgid="8713560351570795743">"Сопственик"</string>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index cbdbd2d..ff07aa0 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"ഒറ്റക്കൈ മോഡ്"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"കൂടുതൽ ഡിം ചെയ്യൽ"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"ശ്രവണ ഉപകരണങ്ങൾ"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"ഓട്ടോക്ലിക്ക്"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"വിച്ഛേദിച്ചു"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"കണക്റ്റ് ചെയ്തു"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"സജീവം"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"ഹാൻഡ്സ്-ഫ്രീ ആയി കോൾ ചെയ്യാൻ നിങ്ങളുടെ ശ്രവണ സഹായി മൈക്രോഫോൺ ഉപയോഗിക്കാം. ഇത് കോൾ സമയത്ത് മാത്രം നിങ്ങളുടെ മൈക്ക് മാറ്റുന്നു."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"മാറുക"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"ക്രമീകരണം"</string>
- <string name="user_switched" msgid="7249833311585228097">"നിലവിലെ ഉപയോക്താവ് <xliff:g id="NAME">%1$s</xliff:g> ആണ്."</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> എന്ന ഉപയോക്താവിലേക്ക് മാറുന്നു…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> ലോഗൌട്ട് ചെയ്യുന്നു…"</string>
<string name="owner_name" msgid="8713560351570795743">"ഉടമ"</string>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index 4c72a86..3a1e0a6 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Та гараас хамаарахгүй дуудлагад сонсголын төхөөрөмжийнхөө микрофоныг ашиглаж болно. Энэ нь зөвхөн дуудлагын үеэр таны микрофоныг сэлгэнэ."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Сэлгэх"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Тохиргоо"</string>
- <string name="user_switched" msgid="7249833311585228097">"Одоогийн хэрэглэгч <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> руу сэлгэж байна…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g>-с гарч байна…"</string>
<string name="owner_name" msgid="8713560351570795743">"Өмчлөгч"</string>
@@ -2502,7 +2501,7 @@
<string name="satellite_manual_selection_state_popup_ok" msgid="2459664752624985095">"Асаах"</string>
<string name="satellite_manual_selection_state_popup_cancel" msgid="973605633339469252">"Буцах"</string>
<string name="unarchival_session_app_label" msgid="6811856981546348205">"Хүлээгдэж буй..."</string>
- <string name="satellite_sos_available_notification_title" msgid="5396708154268096124">"Хиймэл дагуул SOS одоо боломжтой боллоо"</string>
+ <string name="satellite_sos_available_notification_title" msgid="5396708154268096124">"Хиймэл дагуулын SOS одоо боломжтой боллоо"</string>
<string name="satellite_sos_available_notification_summary" msgid="1727088812951848330">"Та хөдөлгөөнт холбооны эсвэл Wi-Fi сүлжээ байхгүй бол яаралтай тусламжийн үйлчилгээ рүү мессеж бичих боломжтой. Google Мессеж таны өгөгдмөл мессеж апп байх ёстой."</string>
<string name="satellite_sos_not_supported_notification_title" msgid="2659100983227637285">"Хиймэл дагуул SOS-г дэмждэггүй"</string>
<string name="satellite_sos_not_supported_notification_summary" msgid="1071762454665310549">"Хиймэл дагуул SOS-г энэ төхөөрөмж дээр дэмждэггүй"</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index ea1e92b..d79dc6d 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -115,8 +115,8 @@
<string name="roamingText2" msgid="2834048284153110598">"रोमिंग दर्शक फ्लॅशिंग"</string>
<string name="roamingText3" msgid="831690234035748988">"अतिपरिचित क्षेत्राबाहेर"</string>
<string name="roamingText4" msgid="2171252529065590728">"इमारती बाहेर"</string>
- <string name="roamingText5" msgid="4294671587635796641">"रोमिंग - प्राधान्यीकृत सिस्टम"</string>
- <string name="roamingText6" msgid="5536156746637992029">"रोमिंग - उपलब्ध सिस्टम"</string>
+ <string name="roamingText5" msgid="4294671587635796641">"रोमिंग - प्राधान्य दिलेली सिस्टीम"</string>
+ <string name="roamingText6" msgid="5536156746637992029">"रोमिंग - उपलब्ध सिस्टीम"</string>
<string name="roamingText7" msgid="1783303085512907706">"रोमिंग - युती भागीदार"</string>
<string name="roamingText8" msgid="7774800704373721973">"रोमिंग - प्रीमियम भागीदार"</string>
<string name="roamingText9" msgid="1933460020190244004">"रोमिंग - पूर्ण सेवा कार्यक्षमता"</string>
@@ -242,7 +242,7 @@
<string name="silent_mode_silent" msgid="5079789070221150912">"रिंगर बंद"</string>
<string name="silent_mode_vibrate" msgid="8821830448369552678">"रिंगर व्हायब्रेट"</string>
<string name="silent_mode_ring" msgid="6039011004781526678">"रिंगर सुरू"</string>
- <string name="reboot_to_update_title" msgid="2125818841916373708">"Android सिस्टम अपडेट"</string>
+ <string name="reboot_to_update_title" msgid="2125818841916373708">"Android सिस्टीम अपडेट"</string>
<string name="reboot_to_update_prepare" msgid="6978842143587422365">"अपडेट करण्याची तयारी करत आहे…"</string>
<string name="reboot_to_update_package" msgid="4644104795527534811">"अपडेट पॅकेज प्रक्रिया करत आहे…"</string>
<string name="reboot_to_update_reboot" msgid="4474726009984452312">"रीस्टार्ट करत आहे..."</string>
@@ -274,7 +274,7 @@
<string name="bugreport_option_interactive_title" msgid="7968287837902871289">"परस्परसंवादी अहवाल"</string>
<string name="bugreport_option_interactive_summary" msgid="8493795476325339542">"बहुतांश प्रसंगांमध्ये याचा वापर करा. ते तुम्हाला अहवालाच्या प्रगतीचा मागोवा घेण्याची, समस्येविषयी आणखी तपाशील एंटर करण्याची आणि स्क्रीनशॉट घेण्याची अनुमती देते. ते कदाचित अहवाल देण्यासाठी बराच वेळ घेणारे कमी-वापरलेले विभाग वगळू शकते."</string>
<string name="bugreport_option_full_title" msgid="7681035745950045690">"संपूर्ण अहवाल"</string>
- <string name="bugreport_option_full_summary" msgid="1975130009258435885">"तुमचे डिव्हाइस प्रतिसाद देत नाही किंवा खूप धीमे असते अथवा तुम्हाला सर्व अहवाल विभागांची आवश्यकता असते तेव्हा कमीतकमी सिस्टम हस्तक्षेपासाठी या पर्यायाचा वापर करा. तुम्हाला आणखी तपशील एंटर करण्याची किंवा अतिरिक्त स्क्रीनशॉट घेण्याची अनुमती देत नाही."</string>
+ <string name="bugreport_option_full_summary" msgid="1975130009258435885">"तुमचे डिव्हाइस प्रतिसाद देत नाही किंवा खूप धीमे असते अथवा तुम्हाला सर्व अहवाल विभागांची आवश्यकता असते तेव्हा कमीतकमी सिस्टीम हस्तक्षेपासाठी या पर्यायाचा वापर करा. तुम्हाला आणखी तपशील एंटर करण्याची किंवा अतिरिक्त स्क्रीनशॉट घेण्याची अनुमती देत नाही."</string>
<string name="bugreport_countdown" msgid="6418620521782120755">"{count,plural, =1{बग रिपोर्टसाठी # सेकंदामध्ये स्क्रीनशॉट घेत आहे.}other{बग रिपोर्टसाठी # सेकंदांमध्ये स्क्रीनशॉट घेत आहे.}}"</string>
<string name="bugreport_screenshot_success_toast" msgid="7986095104151473745">"बग रिपोर्टसह घेतलेला स्क्रीनशॉट"</string>
<string name="bugreport_screenshot_failure_toast" msgid="6736320861311294294">"बग रिपोर्टसह स्क्रीनशॉट घेता आला नाही"</string>
@@ -374,7 +374,7 @@
<string name="dream_preview_title" msgid="5570751491996100804">"पूर्वावलोकन, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string>
<string name="dream_accessibility_action_click" msgid="7392398629967797805">"डिसमिस करा"</string>
<string name="permlab_statusBar" msgid="8798267849526214017">"स्टेटस बार अक्षम करा किंवा सुधारित करा"</string>
- <string name="permdesc_statusBar" msgid="5809162768651019642">"स्टेटस बार अक्षम करण्यासाठी किंवा सिस्टम चिन्हे जोडण्यासाठी आणि काढण्यासाठी अॅप ला अनुमती देते."</string>
+ <string name="permdesc_statusBar" msgid="5809162768651019642">"स्टेटस बार बंद करण्यासाठी किंवा सिस्टीम आयकन जोडण्यासाठी आणि काढण्यासाठी ॲपला परवानगी देते."</string>
<string name="permlab_statusBarService" msgid="2523421018081437981">"स्टेटस बार होऊ द्या"</string>
<string name="permdesc_statusBarService" msgid="6652917399085712557">"स्टेटस बार होण्यासाठी अॅप ला अनुमती देते."</string>
<string name="permlab_expandStatusBar" msgid="1184232794782141698">"स्टेटस बार विस्तृत करा/संकुचित करा"</string>
@@ -472,9 +472,9 @@
<string name="permlab_writeSettings" msgid="8057285063719277394">"सिस्टीम सेटिंग्ज सुधारित करा"</string>
<string name="permdesc_writeSettings" msgid="8293047411196067188">"सिस्टीमचा सेटिंग्ज डेटा सुधारित करण्यासाठी अॅपला अनुमती देते. दुर्भावनापूर्ण अॅप्स तुमच्या सिस्टीमचे कॉन्फिगरेशन दूषित करू शकतात."</string>
<string name="permlab_receiveBootCompleted" msgid="6643339400247325379">"सुरूवातीस चालवा"</string>
- <string name="permdesc_receiveBootCompleted" product="tablet" msgid="5565659082718177484">"जसे सिस्टम बूट करणे समाप्त करते तसे अॅप ला स्वतः सुरू करण्यास अनुमती देते. यामुळे टॅबलेट सुरू करण्यास वेळ लागू शकतो आणि नेहमी सुरू राहून एकंदर टॅबलेटला धीमे करण्यास अॅप ला अनुमती देते."</string>
- <string name="permdesc_receiveBootCompleted" product="tv" msgid="4900842256047614307">"सिस्टम बूट होणे संपल्यावर ॲपला स्वतः सुरू होण्याची अनुमती देते. यामुळे तुमच्या Android TV डिव्हाइसला सुरू होण्यास वेळ लागू शकतो आणि नेहमी सुरू राहून एकंदर डिव्हाइसलाच धीमे करण्याची अनुमती ॲपला देते."</string>
- <string name="permdesc_receiveBootCompleted" product="default" msgid="7912677044558690092">"जसे सिस्टम बूट करणे समाप्त करते तसे अॅप ला स्वतः सुरू करण्यास अनुमती देते. यामुळे फोन सुरू करण्यास वेळ लागू शकतो आणि नेहमी सुरू राहून एकंदर फोनला धीमे करण्यास अॅप ला अनुमती देते."</string>
+ <string name="permdesc_receiveBootCompleted" product="tablet" msgid="5565659082718177484">"जसे सिस्टीम बूट करणे समाप्त करते तसे ॲपला स्वतः सुरू करण्यास अनुमती देते. यामुळे टॅबलेट सुरू करण्यास वेळ लागू शकतो आणि नेहमी सुरू राहून एकंदर टॅबलेटला धीमे करण्यास ॲपला अनुमती देते."</string>
+ <string name="permdesc_receiveBootCompleted" product="tv" msgid="4900842256047614307">"सिस्टीम बूट होणे संपल्यावर ॲपला स्वतः सुरू होण्याची अनुमती देते. यामुळे तुमच्या Android TV डिव्हाइसला सुरू होण्यास वेळ लागू शकतो आणि नेहमी सुरू राहून एकंदर डिव्हाइसलाच धीमे करण्याची अनुमती ॲपला देते."</string>
+ <string name="permdesc_receiveBootCompleted" product="default" msgid="7912677044558690092">"जसे सिस्टीम बूट करणे समाप्त करते तसे ॲपला स्वतः सुरू करण्यास अनुमती देते. यामुळे फोन सुरू करण्यास वेळ लागू शकतो आणि नेहमी सुरू राहून एकंदर फोनला धीमे करण्यास ॲपला अनुमती देते."</string>
<string name="permlab_broadcastSticky" msgid="4552241916400572230">"रोचक प्रसारण पाठवा"</string>
<string name="permdesc_broadcastSticky" product="tablet" msgid="5058486069846384013">"रोचक प्रसारणे पाठविण्यासाठी अॅप ला अनुमती देते, जे प्रसारण समाप्त झाल्यानंतर देखील तसेच राहते. अत्याधिक वापरामुळे बरीच मेमरी वापरली जाऊन तो टॅब्लेटला धीमा किंवा अस्थिर करू शकतो."</string>
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"रोचक प्रसारणे पाठविण्यासाठी अॅपला अनुमती देते, जे प्रसारण समाप्त झाल्यानंतर देखील तसेच राहते. अत्याधिक वापरामुळे बरीच मेमरी वापरली जाऊन तो तुमच्या Android TV डिव्हाइसला धिमा किंवा अस्थिर करू शकतो."</string>
@@ -529,8 +529,8 @@
<string name="permdesc_camera" msgid="5240801376168647151">"ॲप वापरात असताना, हे ॲप कॅमेरा वापरून फोटो काढू शकते आणि व्हिडिओ रेकॉर्ड करू शकते."</string>
<string name="permlab_backgroundCamera" msgid="7549917926079731681">"बॅकग्राउंडमध्ये फोटो काढा आणि व्हिडिओ रेकॉर्ड करा"</string>
<string name="permdesc_backgroundCamera" msgid="1615291686191138250">"हे ॲप कॅमेरा वापरून कधीही फोटो काढू शकते आणि व्हिडिओ रेकॉर्ड करू शकते."</string>
- <string name="permlab_systemCamera" msgid="3642917457796210580">"फोटो आणि व्हिडिओ काढण्यासाठी ॲप्लिकेशन किंवा सेवेला सिस्टम कॅमेरे ॲक्सेस करण्याची अनुमती द्या"</string>
- <string name="permdesc_systemCamera" msgid="5938360914419175986">"हे विशेषाधिकृत किंवा सिस्टम ॲप कधीही सिस्टम कॅमेरा वापरून फोटो आणि व्हिडिओ रेकॉर्ड करू शकते. ॲपकडे android.permission.CAMERA परवानगी असण्याचीदेखील आवश्यकता आहे"</string>
+ <string name="permlab_systemCamera" msgid="3642917457796210580">"फोटो आणि व्हिडिओ काढण्यासाठी ॲप्लिकेशन किंवा सेवेला सिस्टीम कॅमेरे ॲक्सेस करण्याची अनुमती द्या"</string>
+ <string name="permdesc_systemCamera" msgid="5938360914419175986">"हे विशेष महत्त्वाचे किंवा सिस्टीम ॲप कधीही सिस्टीम कॅमेरा वापरून फोटो आणि व्हिडिओ रेकॉर्ड करू शकते. ॲपकडे android.permission.CAMERA परवानगी असण्याचीदेखील आवश्यकता आहे"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"एखाद्या अॅप्लिकेशन किंवा सेवेला कॅमेरा डिव्हाइस सुरू किंवा बंद केल्याची कॉलबॅक मिळवण्याची अनुमती द्या."</string>
<string name="permdesc_cameraOpenCloseListener" msgid="2002636131008772908">"कोणतेही कॅमेरा डिव्हाइस (कोणत्या अॅप्लिकेशनने) सुरू किंवा बंद केले जाते तेव्हा हे ॲप कॉलबॅक मिळवू शकते."</string>
<string name="permlab_cameraHeadlessSystemUser" msgid="680194666834500050">"अॅप्लिकेशन किंवा सेवेला हेडलेस सिस्टीम वापरकर्ता म्हणून कॅमेरा अॅक्सेस करण्याची अनुमती द्या."</string>
@@ -548,7 +548,7 @@
<string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"यामुळे ॲपला डिव्हाइसची मूलभूत टेलिफोनी वैशिष्ट्ये अॅक्सेस करण्याची अनुमती मिळते."</string>
<string name="permlab_manageOwnCalls" msgid="9033349060307561370">"प्रणालीच्या माध्यमातून कॉल रूट करा"</string>
<string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"कॉल करण्याचा अनुभव सुधारण्यासाठी ॲपला त्याचे कॉल प्रणालीच्या माध्यमातून रूट करू देते."</string>
- <string name="permlab_callCompanionApp" msgid="3654373653014126884">"सिस्टम वापरून कॉल पहा आणि नियंत्रण ठेवा."</string>
+ <string name="permlab_callCompanionApp" msgid="3654373653014126884">"सिस्टीम वापरून कॉल पहा आणि नियंत्रण ठेवा."</string>
<string name="permdesc_callCompanionApp" msgid="8474168926184156261">"डिव्हाइसवर येणार कॉल पाहण्यासाठी आणि नियंत्रित करण्यासाठी ॲपला अनुमती देते. यामध्ये कॉल करण्यासाठी कॉलचा नंबर आणि कॉलची स्थिती यासारख्या माहितीचा समावेश असतो."</string>
<string name="permlab_exemptFromAudioRecordRestrictions" msgid="1164725468350759486">"ऑडिओ रेकॉर्ड प्रतिबंधांपासून मुक्त"</string>
<string name="permdesc_exemptFromAudioRecordRestrictions" msgid="2425117015896871976">"ऑडिओ रेकॉर्ड करण्यासाठी प्रतिबंधांपासून ॲपला मुक्त करा."</string>
@@ -569,11 +569,11 @@
<string name="permdesc_transmitIr" product="tv" msgid="3278506969529173281">"ॲपला Android TV डिव्हाइसचा इन्फ्रारेड ट्रान्समीटर वापरण्याची अनुमती देते."</string>
<string name="permdesc_transmitIr" product="default" msgid="8484193849295581808">"अॅप ला फोनच्या इन्फ्रारेड ट्रान्समीटरचा वापर करण्याची अनुमती देते."</string>
<string name="permlab_setWallpaper" msgid="6959514622698794511">"वॉलपेपर सेट करा"</string>
- <string name="permdesc_setWallpaper" msgid="2973996714129021397">"सिस्टम वॉलपेपर सेट करण्यासाठी अॅप ला अनुमती देते."</string>
+ <string name="permdesc_setWallpaper" msgid="2973996714129021397">"सिस्टीम वॉलपेपर सेट करण्यासाठी ॲपला परवानगी देते."</string>
<string name="permlab_accessHiddenProfile" msgid="8607094418491556823">"लपवलेल्या प्रोफाइल अॅक्सेस करा"</string>
<string name="permdesc_accessHiddenProfile" msgid="1543153202481009676">"अॅपला लपवलेल्या प्रोफाइल अॅक्सेस करण्याची अनुमती देते."</string>
<string name="permlab_setWallpaperHints" msgid="1153485176642032714">"तुमचा वॉलपेपर आकार समायोजित करा"</string>
- <string name="permdesc_setWallpaperHints" msgid="6257053376990044668">"सिस्टम वॉलपेपर आकार सूचना सेट करण्यासाठी अॅप ला अनुमती देते."</string>
+ <string name="permdesc_setWallpaperHints" msgid="6257053376990044668">"सिस्टीम वॉलपेपर आकार सूचना सेट करण्याची ॲपला परवानगी देते."</string>
<string name="permlab_setTimeZone" msgid="7922618798611542432">"टाइम झोन सेट करा"</string>
<string name="permdesc_setTimeZone" product="tablet" msgid="1788868809638682503">"टॅब्लेटचा टाइम झोन बदलण्यासाठी अॅप ला अनुमती देते."</string>
<string name="permdesc_setTimeZone" product="tv" msgid="9069045914174455938">"तुमच्या Android TV डिव्हाइसचा टाइम झोन बदलण्यासाठी ॲपला अनुमती देते."</string>
@@ -1241,7 +1241,7 @@
<string name="input_method_ime_switch_long_click_action_desc" msgid="3161942124116646998">"इनपुट पद्धत पिकर उघडा"</string>
<string name="input_method_switcher_settings_button" msgid="5609835654697108485">"सेटिंग्ज"</string>
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"संचयन स्थान संपत आहे"</string>
- <string name="low_internal_storage_view_text" msgid="8172166728369697835">"काही सिस्टम कार्ये कार्य करू शकत नाहीत"</string>
+ <string name="low_internal_storage_view_text" msgid="8172166728369697835">"काही सिस्टीम फंक्शन कार्य करू शकत नाहीत"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"सिस्टीमसाठी पुरेसे संचयन नाही. आपल्याकडे 250MB मोकळे स्थान असल्याचे सुनिश्चित करा आणि रीस्टार्ट करा."</string>
<string name="app_running_notification_title" msgid="8985999749231486569">"<xliff:g id="APP_NAME">%1$s</xliff:g> चालत आहे"</string>
<string name="app_running_notification_text" msgid="5120815883400228566">"अधिक माहितीसाठी किंवा अॅप थांबविण्यासाठी टॅप करा."</string>
@@ -1287,7 +1287,7 @@
<string name="whichImageCaptureApplicationLabel" msgid="6505433734824988277">"इमेज कॅप्चर करा"</string>
<string name="alwaysUse" msgid="3153558199076112903">"या क्रियेसाठी डीफॉल्टनुसार वापरा."</string>
<string name="use_a_different_app" msgid="4987790276170972776">"एक भिन्न अॅप वापरा"</string>
- <string name="clearDefaultHintMsg" msgid="1325866337702524936">"डाउनलोड केलेल्या सिस्टम सेटिंग्ज > Apps > मधील डीफॉल्ट साफ करा."</string>
+ <string name="clearDefaultHintMsg" msgid="1325866337702524936">"डाउनलोड केलेल्या सिस्टीम सेटिंग्ज > Apps > मधील डीफॉल्ट साफ करा."</string>
<string name="chooseActivity" msgid="8563390197659779956">"क्रिया निवडा"</string>
<string name="chooseUsbActivity" msgid="2096269989990986612">"USB डिव्हाइससाठी अॅप निवडा"</string>
<string name="noApplications" msgid="1186909265235544019">"कोणतेही अॅप्स ही क्रिया करू शकत नाहीत."</string>
@@ -1315,7 +1315,7 @@
<string name="launch_warning_original" msgid="3332206576800169626">"<xliff:g id="APP_NAME">%1$s</xliff:g> मूळतः लाँच केले."</string>
<string name="screen_compat_mode_scale" msgid="8627359598437527726">"स्केल"</string>
<string name="screen_compat_mode_show" msgid="5080361367584709857">"नेहमी दर्शवा"</string>
- <string name="screen_compat_mode_hint" msgid="4032272159093750908">"सिस्टम सेटिंग्ज > Apps > डाउनलोड केलेले मध्ये हे पुन्हा-सुरू करा."</string>
+ <string name="screen_compat_mode_hint" msgid="4032272159093750908">"सिस्टीम सेटिंग्ज > Apps > डाउनलोड केलेले मध्ये हे पुन्हा-सुरू करा."</string>
<string name="unsupported_display_size_message" msgid="7265211375269394699">"<xliff:g id="APP_NAME">%1$s</xliff:g> वर्तमान डिस्प्ले आकार सेटिंगला समर्थन देत नाही आणि अनपेक्षित वर्तन करू शकते."</string>
<string name="unsupported_display_size_show" msgid="980129850974919375">"नेहमी दर्शवा"</string>
<string name="unsupported_compile_sdk_message" msgid="7326293500707890537">"<xliff:g id="APP_NAME">%1$s</xliff:g> हे Android OS च्या विसंगत आवृत्तीसाठी तयार केले होते आणि ते अनपेक्षित पद्धतीने काम करू शकते. ॲपची अपडेट केलेली आवृत्ती उपलब्ध असू शकते."</string>
@@ -1330,7 +1330,7 @@
<string name="android_start_title" product="automotive" msgid="7917984412828168079">"Android सुरू करत आहे…"</string>
<string name="android_start_title" product="tablet" msgid="4429767260263190344">"टॅबलेट सुरू होत आहे…"</string>
<string name="android_start_title" product="device" msgid="6967413819673299309">"डिव्हाइस सुरू होत आहे…"</string>
- <string name="android_upgrading_notification_title" product="default" msgid="3509927005342279257">"सिस्टम अपडेट संपत आहे…"</string>
+ <string name="android_upgrading_notification_title" product="default" msgid="3509927005342279257">"सिस्टीम अपडेट संपत आहे…"</string>
<string name="app_upgrading_toast" msgid="1016267296049455585">"<xliff:g id="APPLICATION">%1$s</xliff:g> श्रेणीसुधारित करत आहे…"</string>
<string name="android_preparing_apk" msgid="589736917792300956">"<xliff:g id="APPNAME">%1$s</xliff:g> तयार करत आहे."</string>
<string name="android_upgrading_starting_apps" msgid="6206161195076057075">"अॅप्स सुरू करत आहे."</string>
@@ -1708,7 +1708,7 @@
<string name="default_audio_route_name_external_device" msgid="8124229858618975">"बाह्य डिव्हाइस"</string>
<string name="default_audio_route_name_headphones" msgid="6954070994792640762">"हेडफोन"</string>
<string name="default_audio_route_name_usb" msgid="895668743163316932">"USB"</string>
- <string name="default_audio_route_category_name" msgid="5241740395748134483">"सिस्टम"</string>
+ <string name="default_audio_route_category_name" msgid="5241740395748134483">"सिस्टीम"</string>
<string name="bluetooth_a2dp_audio_route_name" msgid="4214648773120426288">"ब्लूटूथ ऑडिओ"</string>
<string name="wireless_display_route_description" msgid="8297563323032966831">"वायरलेस डिस्प्ले"</string>
<string name="media_route_button_content_description" msgid="2299223698196869956">"कास्ट करा"</string>
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"हँड्स-फ्री कॉलिंगसाठी तुम्ही तुमच्या श्रवणयंत्राचा मायक्रोफोन वापरू शकता. यामुळे कॉलदरम्यान फक्त तुमचा माइक स्विच केला जातो."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"स्विच करा"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"सेटिंग्ज"</string>
- <string name="user_switched" msgid="7249833311585228097">"वर्तमान वापरकर्ता <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> वर स्विच करत आहे…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> लॉग आउट करत आहे…"</string>
<string name="owner_name" msgid="8713560351570795743">"मालक"</string>
@@ -2192,12 +2191,12 @@
<string name="screenshot_edit" msgid="7408934887203689207">"संपादित करा"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"कॉल आणि सूचनांवर व्हायब्रेट होईल"</string>
<string name="volume_dialog_ringer_guidance_silent" msgid="1011246774949993783">"कॉल आणि सूचना म्यूट केल्या जातील"</string>
- <string name="notification_channel_system_changes" msgid="2462010596920209678">"सिस्टम बदल"</string>
+ <string name="notification_channel_system_changes" msgid="2462010596920209678">"सिस्टीम बदल"</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"सूचना सेटिंग्जचे पुनरावलोकन करा"</string>
<string name="review_notification_settings_text" msgid="5916244866751849279">"Android 13 पासून, तुम्ही त्यामध्ये इंस्टॉल केलेल्या अॅप्सना सूचना पाठवण्यासाठी तुमच्या परवानगीची आवश्यकता आहे. सध्याच्या अॅप्ससाठी ही परवानगी बदलण्याकरिता टॅप करा."</string>
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"मला आठवण करून द्या"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"डिसमिस करा"</string>
- <string name="notification_app_name_system" msgid="3045196791746735601">"सिस्टम"</string>
+ <string name="notification_app_name_system" msgid="3045196791746735601">"सिस्टीम"</string>
<string name="notification_app_name_settings" msgid="9088548800899952531">"सेटिंग्ज"</string>
<string name="notification_appops_camera_active" msgid="8177643089272352083">"कॅमेरा"</string>
<string name="notification_appops_microphone_active" msgid="581333393214739332">"मायक्रोफोन"</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 43acbac..76d4027 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Mod sebelah tangan"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Amat malap"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Peranti pendengaran"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Autoklik"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Diputuskan sambungan"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Disambungkan"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Aktif"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Anda boleh menggunakan mikrofon alat bantu pendengaran anda untuk membuat panggilan bebas tangan. Tindakan ini hanya menukar mikrofon anda semasa panggilan."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Tukar"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Tetapan"</string>
- <string name="user_switched" msgid="7249833311585228097">"Pengguna semasa <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Beralih kepada <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Log keluar daripada <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Pemilik"</string>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index 2333539..fc6df7a 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"လက်လွတ်ခေါ်ဆိုခြင်းအတွက် နားကြားကိရိယာ၏ မိုက်ခရိုဖုန်းကို သုံးနိုင်သည်။ ခေါ်ဆိုနေစဉ်သာ သင့်မိုက်ကို ပြောင်းပေးသည်။"</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"ပြောင်းရန်"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"ဆက်တင်များ"</string>
- <string name="user_switched" msgid="7249833311585228097">"လက်ရှိအသုံးပြုနေသူ <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g>သို့ ပြောင်းနေသည်…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g>ကို ထွက်ပစ်ပါတော့မည်..."</string>
<string name="owner_name" msgid="8713560351570795743">"ပိုင်ရှင်"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 3a337f6..b8cc711 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Du kan bruke mikrofonen i høreapparatet til å ringe håndfritt. Dette fører bare til at mikrofonen byttes under samtalen."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Bytt"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Innstillinger"</string>
- <string name="user_switched" msgid="7249833311585228097">"Gjeldende bruker: <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Bytter til <xliff:g id="NAME">%1$s</xliff:g> …"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Logger av <xliff:g id="NAME">%1$s</xliff:g> …"</string>
<string name="owner_name" msgid="8713560351570795743">"Eier"</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index ce64589..5907182 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"तपाईं ह्यान्ड्सफ्री तरिकाले कल गर्न आफ्नो श्रवण यन्त्रको माइक्रोफोन प्रयोग गर्न सक्नुहुन्छ। यसले कल भइरहेका बेला मात्र तपाईंको माइक बदल्छ।"</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"बदल्नुहोस्"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"सेटिङ"</string>
- <string name="user_switched" msgid="7249833311585228097">"अहिलेको प्रयोगकर्ता <xliff:g id="NAME">%1$s</xliff:g>।"</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> मा स्विच गरिँदै छ…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"लग आउट गर्दै <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"मालिक"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index f7b6cc8..98ce463 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Je kunt de microfoon van je hoortoestel gebruiken om handsfree te bellen. Hiermee wordt de microfoon alleen gewisseld tijdens het gesprek."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Wisselen"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Instellingen"</string>
- <string name="user_switched" msgid="7249833311585228097">"Huidige gebruiker <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Overschakelen naar <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> uitloggen…"</string>
<string name="owner_name" msgid="8713560351570795743">"Eigenaar"</string>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index ef567e5..4931bd2e 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"ଏକ-ହାତ ମୋଡ୍"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"ଅତ୍ୟଧିକ ଡିମ"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"ଶ୍ରବଣ ଡିଭାଇସଗୁଡ଼ିକ"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"ଅଟୋକ୍ଲିକ"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"ଡିସକନେକ୍ଟ କରାଯାଇଛି"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"କନେକ୍ଟ କରାଯାଇଛି"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"ସକ୍ରିୟ"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"ହେଣ୍ଡସ-ଫ୍ରି କଲିଂ ପାଇଁ ଆପଣ ଆପଣଙ୍କର ଶ୍ରବଣ ଯନ୍ତ୍ର ମାଇକ୍ରୋଫୋନ ବ୍ୟବହାର କରିପାରିବେ। କଲ ସମୟରେ ଏହା କେବଳ ଆପଣଙ୍କର ମାଇକକୁ ସୁଇଚ କରିଥାଏ।"</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"ସୁଇଚ କରନ୍ତୁ"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"ସେଟିଂସ"</string>
- <string name="user_switched" msgid="7249833311585228097">"ବର୍ତ୍ତମାନର ୟୁଜର୍ ହେଉଛନ୍ତି <xliff:g id="NAME">%1$s</xliff:g>।"</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g>ରେ ସୁଇଚ ହେଉଛି…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g>ଙ୍କୁ ଲଗଆଉଟ୍ କରାଯାଉଛି…"</string>
<string name="owner_name" msgid="8713560351570795743">"ମାଲିକ"</string>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index 7f06227..80ed484 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"ਇੱਕ ਹੱਥ ਮੋਡ"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"ਜ਼ਿਆਦਾ ਘੱਟ ਚਮਕ"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"ਸੁਣਨ ਵਾਲੇ ਡੀਵਾਈਸ"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"ਸਵੈ-ਕਲਿੱਕ"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"ਡਿਸਕਨੈਕਟ ਹੋ ਗਿਆ"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"ਕਨੈਕਟ ਹੈ"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"ਕਿਰਿਆਸ਼ੀਲ"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"ਤੁਸੀਂ ਹੱਥ-ਰਹਿਤ ਕਾਲਿੰਗ ਲਈ ਆਪਣੇ ਸੁਣਨ ਦੇ ਸਾਧਨ ਦੇ ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਦੀ ਵਰਤੋਂ ਕਰ ਸਕਦੇ ਹੋ। ਇਹ ਸਿਰਫ਼ ਕਾਲ ਦੌਰਾਨ ਹੀ ਤੁਹਾਡੇ ਮਾਈਕ ਨੂੰ ਸਵਿੱਚ ਕਰਦਾ ਹੈ।"</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"ਸਵਿੱਚ ਕਰੋ"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"ਸੈਟਿੰਗਾਂ"</string>
- <string name="user_switched" msgid="7249833311585228097">"ਮੌਜੂਦਾ ਉਪਭੋਗਤਾ <xliff:g id="NAME">%1$s</xliff:g>।"</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> \'ਤੇ ਸਵਿੱਚ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> ਨੂੰ ਲਾਗ-ਆਉਟ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ …"</string>
<string name="owner_name" msgid="8713560351570795743">"ਮਾਲਕ"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 73e4d24..efae947 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -1715,7 +1715,7 @@
<string name="wireless_display_route_description" msgid="8297563323032966831">"Wyświetlacz bezprzewodowy"</string>
<string name="media_route_button_content_description" msgid="2299223698196869956">"Przesyłaj"</string>
<string name="media_route_chooser_title" msgid="6646594924991269208">"Połącz z urządzeniem"</string>
- <string name="media_route_chooser_title_for_remote_display" msgid="3105906508794326446">"Prześlij ekran na urządzenie"</string>
+ <string name="media_route_chooser_title_for_remote_display" msgid="3105906508794326446">"Prześlij treści z ekranu na urządzenie"</string>
<string name="media_route_chooser_searching" msgid="6119673534251329535">"Szukam urządzeń…"</string>
<string name="media_route_chooser_extended_settings" msgid="2506352159381327741">"Ustawienia"</string>
<string name="media_route_controller_disconnect" msgid="7362617572732576959">"Rozłącz"</string>
@@ -1827,7 +1827,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Możesz używać mikrofonu w aparacie słuchowym, aby dzwonić bez użycia rąk. Mikrofon zostanie przełączony tylko na czas połączenia."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Przełącz"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Ustawienia"</string>
- <string name="user_switched" msgid="7249833311585228097">"Bieżący użytkownik: <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Przełączam na użytkownika <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Wylogowuję użytkownika <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Właściciel"</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index f15ab35..4036d636 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -1826,7 +1826,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Você pode usar o microfone do aparelho auditivo para fazer ligações sem usar as mãos. A troca do microfone só será feita durante a ligação."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Trocar"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Configurações"</string>
- <string name="user_switched" msgid="7249833311585228097">"Usuário atual <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Mudando para <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Desconectando <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Proprietário"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index e80b780..df3f9c6 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -1714,7 +1714,7 @@
<string name="wireless_display_route_description" msgid="8297563323032966831">"Visualização sem fios"</string>
<string name="media_route_button_content_description" msgid="2299223698196869956">"Transmitir"</string>
<string name="media_route_chooser_title" msgid="6646594924991269208">"Ligar ao dispositivo"</string>
- <string name="media_route_chooser_title_for_remote_display" msgid="3105906508794326446">"Transmitir ecrã para dispositivo"</string>
+ <string name="media_route_chooser_title_for_remote_display" msgid="3105906508794326446">"Transmita o ecrã para o dispositivo"</string>
<string name="media_route_chooser_searching" msgid="6119673534251329535">"A pesquisar dispositivos…"</string>
<string name="media_route_chooser_extended_settings" msgid="2506352159381327741">"Definições"</string>
<string name="media_route_controller_disconnect" msgid="7362617572732576959">"Desligar"</string>
@@ -1826,7 +1826,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Pode usar o microfone do aparelho auditivo para chamadas mãos-livres. Esta ação apenas muda o microfone durante a chamada."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Mudar"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Definições"</string>
- <string name="user_switched" msgid="7249833311585228097">"<xliff:g id="NAME">%1$s</xliff:g> do utilizador atual."</string>
<string name="user_switching_message" msgid="1912993630661332336">"A mudar para <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"A terminar a sessão de <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Proprietário"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index f15ab35..4036d636 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -1826,7 +1826,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Você pode usar o microfone do aparelho auditivo para fazer ligações sem usar as mãos. A troca do microfone só será feita durante a ligação."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Trocar"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Configurações"</string>
- <string name="user_switched" msgid="7249833311585228097">"Usuário atual <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Mudando para <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Desconectando <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Proprietário"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index f02923b..b12af39 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -1826,8 +1826,7 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Poți folosi microfonul aparatului auditiv pentru apelarea hands-free. Astfel, microfonul pornește numai în timpul apelului."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Schimbă"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Setări"</string>
- <string name="user_switched" msgid="7249833311585228097">"Utilizator curent: <xliff:g id="NAME">%1$s</xliff:g>."</string>
- <string name="user_switching_message" msgid="1912993630661332336">"Se comută la <xliff:g id="NAME">%1$s</xliff:g>…"</string>
+ <string name="user_switching_message" msgid="1912993630661332336">"Se trece la <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Se deconectează utilizatorul <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Proprietar"</string>
<string name="guest_name" msgid="8502103277839834324">"Invitat"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index e32ec41..4911fc6 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -1827,7 +1827,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Вы можете использовать микрофон слухового аппарата, когда разговариваете по телефону. Микрофон будет переключен только на время звонка."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Переключиться"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Настройки"</string>
- <string name="user_switched" msgid="7249833311585228097">"Выбран аккаунт пользователя <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Смена профиля на \"<xliff:g id="NAME">%1$s</xliff:g>\"…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Выход из аккаунта <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Владелец"</string>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index 35251bc..b11e442 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -1713,7 +1713,7 @@
<string name="wireless_display_route_description" msgid="8297563323032966831">"නොරැහැන් සංදර්ශකය"</string>
<string name="media_route_button_content_description" msgid="2299223698196869956">"Cast"</string>
<string name="media_route_chooser_title" msgid="6646594924991269208">"උපාංගයට සම්බන්ධ වන්න"</string>
- <string name="media_route_chooser_title_for_remote_display" msgid="3105906508794326446">"තිරය උපාංගයට යොමු කරන්න"</string>
+ <string name="media_route_chooser_title_for_remote_display" msgid="3105906508794326446">"තිරය උපාංගයට විකාශය කරන්න"</string>
<string name="media_route_chooser_searching" msgid="6119673534251329535">"උපාංග සඳහා සොයමින්…"</string>
<string name="media_route_chooser_extended_settings" msgid="2506352159381327741">"සැකසීම්"</string>
<string name="media_route_controller_disconnect" msgid="7362617572732576959">"විසන්ධි කරන්න"</string>
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"දෑත් නොයොදන ඇමතුම් සඳහා ඔබට ඔබේ ශ්රවණාධාර මයික්රෆෝනය භාවිතා කළ හැක. මෙය ඇමතුම අතරතුර පමණක් ඔබේ මයික්රෆෝනය මාරු කරයි."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"මාරු කරන්න"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"සැකසීම්"</string>
- <string name="user_switched" msgid="7249833311585228097">"දැනට සිටින පරිශීලකයා <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> වෙත මාරු කරමින්…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> වරමින්…"</string>
<string name="owner_name" msgid="8713560351570795743">"හිමිකරු"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 391b0d7..47bee0a 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -1827,7 +1827,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Volať handsfree môžete pomocou mikrofónu načúvadla. Týmto iba prepnete mikrofón počas hovoru."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Prepnúť"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Nastavenia"</string>
- <string name="user_switched" msgid="7249833311585228097">"Aktuálny používateľ je <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Prepína sa na účet <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Prebieha odhlásenie používateľa <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Vlastník"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index c84fd17..208c174 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -1827,7 +1827,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Mikrofon za slušni aparat lahko uporabljate za prostoročno klicanje. S tem preklopite mikrofon samo med klicem."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Preklopi"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Nastavitve"</string>
- <string name="user_switched" msgid="7249833311585228097">"Trenutni uporabnik <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Preklapljanje na uporabnika <xliff:g id="NAME">%1$s</xliff:g> …"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Odjavljanje uporabnika <xliff:g id="NAME">%1$s</xliff:g> …"</string>
<string name="owner_name" msgid="8713560351570795743">"Lastnik"</string>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index 25d51a3..5f84908 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -653,18 +653,18 @@
<string name="permlab_mediaLocation" msgid="7368098373378598066">"lexo vendndodhjet nga koleksioni yt i medias"</string>
<string name="permdesc_mediaLocation" msgid="597912899423578138">"Lejon aplikacionin të lexojë vendndodhjet nga koleksioni yt i medias."</string>
<string name="permlab_eye_tracking_coarse" msgid="7989596289790269059">"të gjurmojë vështrimin e përafërt të syve"</string>
- <string name="permdesc_eye_tracking_coarse" msgid="870510233930553355">"Lejon që aplikacioni të gjurmojë vështrimin e përafërt të syve"</string>
+ <string name="permdesc_eye_tracking_coarse" msgid="870510233930553355">"Lejon që aplikacioni të gjurmojë vështrimin e përafërt të syve."</string>
<string name="permlab_eye_tracking_fine" msgid="6914457357027049512">"të gjurmojë se ku po shikon ti"</string>
<string name="permdesc_eye_tracking_fine" msgid="5788889152304524730">"Lejon që aplikacioni të qaset në të dhënat e vështrimit të saktë të syve."</string>
<string name="permlab_face_tracking" msgid="2272048395128283324">"të gjurmojë fytyrën tënde"</string>
- <string name="permdesc_face_tracking" msgid="2622783922311211866">"Lejo që aplikacioni të qaset në të dhënat e gjurmimit të fytyrës."</string>
+ <string name="permdesc_face_tracking" msgid="2622783922311211866">"Lejon që aplikacioni të qaset në të dhënat e gjurmimit të fytyrës."</string>
<string name="permlab_hand_tracking" msgid="6478233866595566940">"të gjurmojë duart e tua"</string>
<string name="permdesc_hand_tracking" msgid="8639715900104966456">"Lejon që aplikacioni të qaset në të dhënat e gjurmimit të duarve."</string>
<string name="permlab_head_tracking" msgid="1309731456372087270">"të gjurmojë kokën tënde"</string>
- <string name="permdesc_head_tracking" msgid="231597390513699188">"Lejo që aplikacioni të qaset në të dhënat e gjurmimit të kokës."</string>
+ <string name="permdesc_head_tracking" msgid="231597390513699188">"Lejon që aplikacioni të qaset në të dhënat e gjurmimit të kokës."</string>
<string name="permlab_scene_understanding_coarse" msgid="6518646430502858641">"të kuptojë mjedisin tënd"</string>
<string name="permdesc_scene_understanding_coarse" msgid="4508880777646198656">"Lejon që aplikacioni të qaset në të dhënat e gjurmimit për mjedisin drejtpërdrejt rreth teje."</string>
- <string name="permlab_scene_understanding_fine" msgid="409126403264393251">"të kuptojë mjedisin tënd të drejtpërdrejtë me nivel të lartë të detajeve"</string>
+ <string name="permlab_scene_understanding_fine" msgid="409126403264393251">"të kuptojë mjedisin drejtpërdrejtë rreth teje me nivel të lartë të detajeve"</string>
<string name="permdesc_scene_understanding_fine" msgid="6223368011593524179">"Lejon që aplikacioni të qaset në të dhënat e gjurmimit për mjedisin drejtpërdrejt rreth teje me nivel shumë të lartë të detajeve."</string>
<string name="permlab_xr_tracking_in_background" msgid="7117098718465619023">"të qaset në të dhënat për XR kur nuk është në plan të parë"</string>
<string name="permdesc_xr_tracking_in_background" msgid="939504041387836853">"Lejon që aplikacioni të qaset në të dhënat për XR kur nuk është në plan të parë."</string>
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Mund ta përdorësh mikrofonin e aparatit të dëgjimit për telefonata pa përdorur duart. Kjo vetëm ndërron mikrofonin gjatë telefonatës."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Ndërro"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Cilësimet"</string>
- <string name="user_switched" msgid="7249833311585228097">"Emri i përdoruesit aktual: <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="user_switching_message" msgid="1912993630661332336">"Po kalon në \"<xliff:g id="NAME">%1$s</xliff:g>\"…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> po del…"</string>
<string name="owner_name" msgid="8713560351570795743">"Zotëruesi"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index 77ceff2..61102d3 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -1804,8 +1804,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Режим једном руком"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Додатно затамни"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"Слушни апарати"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"Аутоматски клик"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"Веза је прекинута"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"Повезано"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"Активно"</string>
@@ -1826,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Можете да користите микрофон слушног апарата за хендсфри позивање. Тиме се микрофон мења само током позива."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Промени"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Подешавања"</string>
- <string name="user_switched" msgid="7249833311585228097">"Актуелни корисник <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Пребацивање на <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Одјављује се <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Власник"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 1ee2564..d5fc694 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -661,7 +661,7 @@
<string name="permlab_hand_tracking" msgid="6478233866595566940">"spåra dina händer"</string>
<string name="permdesc_hand_tracking" msgid="8639715900104966456">"Tillåter att appen får åtkomst till data om handspårning."</string>
<string name="permlab_head_tracking" msgid="1309731456372087270">"spåra ditt huvud"</string>
- <string name="permdesc_head_tracking" msgid="231597390513699188">"Tillåter att appen får åtkomst till data om huvudföljning."</string>
+ <string name="permdesc_head_tracking" msgid="231597390513699188">"Tillåter att appen får åtkomst till data om huvudspårning."</string>
<string name="permlab_scene_understanding_coarse" msgid="6518646430502858641">"förstå din omgivning"</string>
<string name="permdesc_scene_understanding_coarse" msgid="4508880777646198656">"Tillåter att appen får åtkomst till spårningsdata om omgivningen runtomkring dig."</string>
<string name="permlab_scene_understanding_fine" msgid="409126403264393251">"förstå din omgivning i detalj"</string>
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Du kan använda hörapparatens mikrofon för handsfree-samtal. Detta byter bara mikrofon under samtalet."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Byt"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Inställningar"</string>
- <string name="user_switched" msgid="7249833311585228097">"Nuvarande användare: <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Byter till <xliff:g id="NAME">%1$s</xliff:g> …"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Loggar ut <xliff:g id="NAME">%1$s</xliff:g> …"</string>
<string name="owner_name" msgid="8713560351570795743">"Ägare"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 36bf76d..49e9f1c 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Unaweza kutumia maikrofoni ya visaidizi vyako vya kusikia ili kupiga simu bila kugusa. Hali hii hubadilisha maikrofoni yako tu wakati unapiga simu."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Badilisha"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Mipangilio"</string>
- <string name="user_switched" msgid="7249833311585228097">"Mtumiaji wa sasa <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Inaenda kwa <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Inamwondoa <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Mmiliki"</string>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index 3fa3827..dad40dc 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"கைகளைப் பயன்படுத்தாமல் அழைக்க உங்கள் செவித்துணைக் கருவியின் மைக்ரோஃபோனைப் பயன்படுத்தலாம். அழைப்பின்போது உங்கள் மைக்கை மட்டுமே இது மாற்றுகிறது."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"மாற்று"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"அமைப்புகள்"</string>
- <string name="user_switched" msgid="7249833311585228097">"நடப்பு பயனர் <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g>க்கு மாறுகிறது…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> வெளியேறுகிறார்…"</string>
<string name="owner_name" msgid="8713560351570795743">"உரிமையாளர்"</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 027161e..322a703 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"హ్యాండ్స్-ఫ్రీ కాలింగ్ కోసం మీరు మీ వినికిడి పరికరాన్ని ఉపయోగించవచ్చు. ఇది కాల్ సమయంలో మాత్రమే మీ మైక్ను మారుస్తుంది."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"మారండి"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"సెట్టింగ్లు"</string>
- <string name="user_switched" msgid="7249833311585228097">"ప్రస్తుత వినియోగదారు <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> యూజర్కు స్విచ్ అవుతోంది…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g>ని లాగ్ అవుట్ చేస్తోంది…"</string>
<string name="owner_name" msgid="8713560351570795743">"ఓనర్"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index f82adc6..107a59f 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -1803,8 +1803,7 @@
<string name="one_handed_mode_feature_name" msgid="2334330034828094891">"โหมดมือเดียว"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"หรี่แสงเพิ่มเติม"</string>
<string name="hearing_aids_feature_name" msgid="1125892105105852542">"เครื่องช่วยฟัง"</string>
- <!-- no translation found for autoclick_feature_name (8149248738736949630) -->
- <skip />
+ <string name="autoclick_feature_name" msgid="8149248738736949630">"การคลิกอัตโนมัติ"</string>
<string name="hearing_device_status_disconnected" msgid="497547752953543832">"เลิกเชื่อมต่อแล้ว"</string>
<string name="hearing_device_status_connected" msgid="2149385149669918764">"เชื่อมต่อแล้ว"</string>
<string name="hearing_device_status_active" msgid="4770378695482566032">"ใช้งานอยู่"</string>
@@ -1825,7 +1824,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"คุณใช้ไมโครโฟนของเครื่องช่วยฟังเพื่อโทรแบบแฮนด์ฟรีได้ การดำเนินการนี้เพียงแค่เปลี่ยนไมโครโฟนระหว่างการโทรเท่านั้น"</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"เปลี่ยน"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"การตั้งค่า"</string>
- <string name="user_switched" msgid="7249833311585228097">"ผู้ใช้ปัจจุบัน <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="user_switching_message" msgid="1912993630661332336">"กำลังเปลี่ยนเป็น<xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"กำลังออกจากระบบ <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"เจ้าของ"</string>
@@ -2281,7 +2279,7 @@
<string name="conversation_title_fallback_one_to_one" msgid="1980753619726908614">"การสนทนา"</string>
<string name="conversation_title_fallback_group_chat" msgid="456073374993104303">"บทสนทนากลุ่ม"</string>
<string name="unread_convo_overflow" msgid="920517615597353833">"<xliff:g id="MAX_UNREAD_COUNT">%1$d</xliff:g>+"</string>
- <string name="resolver_personal_tab" msgid="2051260504014442073">"ส่วนบุคคล"</string>
+ <string name="resolver_personal_tab" msgid="2051260504014442073">"ส่วนตัว"</string>
<string name="resolver_work_tab" msgid="2690019516263167035">"งาน"</string>
<string name="resolver_personal_tab_accessibility" msgid="5739524949153091224">"มุมมองส่วนตัว"</string>
<string name="resolver_work_tab_accessibility" msgid="4753168230363802734">"ดูงาน"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 328997d..9788327 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Puwede mong gamitin ang mikropono ng iyong hearing aid para sa hands-free na pagtawag. Inililipat lang nito ang iyong mikropono habang nasa tawag ka."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Lumipat"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Mga Setting"</string>
- <string name="user_switched" msgid="7249833311585228097">"Kasalukuyang user <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Lumilipat kay <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Nila-log out si <xliff:g id="NAME">%1$s</xliff:g>..."</string>
<string name="owner_name" msgid="8713560351570795743">"May-ari"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 5106e1a..095a3da 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Eller serbest modunda arama yapmak için işitme cihazı mikrofonunu kullanabilirsiniz. Bu işlem yalnızca görüşme sırasında mikrofonunuzu değiştirir."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Geçiş yap"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Ayarlar"</string>
- <string name="user_switched" msgid="7249833311585228097">"Geçerli kullanıcı: <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> adlı kullanıcıya geçiliyor…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> hesabından çıkış yapılıyor…"</string>
<string name="owner_name" msgid="8713560351570795743">"Sahip"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 203cdfa..bc05f49 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -1827,7 +1827,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Ви можете використовувати мікрофон слухового апарата для голосового керування викликами. Мікрофон перемикатиметься лише на час дзвінка."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Перемкнути"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Налаштування"</string>
- <string name="user_switched" msgid="7249833311585228097">"Поточний користувач: <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Перехід в обліковий запис \"<xliff:g id="NAME">%1$s</xliff:g>\"…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Вихід з облікового запису користувача <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Власник"</string>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index 376efd1..7794c9a 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"آپ ہینڈز فری کالنگ کے لیے اپنا سماعتی آلہ مائیکروفون استعمال کر سکتے ہیں۔ یہ صرف کال کے دوران آپ کا مائیک سوئچ کرتا ہے۔"</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"سوئچ کریں"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"ترتیبات"</string>
- <string name="user_switched" msgid="7249833311585228097">"موجودہ صارف <xliff:g id="NAME">%1$s</xliff:g>۔"</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> پر سوئچ کیا جا رہا ہے…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> لاگ آؤٹ ہو رہا ہے…"</string>
<string name="owner_name" msgid="8713560351570795743">"مالک"</string>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index a1b9e5d..95a44bb 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Garniturali chaqiruv uchun eshitish moslamasi mikrofonidan foydalanish mumkin. Bu faqat chaqiruv paytida mikrofonni almashtiradi."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Almashtirish"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Sozlamalar"</string>
- <string name="user_switched" msgid="7249833311585228097">"Joriy foydalanuvchi <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Bunga almashilmoqda: <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> hisobidan chiqilmoqda…"</string>
<string name="owner_name" msgid="8713560351570795743">"Egasi"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 473568d..bb9b797 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Bạn có thể sử dụng micrô của thiết bị trợ thính để gọi điện mà không cần dùng tay. Thao tác này chỉ chuyển micrô của bạn trong cuộc gọi."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Chuyển"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Cài đặt"</string>
- <string name="user_switched" msgid="7249833311585228097">"Người dùng hiện tại <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Đang chuyển sang <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Đang đăng xuất <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Chủ sở hữu"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index f91f457..108d62e 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"您可以使用助听器麦克风进行免提通话。此操作只会切换通话期间的麦克风。"</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"切换"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"设置"</string>
- <string name="user_switched" msgid="7249833311585228097">"当前用户是<xliff:g id="NAME">%1$s</xliff:g>。"</string>
<string name="user_switching_message" msgid="1912993630661332336">"正在切换为<xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"正在将<xliff:g id="NAME">%1$s</xliff:g>退出账号…"</string>
<string name="owner_name" msgid="8713560351570795743">"机主"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 9bcb173..fc9e6e8 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"你可使用助聽器麥克風進行免提通話。此操作只會切換通話期間的麥克風。"</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"切換"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"設定"</string>
- <string name="user_switched" msgid="7249833311585228097">"目前的使用者是<xliff:g id="NAME">%1$s</xliff:g>。"</string>
<string name="user_switching_message" msgid="1912993630661332336">"正在切換至<xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"正在登出 <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"擁有者"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 9f8fd67..d2ca941 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"免持通話時,可以使用助聽器麥克風。麥克風只會在通話期間切換。"</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"切換"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"設定"</string>
- <string name="user_switched" msgid="7249833311585228097">"目前的使用者是 <xliff:g id="NAME">%1$s</xliff:g>。"</string>
<string name="user_switching_message" msgid="1912993630661332336">"正在切換至<xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"正在將<xliff:g id="NAME">%1$s</xliff:g>登出帳戶…"</string>
<string name="owner_name" msgid="8713560351570795743">"擁有者"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index a0df4a1..e116832 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -1825,7 +1825,6 @@
<string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Ungasebenzisa imakrofoni yakho yomshini wendlebe ekufoneni ngehands-free. Lokhu kushintsha kuphela imakrofoni yakho ngesikhathi sekholi."</string>
<string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Shintsha"</string>
<string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Amasethingi"</string>
- <string name="user_switched" msgid="7249833311585228097">"Umsebenzisi wamanje <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"Ishintshela ku-<xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="user_logging_out_message" msgid="7216437629179710359">"Iyaphuma <xliff:g id="NAME">%1$s</xliff:g>…"</string>
<string name="owner_name" msgid="8713560351570795743">"Umnikazi"</string>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 9983c45..0905ae0 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -854,8 +854,21 @@
<aside class="note"><b>Note:</b>
<ul>
<li>To improve the layout of apps on form factors with smallest width >= 600dp, the
- system ignores this attribute for apps that target Android 16 (API level 36) or
- higher.</li>
+ system ignores the following values of this attribute for apps that target
+ Android 16 (API level 36) or higher:
+ <ul>
+ <li><code>portrait</code></li>
+ <li><code>landscape</code></li>
+ <li><code>reversePortrait</code></li>
+ <li><code>reverseLandscape</code></li>
+ <li><code>sensorPortrait</code></li>
+ <li><code>sensorLandscape</code></li>
+ <li><code>userPortrait</code></li>
+ <li><code>userLandscape</code></li>
+ </ul>
+ <p>The values are treated as if the app had set orientation as
+ <code>unspecified</code>.</p>
+ </li>
<li>Device manufacturers can configure devices to override (ignore) this attribute
to improve the layout of apps.</li>
<li>On devices with Android 16 (API level 36) or higher installed, virtual device
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 4e93c44..bf1423b 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -557,6 +557,7 @@
<color name="accessibility_magnification_background">#F50D60</color>
<color name="accessibility_daltonizer_background">#00BCD4</color>
<color name="accessibility_color_inversion_background">#546E7A</color>
+ <color name="accessibility_autoclick_background">#67D4FF</color>
<!-- Fullscreen magnification thumbnail color -->
<color name="accessibility_magnification_thumbnail_stroke_color">#0C0C0C</color>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 1cb38be..fbb8e25 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3072,6 +3072,43 @@
{@link MotionEvent#ACTION_SCROLL} event. -->
<dimen name="config_scrollFactor">64dp</dimen>
+ <!-- Duration in milliseconds of the pressed state in child components. -->
+ <integer name="config_pressedStateDurationMillis">64</integer>
+
+ <!-- Duration in milliseconds we will wait to see if a touch event is a tap or a scroll.
+ If the user does not move within this interval, it is considered to be a tap. -->
+ <integer name="config_tapTimeoutMillis">100</integer>
+
+ <!-- Duration in milliseconds we will wait to see if a touch event is a jump tap.
+ If the user does not move within this interval, it is considered to be a tap. -->
+ <integer name="config_jumpTapTimeoutMillis">500</integer>
+
+ <!-- Duration in milliseconds between the first tap's up event and the second tap's down
+ event for an interaction to be considered a double-tap. -->
+ <integer name="config_doubleTapTimeoutMillis">300</integer>
+
+ <!-- Minimum duration in milliseconds between the first tap's up event and the second tap's
+ down event for an interaction to be considered a double-tap. -->
+ <integer name="config_doubleTapMinTimeMillis">40</integer>
+
+ <!-- Maximum duration in milliseconds between a touch pad touch and release for a given touch
+ to be considered a tap (click) as opposed to a hover movement gesture. -->
+ <integer name="config_hoverTapTimeoutMillis">150</integer>
+
+ <!-- The amount of time in milliseconds that the zoom controls should be displayed on the
+ screen. -->
+ <integer name="config_zoomControlsTimeoutMillis">3000</integer>
+
+ <!-- Default duration in milliseconds for {@link ActionMode#hide(long)}. -->
+ <integer name="config_defaultActionModeHideDurationMillis">2000</integer>
+
+ <!-- Maximum distance in pixels that a touch pad touch can move before being released
+ for it to be considered a tap (click) as opposed to a hover movement gesture. -->
+ <dimen name="config_hoverTapSlop">20px</dimen>
+
+ <!-- The amount of friction applied to scrolls and flings. -->
+ <item name="config_scrollFriction" format="float" type="dimen">0.015</item>
+
<!-- Maximum number of grid columns permitted in the ResolverActivity
used for picking activities to handle an intent. -->
<integer name="config_maxResolverActivityColumns">3</integer>
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 59ed25a..ef6b918 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -80,11 +80,6 @@
<bool name="auto_data_switch_ping_test_before_switch">true</bool>
<java-symbol type="bool" name="auto_data_switch_ping_test_before_switch" />
- <!-- TODO: remove after V -->
- <!-- Boolean indicating whether allow to use a roaming nonDDS if user enabled its roaming. -->
- <bool name="auto_data_switch_allow_roaming">true</bool>
- <java-symbol type="bool" name="auto_data_switch_allow_roaming" />
-
<!-- Define the tolerated gap of score for auto data switch decision, larger than which the
device will switch to the SIM with higher score. The score is used in conjunction with the
score table defined in
@@ -524,4 +519,9 @@
capabilities. -->
<string-array name="config_unsupported_network_capabilities" translatable="false"></string-array>
<java-symbol type="array" name="config_unsupported_network_capabilities" />
+
+ <!-- Whether to enable APDU sender optimization i.e. a logical channel is opened and
+ kept open for multiple APDU commands within one session.-->
+ <bool name="euicc_optimize_apdu_sender">false</bool>
+ <java-symbol type="bool" name="euicc_optimize_apdu_sender" />
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index cb3dfc7..b3cc883 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5412,7 +5412,7 @@
<!-- Notification shown when device owner silently installs a package [CHAR LIMIT=NONE] -->
<string name="package_installed_device_owner">Installed by your admin.\nGo to settings to view granted permissions</string>
<!-- Notification shown when device owner silently updates a package [CHAR LIMIT=NONE] -->
- <string name="package_updated_device_owner">Updated by your admin</string>
+ <string name="package_updated_device_owner">Updated by your admin.\nGo to settings to view granted permissions</string>
<!-- Notification shown when device owner silently deletes a package [CHAR LIMIT=NONE] -->
<string name="package_deleted_device_owner">Deleted by your admin</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c8c1e73..f5424db 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4159,6 +4159,17 @@
<java-symbol type="string" name="config_headlineFontFamily" />
<java-symbol type="string" name="config_headlineFontFamilyMedium" />
+ <java-symbol type="integer" name="config_pressedStateDurationMillis" />
+ <java-symbol type="integer" name="config_tapTimeoutMillis" />
+ <java-symbol type="integer" name="config_jumpTapTimeoutMillis" />
+ <java-symbol type="integer" name="config_doubleTapTimeoutMillis" />
+ <java-symbol type="integer" name="config_doubleTapMinTimeMillis" />
+ <java-symbol type="integer" name="config_hoverTapTimeoutMillis" />
+ <java-symbol type="integer" name="config_zoomControlsTimeoutMillis" />
+ <java-symbol type="integer" name="config_defaultActionModeHideDurationMillis" />
+ <java-symbol type="dimen" name="config_hoverTapSlop" />
+ <java-symbol type="dimen" name="config_scrollFriction" />
+
<java-symbol type="drawable" name="stat_sys_vitals" />
<java-symbol type="color" name="text_color_primary" />
@@ -5652,6 +5663,7 @@
<java-symbol type="id" name="accessibility_autoclick_position_button" />
<java-symbol type="drawable" name="accessibility_autoclick_pause" />
<java-symbol type="drawable" name="accessibility_autoclick_resume" />
+ <java-symbol type="drawable" name="ic_accessibility_autoclick" />
<!-- For HapticFeedbackConstants configurability defined at HapticFeedbackCustomization -->
<java-symbol type="string" name="config_hapticFeedbackCustomizationFile" />
diff --git a/core/tests/coretests/src/android/os/PerfettoTraceTest.java b/core/tests/coretests/src/android/os/PerfettoTraceTest.java
index 6915015..790ac4a 100644
--- a/core/tests/coretests/src/android/os/PerfettoTraceTest.java
+++ b/core/tests/coretests/src/android/os/PerfettoTraceTest.java
@@ -33,6 +33,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -611,6 +612,7 @@
@Test
@RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2)
+ @Ignore("b/303199244")
public void testMessageQueue() throws Exception {
TraceConfig traceConfig = getTraceConfig("mq");
diff --git a/core/tests/coretests/src/com/android/internal/os/DebugStoreTest.java b/core/tests/coretests/src/com/android/internal/os/DebugStoreTest.java
index 786c2fc..bc452e7 100644
--- a/core/tests/coretests/src/com/android/internal/os/DebugStoreTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/DebugStoreTest.java
@@ -47,21 +47,17 @@
/**
* Test class for {@link DebugStore}.
*
- * To run it:
- * atest FrameworksCoreTests:com.android.internal.os.DebugStoreTest
+ * <p>To run it: atest FrameworksCoreTests:com.android.internal.os.DebugStoreTest
*/
@RunWith(AndroidJUnit4.class)
@DisabledOnRavenwood(blockedBy = DebugStore.class)
@SmallTest
public class DebugStoreTest {
- @Rule
- public final RavenwoodRule mRavenwood = new RavenwoodRule();
+ @Rule public final RavenwoodRule mRavenwood = new RavenwoodRule();
- @Mock
- private DebugStore.DebugStoreNative mDebugStoreNativeMock;
+ @Mock private DebugStore.DebugStoreNative mDebugStoreNativeMock;
- @Captor
- private ArgumentCaptor<List<String>> mListCaptor;
+ @Captor private ArgumentCaptor<List<String>> mListCaptor;
@Before
public void setUp() {
@@ -79,16 +75,14 @@
when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(1L);
long eventId = DebugStore.recordServiceOnStart(1, 0, intent);
-
- verify(mDebugStoreNativeMock).beginEvent(eq("SvcStart"), mListCaptor.capture());
- List<String> capturedList = mListCaptor.getValue();
- assertThat(capturedList).containsExactly(
- "stId", "1",
- "flg", "0",
- "act", "com.android.ACTION",
- "comp", "ComponentInfo{com.android/androidService}",
- "pkg", "com.android"
- ).inOrder();
+ assertThat(paramsForBeginEvent("SvcStart"))
+ .containsExactly(
+ "stId", "1",
+ "flg", "0",
+ "act", "com.android.ACTION",
+ "comp", "ComponentInfo{com.android/androidService}",
+ "pkg", "com.android")
+ .inOrder();
assertThat(eventId).isEqualTo(1L);
}
@@ -101,13 +95,11 @@
when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(2L);
long eventId = DebugStore.recordServiceCreate(serviceInfo);
-
- verify(mDebugStoreNativeMock).beginEvent(eq("SvcCreate"), mListCaptor.capture());
- List<String> capturedList = mListCaptor.getValue();
- assertThat(capturedList).containsExactly(
- "name", "androidService",
- "pkg", "com.android"
- ).inOrder();
+ assertThat(paramsForBeginEvent("SvcCreate"))
+ .containsExactly(
+ "name", "androidService",
+ "pkg", "com.android")
+ .inOrder();
assertThat(eventId).isEqualTo(2L);
}
@@ -121,59 +113,60 @@
when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(3L);
long eventId = DebugStore.recordServiceBind(true, intent);
-
- verify(mDebugStoreNativeMock).beginEvent(eq("SvcBind"), mListCaptor.capture());
- List<String> capturedList = mListCaptor.getValue();
- assertThat(capturedList).containsExactly(
- "rebind", "true",
- "act", "com.android.ACTION",
- "cmp", "ComponentInfo{com.android/androidService}",
- "pkg", "com.android"
- ).inOrder();
+ assertThat(paramsForBeginEvent("SvcBind"))
+ .containsExactly(
+ "rebind", "true",
+ "act", "com.android.ACTION",
+ "cmp", "ComponentInfo{com.android/androidService}",
+ "pkg", "com.android")
+ .inOrder();
assertThat(eventId).isEqualTo(3L);
}
@Test
public void testRecordGoAsync() {
- DebugStore.recordGoAsync("androidReceiver");
+ DebugStore.recordGoAsync(3840 /* 0xf00 */);
- verify(mDebugStoreNativeMock).recordEvent(eq("GoAsync"), mListCaptor.capture());
- List<String> capturedList = mListCaptor.getValue();
- assertThat(capturedList).containsExactly(
- "tname", Thread.currentThread().getName(),
- "tid", String.valueOf(Thread.currentThread().getId()),
- "rcv", "androidReceiver"
- ).inOrder();
+ assertThat(paramsForRecordEvent("GoAsync"))
+ .containsExactly(
+ "tname",
+ Thread.currentThread().getName(),
+ "tid",
+ String.valueOf(Thread.currentThread().getId()),
+ "prid",
+ "f00")
+ .inOrder();
}
@Test
public void testRecordFinish() {
- DebugStore.recordFinish("androidReceiver");
+ DebugStore.recordFinish(3840 /* 0xf00 */);
- verify(mDebugStoreNativeMock).recordEvent(eq("Finish"), mListCaptor.capture());
- List<String> capturedList = mListCaptor.getValue();
- assertThat(capturedList).containsExactly(
- "tname", Thread.currentThread().getName(),
- "tid", String.valueOf(Thread.currentThread().getId()),
- "rcv", "androidReceiver"
- ).inOrder();
+ assertThat(paramsForRecordEvent("Finish"))
+ .containsExactly(
+ "tname",
+ Thread.currentThread().getName(),
+ "tid",
+ String.valueOf(Thread.currentThread().getId()),
+ "prid",
+ "f00")
+ .inOrder();
}
@Test
public void testRecordLongLooperMessage() {
DebugStore.recordLongLooperMessage(100, "androidHandler", 500L);
- verify(mDebugStoreNativeMock).recordEvent(eq("LooperMsg"), mListCaptor.capture());
- List<String> capturedList = mListCaptor.getValue();
- assertThat(capturedList).containsExactly(
- "code", "100",
- "trgt", "androidHandler",
- "elapsed", "500"
- ).inOrder();
+ assertThat(paramsForRecordEvent("LooperMsg"))
+ .containsExactly(
+ "code", "100",
+ "trgt", "androidHandler",
+ "elapsed", "500")
+ .inOrder();
}
@Test
- public void testRecordBroadcastHandleReceiver() {
+ public void testRecordBroadcastReceive() {
Intent intent = new Intent();
intent.setAction("com.android.ACTION");
intent.setComponent(new ComponentName("com.android", "androidReceiver"));
@@ -181,21 +174,87 @@
when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(4L);
- long eventId = DebugStore.recordBroadcastHandleReceiver(intent);
-
- verify(mDebugStoreNativeMock).beginEvent(eq("HandleReceiver"), mListCaptor.capture());
- List<String> capturedList = mListCaptor.getValue();
- assertThat(capturedList).containsExactly(
- "tname", Thread.currentThread().getName(),
- "tid", String.valueOf(Thread.currentThread().getId()),
- "act", "com.android.ACTION",
- "cmp", "ComponentInfo{com.android/androidReceiver}",
- "pkg", "com.android"
- ).inOrder();
+ long eventId = DebugStore.recordBroadcastReceive(intent, 3840 /* 0xf00 */);
+ assertThat(paramsForBeginEvent("BcRcv"))
+ .containsExactly(
+ "tname", Thread.currentThread().getName(),
+ "tid", String.valueOf(Thread.currentThread().getId()),
+ "act", "com.android.ACTION",
+ "cmp", "ComponentInfo{com.android/androidReceiver}",
+ "pkg", "com.android",
+ "prid", "f00")
+ .inOrder();
assertThat(eventId).isEqualTo(4L);
}
@Test
+ public void testRecordBroadcastReceiveReg() {
+ Intent intent = new Intent();
+ intent.setAction("com.android.ACTION");
+ intent.setComponent(new ComponentName("com.android", "androidReceiver"));
+ intent.setPackage("com.android");
+
+ when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(5L);
+
+ long eventId = DebugStore.recordBroadcastReceiveReg(intent, 3840 /* 0xf00 */);
+ assertThat(paramsForBeginEvent("BcRcvReg"))
+ .containsExactly(
+ "tname",
+ Thread.currentThread().getName(),
+ "tid",
+ String.valueOf(Thread.currentThread().getId()),
+ "act",
+ "com.android.ACTION",
+ "cmp",
+ "ComponentInfo{com.android/androidReceiver}",
+ "pkg",
+ "com.android",
+ "prid",
+ "f00")
+ .inOrder();
+ assertThat(eventId).isEqualTo(5L);
+ }
+
+ @Test
+ public void testRecordHandleBindApplication() {
+ when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(6L);
+ long eventId = DebugStore.recordHandleBindApplication();
+
+ assertThat(paramsForBeginEvent("BindApp")).isEmpty();
+ assertThat(eventId).isEqualTo(6L);
+ }
+
+ @Test
+ public void testRecordScheduleReceiver() {
+ when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(7L);
+ long eventId = DebugStore.recordScheduleReceiver();
+
+ assertThat(paramsForBeginEvent("SchRcv"))
+ .containsExactly(
+ "tname",
+ Thread.currentThread().getName(),
+ "tid",
+ String.valueOf(Thread.currentThread().getId()))
+ .inOrder();
+ assertThat(eventId).isEqualTo(7L);
+ }
+
+ @Test
+ public void testRecordScheduleRegisteredReceiver() {
+ when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(8L);
+ long eventId = DebugStore.recordScheduleRegisteredReceiver();
+
+ assertThat(paramsForBeginEvent("SchRcvReg"))
+ .containsExactly(
+ "tname",
+ Thread.currentThread().getName(),
+ "tid",
+ String.valueOf(Thread.currentThread().getId()))
+ .inOrder();
+ assertThat(eventId).isEqualTo(8L);
+ }
+
+ @Test
public void testRecordEventEnd() {
DebugStore.recordEventEnd(1L);
@@ -203,109 +262,124 @@
}
@Test
- public void testRecordServiceOnStartWithNullIntent() {
+ public void testRecordServiceOnStart_withNullIntent() {
when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(5L);
long eventId = DebugStore.recordServiceOnStart(1, 0, null);
-
- verify(mDebugStoreNativeMock).beginEvent(eq("SvcStart"), mListCaptor.capture());
- List<String> capturedList = mListCaptor.getValue();
- assertThat(capturedList).containsExactly(
- "stId", "1",
- "flg", "0",
- "act", "null",
- "comp", "null",
- "pkg", "null"
- ).inOrder();
+ assertThat(paramsForBeginEvent("SvcStart"))
+ .containsExactly(
+ "stId", "1",
+ "flg", "0",
+ "act", "null",
+ "comp", "null",
+ "pkg", "null")
+ .inOrder();
assertThat(eventId).isEqualTo(5L);
}
@Test
- public void testRecordServiceCreateWithNullServiceInfo() {
+ public void testRecordServiceCreate_withNullServiceInfo() {
when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(6L);
long eventId = DebugStore.recordServiceCreate(null);
-
- verify(mDebugStoreNativeMock).beginEvent(eq("SvcCreate"), mListCaptor.capture());
- List<String> capturedList = mListCaptor.getValue();
- assertThat(capturedList).containsExactly(
- "name", "null",
- "pkg", "null"
- ).inOrder();
+ assertThat(paramsForBeginEvent("SvcCreate"))
+ .containsExactly(
+ "name", "null",
+ "pkg", "null")
+ .inOrder();
assertThat(eventId).isEqualTo(6L);
}
@Test
- public void testRecordServiceBindWithNullIntent() {
+ public void testRecordServiceBind_withNullIntent() {
when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(7L);
long eventId = DebugStore.recordServiceBind(false, null);
-
- verify(mDebugStoreNativeMock).beginEvent(eq("SvcBind"), mListCaptor.capture());
- List<String> capturedList = mListCaptor.getValue();
- assertThat(capturedList).containsExactly(
- "rebind", "false",
- "act", "null",
- "cmp", "null",
- "pkg", "null"
- ).inOrder();
+ assertThat(paramsForBeginEvent("SvcBind"))
+ .containsExactly(
+ "rebind", "false",
+ "act", "null",
+ "cmp", "null",
+ "pkg", "null")
+ .inOrder();
assertThat(eventId).isEqualTo(7L);
}
@Test
- public void testRecordBroadcastHandleReceiverWithNullIntent() {
+ public void testRecordBroadcastReceive_withNullIntent() {
when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(8L);
- long eventId = DebugStore.recordBroadcastHandleReceiver(null);
-
- verify(mDebugStoreNativeMock).beginEvent(eq("HandleReceiver"), mListCaptor.capture());
- List<String> capturedList = mListCaptor.getValue();
- assertThat(capturedList).containsExactly(
- "tname", Thread.currentThread().getName(),
- "tid", String.valueOf(Thread.currentThread().getId()),
- "act", "null",
- "cmp", "null",
- "pkg", "null"
- ).inOrder();
+ long eventId = DebugStore.recordBroadcastReceive(null, 3840 /* 0xf00 */);
+ assertThat(paramsForBeginEvent("BcRcv"))
+ .containsExactly(
+ "tname", Thread.currentThread().getName(),
+ "tid", String.valueOf(Thread.currentThread().getId()),
+ "act", "null",
+ "cmp", "null",
+ "pkg", "null",
+ "prid", "f00")
+ .inOrder();
assertThat(eventId).isEqualTo(8L);
}
@Test
- public void testRecordGoAsyncWithNullReceiverClassName() {
- DebugStore.recordGoAsync(null);
+ public void testRecordBroadcastReceiveReg_withNullIntent() {
+ when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(8L);
- verify(mDebugStoreNativeMock).recordEvent(eq("GoAsync"), mListCaptor.capture());
- List<String> capturedList = mListCaptor.getValue();
- assertThat(capturedList).containsExactly(
- "tname", Thread.currentThread().getName(),
- "tid", String.valueOf(Thread.currentThread().getId()),
- "rcv", "null"
- ).inOrder();
+ long eventId = DebugStore.recordBroadcastReceiveReg(null, 3840 /* 0xf00 */);
+ assertThat(paramsForBeginEvent("BcRcvReg"))
+ .containsExactly(
+ "tname",
+ Thread.currentThread().getName(),
+ "tid",
+ String.valueOf(Thread.currentThread().getId()),
+ "act",
+ "null",
+ "cmp",
+ "null",
+ "pkg",
+ "null",
+ "prid",
+ "f00")
+ .inOrder();
+ assertThat(eventId).isEqualTo(8L);
}
@Test
- public void testRecordFinishWithNullReceiverClassName() {
- DebugStore.recordFinish(null);
+ public void testRecordFinish_withNullReceiverClassName() {
+ DebugStore.recordFinish(3840 /* 0xf00 */);
- verify(mDebugStoreNativeMock).recordEvent(eq("Finish"), mListCaptor.capture());
- List<String> capturedList = mListCaptor.getValue();
- assertThat(capturedList).containsExactly(
- "tname", Thread.currentThread().getName(),
- "tid", String.valueOf(Thread.currentThread().getId()),
- "rcv", "null"
- ).inOrder();
+ assertThat(paramsForRecordEvent("Finish"))
+ .containsExactly(
+ "tname",
+ Thread.currentThread().getName(),
+ "tid",
+ String.valueOf(Thread.currentThread().getId()),
+ "prid",
+ "f00")
+ .inOrder();
}
@Test
- public void testRecordLongLooperMessageWithNullTargetClass() {
+ public void testRecordLongLooperMessage_withNullTargetClass() {
DebugStore.recordLongLooperMessage(200, null, 1000L);
- verify(mDebugStoreNativeMock).recordEvent(eq("LooperMsg"), mListCaptor.capture());
- List<String> capturedList = mListCaptor.getValue();
- assertThat(capturedList).containsExactly(
- "code", "200",
- "trgt", "null",
- "elapsed", "1000"
- ).inOrder();
+ assertThat(paramsForRecordEvent("LooperMsg"))
+ .containsExactly(
+ "code", "200",
+ "trgt", "null",
+ "elapsed", "1000")
+ .inOrder();
}
+
+ private List<String> paramsForBeginEvent(String eventName) {
+ verify(mDebugStoreNativeMock).beginEvent(eq(eventName), mListCaptor.capture());
+ return mListCaptor.getValue();
+ }
+
+ private List<String> paramsForRecordEvent(String eventName) {
+ verify(mDebugStoreNativeMock).recordEvent(eq(eventName), mListCaptor.capture());
+ return mListCaptor.getValue();
+ }
+
}
diff --git a/core/tests/coretests/src/com/android/internal/os/OWNERS b/core/tests/coretests/src/com/android/internal/os/OWNERS
index 3f8f9e2..ec236ca 100644
--- a/core/tests/coretests/src/com/android/internal/os/OWNERS
+++ b/core/tests/coretests/src/com/android/internal/os/OWNERS
@@ -2,3 +2,4 @@
# CPU
per-file *Cpu* = file:/core/java/com/android/internal/os/CPU_OWNERS
+per-file *DebugStore* = file:/core/java/com/android/internal/os/DEBUG_STORE_OWNERS
diff --git a/core/tests/vibrator/src/android/os/ExternalVibrationTest.java b/core/tests/vibrator/src/android/os/ExternalVibrationTest.java
index 8741907..9c9d502 100644
--- a/core/tests/vibrator/src/android/os/ExternalVibrationTest.java
+++ b/core/tests/vibrator/src/android/os/ExternalVibrationTest.java
@@ -49,4 +49,22 @@
assertThat(restored.getAudioAttributes()).isEqualTo(original.getAudioAttributes());
assertThat(restored.getToken()).isEqualTo(original.getToken());
}
+
+ @Test
+ public void testSerialization_systemUsage() {
+ ExternalVibration original =
+ new ExternalVibration(
+ 123,
+ "pkg",
+ new AudioAttributes.Builder()
+ .setSystemUsage(AudioAttributes.USAGE_SPEAKER_CLEANUP)
+ .build(),
+ IExternalVibrationController.Stub.asInterface(new Binder()));
+ Parcel p = Parcel.obtain();
+ original.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ ExternalVibration restored = ExternalVibration.CREATOR.createFromParcel(p);
+
+ assertThat(restored.getAudioAttributes()).isEqualTo(original.getAudioAttributes());
+ }
}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 15f7029..9234902 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -619,6 +619,8 @@
<permission name="android.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE"/>
<permission name="android.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE"/>
<permission name="android.permission.READ_COLOR_ZONES"/>
+ <!-- Permission required for CTS test - CtsTextClassifierTestCases -->
+ <permission name="android.permission.ACCESS_TEXT_CLASSIFIER_BY_TYPE"/>
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
index 7cb6f87..4ee7d7e 100644
--- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Levi ekran 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Levi ekran 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Režim celog ekrana za donji ekran"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Zamenite gornju aplikaciju donjom"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Zamenite levu aplikaciju desnom"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Režim celog ekrana za gornji ekran"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Gornji ekran 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Gornji ekran 50%"</string>
@@ -115,14 +113,10 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Restartuj"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne prikazuj ponovo"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dvaput dodirnite da biste\npremestili ovu aplikaciju"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Uvećajte: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Vratite: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Umanjite: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Zatvorite: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Nazad"</string>
<string name="handle_text" msgid="4419667835599523257">"Identifikator aplikacije"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Ikona aplikacije"</string>
@@ -158,14 +152,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Promenite veličinu prozora nalevo"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Promenite veličinu prozora nadesno"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Uvećajte ili vratite veličinu prozora"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Uvećajte veličinu prozora aplikacije"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Vratite veličinu prozora"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Umanjite prozor aplikacije"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Zatvorite prozor aplikacije"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Podešavanje Podrazumevano otvaraj"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Odaberite način otvaranja veb-linkova za ovu aplikaciju"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"U aplikaciji"</string>
diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
index ebfdc6d..1d5d385 100644
--- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
@@ -142,7 +142,7 @@
<string name="collapse_menu_text" msgid="7515008122450342029">"Cerrar menú"</string>
<string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (vista para computadoras de escritorio)"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximizar pantalla"</string>
- <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Cambiar el tamaño"</string>
+ <string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Dividir pantalla"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"No se puede mover la app aquí"</string>
<string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Inmersivo"</string>
<string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restablecer"</string>
diff --git a/libs/WindowManager/Shell/res/values-it/strings.xml b/libs/WindowManager/Shell/res/values-it/strings.xml
index 85f0f8c..a2787a7 100644
--- a/libs/WindowManager/Shell/res/values-it/strings.xml
+++ b/libs/WindowManager/Shell/res/values-it/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Schermata sinistra al 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Schermata sinistra al 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Schermata destra a schermo intero"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Scambia l\'app in alto con quella in basso"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Scambia l\'app di sinistra con quella di destra"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Schermata superiore a schermo intero"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Schermata superiore al 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Schermata superiore al 50%"</string>
@@ -115,14 +113,10 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Riavvia"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Non mostrare più"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Tocca due volte per\nspostare questa app"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Ingrandisci <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Ripristina <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Riduci a icona <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Chiudi <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Indietro"</string>
<string name="handle_text" msgid="4419667835599523257">"Punto di manipolazione app"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Icona dell\'app"</string>
@@ -158,14 +152,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Ridimensiona la finestra a sinistra"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Ridimensiona la finestra a destra"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Ingrandisci o ripristina le dimensioni della finestra"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Ingrandisci le dimensioni della finestra dell\'app"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Ripristina le dimensioni della finestra"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Riduci a icona la finestra dell\'app"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Chiudi finestra dell\'app"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Apri in base alle impostazioni predefinite"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Scegli come aprire i link web per questa app"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"All\'interno dell\'app"</string>
diff --git a/libs/WindowManager/Shell/res/values-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml
index 3a12680..d68581b 100644
--- a/libs/WindowManager/Shell/res/values-ja/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ja/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"左 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"左 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"右全画面"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"上下のアプリを入れ替える"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"左右のアプリを入れ替える"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"上部全画面"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"上 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"上 50%"</string>
@@ -115,14 +113,10 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"再起動"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"次回から表示しない"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ダブルタップすると\nこのアプリを移動できます"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」を最大化する"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」を元に戻す"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」を最小化する"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」を閉じる"</string>
<string name="back_button_text" msgid="1469718707134137085">"戻る"</string>
<string name="handle_text" msgid="4419667835599523257">"アプリハンドル"</string>
<string name="app_icon_text" msgid="2823268023931811747">"アプリのアイコン"</string>
@@ -158,14 +152,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"ウィンドウを左側にサイズ変更する"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"ウィンドウを右側にサイズ変更する"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"ウィンドウを最大化する、またはウィンドウを元のサイズに戻す"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"アプリ ウィンドウを最大化する"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"ウィンドウを元のサイズに戻す"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"アプリ ウィンドウを最小化する"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"アプリ ウィンドウを閉じる"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"デフォルトの設定で開く"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"このアプリのウェブリンクを開く方法を選択"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"アプリ内"</string>
diff --git a/libs/WindowManager/Shell/res/values-ml/strings.xml b/libs/WindowManager/Shell/res/values-ml/strings.xml
index c48cbce..cb84dec8 100644
--- a/libs/WindowManager/Shell/res/values-ml/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ml/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ഇടത് 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"ഇടത് 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"വലത് പൂർണ്ണ സ്ക്രീൻ"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"മുകളിലെയും താഴത്തെയും ആപ്പ് സ്വാപ്പ് ചെയ്യുക"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"ഇടതുവശത്തെയും വലതുവശത്തെയും ആപ്പ് സ്വാപ്പ് ചെയ്യുക"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"മുകളിൽ പൂർണ്ണ സ്ക്രീൻ"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"മുകളിൽ 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"മുകളിൽ 50%"</string>
@@ -115,14 +113,10 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"റീസ്റ്റാർട്ട് ചെയ്യൂ"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"വീണ്ടും കാണിക്കരുത്"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ഈ ആപ്പ് നീക്കാൻ\nഡബിൾ ടാപ്പ് ചെയ്യുക"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g> പരമാവധിയാക്കുക"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g> പുനഃസ്ഥാപിക്കുക"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g> ചുരുക്കുക"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g> അടയ്ക്കുക"</string>
<string name="back_button_text" msgid="1469718707134137085">"മടങ്ങുക"</string>
<string name="handle_text" msgid="4419667835599523257">"ആപ്പ് ഹാൻഡിൽ"</string>
<string name="app_icon_text" msgid="2823268023931811747">"ആപ്പ് ഐക്കൺ"</string>
@@ -158,14 +152,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"ഇടത്തേക്ക് ആപ്പ് വിൻഡോ വലുപ്പം മാറ്റുക"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"വലത്തേക്ക് ആപ്പ് വിൻഡോ വലുപ്പം മാറ്റുക"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"വിന്ഡോ വലുപ്പം വലുതാക്കുക അല്ലെങ്കിൽ പഴയത് പുനഃസ്ഥാപിക്കുക"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"ആപ്പ് വിൻഡോ വലുപ്പം പരമാവധിയാക്കുക"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"വിൻഡോ വലുപ്പം പുനഃസ്ഥാപിക്കുക"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"ആപ്പ് വിൻഡോ ചുരുക്കുക"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"ആപ്പ് വിൻഡോ അടയ്ക്കുക"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"ഡിഫോൾട്ട് ക്രമീകരണം ഉപയോഗിച്ച് തുറക്കുക"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"ഈ ആപ്പിനായി വെബ് ലിങ്കുകൾ എങ്ങനെ തുറക്കണമെന്ന് തിരഞ്ഞെടുക്കൂ"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"ആപ്പിൽ"</string>
diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml
index f0cd560..e954b0f 100644
--- a/libs/WindowManager/Shell/res/values-ms/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ms/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Kiri 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Kiri 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Skrin penuh kanan"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Tukar apl bahagian atas dengan apl bahagian bawah"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Tukar apl sebelah kiri dengan apl sebelah kanan"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Skrin penuh atas"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Atas 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Atas 50%"</string>
@@ -115,14 +113,10 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Mulakan semula"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Jangan tunjukkan lagi"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Ketik dua kali untuk\nalih apl ini"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Maksimumkan <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Pulihkan <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Minimumkan <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Tutup <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Kembali"</string>
<string name="handle_text" msgid="4419667835599523257">"Pengendalian apl"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Ikon Apl"</string>
@@ -158,14 +152,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Ubah saiz tetingkap ke sebelah kiri"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Ubah saiz tetingkap ke sebelah kanan"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maksimumkan atau pulihkan saiz tetingkap"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maksimumkan saiz tetingkap apl"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Pulihkan saiz tetingkap"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimumkan tetingkap apl"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Tutup tetingkap apl"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Buka tetapan secara lalai"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Pilih cara membuka pautan web untuk apl ini"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Pada apl"</string>
diff --git a/libs/WindowManager/Shell/res/values-or/strings.xml b/libs/WindowManager/Shell/res/values-or/strings.xml
index 6390d37..62ee4a0 100644
--- a/libs/WindowManager/Shell/res/values-or/strings.xml
+++ b/libs/WindowManager/Shell/res/values-or/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ବାମ ପଟକୁ 50% କରନ୍ତୁ"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"ବାମ ପଟେ 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"ଡାହାଣ ପଟକୁ ପୂର୍ଣ୍ଣ ସ୍କ୍ରୀନ୍ କରନ୍ତୁ"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"ବଟମ ସହ ଟପକୁ ଆପ ସ୍ବାପ କରନ୍ତୁ"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"ଡାହାଣ ସହ ବାମକୁ ଆପ ସ୍ବାପ କରନ୍ତୁ"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"ଉପର ଆଡ଼କୁ ପୂର୍ଣ୍ଣ ସ୍କ୍ରୀନ୍ କରନ୍ତୁ"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"ଉପର ଆଡ଼କୁ 70% କରନ୍ତୁ"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ଉପର ଆଡ଼କୁ 50% କରନ୍ତୁ"</string>
@@ -115,14 +113,10 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"ରିଷ୍ଟାର୍ଟ କରନ୍ତୁ"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ପୁଣି ଦେଖାନ୍ତୁ ନାହିଁ"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ଏହି ଆପକୁ ମୁଭ\nକରିବା ପାଇଁ ଦୁଇଥର-ଟାପ କରନ୍ତୁ"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g> ବଡ଼ କରନ୍ତୁ"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g> ରିଷ୍ଟୋର କରନ୍ତୁ"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g> ଛୋଟ କରନ୍ତୁ"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g> ବନ୍ଦ କରନ୍ତୁ"</string>
<string name="back_button_text" msgid="1469718707134137085">"ପଛକୁ ଫେରନ୍ତୁ"</string>
<string name="handle_text" msgid="4419667835599523257">"ଆପର ହେଣ୍ଡେଲ"</string>
<string name="app_icon_text" msgid="2823268023931811747">"ଆପ ଆଇକନ"</string>
@@ -158,14 +152,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"ବାମପଟକୁ ୱିଣ୍ଡୋ ରିସାଇଜ କରନ୍ତୁ"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"ଡାହାଣପଟକୁ ୱିଣ୍ଡୋ ରିସାଇଜ କରନ୍ତୁ"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"ୱିଣ୍ଡୋ ସାଇଜକୁ ମେକ୍ସିମାଇଜ କିମ୍ବା ରିଷ୍ଟୋର କରନ୍ତୁ"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"ଆପ ଇଣ୍ଡୋ ଆକାର ବଡ଼ କରନ୍ତୁ"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"ୱିଣ୍ଡୋ ଆକାର ରିଷ୍ଟୋର କରନ୍ତୁ"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"ଆପ ୱିଣ୍ଡୋକୁ ମିନିମାଇଜ କରନ୍ତୁ"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"ଆପ ୱିଣ୍ଡୋ ବନ୍ଦ କରନ୍ତୁ"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"ଡିଫଲ୍ଟ ସେଟିଂସକୁ ଖୋଲନ୍ତୁ"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"ଏହି ଆପ ପାଇଁ ୱେବ ଲିଙ୍କଗୁଡ଼ିକୁ କିପରି ଖୋଲିବେ, ତାହା ବାଛନ୍ତୁ"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"ଆପରେ"</string>
diff --git a/libs/WindowManager/Shell/res/values-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml
index d2b837c..4dde6d2 100644
--- a/libs/WindowManager/Shell/res/values-pa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pa/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ਖੱਬੇ 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"ਖੱਬੇ 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"ਸੱਜੇ ਪੂਰੀ ਸਕ੍ਰੀਨ"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"ਸਿਖਰਲੀ ਐਪ ਨੂੰ ਹੇਠਲੀ ਨਾਲ ਬਦਲੋ"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"ਖੱਬੀ ਐਪ ਨੂੰ ਸੱਜੀ ਨਾਲ ਬਦਲੋ"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"ਉੱਪਰ ਪੂਰੀ ਸਕ੍ਰੀਨ"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"ਉੱਪਰ 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ਉੱਪਰ 50%"</string>
@@ -115,14 +113,10 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"ਮੁੜ-ਸ਼ੁਰੂ ਕਰੋ"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ਦੁਬਾਰਾ ਨਾ ਦਿਖਾਓ"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ਇਸ ਐਪ ਦਾ ਟਿਕਾਣਾ ਬਦਲਣ ਲਈ\nਡਬਲ ਟੈਪ ਕਰੋ"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਨੂੰ ਵੱਡਾ ਕਰੋ"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਨੂੰ ਮੁੜ-ਬਹਾਲ ਕਰੋ"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਨੂੰ ਛੋਟਾ ਕਰੋ"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਨੂੰ ਬੰਦ ਕਰੋ"</string>
<string name="back_button_text" msgid="1469718707134137085">"ਪਿੱਛੇ"</string>
<string name="handle_text" msgid="4419667835599523257">"ਐਪ ਹੈਂਡਲ"</string>
<string name="app_icon_text" msgid="2823268023931811747">"ਐਪ ਪ੍ਰਤੀਕ"</string>
@@ -158,14 +152,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"ਵਿੰਡੋ ਦਾ ਆਕਾਰ ਬਦਲ ਕੇ ਖੱਬੇ ਪਾਸੇ ਕਰੋ"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"ਵਿੰਡੋ ਦਾ ਆਕਾਰ ਬਦਲ ਕੇ ਸੱਜੇ ਪਾਸੇ ਕਰੋ"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"ਵਿੰਡੋ ਦਾ ਆਕਾਰ ਵਧਾਓ ਜਾਂ ਮੁੜ-ਬਹਾਲ ਕਰੋ"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"ਐਪ ਵਿੰਡੋ ਦਾ ਆਕਾਰ ਵਧਾਓ"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"ਵਿੰਡੋ ਦੇ ਆਕਾਰ ਨੂੰ ਮੁੜ-ਬਹਾਲ ਕਰੋ"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"ਐਪ ਵਿੰਡੋ ਨੂੰ ਛੋਟਾ ਕਰੋ"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"ਐਪ ਵਿੰਡੋ ਬੰਦ ਕਰੋ"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"ਪੂਰਵ-ਨਿਰਧਾਰਿਤ ਸੈਟਿੰਗਾਂ ਮੁਤਾਬਕ ਖੋਲ੍ਹੋ"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"ਇਸ ਐਪ ਲਈ ਵੈੱਬ ਲਿੰਕਾਂ ਨੂੰ ਖੋਲ੍ਹਣ ਦਾ ਤਰੀਕਾ ਚੁਣੋ"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"ਐਪ ਵਿੱਚ"</string>
diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml
index c64df85..3e88ed1 100644
--- a/libs/WindowManager/Shell/res/values-sq/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sq/strings.xml
@@ -127,7 +127,7 @@
<string name="handle_text" msgid="4419667835599523257">"Emërtimi i aplikacionit"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Ikona e aplikacionit"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Ekrani i plotë"</string>
- <string name="desktop_text" msgid="1582173066857454541">"Pamja për desktop"</string>
+ <string name="desktop_text" msgid="1582173066857454541">"Pamja e desktopit"</string>
<string name="split_screen_text" msgid="1396336058129570886">"Ekrani i ndarë"</string>
<string name="more_button_text" msgid="3655388105592893530">"Më shumë"</string>
<string name="float_button_text" msgid="9221657008391364581">"Pluskuese"</string>
@@ -140,7 +140,7 @@
<string name="change_aspect_ratio_text" msgid="9104456064548212806">"Ndrysho raportin e pamjes"</string>
<string name="close_text" msgid="4986518933445178928">"Mbyll"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Mbyll menynë"</string>
- <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (Pamja për desktop)"</string>
+ <string name="desktop_mode_app_header_chip_text" msgid="8300164817452574565">"<xliff:g id="APP_NAME">%1$s</xliff:g> (Pamja e desktopit)"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimizo ekranin"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="5673738963174074006">"Ndrysho përmasat"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikacioni nuk mund të zhvendoset këtu"</string>
diff --git a/libs/WindowManager/Shell/res/values-sr/strings.xml b/libs/WindowManager/Shell/res/values-sr/strings.xml
index d2462a7..287d34f 100644
--- a/libs/WindowManager/Shell/res/values-sr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sr/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Леви екран 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Леви екран 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Режим целог екрана за доњи екран"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Замените горњу апликацију доњом"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Замените леву апликацију десном"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Режим целог екрана за горњи екран"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Горњи екран 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Горњи екран 50%"</string>
@@ -115,14 +113,10 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"Рестартуј"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Не приказуј поново"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Двапут додирните да бисте\nпреместили ову апликацију"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"Увећајте: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"Вратите: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"Умањите: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"Затворите: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
<string name="handle_text" msgid="4419667835599523257">"Идентификатор апликације"</string>
<string name="app_icon_text" msgid="2823268023931811747">"Икона апликације"</string>
@@ -158,14 +152,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Промените величину прозора налево"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Промените величину прозора надесно"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Увећајте или вратите величину прозора"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Увећајте величину прозора апликације"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Вратите величину прозора"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Умањите прозор апликације"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Затворите прозор апликације"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"Подешавање Подразумевано отварај"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Одаберите начин отварања веб-линкова за ову апликацију"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"У апликацији"</string>
diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml
index 16372d2..594ee6c9 100644
--- a/libs/WindowManager/Shell/res/values-th/strings.xml
+++ b/libs/WindowManager/Shell/res/values-th/strings.xml
@@ -43,10 +43,8 @@
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ซ้าย 50%"</string>
<string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"ซ้าย 30%"</string>
<string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"เต็มหน้าจอทางขวา"</string>
- <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) -->
- <skip />
- <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) -->
- <skip />
+ <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"สลับแอปด้านบนกับด้านล่าง"</string>
+ <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"สลับแอปด้านซ้ายกับด้านขวา"</string>
<string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"เต็มหน้าจอด้านบน"</string>
<string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"ด้านบน 70%"</string>
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ด้านบน 50%"</string>
@@ -115,14 +113,10 @@
<string name="letterbox_restart_restart" msgid="8529976234412442973">"รีสตาร์ท"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ไม่ต้องแสดงข้อความนี้อีก"</string>
<string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"แตะสองครั้ง\nเพื่อย้ายแอปนี้"</string>
- <!-- no translation found for maximize_button_text (8106849394538234709) -->
- <skip />
- <!-- no translation found for restore_button_text (5377571986086775288) -->
- <skip />
- <!-- no translation found for minimize_button_text (5213953162664451152) -->
- <skip />
- <!-- no translation found for close_button_text (4544839489310949894) -->
- <skip />
+ <string name="maximize_button_text" msgid="8106849394538234709">"ขยาย <xliff:g id="APP_NAME">%1$s</xliff:g> ให้ใหญ่สุด"</string>
+ <string name="restore_button_text" msgid="5377571986086775288">"คืนค่า <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="minimize_button_text" msgid="5213953162664451152">"ย่อ <xliff:g id="APP_NAME">%1$s</xliff:g> ให้เล็กสุด"</string>
+ <string name="close_button_text" msgid="4544839489310949894">"ปิด <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="back_button_text" msgid="1469718707134137085">"กลับ"</string>
<string name="handle_text" msgid="4419667835599523257">"แฮนเดิลแอป"</string>
<string name="app_icon_text" msgid="2823268023931811747">"ไอคอนแอป"</string>
@@ -158,14 +152,10 @@
<string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"ปรับขนาดหน้าต่างไปทางซ้าย"</string>
<string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"ปรับขนาดหน้าต่างไปทางขวา"</string>
<string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"ขยายหรือคืนค่าขนาดหน้าต่าง"</string>
- <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) -->
- <skip />
- <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) -->
- <skip />
+ <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"ขยายขนาดหน้าต่างแอปให้ใหญ่สุด"</string>
+ <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"คืนค่าขนาดหน้าต่าง"</string>
+ <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"ย่อหน้าต่างแอปให้เล็กสุด"</string>
+ <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"ปิดหน้าต่างแอป"</string>
<string name="open_by_default_settings_text" msgid="2526548548598185500">"เปิดตามการตั้งค่าเริ่มต้น"</string>
<string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"เลือกวิธีเปิดเว็บลิงก์สำหรับแอปนี้"</string>
<string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"ในแอป"</string>
diff --git a/libs/WindowManager/Shell/shared/res/values/dimen.xml b/libs/WindowManager/Shell/shared/res/values/dimen.xml
index 23c9caf..f6a176f 100644
--- a/libs/WindowManager/Shell/shared/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/shared/res/values/dimen.xml
@@ -39,6 +39,8 @@
<!-- Bubble drop target dimensions -->
<dimen name="drop_target_elevation">1dp</dimen>
+ <dimen name="drop_target_radius">28dp</dimen>
+ <dimen name="drop_target_stroke">1dp</dimen>
<dimen name="drop_target_full_screen_padding">20dp</dimen>
<dimen name="drop_target_desktop_window_padding_small">100dp</dimen>
<dimen name="drop_target_desktop_window_padding_large">130dp</dimen>
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DeviceConfig.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DeviceConfig.kt
index f479da0..1b7c9c2 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DeviceConfig.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DeviceConfig.kt
@@ -49,19 +49,29 @@
or WindowInsets.Type.displayCutout())
val windowBounds = windowMetrics.bounds
val config: Configuration = context.resources.configuration
- val isLargeScreen = config.smallestScreenWidthDp >= LARGE_SCREEN_MIN_EDGE_DP
- val largestEdgeDp = max(config.screenWidthDp, config.screenHeightDp)
- val isSmallTablet = isLargeScreen && largestEdgeDp < SMALL_TABLET_MAX_EDGE_DP
val isLandscape = context.resources.configuration.orientation == ORIENTATION_LANDSCAPE
val isRtl = context.resources.configuration.layoutDirection == LAYOUT_DIRECTION_RTL
return DeviceConfig(
- isLargeScreen = isLargeScreen,
- isSmallTablet = isSmallTablet,
+ isLargeScreen = isLargeScreen(config),
+ isSmallTablet = isSmallTablet(context),
isLandscape = isLandscape,
isRtl = isRtl,
windowBounds = windowBounds,
insets = insets
)
}
+
+ @JvmStatic
+ fun isSmallTablet(context: Context): Boolean {
+ val config: Configuration = context.resources.configuration
+ if (!isLargeScreen(config)) {
+ return false
+ }
+ val largestEdgeDp = max(config.screenWidthDp, config.screenHeightDp)
+ return largestEdgeDp < SMALL_TABLET_MAX_EDGE_DP
+ }
+
+ private fun isLargeScreen(config: Configuration) =
+ config.smallestScreenWidthDp >= LARGE_SCREEN_MIN_EDGE_DP
}
}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetManager.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetManager.kt
index 2dc183f..2dbbeae 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetManager.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetManager.kt
@@ -18,29 +18,34 @@
import android.content.Context
import android.graphics.Rect
-import android.view.View
+import android.graphics.RectF
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.widget.FrameLayout
import androidx.core.animation.Animator
import androidx.core.animation.AnimatorListenerAdapter
import androidx.core.animation.ValueAnimator
+import com.android.wm.shell.shared.R
/**
* Manages animating drop targets in response to dragging bubble icons or bubble expanded views
* across different drag zones.
*/
class DropTargetManager(
- context: Context,
+ private val context: Context,
private val container: FrameLayout,
- private val isLayoutRtl: Boolean,
private val dragZoneChangedListener: DragZoneChangedListener,
) {
private var state: DragState? = null
- private val dropTargetView = View(context)
+ private val dropTargetView = DropTargetView(context)
private var animator: ValueAnimator? = null
+ private var morphRect: RectF = RectF(0f, 0f, 0f, 0f)
+ private val isLayoutRtl = container.isLayoutRtl
private companion object {
- const val ANIMATION_DURATION_MS = 250L
+ const val MORPH_ANIM_DURATION = 250L
+ const val DROP_TARGET_ALPHA_IN_DURATION = 150L
+ const val DROP_TARGET_ALPHA_OUT_DURATION = 100L
}
/** Must be called when a drag gesture is starting. */
@@ -55,15 +60,10 @@
private fun setupDropTarget() {
if (dropTargetView.parent != null) container.removeView(dropTargetView)
container.addView(dropTargetView, 0)
- // TODO b/393173014: set elevation and background
dropTargetView.alpha = 0f
- dropTargetView.scaleX = 1f
- dropTargetView.scaleY = 1f
- dropTargetView.translationX = 0f
- dropTargetView.translationY = 0f
- // the drop target is added with a width and height of 1 pixel. when it gets resized, we use
- // set its scale to the width and height of the bounds it should have to avoid layout passes
- dropTargetView.layoutParams = FrameLayout.LayoutParams(/* width= */ 1, /* height= */ 1)
+ dropTargetView.elevation = context.resources.getDimension(R.dimen.drop_target_elevation)
+ // Match parent and the target is drawn within the view
+ dropTargetView.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
}
/** Called when the user drags to a new location. */
@@ -80,9 +80,11 @@
/** Called when the drag ended. */
fun onDragEnded() {
+ val dropState = state ?: return
startFadeAnimation(from = dropTargetView.alpha, to = 0f) {
container.removeView(dropTargetView)
}
+ dragZoneChangedListener.onDragEnded(dropState.currentDragZone)
state = null
}
@@ -92,10 +94,7 @@
when {
dropTargetBounds == null -> startFadeAnimation(from = dropTargetView.alpha, to = 0f)
dropTargetView.alpha == 0f -> {
- dropTargetView.translationX = dropTargetBounds.exactCenterX()
- dropTargetView.translationY = dropTargetBounds.exactCenterY()
- dropTargetView.scaleX = dropTargetBounds.width().toFloat()
- dropTargetView.scaleY = dropTargetBounds.height().toFloat()
+ dropTargetView.update(RectF(dropTargetBounds))
startFadeAnimation(from = 0f, to = 1f)
}
else -> startMorphAnimation(dropTargetBounds)
@@ -104,7 +103,9 @@
private fun startFadeAnimation(from: Float, to: Float, onEnd: (() -> Unit)? = null) {
animator?.cancel()
- val animator = ValueAnimator.ofFloat(from, to).setDuration(ANIMATION_DURATION_MS)
+ val duration =
+ if (from < to) DROP_TARGET_ALPHA_IN_DURATION else DROP_TARGET_ALPHA_OUT_DURATION
+ val animator = ValueAnimator.ofFloat(from, to).setDuration(duration)
animator.addUpdateListener { _ -> dropTargetView.alpha = animator.animatedValue as Float }
if (onEnd != null) {
animator.doOnEnd(onEnd)
@@ -113,23 +114,20 @@
animator.start()
}
- private fun startMorphAnimation(bounds: Rect) {
+ private fun startMorphAnimation(endBounds: Rect) {
animator?.cancel()
val startAlpha = dropTargetView.alpha
- val startTx = dropTargetView.translationX
- val startTy = dropTargetView.translationY
- val startScaleX = dropTargetView.scaleX
- val startScaleY = dropTargetView.scaleY
- val animator = ValueAnimator.ofFloat(0f, 1f).setDuration(ANIMATION_DURATION_MS)
+ val startRect = dropTargetView.getRect()
+ val animator = ValueAnimator.ofFloat(0f, 1f).setDuration(MORPH_ANIM_DURATION)
animator.addUpdateListener { _ ->
val fraction = animator.animatedValue as Float
dropTargetView.alpha = startAlpha + (1 - startAlpha) * fraction
- dropTargetView.translationX = startTx + (bounds.exactCenterX() - startTx) * fraction
- dropTargetView.translationY = startTy + (bounds.exactCenterY() - startTy) * fraction
- dropTargetView.scaleX =
- startScaleX + (bounds.width().toFloat() - startScaleX) * fraction
- dropTargetView.scaleY =
- startScaleY + (bounds.height().toFloat() - startScaleY) * fraction
+
+ morphRect.left = (startRect.left + (endBounds.left - startRect.left) * fraction)
+ morphRect.top = (startRect.top + (endBounds.top - startRect.top) * fraction)
+ morphRect.right = (startRect.right + (endBounds.right - startRect.right) * fraction)
+ morphRect.bottom = (startRect.bottom + (endBounds.bottom - startRect.bottom) * fraction)
+ dropTargetView.update(morphRect)
}
this.animator = animator
animator.start()
@@ -160,6 +158,9 @@
/** Called when the object was dragged to a different drag zone. */
fun onDragZoneChanged(from: DragZone, to: DragZone)
+
+ /** Called when the drag has ended with the zone it ended in. */
+ fun onDragEnded(zone: DragZone)
}
private fun Animator.doOnEnd(onEnd: () -> Unit) {
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetView.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetView.kt
new file mode 100644
index 0000000..2bb6cf4
--- /dev/null
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetView.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.wm.shell.shared.bubbles
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.graphics.RectF
+import android.view.View
+import com.android.wm.shell.shared.R
+
+/**
+ * Shows a drop target within this view.
+ */
+class DropTargetView(context: Context) : View(context) {
+
+ private val rectPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
+ color = context.getColor(com.android.internal.R.color.materialColorPrimaryContainer)
+ style = Paint.Style.FILL
+ alpha = (0.35f * 255).toInt()
+ }
+
+ private val strokePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
+ color = context.getColor(com.android.internal.R.color.materialColorPrimaryContainer)
+ style = Paint.Style.STROKE
+ strokeWidth = context.resources.getDimensionPixelSize(R.dimen.drop_target_stroke).toFloat()
+ }
+
+ private val cornerRadius = context.resources.getDimensionPixelSize(
+ R.dimen.drop_target_radius).toFloat()
+
+ private val rect = RectF(0f, 0f, 0f, 0f)
+
+ override fun onDraw(canvas: Canvas) {
+ canvas.save()
+ canvas.drawRoundRect(rect, cornerRadius, cornerRadius, rectPaint)
+ canvas.drawRoundRect(rect, cornerRadius, cornerRadius, strokePaint)
+ canvas.restore()
+ }
+
+ fun update(positionRect: RectF) {
+ rect.set(positionRect)
+ invalidate()
+ }
+
+ fun getRect(): RectF {
+ return RectF(rect)
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/SizeChangeAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/SizeChangeAnimation.java
index 5018fdb..8e78686 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/SizeChangeAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/SizeChangeAnimation.java
@@ -83,7 +83,11 @@
private static final int ANIMATION_RESOLUTION = 1000;
public SizeChangeAnimation(Rect startBounds, Rect endBounds) {
- mAnimation = buildContainerAnimation(startBounds, endBounds);
+ this(startBounds, endBounds, 1f);
+ }
+
+ public SizeChangeAnimation(Rect startBounds, Rect endBounds, float initialScale) {
+ mAnimation = buildContainerAnimation(startBounds, endBounds, initialScale);
mSnapshotAnim = buildSnapshotAnimation(startBounds, endBounds);
}
@@ -167,7 +171,8 @@
}
/** Animation for the whole container (snapshot is inside this container). */
- private static AnimationSet buildContainerAnimation(Rect startBounds, Rect endBounds) {
+ private static AnimationSet buildContainerAnimation(Rect startBounds, Rect endBounds,
+ float initialScale) {
final long duration = ANIMATION_RESOLUTION;
boolean growing = endBounds.width() - startBounds.width()
+ endBounds.height() - startBounds.height() >= 0;
@@ -180,15 +185,27 @@
final Animation scaleAnim = new ScaleAnimation(startScaleX, 1, startScaleY, 1);
scaleAnim.setDuration(scalePeriod);
+ long scaleStartOffset = 0;
if (!growing) {
- scaleAnim.setStartOffset(duration - scalePeriod);
+ scaleStartOffset = duration - scalePeriod;
}
+ scaleAnim.setStartOffset(scaleStartOffset);
animSet.addAnimation(scaleAnim);
+
+ if (initialScale != 1f) {
+ final Animation initialScaleAnim = new ScaleAnimation(initialScale, 1f, initialScale,
+ 1f);
+ initialScaleAnim.setDuration(scalePeriod);
+ initialScaleAnim.setStartOffset(scaleStartOffset);
+ animSet.addAnimation(initialScaleAnim);
+ }
+
final Animation translateAnim = new TranslateAnimation(startBounds.left,
endBounds.left, startBounds.top, endBounds.top);
translateAnim.setDuration(duration);
animSet.addAnimation(translateAnim);
Rect startClip = new Rect(startBounds);
+ startClip.scale(initialScale);
Rect endClip = new Rect(endBounds);
startClip.offsetTo(0, 0);
endClip.offsetTo(0, 0);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index be22402..50e2f4d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -2643,6 +2643,15 @@
mBubbleData.setSelectedBubbleAndExpandStack(bubbleToSelect);
}
+ private void moveBubbleToFullscreen(String key) {
+ Bubble b = mBubbleData.getBubbleInStackWithKey(key);
+ if (b == null) {
+ Log.w(TAG, "can't find bubble with key " + key + " to move to fullscreen");
+ return;
+ }
+ b.getTaskView().moveToFullscreen();
+ }
+
private boolean isDeviceLocked() {
return !mIsStatusBarShade;
}
@@ -2929,6 +2938,11 @@
}
});
}
+
+ @Override
+ public void moveBubbleToFullscreen(String key) {
+ mMainExecutor.execute(() -> mController.moveBubbleToFullscreen(key));
+ }
}
private class BubblesImpl implements Bubbles {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index abcdb7e..226ad57 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -1236,7 +1236,6 @@
/** @return the bubble in the stack that matches the provided key. */
@Nullable
- @VisibleForTesting(visibility = PRIVATE)
public Bubble getBubbleInStackWithKey(String key) {
return getBubbleWithPredicate(mBubbles, b -> b.getKey().equals(key));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index 33f1b94..70340d7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -136,6 +136,11 @@
updateInternal(mRotation, deviceConfig.getInsets(), deviceConfig.getWindowBounds());
}
+ /** Returns the device config being used. */
+ public DeviceConfig getCurrentConfig() {
+ return mDeviceConfig;
+ }
+
@VisibleForTesting
public void updateInternal(int rotation, Insets insets, Rect bounds) {
BubbleStackView.RelativeStackPosition prevStackPosition = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java
index 338ffe7..e7e7be9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java
@@ -30,6 +30,7 @@
import android.app.ActivityManager;
import android.app.TaskInfo;
import android.content.Context;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.os.IBinder;
import android.util.Slog;
@@ -155,28 +156,23 @@
* Information about the task when it is being dragged to a bubble
*/
public static class DragData {
- private final Rect mBounds;
private final WindowContainerTransaction mPendingWct;
private final boolean mReleasedOnLeft;
+ private final float mTaskScale;
+ private final PointF mDragPosition;
/**
- * @param bounds bounds of the dragged task when the drag was released
- * @param wct pending operations to be applied when finishing the drag
* @param releasedOnLeft true if the bubble was released in the left drop target
+ * @param taskScale the scale of the task when it was dragged to bubble
+ * @param dragPosition the position of the task when it was dragged to bubble
+ * @param wct pending operations to be applied when finishing the drag
*/
- public DragData(@Nullable Rect bounds, @Nullable WindowContainerTransaction wct,
- boolean releasedOnLeft) {
- mBounds = bounds;
+ public DragData(boolean releasedOnLeft, float taskScale, @Nullable PointF dragPosition,
+ @Nullable WindowContainerTransaction wct) {
mPendingWct = wct;
mReleasedOnLeft = releasedOnLeft;
- }
-
- /**
- * @return bounds of the dragged task when the drag was released
- */
- @Nullable
- public Rect getBounds() {
- return mBounds;
+ mTaskScale = taskScale;
+ mDragPosition = dragPosition != null ? dragPosition : new PointF(0, 0);
}
/**
@@ -193,6 +189,20 @@
public boolean isReleasedOnLeft() {
return mReleasedOnLeft;
}
+
+ /**
+ * @return the scale of the task when it was dragged to bubble
+ */
+ public float getTaskScale() {
+ return mTaskScale;
+ }
+
+ /**
+ * @return position of the task when it was dragged to bubble
+ */
+ public PointF getDragPosition() {
+ return mDragPosition;
+ }
}
/**
@@ -347,21 +357,24 @@
}
mFinishCb = finishCallback;
- if (mDragData != null && mDragData.getBounds() != null) {
- // Override start bounds with the dragged task bounds
- mStartBounds.set(mDragData.getBounds());
+ if (mDragData != null) {
+ mStartBounds.offsetTo((int) mDragData.getDragPosition().x,
+ (int) mDragData.getDragPosition().y);
+ startTransaction.setScale(mSnapshot, mDragData.getTaskScale(),
+ mDragData.getTaskScale());
}
// Now update state (and talk to launcher) in parallel with snapshot stuff
mBubbleData.notificationEntryUpdated(mBubble, /* suppressFlyout= */ true,
/* showInShade= */ false);
+ final int left = mStartBounds.left - info.getRoot(0).getOffset().x;
+ final int top = mStartBounds.top - info.getRoot(0).getOffset().y;
+ startTransaction.setPosition(mTaskLeash, left, top);
startTransaction.show(mSnapshot);
// Move snapshot to root so that it remains visible while task is moved to taskview
startTransaction.reparent(mSnapshot, info.getRoot(0).getLeash());
- startTransaction.setPosition(mSnapshot,
- mStartBounds.left - info.getRoot(0).getOffset().x,
- mStartBounds.top - info.getRoot(0).getOffset().y);
+ startTransaction.setPosition(mSnapshot, left, top);
startTransaction.setLayer(mSnapshot, Integer.MAX_VALUE);
BubbleBarExpandedView bbev = mBubble.getBubbleBarExpandedView();
@@ -416,6 +429,8 @@
private void playAnimation(boolean animate) {
final TaskViewTaskController tv = mBubble.getTaskView().getController();
final SurfaceControl.Transaction startT = new SurfaceControl.Transaction();
+ // Set task position to 0,0 as it will be placed inside the TaskView
+ startT.setPosition(mTaskLeash, 0, 0);
mTaskViewTransitions.prepareOpenAnimation(tv, true /* new */, startT, mFinishT,
(ActivityManager.RunningTaskInfo) mTaskInfo, mTaskLeash, mFinishWct);
@@ -424,10 +439,12 @@
}
if (animate) {
- mLayerView.animateConvert(startT, mStartBounds, mSnapshot, mTaskLeash, () -> {
- mFinishCb.onTransitionFinished(mFinishWct);
- mFinishCb = null;
- });
+ float startScale = mDragData != null ? mDragData.getTaskScale() : 1f;
+ mLayerView.animateConvert(startT, mStartBounds, startScale, mSnapshot, mTaskLeash,
+ () -> {
+ mFinishCb.onTransitionFinished(mFinishWct);
+ mFinishCb = null;
+ });
} else {
startT.apply();
mFinishCb.onTransitionFinished(mFinishWct);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
index ae1b407..079edb3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
@@ -58,4 +58,6 @@
oneway void showExpandedView() = 14;
oneway void showDropTarget(in boolean show, in @nullable BubbleBarLocation location) = 15;
+
+ oneway void moveBubbleToFullscreen(in String key) = 16;
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
index 52f2064..fa22a39 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
@@ -580,6 +580,7 @@
public void animateConvert(BubbleViewProvider expandedBubble,
@NonNull SurfaceControl.Transaction startT,
@NonNull Rect origBounds,
+ float origScale,
@NonNull SurfaceControl snapshot,
@NonNull SurfaceControl taskLeash,
@Nullable Runnable afterAnimation) {
@@ -599,7 +600,7 @@
new SizeChangeAnimation(
new Rect(origBounds.left - position.x, origBounds.top - position.y,
origBounds.right - position.x, origBounds.bottom - position.y),
- new Rect(0, 0, size.getWidth(), size.getHeight()));
+ new Rect(0, 0, size.getWidth(), size.getHeight()), origScale);
sca.initialize(bbev, taskLeash, snapshot, startT);
Animator a = sca.buildViewAnimator(bbev, tvSf, snapshot, /* onFinish */ (va) -> {
@@ -614,6 +615,7 @@
bbev.setSurfaceZOrderedOnTop(true);
a.setDuration(EXPANDED_VIEW_ANIMATE_TO_REST_DURATION);
+ a.setInterpolator(Interpolators.EMPHASIZED);
a.start();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
index 34259bf..9d4f904 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
@@ -21,7 +21,11 @@
import android.view.View
import androidx.annotation.VisibleForTesting
import com.android.wm.shell.bubbles.BubblePositioner
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation
import com.android.wm.shell.shared.bubbles.DismissView
+import com.android.wm.shell.shared.bubbles.DragZoneFactory
+import com.android.wm.shell.shared.bubbles.DraggedObject
+import com.android.wm.shell.shared.bubbles.DropTargetManager
import com.android.wm.shell.shared.bubbles.RelativeTouchListener
import com.android.wm.shell.shared.magnetictarget.MagnetizedObject
@@ -33,6 +37,8 @@
private val animationHelper: BubbleBarAnimationHelper,
private val bubblePositioner: BubblePositioner,
private val pinController: BubbleExpandedViewPinController,
+ private val dropTargetManager: DropTargetManager?,
+ private val dragZoneFactory: DragZoneFactory?,
@get:VisibleForTesting val dragListener: DragListener,
) {
@@ -97,7 +103,21 @@
override fun onDown(v: View, ev: MotionEvent): Boolean {
// While animating, don't allow new touch events
if (expandedView.isAnimating) return false
- pinController.onDragStart(bubblePositioner.isBubbleBarOnLeft)
+ if (dropTargetManager != null && dragZoneFactory != null) {
+ val draggedObject = DraggedObject.ExpandedView(
+ if (bubblePositioner.isBubbleBarOnLeft) {
+ BubbleBarLocation.LEFT
+ } else {
+ BubbleBarLocation.RIGHT
+ }
+ )
+ dropTargetManager.onDragStarted(
+ draggedObject,
+ dragZoneFactory.createSortedDragZones(draggedObject)
+ )
+ } else {
+ pinController.onDragStart(bubblePositioner.isBubbleBarOnLeft)
+ }
isDragged = true
return true
}
@@ -117,7 +137,11 @@
expandedView.translationX = expandedViewInitialTranslationX + dx
expandedView.translationY = expandedViewInitialTranslationY + dy
dismissView.show()
- pinController.onDragUpdate(ev.rawX, ev.rawY)
+ if (dropTargetManager != null) {
+ dropTargetManager.onDragUpdated(ev.rawX.toInt(), ev.rawY.toInt())
+ } else {
+ pinController.onDragUpdate(ev.rawX, ev.rawY)
+ }
}
override fun onUp(
@@ -140,7 +164,11 @@
private fun finishDrag() {
if (!isStuckToDismiss) {
- pinController.onDragEnd()
+ if (dropTargetManager != null) {
+ dropTargetManager.onDragEnded()
+ } else {
+ pinController.onDragEnd()
+ }
dragListener.onReleased(inDismiss = false)
animationHelper.animateToRestPosition()
dismissView.hide()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index 677c21c..6c840f0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -27,6 +27,7 @@
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.ColorDrawable;
+import android.util.Log;
import android.view.Gravity;
import android.view.SurfaceControl;
import android.view.TouchDelegate;
@@ -48,9 +49,13 @@
import com.android.wm.shell.bubbles.DismissViewUtils;
import com.android.wm.shell.bubbles.bar.BubbleBarExpandedViewDragController.DragListener;
import com.android.wm.shell.shared.bubbles.BaseBubblePinController;
+import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
import com.android.wm.shell.shared.bubbles.DeviceConfig;
import com.android.wm.shell.shared.bubbles.DismissView;
+import com.android.wm.shell.shared.bubbles.DragZone;
+import com.android.wm.shell.shared.bubbles.DragZoneFactory;
+import com.android.wm.shell.shared.bubbles.DropTargetManager;
import kotlin.Unit;
@@ -76,6 +81,10 @@
private final BubbleEducationViewController mEducationViewController;
private final View mScrimView;
private final BubbleExpandedViewPinController mBubbleExpandedViewPinController;
+ @Nullable
+ private DropTargetManager mDropTargetManager = null;
+ @Nullable
+ private DragZoneFactory mDragZoneFactory = null;
@Nullable
private BubbleViewProvider mExpandedBubble;
@@ -123,8 +132,72 @@
mBubbleExpandedViewPinController = new BubbleExpandedViewPinController(
context, this, mPositioner);
- mBubbleExpandedViewPinController.setListener(new LocationChangeListener());
+ LocationChangeListener locationChangeListener = new LocationChangeListener();
+ mBubbleExpandedViewPinController.setListener(locationChangeListener);
+ if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
+ mDropTargetManager = new DropTargetManager(context, this,
+ new DropTargetManager.DragZoneChangedListener() {
+ private DragZone mLastBubbleLocationDragZone = null;
+ private BubbleBarLocation mInitialLocation = null;
+ @Override
+ public void onDragEnded(@NonNull DragZone zone) {
+ if (mExpandedBubble == null || !(mExpandedBubble instanceof Bubble)) {
+ Log.w(TAG, "dropped invalid bubble: " + mExpandedBubble);
+ return;
+ }
+ if (zone instanceof DragZone.FullScreen) {
+ ((Bubble) mExpandedBubble).getTaskView().moveToFullscreen();
+ // Make sure location change listener is updated with the initial
+ // location -- even if we "switched sides" during the drag, since
+ // we've ended up in fullscreen, the location shouldn't change.
+ locationChangeListener.onRelease(mInitialLocation);
+ } else if (zone instanceof DragZone.Bubble.Left) {
+ locationChangeListener.onRelease(BubbleBarLocation.LEFT);
+ } else if (zone instanceof DragZone.Bubble.Right) {
+ locationChangeListener.onRelease(BubbleBarLocation.RIGHT);
+ }
+ }
+
+ @Override
+ public void onInitialDragZoneSet(@NonNull DragZone dragZone) {
+ mInitialLocation = dragZone instanceof DragZone.Bubble.Left
+ ? BubbleBarLocation.LEFT
+ : BubbleBarLocation.RIGHT;
+ locationChangeListener.onStart(mInitialLocation);
+ }
+
+ @Override
+ public void onDragZoneChanged(@NonNull DragZone from,
+ @NonNull DragZone to) {
+ final boolean isBubbleLeft = to instanceof DragZone.Bubble.Left;
+ final boolean isBubbleRight = to instanceof DragZone.Bubble.Right;
+ if ((isBubbleLeft || isBubbleRight)
+ && to != mLastBubbleLocationDragZone) {
+ mLastBubbleLocationDragZone = to;
+ locationChangeListener.onChange(isBubbleLeft
+ ? BubbleBarLocation.LEFT
+ : BubbleBarLocation.RIGHT);
+
+ }
+ }
+ });
+ // TODO - currently only fullscreen is supported, should enable for split & desktop
+ mDragZoneFactory = new DragZoneFactory(context, mPositioner.getCurrentConfig(),
+ new DragZoneFactory.SplitScreenModeChecker() {
+ @NonNull
+ @Override
+ public SplitScreenMode getSplitScreenMode() {
+ return SplitScreenMode.NONE;
+ }
+ },
+ new DragZoneFactory.DesktopWindowModeChecker() {
+ @Override
+ public boolean isSupported() {
+ return false;
+ }
+ });
+ }
setOnClickListener(view -> hideModalOrCollapse());
}
@@ -272,6 +345,8 @@
mAnimationHelper,
mPositioner,
mBubbleExpandedViewPinController,
+ mDropTargetManager,
+ mDragZoneFactory,
dragListener);
addView(mExpandedView, new LayoutParams(width, height, Gravity.LEFT));
@@ -348,13 +423,13 @@
* @param startT A transaction with first-frame work. this *will* be applied here!
*/
public void animateConvert(@NonNull SurfaceControl.Transaction startT,
- @NonNull Rect startBounds, @NonNull SurfaceControl snapshot, SurfaceControl taskLeash,
- Runnable animFinish) {
+ @NonNull Rect startBounds, float startScale, @NonNull SurfaceControl snapshot,
+ SurfaceControl taskLeash, Runnable animFinish) {
if (!mIsExpanded || mExpandedBubble == null) {
throw new IllegalStateException("Can't animateExpand without expanded state");
}
- mAnimationHelper.animateConvert(mExpandedBubble, startT, startBounds, snapshot, taskLeash,
- animFinish);
+ mAnimationHelper.animateConvert(mExpandedBubble, startT, startBounds, startScale, snapshot,
+ taskLeash, animFinish);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java
index b507ca2..01fd344 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java
@@ -19,17 +19,13 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import android.app.ActivityManager;
+import android.window.DesktopExperienceFlags;
import android.window.DisplayAreaInfo;
-import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
import com.android.window.flags.Flags;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
-import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.desktopmode.DesktopUserRepositories;
-import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider;
-import com.android.wm.shell.pip2.phone.PipTransition;
+import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler;
import java.util.Optional;
@@ -37,37 +33,33 @@
public class PipDesktopState {
private final PipDisplayLayoutState mPipDisplayLayoutState;
private final Optional<DesktopUserRepositories> mDesktopUserRepositoriesOptional;
- private final Optional<DesktopWallpaperActivityTokenProvider>
- mDesktopWallpaperActivityTokenProviderOptional;
+ private final Optional<DragToDesktopTransitionHandler> mDragToDesktopTransitionHandlerOptional;
private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
public PipDesktopState(PipDisplayLayoutState pipDisplayLayoutState,
Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
- Optional<DesktopWallpaperActivityTokenProvider>
- desktopWallpaperActivityTokenProviderOptional,
+ Optional<DragToDesktopTransitionHandler> dragToDesktopTransitionHandlerOptional,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
mPipDisplayLayoutState = pipDisplayLayoutState;
mDesktopUserRepositoriesOptional = desktopUserRepositoriesOptional;
- mDesktopWallpaperActivityTokenProviderOptional =
- desktopWallpaperActivityTokenProviderOptional;
+ mDragToDesktopTransitionHandlerOptional = dragToDesktopTransitionHandlerOptional;
mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
}
/**
* Returns whether PiP in Desktop Windowing is enabled by checking the following:
* - Desktop Windowing in PiP flag is enabled
- * - DesktopWallpaperActivityTokenProvider is injected
* - DesktopUserRepositories is injected
+ * - DragToDesktopTransitionHandler is injected
*/
public boolean isDesktopWindowingPipEnabled() {
- return Flags.enableDesktopWindowingPip()
- && mDesktopWallpaperActivityTokenProviderOptional.isPresent()
- && mDesktopUserRepositoriesOptional.isPresent();
+ return Flags.enableDesktopWindowingPip() && mDesktopUserRepositoriesOptional.isPresent()
+ && mDragToDesktopTransitionHandlerOptional.isPresent();
}
/** Returns whether PiP in Connected Displays is enabled by checking the flag. */
public boolean isConnectedDisplaysPipEnabled() {
- return Flags.enableConnectedDisplaysPip();
+ return DesktopExperienceFlags.ENABLE_CONNECTED_DISPLAYS_PIP.isTrue();
}
/** Returns whether the display with the PiP task is in freeform windowing mode. */
@@ -88,45 +80,10 @@
return false;
}
final int displayId = mPipDisplayLayoutState.getDisplayId();
- return getDesktopRepository().isAnyDeskActive(displayId)
- || getDesktopWallpaperActivityTokenProvider().isWallpaperActivityVisible(displayId)
+ return mDesktopUserRepositoriesOptional.get().getCurrent().isAnyDeskActive(displayId)
|| isDisplayInFreeform();
}
- /** Returns whether {@param pipTask} would be entering in a Desktop Mode session. */
- public boolean isPipEnteringInDesktopMode(ActivityManager.RunningTaskInfo pipTask) {
- // Early return if PiP in Desktop Windowing is not supported.
- if (!isDesktopWindowingPipEnabled()) {
- return false;
- }
- final DesktopRepository desktopRepository = getDesktopRepository();
- return desktopRepository.isAnyDeskActive(pipTask.getDisplayId())
- || desktopRepository.isMinimizedPipPresentInDisplay(pipTask.getDisplayId());
- }
-
- /**
- * Invoked when an EXIT_PiP transition is detected in {@link PipTransition}.
- * Returns whether the PiP exiting should also trigger the active Desktop Mode session to exit.
- */
- public boolean shouldExitPipExitDesktopMode() {
- // Early return if PiP in Desktop Windowing is not supported.
- if (!isDesktopWindowingPipEnabled()) {
- return false;
- }
- final int displayId = mPipDisplayLayoutState.getDisplayId();
- return !getDesktopRepository().isAnyDeskActive(displayId)
- && getDesktopWallpaperActivityTokenProvider().isWallpaperActivityVisible(displayId);
- }
-
- /**
- * Returns a {@link WindowContainerTransaction} that reorders the {@link WindowContainerToken}
- * of the DesktopWallpaperActivity for the display with the given {@param displayId}.
- */
- public WindowContainerTransaction getWallpaperActivityTokenWct(int displayId) {
- return new WindowContainerTransaction().reorder(
- getDesktopWallpaperActivityTokenProvider().getToken(displayId), /* onTop= */ false);
- }
-
/**
* The windowing mode to restore to when resizing out of PIP direction.
* Defaults to undefined and can be overridden to restore to an alternate windowing mode.
@@ -149,11 +106,12 @@
return WINDOWING_MODE_UNDEFINED;
}
- private DesktopRepository getDesktopRepository() {
- return mDesktopUserRepositoriesOptional.get().getCurrent();
- }
-
- private DesktopWallpaperActivityTokenProvider getDesktopWallpaperActivityTokenProvider() {
- return mDesktopWallpaperActivityTokenProviderOptional.get();
+ /** Returns whether there is a drag-to-desktop transition in progress. */
+ public boolean isDragToDesktopInProgress() {
+ // Early return if PiP in Desktop Windowing is not supported.
+ if (!isDesktopWindowingPipEnabled()) {
+ return false;
+ }
+ return mDragToDesktopTransitionHandlerOptional.get().getInProgress();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
index 5b2dd97..bc0a857 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
@@ -39,6 +39,7 @@
import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition;
import java.util.ArrayList;
+import java.util.stream.IntStream;
/**
* Calculates the snap targets and the snap position given a position and a velocity. All positions
@@ -354,10 +355,20 @@
float ratio = areOffscreenRatiosSupported()
? SplitSpec.OFFSCREEN_ASYMMETRIC_RATIO
: SplitSpec.ONSCREEN_ONLY_ASYMMETRIC_RATIO;
+
+ // The intended size of the smaller app, in pixels
int size = (int) (ratio * (end - start)) - mDividerSize / 2;
- int leftTopPosition = start + pinnedTaskbarShiftStart + size;
- int rightBottomPosition = end - pinnedTaskbarShiftEnd - size - mDividerSize;
+ // If there are insets that interfere with the smaller app (visually or blocking touch
+ // targets), make the smaller app bigger by that amount to compensate. This applies to
+ // pinned taskbar, 3-button nav (both create an opaque bar at bottom) and status bar (blocks
+ // touch targets at top).
+ int extraSpace = IntStream.of(
+ getStartInset(), getEndInset(), pinnedTaskbarShiftStart, pinnedTaskbarShiftEnd
+ ).max().getAsInt();
+
+ int leftTopPosition = start + extraSpace + size;
+ int rightBottomPosition = end - extraSpace - size - mDividerSize;
addNonDismissingTargets(isLeftRightSplit, leftTopPosition, rightBottomPosition, dividerMax);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 720e8e5..e20a3d8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -662,14 +662,14 @@
return;
}
- // Check to see if insets changed in such a way that the divider algorithm needs to be
- // recalculated.
+ // Check to see if insets changed in such a way that the divider needs to be animated to
+ // a new position. (We only do this when switching to pinned taskbar mode and back).
Insets pinnedTaskbarInsets = calculatePinnedTaskbarInsets(insetsState);
if (!mPinnedTaskbarInsets.equals(pinnedTaskbarInsets)) {
mPinnedTaskbarInsets = pinnedTaskbarInsets;
// Refresh the DividerSnapAlgorithm.
updateLayouts();
- // If the divider is no longer placed on a snap point, animate it to the nearest one.
+ // If the divider is no longer placed on a snap point, animate it to the nearest one
DividerSnapAlgorithm.SnapTarget snapTarget =
findSnapTarget(mDividerPosition, 0, false /* hardDismiss */);
if (snapTarget.position != mDividerPosition) {
@@ -683,18 +683,12 @@
}
/**
- * Calculates the insets that might trigger a divider algorithm recalculation. Currently, only
- * pinned Taskbar does this, and only when the IME is not showing.
+ * Calculates the insets that might trigger a divider algorithm recalculation.
*/
private Insets calculatePinnedTaskbarInsets(InsetsState insetsState) {
- if (insetsState.isSourceOrDefaultVisible(InsetsSource.ID_IME, WindowInsets.Type.ime())) {
- return Insets.NONE;
- }
-
- // If IME is not showing...
for (int i = insetsState.sourceSize() - 1; i >= 0; i--) {
final InsetsSource source = insetsState.sourceAt(i);
- // and Taskbar is pinned...
+ // If Taskbar is pinned...
if (source.getType() == WindowInsets.Type.navigationBars()
&& source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)) {
// Return Insets representing the pinned taskbar state.
@@ -1543,11 +1537,28 @@
}
}
+ /**
+ * When IME is triggered on the bottom app in split screen, we want to translate the bottom
+ * app up by a certain amount so that it's not covered too much by the IME. But there's also
+ * an upper limit to the amount we want to translate (since we still need some of the top
+ * app to be visible too). So this function essentially says "try to translate the bottom
+ * app up, but stop before you make the top app too small."
+ */
private int getTargetYOffset() {
- final int desireOffset = Math.abs(mEndImeTop - mStartImeTop);
- // Make sure to keep at least 30% visible for the top split.
- final int maxOffset = (int) (getTopLeftBounds().height() * ADJUSTED_SPLIT_FRACTION_MAX);
- return -Math.min(desireOffset, maxOffset);
+ // We want to translate up the bottom app by this amount.
+ final int desiredOffset = Math.abs(mEndImeTop - mStartImeTop);
+
+ // But we also want to keep this much of the top app visible.
+ final float amountOfTopAppToKeepVisible =
+ getTopLeftBounds().height() * (1 - ADJUSTED_SPLIT_FRACTION_MAX);
+
+ // So the current onscreen size of the top app, minus the minimum size, is the max
+ // translation we will allow.
+ final float currentOnScreenSizeOfTopApp = getTopLeftBounds().bottom;
+ final int maxOffset =
+ (int) Math.max(currentOnScreenSizeOfTopApp - amountOfTopAppToKeepVisible, 0);
+
+ return -Math.min(desiredOffset, maxOffset);
}
@SplitPosition
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 0e6481b..318cdee 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -1052,23 +1052,8 @@
});
}
- @WMSingleton
- @Provides
- static DesktopWallpaperActivityTokenProvider provideDesktopWallpaperActivityTokenProvider() {
- return new DesktopWallpaperActivityTokenProvider();
- }
-
- @WMSingleton
- @Provides
- static Optional<DesktopWallpaperActivityTokenProvider>
- provideOptionalDesktopWallpaperActivityTokenProvider(
- Context context,
- DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider) {
- if (DesktopModeStatus.canEnterDesktopMode(context)) {
- return Optional.of(desktopWallpaperActivityTokenProvider);
- }
- return Optional.empty();
- }
+ @BindsOptionalOf
+ abstract DesktopWallpaperActivityTokenProvider optionalDesktopWallpaperActivityTokenProvider();
//
// App zoom out (optional feature)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 79fbd32..2ded48a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -36,6 +36,7 @@
import android.os.UserManager;
import android.view.Choreographer;
import android.view.IWindowManager;
+import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.DesktopModeFlags;
@@ -93,6 +94,7 @@
import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
import com.android.wm.shell.desktopmode.DesktopModeKeyGestureHandler;
import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver;
+import com.android.wm.shell.desktopmode.DesktopModeMoveToDisplayTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger;
import com.android.wm.shell.desktopmode.DesktopTaskChangeListener;
import com.android.wm.shell.desktopmode.DesktopTasksController;
@@ -772,7 +774,8 @@
DesksTransitionObserver desksTransitionObserver,
UserProfileContexts userProfileContexts,
DesktopModeCompatPolicy desktopModeCompatPolicy,
- DragToDisplayTransitionHandler dragToDisplayTransitionHandler) {
+ DragToDisplayTransitionHandler dragToDisplayTransitionHandler,
+ DesktopModeMoveToDisplayTransitionHandler moveToDisplayTransitionHandler) {
return new DesktopTasksController(
context,
shellInit,
@@ -812,7 +815,8 @@
desksTransitionObserver,
userProfileContexts,
desktopModeCompatPolicy,
- dragToDisplayTransitionHandler);
+ dragToDisplayTransitionHandler,
+ moveToDisplayTransitionHandler);
}
@WMSingleton
@@ -938,12 +942,24 @@
@WMSingleton
@Provides
+ static DesktopWallpaperActivityTokenProvider provideDesktopWallpaperActivityTokenProvider() {
+ return new DesktopWallpaperActivityTokenProvider();
+ }
+
+ @WMSingleton
+ @Provides
static DragToDisplayTransitionHandler provideDragToDisplayTransitionHandler() {
return new DragToDisplayTransitionHandler();
}
@WMSingleton
@Provides
+ static DesktopModeMoveToDisplayTransitionHandler provideMoveToDisplayTransitionHandler() {
+ return new DesktopModeMoveToDisplayTransitionHandler(new SurfaceControl.Transaction());
+ }
+
+ @WMSingleton
+ @Provides
static Optional<DesktopModeKeyGestureHandler> provideDesktopModeKeyGestureHandler(
Context context,
Optional<DesktopModeWindowDecorViewModel> desktopModeWindowDecorViewModel,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index f8b18f2..6f0919e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.os.Handler;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
@@ -42,9 +43,10 @@
import com.android.wm.shell.dagger.WMShellBaseModule;
import com.android.wm.shell.dagger.WMSingleton;
import com.android.wm.shell.desktopmode.DesktopUserRepositories;
-import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider;
+import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler;
import com.android.wm.shell.pip2.phone.PhonePipMenuController;
import com.android.wm.shell.pip2.phone.PipController;
+import com.android.wm.shell.pip2.phone.PipInteractionHandler;
import com.android.wm.shell.pip2.phone.PipMotionHelper;
import com.android.wm.shell.pip2.phone.PipScheduler;
import com.android.wm.shell.pip2.phone.PipTaskListener;
@@ -59,6 +61,7 @@
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
+import dagger.BindsOptionalOf;
import dagger.Module;
import dagger.Provides;
@@ -87,12 +90,13 @@
@NonNull PipUiStateChangeController pipUiStateChangeController,
DisplayController displayController,
Optional<SplitScreenController> splitScreenControllerOptional,
- PipDesktopState pipDesktopState) {
+ PipDesktopState pipDesktopState,
+ PipInteractionHandler pipInteractionHandler) {
return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
pipBoundsState, null, pipBoundsAlgorithm, pipTaskListener,
pipScheduler, pipStackListenerController, pipDisplayLayoutState,
pipUiStateChangeController, displayController, splitScreenControllerOptional,
- pipDesktopState);
+ pipDesktopState, pipInteractionHandler);
}
@WMSingleton
@@ -209,8 +213,9 @@
@WMSingleton
@Provides
- static PipTransitionState providePipTransitionState(@ShellMainThread Handler handler) {
- return new PipTransitionState(handler);
+ static PipTransitionState providePipTransitionState(@ShellMainThread Handler handler,
+ PipDesktopState pipDesktopState) {
+ return new PipTransitionState(handler, pipDesktopState);
}
@WMSingleton
@@ -238,11 +243,23 @@
static PipDesktopState providePipDesktopState(
PipDisplayLayoutState pipDisplayLayoutState,
Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
- Optional<DesktopWallpaperActivityTokenProvider>
- desktopWallpaperActivityTokenProviderOptional,
+ Optional<DragToDesktopTransitionHandler> dragToDesktopTransitionHandlerOptional,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer
) {
return new PipDesktopState(pipDisplayLayoutState, desktopUserRepositoriesOptional,
- desktopWallpaperActivityTokenProviderOptional, rootTaskDisplayAreaOrganizer);
+ dragToDesktopTransitionHandlerOptional, rootTaskDisplayAreaOrganizer);
+ }
+
+ @BindsOptionalOf
+ abstract DragToDesktopTransitionHandler optionalDragToDesktopTransitionHandler();
+
+ @WMSingleton
+ @Provides
+ static PipInteractionHandler providePipInteractionHandler(
+ Context context,
+ @ShellMainThread Handler mainHandler
+ ) {
+ return new PipInteractionHandler(context, mainHandler,
+ InteractionJankMonitor.getInstance());
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandler.kt
index 7074e8b..6c6d830 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandler.kt
@@ -21,6 +21,7 @@
import android.os.Handler
import android.os.IBinder
import android.view.SurfaceControl.Transaction
+import android.view.WindowManager.TRANSIT_TO_BACK
import android.window.TransitionInfo
import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
@@ -61,7 +62,9 @@
finishTransaction: Transaction,
finishCallback: Transitions.TransitionFinishCallback,
): Boolean {
- if (!TransitionUtil.isClosingType(info.type)) return false
+ val shouldAnimate =
+ TransitionUtil.isClosingType(info.type) || info.type == Transitions.TRANSIT_MINIMIZE
+ if (!shouldAnimate) return false
val animations = mutableListOf<Animator>()
val onAnimFinish: (Animator) -> Unit = { animator ->
@@ -75,10 +78,14 @@
}
}
+ val checkChangeMode = { change: TransitionInfo.Change ->
+ change.mode == info.type ||
+ (info.type == Transitions.TRANSIT_MINIMIZE && change.mode == TRANSIT_TO_BACK)
+ }
animations +=
info.changes
.filter {
- it.mode == info.type && it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM
+ checkChangeMode(it) && it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM
}
.mapNotNull { createMinimizeAnimation(it, finishTransaction, onAnimFinish) }
if (animations.isEmpty()) return false
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeMoveToDisplayTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeMoveToDisplayTransitionHandler.kt
new file mode 100644
index 0000000..fbf170f13
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeMoveToDisplayTransitionHandler.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.animation.Animator
+import android.animation.ValueAnimator
+import android.os.IBinder
+import android.view.Choreographer
+import android.view.SurfaceControl
+import android.window.TransitionInfo
+import android.window.TransitionRequestInfo
+import android.window.WindowContainerTransaction
+import com.android.wm.shell.shared.animation.Interpolators
+import com.android.wm.shell.transition.Transitions
+import kotlin.time.Duration.Companion.milliseconds
+
+/**
+ * Transition handler for moving a window to a different display.
+ */
+class DesktopModeMoveToDisplayTransitionHandler(
+ private val animationTransaction: SurfaceControl.Transaction
+) : Transitions.TransitionHandler {
+
+ override fun handleRequest(
+ transition: IBinder,
+ request: TransitionRequestInfo,
+ ): WindowContainerTransaction? = null
+
+ override fun startAnimation(
+ transition: IBinder,
+ info: TransitionInfo,
+ startTransaction: SurfaceControl.Transaction,
+ finishTransaction: SurfaceControl.Transaction,
+ finishCallback: Transitions.TransitionFinishCallback,
+ ): Boolean {
+ val change = info.changes.find { it.startDisplayId != it.endDisplayId } ?: return false
+ ValueAnimator.ofFloat(0f, 1f)
+ .apply {
+ duration = ANIM_DURATION.inWholeMilliseconds
+ interpolator = Interpolators.LINEAR
+ addUpdateListener { animation ->
+ animationTransaction
+ .setAlpha(change.leash, animation.animatedValue as Float)
+ .setFrameTimeline(Choreographer.getInstance().vsyncId)
+ .apply()
+ }
+ addListener(
+ object : Animator.AnimatorListener {
+ override fun onAnimationStart(animation: Animator) {
+ val endBounds = change.endAbsBounds
+ startTransaction
+ .setPosition(
+ change.leash,
+ endBounds.left.toFloat(),
+ endBounds.top.toFloat(),
+ )
+ .setWindowCrop(change.leash, endBounds.width(), endBounds.height())
+ .apply()
+ }
+
+ override fun onAnimationEnd(animation: Animator) {
+ finishTransaction.apply()
+ finishCallback.onTransitionFinished(null)
+ }
+
+ override fun onAnimationCancel(animation: Animator) {
+ finishTransaction.apply()
+ finishCallback.onTransitionFinished(null)
+ }
+
+ override fun onAnimationRepeat(animation: Animator) = Unit
+ }
+ )
+ }
+ .start()
+ return true
+ }
+
+ private companion object {
+ val ANIM_DURATION = 100.milliseconds
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index 56de48d..7053990 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -103,6 +103,10 @@
return null;
}
}
+
+ private static boolean isDragToDesktopStartState(DragStartState startState) {
+ return startState == FROM_FULLSCREEN || startState == FROM_SPLIT;
+ }
}
private final VisualIndicatorViewContainer mVisualIndicatorViewContainer;
@@ -125,7 +129,12 @@
@Nullable BubbleDropTargetBoundsProvider bubbleBoundsProvider,
SnapEventHandler snapEventHandler) {
SurfaceControl.Builder builder = new SurfaceControl.Builder();
- taskDisplayAreaOrganizer.attachToDisplayArea(taskInfo.displayId, builder);
+ if (!DragStartState.isDragToDesktopStartState(dragStartState)
+ || !DesktopModeFlags.ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX.isTrue()) {
+ // In the DragToDesktop transition we attach the indicator to the transition root once
+ // that is available - for all other cases attach the indicator here.
+ taskDisplayAreaOrganizer.attachToDisplayArea(taskInfo.displayId, builder);
+ }
mVisualIndicatorViewContainer = new VisualIndicatorViewContainer(
DesktopModeFlags.ENABLE_DESKTOP_INDICATOR_IN_SEPARATE_THREAD_BUGFIX.isTrue()
? desktopExecutor : mainExecutor,
@@ -159,6 +168,18 @@
mVisualIndicatorViewContainer.releaseVisualIndicator();
}
+ /** Reparent the visual indicator to {@code newParent}. */
+ void reparentLeash(SurfaceControl.Transaction t, SurfaceControl newParent) {
+ mVisualIndicatorViewContainer.reparentLeash(t, newParent);
+ }
+
+ /** Start the fade-in animation. */
+ void fadeInIndicator() {
+ mVisualIndicatorViewContainer.fadeInIndicator(
+ mDisplayController.getDisplayLayout(mTaskInfo.displayId), mCurrentType,
+ mTaskInfo.displayId);
+ }
+
/**
* Based on the coordinates of the current drag event, determine which indicator type we should
* display, including no visible indicator.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
index 73df976..8c27487 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
@@ -28,7 +28,6 @@
import androidx.core.util.valueIterator
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.protolog.ProtoLog
-import com.android.window.flags.Flags
import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.annotations.ShellMainThread
@@ -70,9 +69,6 @@
* fullscreen task launched on top of the desk. Cleared when the transparent task is closed or
* sent to back. (top is at index 0).
* @property pipTaskId the task id of PiP task entered while in Desktop Mode.
- * @property pipShouldKeepDesktopActive whether an active PiP window should keep the desk
- * active. Only false when we are explicitly exiting Desktop Mode (via user action) while
- * there is an active PiP window.
*/
private data class Desk(
val deskId: Int,
@@ -86,8 +82,6 @@
var fullImmersiveTaskId: Int? = null,
var topTransparentFullscreenTaskId: Int? = null,
var pipTaskId: Int? = null,
- // TODO: b/389960283 - consolidate this with [DesktopDisplay#activeDeskId].
- var pipShouldKeepDesktopActive: Boolean = true,
) {
fun deepCopy(): Desk =
Desk(
@@ -101,7 +95,6 @@
fullImmersiveTaskId = fullImmersiveTaskId,
topTransparentFullscreenTaskId = topTransparentFullscreenTaskId,
pipTaskId = pipTaskId,
- pipShouldKeepDesktopActive = pipShouldKeepDesktopActive,
)
// TODO: b/362720497 - remove when multi-desktops is enabled where instances aren't
@@ -115,7 +108,6 @@
fullImmersiveTaskId = null
topTransparentFullscreenTaskId = null
pipTaskId = null
- pipShouldKeepDesktopActive = true
}
}
@@ -625,7 +617,6 @@
?: error("Expected active desk in display: $displayId")
if (enterPip) {
activeDesk.pipTaskId = taskId
- activeDesk.pipShouldKeepDesktopActive = true
} else {
activeDesk.pipTaskId =
if (activeDesk.pipTaskId == taskId) null
@@ -638,19 +629,9 @@
activeDesk.pipTaskId
}
}
- notifyVisibleTaskListeners(displayId, getVisibleTaskCount(displayId))
}
/**
- * Returns whether there is a PiP that was entered/minimized from Desktop in this display's
- * active desk.
- *
- * TODO: b/389960283 - add explicit [deskId] argument.
- */
- fun isMinimizedPipPresentInDisplay(displayId: Int): Boolean =
- desktopData.getActiveDesk(displayId)?.pipTaskId != null
-
- /**
* Returns whether the given task is the Desktop-entered PiP task in this display's active desk.
*
* TODO: b/389960283 - add explicit [deskId] argument.
@@ -659,25 +640,6 @@
desktopData.getActiveDesk(displayId)?.pipTaskId == taskId
/**
- * Returns whether a desk should be active in this display due to active PiP.
- *
- * TODO: b/389960283 - add explicit [deskId] argument.
- */
- fun shouldDesktopBeActiveForPip(displayId: Int): Boolean =
- Flags.enableDesktopWindowingPip() &&
- isMinimizedPipPresentInDisplay(displayId) &&
- (desktopData.getActiveDesk(displayId)?.pipShouldKeepDesktopActive ?: false)
-
- /**
- * Saves whether a PiP window should keep Desktop session active in this display.
- *
- * TODO: b/389960283 - add explicit [deskId] argument.
- */
- fun setPipShouldKeepDesktopActive(displayId: Int, keepActive: Boolean) {
- desktopData.getActiveDesk(displayId)?.pipShouldKeepDesktopActive = keepActive
- }
-
- /**
* Saves callback to handle a pending PiP transition being aborted.
*
* TODO: b/389960283 - add explicit [deskId] argument.
@@ -772,12 +734,8 @@
}
private fun notifyVisibleTaskListeners(displayId: Int, visibleTasksCount: Int) {
- val visibleAndPipTasksCount =
- if (shouldDesktopBeActiveForPip(displayId)) visibleTasksCount + 1 else visibleTasksCount
visibleTasksListeners.forEach { (listener, executor) ->
- executor.execute {
- listener.onTasksVisibilityChanged(displayId, visibleAndPipTasksCount)
- }
+ executor.execute { listener.onTasksVisibilityChanged(displayId, visibleTasksCount) }
}
}
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 5de3be4..301b79a 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
@@ -22,6 +22,7 @@
import android.app.ActivityOptions
import android.app.KeyguardManager
import android.app.PendingIntent
+import android.app.TaskInfo
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
@@ -208,6 +209,7 @@
private val userProfileContexts: UserProfileContexts,
private val desktopModeCompatPolicy: DesktopModeCompatPolicy,
private val dragToDisplayTransitionHandler: DragToDisplayTransitionHandler,
+ private val moveToDisplayTransitionHandler: DesktopModeMoveToDisplayTransitionHandler,
) :
RemoteCallable<DesktopTasksController>,
Transitions.TransitionHandler,
@@ -343,45 +345,28 @@
fun isAnyDeskActive(displayId: Int): Boolean = taskRepository.isAnyDeskActive(displayId)
/**
- * Returns true if any of the following is true:
- * - Any freeform tasks are visible
- * - A transparent fullscreen task exists on top in Desktop Mode
- * - PiP on Desktop Windowing is enabled, there is an active PiP window and the desktop
- * wallpaper is visible.
+ * Returns true if any freeform tasks are visible or if a transparent fullscreen task exists on
+ * top in Desktop Mode.
*
* TODO: b/362720497 - consolidate with [isAnyDeskActive].
* - top-transparent-fullscreen case: should not be needed if we allow it to launch inside
* the desk in fullscreen instead of force-exiting desktop and having to trick this method
* into thinking it is in desktop mode when a task in this state exists.
- * - PIP case: a PIP presence should influence desk activation, so
- * [DesktopRepository#isAnyDeskActive] should be sufficient.
*/
fun isDesktopModeShowing(displayId: Int): Boolean {
val hasVisibleTasks = taskRepository.isAnyDeskActive(displayId)
val hasTopTransparentFullscreenTask =
taskRepository.getTopTransparentFullscreenTaskId(displayId) != null
- val hasMinimizedPip =
- Flags.enableDesktopWindowingPip() &&
- taskRepository.isMinimizedPipPresentInDisplay(displayId) &&
- desktopWallpaperActivityTokenProvider.isWallpaperActivityVisible(displayId)
if (
DesktopModeFlags.INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC
.isTrue() && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue()
) {
logV(
- "isDesktopModeShowing: hasVisibleTasks=%s hasTopTransparentFullscreenTask=%s hasMinimizedPip=%s",
+ "isDesktopModeShowing: hasVisibleTasks=%s hasTopTransparentFullscreenTask=%s",
hasVisibleTasks,
hasTopTransparentFullscreenTask,
- hasMinimizedPip,
)
- return hasVisibleTasks || hasTopTransparentFullscreenTask || hasMinimizedPip
- } else if (Flags.enableDesktopWindowingPip()) {
- logV(
- "isDesktopModeShowing: hasVisibleTasks=%s hasMinimizedPip=%s",
- hasVisibleTasks,
- hasMinimizedPip,
- )
- return hasVisibleTasks || hasMinimizedPip
+ return hasVisibleTasks || hasTopTransparentFullscreenTask
}
logV("isDesktopModeShowing: hasVisibleTasks=%s", hasVisibleTasks)
return hasVisibleTasks
@@ -537,8 +522,8 @@
return false
}
logV("moveBackgroundTaskToDesktop with taskId=%d", taskId)
- val taskIdToMinimize =
- bringDesktopAppsToFrontBeforeShowingNewTask(task.displayId, wct, taskId)
+ val deskId = getDefaultDeskId(task.displayId)
+ val runOnTransitStart = addDeskActivationChanges(deskId, wct, task)
val exitResult =
desktopImmersiveController.exitImmersiveIfApplicable(
wct = wct,
@@ -567,9 +552,7 @@
desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted(
FREEFORM_ANIMATION_DURATION
)
- taskIdToMinimize?.let {
- addPendingMinimizeTransition(transition, it, MinimizeReason.TASK_LIMIT)
- }
+ runOnTransitStart?.invoke(transition)
exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
return true
}
@@ -603,13 +586,7 @@
reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH,
)
- val taskIdToMinimize =
- prepareMoveTaskToDeskAndActivate(
- wct = wct,
- displayId = displayId,
- deskId = deskId,
- task = task,
- )
+ val runOnTransitStart = addDeskActivationWithMovingTaskChanges(deskId, wct, task)
val transition: IBinder
if (remoteTransition != null) {
@@ -624,67 +601,14 @@
desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted(
FREEFORM_ANIMATION_DURATION
)
- taskIdToMinimize?.let {
- addPendingMinimizeTransition(transition, it, MinimizeReason.TASK_LIMIT)
- }
+ runOnTransitStart?.invoke(transition)
exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
- if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
- desksTransitionObserver.addPendingTransition(
- DeskTransition.ActiveDeskWithTask(
- token = transition,
- displayId = displayId,
- deskId = deskId,
- enterTaskId = task.taskId,
- )
- )
- } else {
+ if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
taskRepository.setActiveDesk(displayId = displayId, deskId = deskId)
}
return true
}
- /**
- * Applies the necessary changes and operations to [wct] to move a task into a desk and
- * activating that desk. This includes showing pre-existing tasks of that desk behind the new
- * task (but minimizing one of them if needed) and showing Home and the desktop wallpaper.
- *
- * @return the id of the task that is being minimized, if any.
- */
- private fun prepareMoveTaskToDeskAndActivate(
- wct: WindowContainerTransaction,
- displayId: Int,
- deskId: Int,
- task: RunningTaskInfo,
- ): Int? {
- val taskIdToMinimize =
- if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
- // Activate the desk first.
- prepareForDeskActivation(displayId, wct)
- desksOrganizer.activateDesk(wct, deskId)
- if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) {
- // TODO: b/362720497 - do non-running tasks need to be restarted with
- // |wct#startTask|?
- }
- taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(
- doesAnyTaskRequireTaskbarRounding(displayId)
- )
- // TODO: b/362720497 - activating a desk with the intention to move a new task to
- // it means we may need to minimize something in the activating desk. Do so here
- // similar to how it's done in #bringDesktopAppsToFrontBeforeShowingNewTask
- // instead of returning null.
- null
- } else {
- // Bring other apps to front first.
- bringDesktopAppsToFrontBeforeShowingNewTask(displayId, wct, task.taskId)
- }
- if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
- prepareMoveTaskToDesk(wct, task, deskId)
- } else {
- addMoveToDesktopChanges(wct, task)
- }
- return taskIdToMinimize
- }
-
private fun invokeCallbackToOverview(transition: IBinder, callback: IMoveToDesktopCallback?) {
// TODO: b/333524374 - Remove this later.
// This is a temporary implementation for adding CUJ end and
@@ -716,6 +640,7 @@
dragToDesktopTransitionHandler.startDragToDesktopTransition(
taskInfo,
dragToDesktopValueAnimator,
+ visualIndicator,
)
}
@@ -742,13 +667,7 @@
moveHomeTask(context.displayId, wct)
}
}
- val taskIdToMinimize =
- prepareMoveTaskToDeskAndActivate(
- wct = wct,
- displayId = taskInfo.displayId,
- deskId = deskId,
- task = taskInfo,
- )
+ val runOnTransitStart = addDeskActivationWithMovingTaskChanges(deskId, wct, taskInfo)
val exitResult =
desktopImmersiveController.exitImmersiveIfApplicable(
wct = wct,
@@ -761,20 +680,9 @@
DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS.toInt()
)
if (transition != null) {
- taskIdToMinimize?.let {
- addPendingMinimizeTransition(transition, it, MinimizeReason.TASK_LIMIT)
- }
+ runOnTransitStart?.invoke(transition)
exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
- if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
- desksTransitionObserver.addPendingTransition(
- DeskTransition.ActiveDeskWithTask(
- token = transition,
- displayId = taskInfo.displayId,
- deskId = deskId,
- enterTaskId = taskInfo.taskId,
- )
- )
- } else {
+ if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
taskRepository.setActiveDesk(displayId = taskInfo.displayId, deskId = deskId)
}
} else {
@@ -822,7 +730,6 @@
displayId = displayId,
forceExitDesktop = false,
)
- taskRepository.setPipShouldKeepDesktopActive(displayId, keepActive = true)
val desktopExitRunnable =
performDesktopExitCleanUp(
wct = wct,
@@ -892,7 +799,6 @@
val wct = WindowContainerTransaction()
snapEventHandler.removeTaskIfTiled(displayId, taskId)
- taskRepository.setPipShouldKeepDesktopActive(displayId, keepActive = true)
val willExitDesktop = willExitDesktop(taskId, displayId, forceExitDesktop = false)
val desktopExitRunnable =
performDesktopExitCleanUp(
@@ -1007,11 +913,9 @@
// handles case where we are moving to full screen without closing all DW tasks.
if (
!taskRepository.isOnlyVisibleNonClosingTask(task.taskId)
- // This callback is already invoked by |addMoveToFullscreenChanges| when one of these
- // flags is enabled.
- &&
- !DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue &&
- !Flags.enableDesktopWindowingPip()
+ // This callback is already invoked by |addMoveToFullscreenChanges| when this flag is
+ // enabled.
+ && !DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue
) {
desktopModeEnterExitTransitionListener?.onExitDesktopModeTransitionStarted(
FULLSCREEN_ANIMATION_DURATION
@@ -1133,19 +1037,14 @@
?: getDefaultDeskId(displayId)
)
val activateDeskWct = WindowContainerTransaction()
- addDeskActivationChanges(deskIdToActivate, activateDeskWct)
+ // TODO: b/391485148 - pass in the launching task here to apply task-limit policy,
+ // but make sure to not do it twice since it is also done at the start of this
+ // function.
+ activationRunOnTransitStart =
+ addDeskActivationChanges(deskIdToActivate, activateDeskWct)
// 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
)
@@ -1260,6 +1159,8 @@
* Move [task] to display with [displayId].
*
* No-op if task is already on that display per [RunningTaskInfo.displayId].
+ *
+ * TODO: b/399411604 - split this up into smaller functions.
*/
private fun moveToDisplay(task: RunningTaskInfo, displayId: Int) {
logV("moveToDisplay: taskId=%d displayId=%d", task.taskId, displayId)
@@ -1302,7 +1203,8 @@
} else {
null
}
- val transition = transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null)
+ val transition =
+ transitions.startTransition(TRANSIT_CHANGE, wct, moveToDisplayTransitionHandler)
deactivationRunnable?.invoke(transition)
return
}
@@ -1315,27 +1217,22 @@
// TODO: b/393977830 and b/397437641 - do not assume that freeform==desktop.
if (!task.isFreeform) {
- addMoveToDesktopChanges(wct, task, displayId)
- } else if (Flags.enableMoveToNextDisplayShortcut()) {
- applyFreeformDisplayChange(wct, task, displayId)
+ addMoveToDeskTaskChanges(wct = wct, task = task, deskId = destinationDeskId)
+ } else {
+ if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
+ desksOrganizer.moveTaskToDesk(wct, destinationDeskId, task)
+ }
+ if (Flags.enableMoveToNextDisplayShortcut()) {
+ applyFreeformDisplayChange(wct, task, displayId)
+ }
}
- if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
- desksOrganizer.moveTaskToDesk(wct, destinationDeskId, task)
- } else {
+ if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
wct.reparent(task.token, displayAreaInfo.token, /* onTop= */ true)
}
- addDeskActivationChanges(destinationDeskId, wct)
- val activationRunnable: RunOnTransitStart = { transition ->
- desksTransitionObserver.addPendingTransition(
- DeskTransition.ActiveDeskWithTask(
- token = transition,
- displayId = displayId,
- deskId = destinationDeskId,
- enterTaskId = task.taskId,
- )
- )
- }
+
+ // TODO: b/391485148 - pass in the moving-to-desk |task| here to apply task-limit policy.
+ val activationRunnable = addDeskActivationChanges(destinationDeskId, wct)
if (Flags.enableDisplayFocusInShellTransitions()) {
// Bring the destination display to top with includingParents=true, so that the
@@ -1366,7 +1263,8 @@
} else {
null
}
- val transition = transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null)
+ val transition =
+ transitions.startTransition(TRANSIT_CHANGE, wct, moveToDisplayTransitionHandler)
deactivationRunnable?.invoke(transition)
activationRunnable?.invoke(transition)
}
@@ -1731,13 +1629,10 @@
}
}
- private fun bringDesktopAppsToFrontBeforeShowingNewTask(
- displayId: Int,
- wct: WindowContainerTransaction,
- newTaskIdInFront: Int,
- ): Int? = bringDesktopAppsToFront(displayId, wct, newTaskIdInFront)
-
- @Deprecated("Use activeDesk() instead.", ReplaceWith("activateDesk()"))
+ @Deprecated(
+ "Use addDeskActivationChanges() instead.",
+ ReplaceWith("addDeskActivationChanges()"),
+ )
private fun bringDesktopAppsToFront(
displayId: Int,
wct: WindowContainerTransaction,
@@ -1909,11 +1804,7 @@
displayId: Int,
forceExitDesktop: Boolean,
): Boolean {
- if (
- forceExitDesktop &&
- (Flags.enableDesktopWindowingPip() ||
- DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue)
- ) {
+ if (forceExitDesktop && DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
// |forceExitDesktop| is true when the callers knows we'll exit desktop, such as when
// explicitly going fullscreen, so there's no point in checking the desktop state.
return true
@@ -1922,11 +1813,6 @@
if (!taskRepository.isOnlyVisibleNonClosingTask(triggerTaskId, displayId)) {
return false
}
- } else if (
- Flags.enableDesktopWindowingPip() &&
- taskRepository.isMinimizedPipPresentInDisplay(displayId)
- ) {
- return false
} else {
if (!taskRepository.isOnlyVisibleNonClosingTask(triggerTaskId)) {
return false
@@ -1943,7 +1829,6 @@
forceToFullscreen: Boolean,
shouldEndUpAtHome: Boolean = true,
): RunOnTransitStart? {
- taskRepository.setPipShouldKeepDesktopActive(displayId, keepActive = !forceToFullscreen)
if (!willExitDesktop(taskId, displayId, forceToFullscreen)) {
return null
}
@@ -2062,12 +1947,13 @@
triggerTask?.let { task ->
when {
// Check if freeform task launch during recents should be handled
- shouldHandleMidRecentsFreeformLaunch -> handleMidRecentsFreeformTaskLaunch(task)
+ shouldHandleMidRecentsFreeformLaunch ->
+ handleMidRecentsFreeformTaskLaunch(task, transition)
// Check if the closing task needs to be handled
TransitionUtil.isClosingType(request.type) ->
handleTaskClosing(task, transition, request.type)
// Check if the top task shouldn't be allowed to enter desktop mode
- isIncompatibleTask(task) -> handleIncompatibleTaskLaunch(task)
+ isIncompatibleTask(task) -> handleIncompatibleTaskLaunch(task, transition)
// Check if fullscreen task should be updated
task.isFullscreen -> handleFullscreenTaskLaunch(task, transition)
// Check if freeform task should be updated
@@ -2306,20 +2192,23 @@
* This is a special case where we want to launch the task in fullscreen instead of freeform.
*/
private fun handleMidRecentsFreeformTaskLaunch(
- task: RunningTaskInfo
+ task: RunningTaskInfo,
+ transition: IBinder,
): WindowContainerTransaction? {
logV("DesktopTasksController: handleMidRecentsFreeformTaskLaunch")
val wct = WindowContainerTransaction()
- addMoveToFullscreenChanges(
- wct = wct,
- taskInfo = task,
- willExitDesktop =
- willExitDesktop(
- triggerTaskId = task.taskId,
- displayId = task.displayId,
- forceExitDesktop = true,
- ),
- )
+ val runOnTransitStart =
+ addMoveToFullscreenChanges(
+ wct = wct,
+ taskInfo = task,
+ willExitDesktop =
+ willExitDesktop(
+ triggerTaskId = task.taskId,
+ displayId = task.displayId,
+ forceExitDesktop = true,
+ ),
+ )
+ runOnTransitStart?.invoke(transition)
wct.reorder(task.token, true)
return wct
}
@@ -2343,19 +2232,23 @@
// launched. We should make this task go to fullscreen instead of freeform. Note
// that this means any re-launch of a freeform window outside of desktop will be in
// fullscreen as long as default-desktop flag is disabled.
- addMoveToFullscreenChanges(
- wct = wct,
- taskInfo = task,
- willExitDesktop =
- willExitDesktop(
- triggerTaskId = task.taskId,
- displayId = task.displayId,
- forceExitDesktop = true,
- ),
- )
+ val runOnTransitStart =
+ addMoveToFullscreenChanges(
+ wct = wct,
+ taskInfo = task,
+ willExitDesktop =
+ willExitDesktop(
+ triggerTaskId = task.taskId,
+ displayId = task.displayId,
+ forceExitDesktop = true,
+ ),
+ )
+ runOnTransitStart?.invoke(transition)
return wct
}
- bringDesktopAppsToFrontBeforeShowingNewTask(task.displayId, wct, task.taskId)
+ val deskId = getDefaultDeskId(task.displayId)
+ val runOnTransitStart = addDeskActivationChanges(deskId, wct, task)
+ runOnTransitStart?.invoke(transition)
wct.reorder(task.token, true)
return wct
}
@@ -2416,26 +2309,44 @@
if (shouldFullscreenTaskLaunchSwitchToDesktop(task)) {
logD("Switch fullscreen task to freeform on transition: taskId=%d", task.taskId)
return WindowContainerTransaction().also { wct ->
- addMoveToDesktopChanges(wct, task)
- // In some launches home task is moved behind new task being launched. Make sure
- // that's not the case for launches in desktop. Also, if this launch is the first
- // one to trigger the desktop mode (e.g., when [forceEnterDesktop()]), activate the
- // desktop mode here.
- if (
- task.baseIntent.flags.and(Intent.FLAG_ACTIVITY_TASK_ON_HOME) != 0 ||
- !isDesktopModeShowing(task.displayId)
- ) {
- bringDesktopAppsToFrontBeforeShowingNewTask(task.displayId, wct, task.taskId)
- wct.reorder(task.token, true)
- }
-
- // Desktop Mode is already showing and we're launching a new Task - we might need to
- // minimize another Task.
- val taskIdToMinimize = addAndGetMinimizeChanges(task.displayId, wct, task.taskId)
- taskIdToMinimize?.let {
- addPendingMinimizeTransition(transition, it, MinimizeReason.TASK_LIMIT)
- }
- addPendingAppLaunchTransition(transition, task.taskId, taskIdToMinimize)
+ val deskId = getDefaultDeskId(task.displayId)
+ addMoveToDeskTaskChanges(wct = wct, task = task, deskId = deskId)
+ val runOnTransitStart: RunOnTransitStart? =
+ if (
+ task.baseIntent.flags.and(Intent.FLAG_ACTIVITY_TASK_ON_HOME) != 0 ||
+ !isDesktopModeShowing(task.displayId)
+ ) {
+ // In some launches home task is moved behind new task being launched. Make
+ // sure that's not the case for launches in desktop. Also, if this launch is
+ // the first one to trigger the desktop mode (e.g., when
+ // [forceEnterDesktop()]), activate the desk here.
+ val activationRunnable =
+ addDeskActivationChanges(
+ deskId = deskId,
+ wct = wct,
+ newTask = task,
+ addPendingLaunchTransition = true,
+ )
+ wct.reorder(task.token, true)
+ activationRunnable
+ } else {
+ { transition: IBinder ->
+ // The desk was already showing and we're launching a new Task - we
+ // might need to minimize another Task.
+ val taskIdToMinimize =
+ addAndGetMinimizeChanges(task.displayId, wct, task.taskId)
+ taskIdToMinimize?.let { minimizingTaskId ->
+ addPendingMinimizeTransition(
+ transition,
+ minimizingTaskId,
+ MinimizeReason.TASK_LIMIT,
+ )
+ }
+ // Also track the pending launching task.
+ addPendingAppLaunchTransition(transition, task.taskId, taskIdToMinimize)
+ }
+ }
+ runOnTransitStart?.invoke(transition)
desktopImmersiveController.exitImmersiveIfApplicable(
transition,
wct,
@@ -2447,7 +2358,8 @@
// If a freeform task receives a request for a fullscreen launch, apply the same
// changes we do for similar transitions. The task not having WINDOWING_MODE_UNDEFINED
// set when needed can interfere with future split / multi-instance transitions.
- return WindowContainerTransaction().also { wct ->
+ val wct = WindowContainerTransaction()
+ val runOnTransitStart =
addMoveToFullscreenChanges(
wct = wct,
taskInfo = task,
@@ -2458,7 +2370,8 @@
forceExitDesktop = true,
),
)
- }
+ runOnTransitStart?.invoke(transition)
+ return wct
}
return null
}
@@ -2473,7 +2386,10 @@
* If a task is not compatible with desktop mode freeform, it should always be launched in
* fullscreen.
*/
- private fun handleIncompatibleTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? {
+ private fun handleIncompatibleTaskLaunch(
+ task: RunningTaskInfo,
+ transition: IBinder,
+ ): WindowContainerTransaction? {
logV("handleIncompatibleTaskLaunch")
if (!isDesktopModeShowing(task.displayId) && !forceEnterDesktop(task.displayId)) return null
// Only update task repository for transparent task.
@@ -2485,7 +2401,8 @@
}
// Already fullscreen, no-op.
if (task.isFullscreen) return null
- return WindowContainerTransaction().also { wct ->
+ val wct = WindowContainerTransaction()
+ val runOnTransitStart =
addMoveToFullscreenChanges(
wct = wct,
taskInfo = task,
@@ -2496,7 +2413,8 @@
forceExitDesktop = true,
),
)
- }
+ runOnTransitStart?.invoke(transition)
+ return wct
}
/**
@@ -2543,55 +2461,58 @@
}
/**
- * Apply all changes required when task is first added to desktop. Uses the task's current
- * display by default to apply initial bounds and placement relative to the display. Use a
- * different [displayId] if the task should be moved to a different display.
+ * Applies the [wct] changes need when a task is first moving to a desk and the desk needs to be
+ * activated.
*/
- @VisibleForTesting
- @Deprecated("Deprecated with multiple desks", ReplaceWith("prepareMoveTaskToDesk()"))
- fun addMoveToDesktopChanges(
+ private fun addDeskActivationWithMovingTaskChanges(
+ deskId: Int,
wct: WindowContainerTransaction,
- taskInfo: RunningTaskInfo,
- displayId: Int = taskInfo.displayId,
- ) {
- val displayLayout = displayController.getDisplayLayout(displayId) ?: return
- val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId)!!
- val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode
- // TODO: b/397437641 - reconsider the windowing mode choice when multiple desks is enabled.
- val targetWindowingMode =
- if (tdaWindowingMode == WINDOWING_MODE_FREEFORM) {
- // Display windowing is freeform, set to undefined and inherit it
- WINDOWING_MODE_UNDEFINED
- } else {
- WINDOWING_MODE_FREEFORM
- }
- val initialBounds = getInitialBounds(displayLayout, taskInfo, displayId)
-
- if (canChangeTaskPosition(taskInfo)) {
- wct.setBounds(taskInfo.token, initialBounds)
- }
- wct.setWindowingMode(taskInfo.token, targetWindowingMode)
- wct.reorder(taskInfo.token, /* onTop= */ true)
- if (useDesktopOverrideDensity()) {
- wct.setDensityDpi(taskInfo.token, DESKTOP_DENSITY_OVERRIDE)
- }
+ task: RunningTaskInfo,
+ ): RunOnTransitStart? {
+ val runOnTransitStart = addDeskActivationChanges(deskId, wct, task)
+ addMoveToDeskTaskChanges(wct = wct, task = task, deskId = deskId)
+ return runOnTransitStart
}
- private fun prepareMoveTaskToDesk(
+ /**
+ * Applies the [wct] changes needed when a task is first moving to a desk.
+ *
+ * Note that this recalculates the initial bounds of the task, so it should not be used when
+ * transferring a task between desks.
+ *
+ * TODO: b/362720497 - this should be improved to be reusable by desk-to-desk CUJs where
+ * [DesksOrganizer.moveTaskToDesk] needs to be called and even cross-display CUJs where
+ * [applyFreeformDisplayChange] needs to be called. Potentially by comparing source vs
+ * destination desk ids and display ids, or adding extra arguments to the function.
+ */
+ fun addMoveToDeskTaskChanges(
wct: WindowContainerTransaction,
- taskInfo: RunningTaskInfo,
+ task: RunningTaskInfo,
deskId: Int,
) {
- if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) return
- val displayId = taskRepository.getDisplayForDesk(deskId)
- val displayLayout = displayController.getDisplayLayout(displayId) ?: return
- val initialBounds = getInitialBounds(displayLayout, taskInfo, displayId)
- if (canChangeTaskPosition(taskInfo)) {
- wct.setBounds(taskInfo.token, initialBounds)
+ val targetDisplayId = taskRepository.getDisplayForDesk(deskId)
+ val displayLayout = displayController.getDisplayLayout(targetDisplayId) ?: return
+ val initialBounds = getInitialBounds(displayLayout, task, targetDisplayId)
+ if (canChangeTaskPosition(task)) {
+ wct.setBounds(task.token, initialBounds)
}
- desksOrganizer.moveTaskToDesk(wct, deskId = deskId, task = taskInfo)
+ if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
+ desksOrganizer.moveTaskToDesk(wct = wct, deskId = deskId, task = task)
+ } else {
+ val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(targetDisplayId)!!
+ val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode
+ val targetWindowingMode =
+ if (tdaWindowingMode == WINDOWING_MODE_FREEFORM) {
+ // Display windowing is freeform, set to undefined and inherit it
+ WINDOWING_MODE_UNDEFINED
+ } else {
+ WINDOWING_MODE_FREEFORM
+ }
+ wct.setWindowingMode(task.token, targetWindowingMode)
+ wct.reorder(task.token, /* onTop= */ true)
+ }
if (useDesktopOverrideDensity()) {
- wct.setDensityDpi(taskInfo.token, DESKTOP_DENSITY_OVERRIDE)
+ wct.setDensityDpi(task.token, DESKTOP_DENSITY_OVERRIDE)
}
}
@@ -2692,7 +2613,6 @@
if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
wct.reparent(taskInfo.token, tdaInfo.token, /* onTop= */ true)
}
- taskRepository.setPipShouldKeepDesktopActive(taskInfo.displayId, keepActive = false)
val deskId = taskRepository.getDeskIdForTask(taskInfo.taskId)
return performDesktopExitCleanUp(
wct = wct,
@@ -2821,28 +2741,74 @@
activateDesk(deskId, remoteTransition)
}
- /** Activates the given desk but without starting a transition. */
- fun addDeskActivationChanges(deskId: Int, wct: WindowContainerTransaction) {
+ /**
+ * Applies the necessary [wct] changes to activate the given desk.
+ *
+ * When a task is being brought into a desk together with the activation, then [newTask] is not
+ * null and may be used to run other desktop policies, such as minimizing another task if the
+ * task limit has been exceeded.
+ */
+ fun addDeskActivationChanges(
+ deskId: Int,
+ wct: WindowContainerTransaction,
+ newTask: TaskInfo? = null,
+ // TODO: b/362720497 - should this be true in other places? Can it be calculated locally
+ // without having to specify the value?
+ addPendingLaunchTransition: Boolean = false,
+ ): RunOnTransitStart? {
+ logV("addDeskActivationChanges newTaskId=%d deskId=%d", newTask?.taskId, deskId)
+ val newTaskIdInFront = newTask?.taskId
val displayId = taskRepository.getDisplayForDesk(deskId)
- if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
- prepareForDeskActivation(displayId, wct)
- desksOrganizer.activateDesk(wct, deskId)
- if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) {
- // TODO: 362720497 - do non-running tasks need to be restarted with |wct#startTask|?
+ if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
+ val taskIdToMinimize = bringDesktopAppsToFront(displayId, wct, newTask?.taskId)
+ return { transition ->
+ taskIdToMinimize?.let { minimizingTaskId ->
+ addPendingMinimizeTransition(
+ transition = transition,
+ taskIdToMinimize = minimizingTaskId,
+ minimizeReason = MinimizeReason.TASK_LIMIT,
+ )
+ }
+ if (newTask != null && addPendingLaunchTransition) {
+ addPendingAppLaunchTransition(transition, newTask.taskId, taskIdToMinimize)
+ }
}
- taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(
- doesAnyTaskRequireTaskbarRounding(displayId)
- )
- } else {
- bringDesktopAppsToFront(displayId, wct)
+ }
+ prepareForDeskActivation(displayId, wct)
+ desksOrganizer.activateDesk(wct, deskId)
+ if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) {
+ // TODO: 362720497 - do non-running tasks need to be restarted with |wct#startTask|?
+ }
+ taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(
+ doesAnyTaskRequireTaskbarRounding(displayId)
+ )
+ // TODO: b/362720497 - activating a desk with the intention to move a new task to
+ // it means we may need to minimize something in the activating desk. Do so here
+ // similar to how it's done in #bringDesktopAppsToFront.
+ return { transition ->
+ val activateDeskTransition =
+ if (newTaskIdInFront != null) {
+ DeskTransition.ActiveDeskWithTask(
+ token = transition,
+ displayId = displayId,
+ deskId = deskId,
+ enterTaskId = newTaskIdInFront,
+ )
+ } else {
+ DeskTransition.ActivateDesk(
+ token = transition,
+ displayId = displayId,
+ deskId = deskId,
+ )
+ }
+ desksTransitionObserver.addPendingTransition(activateDeskTransition)
}
}
/** Activates the given desk. */
fun activateDesk(deskId: Int, remoteTransition: RemoteTransition? = null) {
- val displayId = taskRepository.getDisplayForDesk(deskId)
val wct = WindowContainerTransaction()
- addDeskActivationChanges(deskId, wct)
+ val runOnTransitStart = addDeskActivationChanges(deskId, wct)
val transitionType = transitionType(remoteTransition)
val handler =
@@ -2852,15 +2818,7 @@
val transition = transitions.startTransition(transitionType, wct, handler)
handler?.setTransition(transition)
- if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
- desksTransitionObserver.addPendingTransition(
- DeskTransition.ActivateDesk(
- token = transition,
- displayId = displayId,
- deskId = deskId,
- )
- )
- }
+ runOnTransitStart?.invoke(transition)
desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted(
FREEFORM_ANIMATION_DURATION
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
index f9ab359..b9b4d9e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
@@ -268,6 +268,7 @@
// If it's a running task, reorder it to back.
taskIdToMinimize
?.let { shellTaskOrganizer.getRunningTaskInfo(it) }
+ // TODO: b/391485148 - this won't really work with multi-desks enabled.
?.let { wct.reorder(it.token, /* onTop= */ false) }
return taskIdToMinimize
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
index df2cf67..26a5d5b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
@@ -25,7 +25,6 @@
import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_PIP
import android.view.WindowManager.TRANSIT_TO_BACK
-import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.DesktopExperienceFlags
import android.window.DesktopModeFlags
import android.window.DesktopModeFlags.ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER
@@ -332,10 +331,6 @@
taskInfo.token,
taskInfo.displayId,
)
- desktopWallpaperActivityTokenProvider.setWallpaperActivityIsVisible(
- isVisible = true,
- taskInfo.displayId,
- )
// After the task for the wallpaper is created, set it non-trimmable.
// This is important to prevent recents from trimming and removing the
// task.
@@ -346,16 +341,6 @@
}
TRANSIT_CLOSE ->
desktopWallpaperActivityTokenProvider.removeToken(taskInfo.displayId)
- TRANSIT_TO_FRONT ->
- desktopWallpaperActivityTokenProvider.setWallpaperActivityIsVisible(
- isVisible = true,
- taskInfo.displayId,
- )
- TRANSIT_TO_BACK ->
- desktopWallpaperActivityTokenProvider.setWallpaperActivityIsVisible(
- isVisible = false,
- taskInfo.displayId,
- )
else -> {}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index d396d8b..e943c42 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -25,6 +25,7 @@
import android.os.UserHandle
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CLOSE
+import android.window.DesktopModeFlags
import android.window.TransitionInfo
import android.window.TransitionInfo.Change
import android.window.TransitionRequestInfo
@@ -118,6 +119,7 @@
fun startDragToDesktopTransition(
taskInfo: RunningTaskInfo,
dragToDesktopAnimator: MoveToDesktopAnimator,
+ visualIndicator: DesktopModeVisualIndicator?,
) {
if (inProgress) {
logV("Drag to desktop transition already in progress.")
@@ -163,12 +165,14 @@
dragAnimator = dragToDesktopAnimator,
startTransitionToken = startTransitionToken,
otherSplitTask = otherTask,
+ visualIndicator = visualIndicator,
)
} else {
TransitionState.FromFullscreen(
draggedTaskId = taskInfo.taskId,
dragAnimator = dragToDesktopAnimator,
startTransitionToken = startTransitionToken,
+ visualIndicator = visualIndicator,
)
}
}
@@ -277,6 +281,7 @@
val state = requireTransitionState()
val taskInfo = state.draggedTaskChange?.taskInfo ?: error("Expected non-null taskInfo")
val animatedTaskBounds = getAnimatedTaskBounds()
+ state.dragAnimator.cancelAnimator()
requestSplitSelect(wct, taskInfo, splitPosition, animatedTaskBounds)
}
@@ -288,7 +293,6 @@
val scaledWidth = taskBounds.width() * taskScale
val scaledHeight = taskBounds.height() * taskScale
val dragPosition = PointF(state.dragAnimator.position)
- state.dragAnimator.cancelAnimator()
return Rect(
dragPosition.x.toInt(),
dragPosition.y.toInt(),
@@ -321,22 +325,24 @@
// TODO(b/391928049): update density once we can drag from desktop to bubble
val state = requireTransitionState()
val taskInfo = state.draggedTaskChange?.taskInfo ?: error("Expected non-null taskInfo")
- val taskBounds = getAnimatedTaskBounds()
+ val dragPosition = PointF(state.dragAnimator.position)
+ val scale = state.dragAnimator.scale
state.dragAnimator.cancelAnimator()
- requestBubble(wct, taskInfo, onLeft, taskBounds)
+ requestBubble(wct, taskInfo, onLeft, scale, dragPosition)
}
private fun requestBubble(
wct: WindowContainerTransaction,
taskInfo: RunningTaskInfo,
onLeft: Boolean,
- taskBounds: Rect = Rect(taskInfo.configuration.windowConfiguration.bounds),
+ taskScale: Float = 1f,
+ dragPosition: PointF = PointF(0f, 0f),
) {
val controller =
bubbleController.orElseThrow { IllegalStateException("BubbleController not set") }
controller.expandStackAndSelectBubble(
taskInfo,
- BubbleTransitions.DragData(taskBounds, wct, onLeft),
+ BubbleTransitions.DragData(onLeft, taskScale, dragPosition, wct),
)
}
@@ -457,6 +463,13 @@
state.surfaceLayers = layers
state.startTransitionFinishCb = finishCallback
state.startTransitionFinishTransaction = finishTransaction
+
+ val taskChange = state.draggedTaskChange ?: error("Expected non-null task change.")
+ val taskInfo = taskChange.taskInfo ?: error("Expected non-null task info.")
+
+ if (DesktopModeFlags.ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX.isTrue) {
+ attachIndicatorToTransitionRoot(state, info, taskInfo, startTransaction)
+ }
startTransaction.apply()
if (state.cancelState == CancelState.NO_CANCEL) {
@@ -485,8 +498,6 @@
} else {
SPLIT_POSITION_BOTTOM_OR_RIGHT
}
- val taskInfo =
- state.draggedTaskChange?.taskInfo ?: error("Expected non-null task info.")
val wct = WindowContainerTransaction()
restoreWindowOrder(wct)
state.startTransitionFinishTransaction?.apply()
@@ -511,6 +522,21 @@
return true
}
+ private fun attachIndicatorToTransitionRoot(
+ state: TransitionState,
+ info: TransitionInfo,
+ taskInfo: RunningTaskInfo,
+ t: SurfaceControl.Transaction,
+ ) {
+ val transitionRoot = info.getRoot(info.findRootIndex(taskInfo.displayId))
+ state.visualIndicator?.let {
+ // Attach the indicator to the transition root so that it's removed at the end of the
+ // transition regardless of whether we managed to release the indicator.
+ it.reparentLeash(t, transitionRoot.leash)
+ it.fadeInIndicator()
+ }
+ }
+
/**
* Calculates start drag to desktop layers for transition [info]. The leash layer is calculated
* based on its change position in the transition, e.g. `appLayer = appLayers - i`, where i is
@@ -901,6 +927,7 @@
abstract var surfaceLayers: DragToDesktopLayers?
abstract var cancelState: CancelState
abstract var startAborted: Boolean
+ abstract val visualIndicator: DesktopModeVisualIndicator?
data class FromFullscreen(
override val draggedTaskId: Int,
@@ -915,6 +942,7 @@
override var surfaceLayers: DragToDesktopLayers? = null,
override var cancelState: CancelState = CancelState.NO_CANCEL,
override var startAborted: Boolean = false,
+ override val visualIndicator: DesktopModeVisualIndicator?,
var otherRootChanges: MutableList<Change> = mutableListOf(),
) : TransitionState()
@@ -931,6 +959,7 @@
override var surfaceLayers: DragToDesktopLayers? = null,
override var cancelState: CancelState = CancelState.NO_CANCEL,
override var startAborted: Boolean = false,
+ override val visualIndicator: DesktopModeVisualIndicator?,
var splitRootChange: Change? = null,
var otherSplitTask: Int,
) : TransitionState()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
index 5e84019..1a66ca8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
@@ -47,6 +47,7 @@
private var boundsAnimator: Animator? = null
private var initialBounds: Rect? = null
+ private var callback: (() -> Unit)? = null
constructor(
transitions: Transitions,
@@ -61,9 +62,14 @@
* bounds of the actual task). This is provided so that the animation resizing can begin where
* the task leash currently is for smoother UX.
*/
- fun startTransition(wct: WindowContainerTransaction, taskLeashBounds: Rect? = null) {
+ fun startTransition(
+ wct: WindowContainerTransaction,
+ taskLeashBounds: Rect? = null,
+ callback: (() -> Unit)? = null,
+ ) {
transitions.startTransition(TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE, wct, this)
initialBounds = taskLeashBounds
+ this.callback = callback
}
fun setOnTaskResizeAnimationListener(listener: OnTaskResizeAnimationListener) {
@@ -121,6 +127,8 @@
interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW)
interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_UNMAXIMIZE_WINDOW)
interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_SNAP_RESIZE)
+ callback?.invoke()
+ callback = null
},
)
addUpdateListener { anim ->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainer.kt
index 919e816..2356238 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainer.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainer.kt
@@ -130,6 +130,12 @@
}
}
+ /** Reparent the indicator to {@code newParent}. */
+ fun reparentLeash(t: SurfaceControl.Transaction, newParent: SurfaceControl) {
+ val leash = indicatorLeash ?: return
+ t.reparent(leash, newParent)
+ }
+
private fun showIndicator(taskSurface: SurfaceControl, leash: SurfaceControl) {
mainExecutor.execute {
indicatorLeash = leash
@@ -166,7 +172,7 @@
displayController.getDisplayLayout(taskInfo.displayId)
?: error("Expected to find DisplayLayout for taskId${taskInfo.taskId}.")
if (currentType == IndicatorType.NO_INDICATOR) {
- fadeInIndicator(layout, newType, taskInfo.displayId, snapEventHandler)
+ fadeInIndicatorInternal(layout, newType, taskInfo.displayId, snapEventHandler)
} else if (newType == IndicatorType.NO_INDICATOR) {
fadeOutIndicator(
layout,
@@ -195,10 +201,22 @@
}
/**
+ * Fade indicator in as provided type.
+ *
+ * Animator fades the indicator in while expanding the bounds outwards.
+ */
+ fun fadeInIndicator(layout: DisplayLayout, type: IndicatorType, displayId: Int) {
+ if (isReleased) return
+ desktopExecutor.execute {
+ fadeInIndicatorInternal(layout, type, displayId, snapEventHandler)
+ }
+ }
+
+ /**
* Fade indicator in as provided type. Animator fades it in while expanding the bounds outwards.
*/
@VisibleForTesting
- fun fadeInIndicator(
+ fun fadeInIndicatorInternal(
layout: DisplayLayout,
type: IndicatorType,
displayId: Int,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/desktopwallpaperactivity/DesktopWallpaperActivityTokenProvider.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/desktopwallpaperactivity/DesktopWallpaperActivityTokenProvider.kt
index b5490cb..d185ed5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/desktopwallpaperactivity/DesktopWallpaperActivityTokenProvider.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/desktopwallpaperactivity/DesktopWallpaperActivityTokenProvider.kt
@@ -17,7 +17,6 @@
package com.android.wm.shell.desktopmode.desktopwallpaperactivity
import android.util.SparseArray
-import android.util.SparseBooleanArray
import android.view.Display.DEFAULT_DISPLAY
import android.window.WindowContainerToken
import androidx.core.util.keyIterator
@@ -28,7 +27,6 @@
class DesktopWallpaperActivityTokenProvider {
private val wallpaperActivityTokenByDisplayId = SparseArray<WindowContainerToken>()
- private val wallpaperActivityVisByDisplayId = SparseBooleanArray()
fun setToken(token: WindowContainerToken, displayId: Int = DEFAULT_DISPLAY) {
logV("Setting desktop wallpaper activity token for display %s", displayId)
@@ -55,18 +53,6 @@
}
}
- fun setWallpaperActivityIsVisible(
- isVisible: Boolean = false,
- displayId: Int = DEFAULT_DISPLAY,
- ) {
- wallpaperActivityVisByDisplayId.put(displayId, isVisible)
- }
-
- fun isWallpaperActivityVisible(displayId: Int = DEFAULT_DISPLAY): Boolean {
- return wallpaperActivityTokenByDisplayId[displayId] != null &&
- wallpaperActivityVisByDisplayId.get(displayId, false)
- }
-
private fun logV(msg: String, vararg arguments: Any?) {
ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md
index 9b09904..84525a7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md
@@ -1,4 +1,5 @@
# Usage of Dagger in the Shell library
+
[Back to home](README.md)
---
@@ -16,36 +17,69 @@
## Modules
All the Dagger related code in the Shell can be found in the `com.android.wm.shell.dagger` package,
-this is intentional as it keeps the "magic" in a single location. The explicit nature of how
+this is intentional as it keeps the "magic" in a single location. The explicit nature of how
components in the shell are provided is as a result a bit more verbose, but it makes it easy for
developers to jump into a few select files and understand how different components are provided
(especially as products override components).
The module dependency tree looks a bit like:
+
- [WMShellConcurrencyModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java)
(provides threading-related components)
- - [WMShellBaseModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java)
- (provides components that are likely common to all products, ie. DisplayController,
- Transactions, etc.)
- - [WMShellModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java)
- (phone/tablet specific components only)
- - [TvPipModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java)
- (PIP specific components for TV)
- - [TvWMShellModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java)
- (TV specific components only)
- - etc.
+ - [WMShellBaseModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java)
+ (provides components that are common to many products, ie. DisplayController, Transactions,
+ etc.)
+ - [WMShellModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java)
+ (phone/tablet specific components only)
+ - [TvPipModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java)
+ (PIP specific components for TV)
+ - [TvWMShellModule](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java)
+ (TV specific components only)
+ - etc.
Ideally features could be abstracted out into their own modules and included as needed by each
product.
+## Changing WMShellBaseModule
+
+Because all products will include WMShellBaseModule, we don't want it to provide instances for
+features that aren't used across multiple products (ie. Handheld, TV, Auto, Wear). This module
+should generally only provide:
+
+- Concrete implementations that are needed for startup
+ (see `provideIndependentShellComponentsToCreate()`)
+- Things used directly/indirectly by interfaces
+ exposed to SysUI
+ in [WMComponent.java](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMComponent.java).
+
+For the latter, not every feature will be enabled on the SysUI form factor including the base
+module, so the recommendation is to have an `@BindsOptionalOf` for the interface, and have the
+actual implementation provided in the form-factor specific module (ie. `WMShellModule`).
+
## Overriding base components
In some rare cases, there are base components that can change behavior depending on which
-product it runs on. If there are hooks that can be added to the component, that is the
+product it runs on. If there are hooks that can be added to the component, that is the
preferable approach.
-The alternative is to use the [@DynamicOverride](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/DynamicOverride.java)
+The alternative is to use
+the [@DynamicOverride](/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/DynamicOverride.java)
annotation to allow the product module to provide an implementation that the base module can
-reference. This is most useful if the existence of the entire component is controlled by the
-product and the override implementation is optional (there is a default implementation). More
-details can be found in the class's javadoc.
\ No newline at end of file
+reference. This is most useful if the existence of the entire component is controlled by the
+product and the override implementation is optional (there is a default implementation). More
+details can be found in the class's javadoc.
+
+## Starting up Shell components aren't dependencies for other components
+
+With Dagger, objects are created in dependency order and individual components can register with
+`ShellInit` (see [Component initialization](changes.md#component-initialization)) to initialize in
+dependency order as well. However, if there is code that needs to run on startup but has nothing
+dependent on it (imagine a background error detector for example), then
+`provideIndependentShellComponentsToCreate()` can serve as the artificial dependent object (itself
+a dependency for `ShellInterface`) to trigger creation of such a component.
+
+This can be declared within each module, so if a product includes `WMShellModule`, all the
+components in `provideIndependentShellComponentsToCreate()` for both it and `WMShellBaseModule` will
+be created.
+
+Note that long term we are looking to move to a `CoreStartable` like infrastructure.
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
index d666126..c0a0f46 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
@@ -22,6 +22,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.service.dreams.Flags.dismissDreamOnKeyguardDismiss;
import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS;
+import static android.view.WindowManager.TRANSIT_FLAG_AOD_APPEARING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
@@ -200,7 +201,8 @@
transition, info, startTransaction, finishTransaction, finishCallback);
}
- if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0) {
+ if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0
+ || (info.getFlags() & TRANSIT_FLAG_AOD_APPEARING) != 0) {
return startAnimation(mAppearTransition, "appearing",
transition, info, startTransaction, finishTransaction, finishCallback);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipAlphaAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipAlphaAnimator.java
index a033b824..7918a21 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipAlphaAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipAlphaAnimator.java
@@ -131,9 +131,10 @@
}
private void onAlphaAnimationUpdate(float alpha, SurfaceControl.Transaction tx) {
+ // only set shadow radius on fade in
tx.setAlpha(mLeash, alpha)
.setCornerRadius(mLeash, mCornerRadius)
- .setShadowRadius(mLeash, mShadowRadius);
+ .setShadowRadius(mLeash, mDirection == FADE_IN ? mShadowRadius : 0f);
tx.apply();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index d16c578..119763f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -32,6 +32,7 @@
import android.graphics.Rect;
import android.os.Bundle;
import android.view.SurfaceControl;
+import android.window.DesktopExperienceFlags;
import android.window.DisplayAreaInfo;
import android.window.WindowContainerTransaction;
@@ -41,7 +42,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
import com.android.internal.util.Preconditions;
-import com.android.window.flags.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayChangeController;
@@ -238,7 +238,10 @@
@Override
public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
- if (task.getWindowingMode() != WINDOWING_MODE_PINNED) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "onActivityRestartAttempt: topActivity=%s, wasVisible=%b",
+ task.topActivity, wasVisible);
+ if (task.getWindowingMode() != WINDOWING_MODE_PINNED || !wasVisible) {
return;
}
mPipScheduler.scheduleExitPipViaExpand();
@@ -303,7 +306,8 @@
public void onDisplayRemoved(int displayId) {
// If PiP was active on an external display that is removed, clean up states and set
// {@link PipDisplayLayoutState} to DEFAULT_DISPLAY.
- if (Flags.enableConnectedDisplaysPip() && mPipTransitionState.isInPip()
+ if (DesktopExperienceFlags.ENABLE_CONNECTED_DISPLAYS_PIP.isTrue()
+ && mPipTransitionState.isInPip()
&& displayId == mPipDisplayLayoutState.getDisplayId()
&& displayId != DEFAULT_DISPLAY) {
mPipTransitionState.setState(PipTransitionState.EXITING_PIP);
@@ -385,7 +389,7 @@
// If PiP is enabled on Connected Displays, update PipDisplayLayoutState to have the correct
// display info that PiP is entering in.
- if (Flags.enableConnectedDisplaysPip()) {
+ if (DesktopExperienceFlags.ENABLE_CONNECTED_DISPLAYS_PIP.isTrue()) {
final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(displayId);
if (displayLayout != null) {
mPipDisplayLayoutState.setDisplayId(displayId);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInteractionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInteractionHandler.java
new file mode 100644
index 0000000..3219524
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInteractionHandler.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip2.phone;
+
+import static com.android.internal.jank.Cuj.CUJ_PIP_TRANSITION;
+
+import android.annotation.IntDef;
+import android.content.Context;
+import android.os.Handler;
+import android.view.SurfaceControl;
+
+import com.android.internal.jank.InteractionJankMonitor;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Helps track PIP CUJ interactions
+ */
+public class PipInteractionHandler {
+ @IntDef(prefix = {"INTERACTION_"}, value = {
+ INTERACTION_EXIT_PIP,
+ INTERACTION_EXIT_PIP_TO_SPLIT
+ })
+
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Interaction {}
+
+ public static final int INTERACTION_EXIT_PIP = 0;
+ public static final int INTERACTION_EXIT_PIP_TO_SPLIT = 1;
+
+ private final Context mContext;
+ private final Handler mHandler;
+ private final InteractionJankMonitor mInteractionJankMonitor;
+
+ public PipInteractionHandler(Context context, Handler handler,
+ InteractionJankMonitor interactionJankMonitor) {
+ mContext = context;
+ mHandler = handler;
+ mInteractionJankMonitor = interactionJankMonitor;
+ }
+
+ /**
+ * Begin tracking PIP CUJ.
+ *
+ * @param leash PIP leash.
+ * @param interaction Tag for interaction.
+ */
+ public void begin(SurfaceControl leash, @Interaction int interaction) {
+ mInteractionJankMonitor.begin(leash, mContext, mHandler, CUJ_PIP_TRANSITION,
+ pipInteractionToString(interaction));
+ }
+
+ /**
+ * End tracking CUJ.
+ */
+ public void end() {
+ mInteractionJankMonitor.end(CUJ_PIP_TRANSITION);
+ }
+
+ /**
+ * Converts an interaction to a string representation used for tagging.
+ *
+ * @param interaction Interaction to track.
+ * @return String representation of the interaction.
+ */
+ public static String pipInteractionToString(@Interaction int interaction) {
+ return switch (interaction) {
+ case INTERACTION_EXIT_PIP -> "EXIT_PIP";
+ case INTERACTION_EXIT_PIP_TO_SPLIT -> "EXIT_PIP_TO_SPLIT";
+ default -> "";
+ };
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 91fbd45..9bb2e38 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -113,6 +113,7 @@
private final DisplayController mDisplayController;
private final PipSurfaceTransactionHelper mPipSurfaceTransactionHelper;
private final PipDesktopState mPipDesktopState;
+ private final PipInteractionHandler mPipInteractionHandler;
//
// Transition caches
@@ -154,7 +155,8 @@
PipUiStateChangeController pipUiStateChangeController,
DisplayController displayController,
Optional<SplitScreenController> splitScreenControllerOptional,
- PipDesktopState pipDesktopState) {
+ PipDesktopState pipDesktopState,
+ PipInteractionHandler pipInteractionHandler) {
super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController,
pipBoundsAlgorithm);
@@ -168,9 +170,11 @@
mDisplayController = displayController;
mPipSurfaceTransactionHelper = new PipSurfaceTransactionHelper(mContext);
mPipDesktopState = pipDesktopState;
+ mPipInteractionHandler = pipInteractionHandler;
mExpandHandler = new PipExpandHandler(mContext, pipBoundsState, pipBoundsAlgorithm,
- pipTransitionState, pipDisplayLayoutState, splitScreenControllerOptional);
+ pipTransitionState, pipDisplayLayoutState, pipInteractionHandler,
+ splitScreenControllerOptional);
}
@Override
@@ -770,7 +774,7 @@
// Since opening a new task while in Desktop Mode always first open in Fullscreen
// until DesktopMode Shell code resolves it to Freeform, PipTransition will get a
// possibility to handle it also. In this case return false to not have it enter PiP.
- if (mPipDesktopState.isPipEnteringInDesktopMode(pipTask)) {
+ if (mPipDesktopState.isPipInDesktopMode()) {
return false;
}
@@ -794,9 +798,26 @@
setEnterAnimationType(ANIM_TYPE_BOUNDS);
return true;
}
- // If the only change in the changes list is a opening type PiP task,
+
+ // Sometimes root PiP task can have TF children. These child containers can be collected
+ // even if they can promote to their parents: e.g. if they are marked as "organized".
+ // So we count the chain of containers under PiP task as one "real" changing target;
+ // iterate through changes bottom-to-top to properly identify parents.
+ int expectedTargetCount = 1;
+ WindowContainerToken lastPipChildToken = pipChange.getContainer();
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ TransitionInfo.Change change = info.getChanges().get(i);
+ if (change == pipChange || change.getContainer() == null) continue;
+ if (change.getParent() != null && change.getParent().equals(lastPipChildToken)) {
+ // Allow an extra change since our pinned root task has a child.
+ ++expectedTargetCount;
+ lastPipChildToken = change.getContainer();
+ }
+ }
+
+ // If the only root task change in the changes list is a opening type PiP task,
// then this is legacy-enter PiP.
- return info.getChanges().size() == 1
+ return info.getChanges().size() == expectedTargetCount
&& TransitionUtil.isOpeningMode(pipChange.getMode());
}
return false;
@@ -930,14 +951,6 @@
"Unexpected bundle for " + mPipTransitionState);
break;
case PipTransitionState.EXITED_PIP:
- if (mPipDesktopState.shouldExitPipExitDesktopMode()) {
- mTransitions.startTransition(
- TRANSIT_TO_BACK,
- mPipDesktopState.getWallpaperActivityTokenWct(
- mPipTransitionState.getPipTaskInfo().getDisplayId()),
- null /* firstHandler */
- );
- }
mPipTransitionState.setPinnedTaskLeash(null);
mPipTransitionState.setPipTaskInfo(null);
mPendingRemoveWithFadeout = false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
index 18c9a70..9e2fbfe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
@@ -26,9 +26,11 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
import com.android.internal.util.Preconditions;
+import com.android.wm.shell.common.pip.PipDesktopState;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.annotations.ShellMainThread;
@@ -123,6 +125,8 @@
@ShellMainThread
private final Handler mMainHandler;
+ private final PipDesktopState mPipDesktopState;
+
//
// Swipe up to enter PiP related state
//
@@ -172,8 +176,9 @@
private final List<PipTransitionStateChangedListener> mCallbacks = new ArrayList<>();
- public PipTransitionState(@ShellMainThread Handler handler) {
+ public PipTransitionState(@ShellMainThread Handler handler, PipDesktopState pipDesktopState) {
mMainHandler = handler;
+ mPipDesktopState = pipDesktopState;
}
/**
@@ -384,12 +389,16 @@
return ++mPrevCustomState;
}
- private boolean shouldTransitionToState(@TransitionState int newState) {
+ @VisibleForTesting
+ boolean shouldTransitionToState(@TransitionState int newState) {
switch (newState) {
case SCHEDULED_BOUNDS_CHANGE:
- // Allow scheduling bounds change only while in PiP, except for if another bounds
- // change was scheduled but hasn't started playing yet.
- return isInPip();
+ // Allow scheduling bounds change only when both of these are true:
+ // - while in PiP, except for if another bounds change was scheduled but hasn't
+ // started playing yet
+ // - there is no drag-to-desktop gesture in progress; otherwise the PiP resize
+ // transition will block the drag-to-desktop transitions from finishing
+ return isInPip() && !mPipDesktopState.isDragToDesktopInProgress();
default:
return true;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/transition/PipExpandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/transition/PipExpandHandler.java
index db4942b..3274f4a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/transition/PipExpandHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/transition/PipExpandHandler.java
@@ -45,6 +45,7 @@
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip2.animation.PipExpandAnimator;
+import com.android.wm.shell.pip2.phone.PipInteractionHandler;
import com.android.wm.shell.pip2.phone.PipTransitionState;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -58,6 +59,7 @@
private final PipBoundsAlgorithm mPipBoundsAlgorithm;
private final PipTransitionState mPipTransitionState;
private final PipDisplayLayoutState mPipDisplayLayoutState;
+ private final PipInteractionHandler mPipInteractionHandler;
private final Optional<SplitScreenController> mSplitScreenControllerOptional;
@Nullable
@@ -72,12 +74,14 @@
PipBoundsAlgorithm pipBoundsAlgorithm,
PipTransitionState pipTransitionState,
PipDisplayLayoutState pipDisplayLayoutState,
+ PipInteractionHandler pipInteractionHandler,
Optional<SplitScreenController> splitScreenControllerOptional) {
mContext = context;
mPipBoundsState = pipBoundsState;
mPipBoundsAlgorithm = pipBoundsAlgorithm;
mPipTransitionState = pipTransitionState;
mPipDisplayLayoutState = pipDisplayLayoutState;
+ mPipInteractionHandler = pipInteractionHandler;
mSplitScreenControllerOptional = splitScreenControllerOptional;
mPipExpandAnimatorSupplier = PipExpandAnimator::new;
@@ -183,6 +187,8 @@
PipExpandAnimator animator = mPipExpandAnimatorSupplier.get(mContext, pipLeash,
startTransaction, finishTransaction, endBounds, startBounds, endBounds,
sourceRectHint, delta);
+ animator.setAnimationStartCallback(() -> mPipInteractionHandler.begin(pipLeash,
+ PipInteractionHandler.INTERACTION_EXIT_PIP));
animator.setAnimationEndCallback(() -> {
if (parentBeforePip != null) {
// TODO b/377362511: Animate local leash instead to also handle letterbox case.
@@ -190,6 +196,7 @@
finishTransaction.setCrop(pipLeash, null);
}
finishTransition();
+ mPipInteractionHandler.end();
});
cacheAndStartTransitionAnimator(animator);
saveReentryState();
@@ -248,6 +255,8 @@
splitController.finishEnterSplitScreen(finishTransaction);
});
+ animator.setAnimationStartCallback(() -> mPipInteractionHandler.begin(pipLeash,
+ PipInteractionHandler.INTERACTION_EXIT_PIP_TO_SPLIT));
animator.setAnimationEndCallback(() -> {
if (parentBeforePip == null) {
// After PipExpandAnimator is done modifying finishTransaction, we need to make
@@ -256,6 +265,7 @@
finishTransaction.setPosition(pipLeash, 0, 0);
}
finishTransition();
+ mPipInteractionHandler.end();
});
cacheAndStartTransitionAnimator(animator);
saveReentryState();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index fed336b..65fa9b4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -52,6 +52,7 @@
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.TransactionPool;
import com.android.wm.shell.shared.TransitionUtil;
+import com.android.wm.shell.shared.split.SplitScreenConstants;
import com.android.wm.shell.transition.OneShotRemoteHandler;
import com.android.wm.shell.transition.Transitions;
@@ -362,7 +363,8 @@
WindowContainerTransaction wct,
@Nullable RemoteTransition remoteTransition,
Transitions.TransitionHandler handler,
- int extraTransitType, boolean resizeAnim) {
+ int extraTransitType, boolean resizeAnim,
+ @SplitScreenConstants.PersistentSnapPosition int snapPosition) {
if (mPendingEnter != null) {
ProtoLog.v(WM_SHELL_TRANSITIONS, " splitTransition "
+ " skip to start enter split transition since it already exist. ");
@@ -373,16 +375,18 @@
.onSplitAnimationInvoked(true /*animationRunning*/));
}
final IBinder transition = mTransitions.startTransition(transitType, wct, handler);
- setEnterTransition(transition, remoteTransition, extraTransitType, resizeAnim);
+ setEnterTransition(transition, remoteTransition, extraTransitType, resizeAnim,
+ snapPosition);
return transition;
}
/** Sets a transition to enter split. */
void setEnterTransition(@NonNull IBinder transition,
@Nullable RemoteTransition remoteTransition,
- int extraTransitType, boolean resizeAnim) {
+ int extraTransitType, boolean resizeAnim,
+ int snapPosition) {
mPendingEnter = new EnterSession(
- transition, remoteTransition, extraTransitType, resizeAnim);
+ transition, remoteTransition, extraTransitType, resizeAnim, snapPosition);
ProtoLog.v(WM_SHELL_TRANSITIONS, " splitTransition "
+ " deduced Enter split screen");
@@ -675,13 +679,16 @@
/** Bundled information of enter transition. */
class EnterSession extends TransitSession {
final boolean mResizeAnim;
+ /** The starting snap position we'll enter into with this transition. */
+ final @SplitScreenConstants.PersistentSnapPosition int mEnteringPosition;
EnterSession(IBinder transition,
@Nullable RemoteTransition remoteTransition,
- int extraTransitType, boolean resizeAnim) {
+ int extraTransitType, boolean resizeAnim, int snapPosition) {
super(transition, null /* consumedCallback */, null /* finishedCallback */,
remoteTransition, extraTransitType);
this.mResizeAnim = resizeAnim;
+ this.mEnteringPosition = snapPosition;
}
}
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 a3a808d..7472b0e 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
@@ -653,7 +653,7 @@
null, this,
isSplitScreenVisible()
? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN,
- !mIsDropEntering);
+ !mIsDropEntering, SNAP_TO_2_50_50);
// Due to drag already pip task entering split by this method so need to reset flag here.
mIsDropEntering = false;
@@ -787,7 +787,7 @@
prepareEnterSplitScreen(wct, null /* taskInfo */, position, !mIsDropEntering, index);
mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, null, this,
- extraTransitType, !mIsDropEntering);
+ extraTransitType, !mIsDropEntering, SNAP_TO_2_50_50);
}
/**
@@ -833,7 +833,7 @@
prepareEnterSplitScreen(wct, null /* taskInfo */, position, !mIsDropEntering, index);
mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, null, this,
- extraTransitType, !mIsDropEntering);
+ extraTransitType, !mIsDropEntering, SNAP_TO_2_50_50);
}
/**
@@ -848,6 +848,27 @@
"startTasks: task1=%d task2=%d position=%d snapPosition=%d",
taskId1, taskId2, splitPosition, snapPosition);
final WindowContainerTransaction wct = new WindowContainerTransaction();
+
+ // If the two tasks are already in split screen on external display, only reparent the
+ // split root to the default display if the app pair is clicked on default display.
+ // TODO(b/393217881): cover more cases and extract this to a new method when split screen
+ // in connected display is fully supported.
+ if (enableNonDefaultDisplaySplit()) {
+ DisplayAreaInfo displayAreaInfo = mRootTDAOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY);
+ ActivityManager.RunningTaskInfo taskInfo1 = mTaskOrganizer.getRunningTaskInfo(taskId1);
+ ActivityManager.RunningTaskInfo taskInfo2 = mTaskOrganizer.getRunningTaskInfo(taskId2);
+
+ if (displayAreaInfo != null && taskInfo1 != null && taskInfo2 != null
+ && getStageOfTask(taskId1) != STAGE_TYPE_UNDEFINED
+ && getStageOfTask(taskId2) != STAGE_TYPE_UNDEFINED
+ && taskInfo1.displayId != DEFAULT_DISPLAY
+ && taskInfo1.displayId == taskInfo2.displayId) {
+ wct.reparent(mRootTaskInfo.token, displayAreaInfo.token, true);
+ mTaskOrganizer.applyTransaction(wct);
+ return;
+ }
+ }
+
if (taskId2 == INVALID_TASK_ID) {
startSingleTask(taskId1, options1, wct, remoteTransition);
return;
@@ -1029,7 +1050,7 @@
mPausingTasks.clear();
}
mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, remoteTransition, this,
- TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false);
+ TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false, snapPosition);
setEnterInstanceId(instanceId);
}
@@ -1119,7 +1140,7 @@
}
mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, remoteTransition, this,
- TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false);
+ TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false, snapPosition);
setEnterInstanceId(instanceId);
}
@@ -1624,6 +1645,14 @@
grantFocusToStage(stageToFocus);
}
+ private void grantFocusForSnapPosition(@PersistentSnapPosition int enteringPosition) {
+ switch (enteringPosition) {
+ case SNAP_TO_2_90_10 -> grantFocusToPosition(true /*leftOrTop*/);
+ case SNAP_TO_2_10_90 -> grantFocusToPosition(false /*leftOrTop*/);
+ default -> { /*no-op*/ }
+ }
+ }
+
private void clearRequestIfPresented() {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "clearRequestIfPresented");
if (mSideStage.mVisible && mSideStage.mHasChildren
@@ -2890,7 +2919,7 @@
// split, prepare to enter split screen.
prepareEnterSplitScreen(out);
mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(),
- TRANSIT_SPLIT_SCREEN_PAIR_OPEN, !mIsDropEntering);
+ TRANSIT_SPLIT_SCREEN_PAIR_OPEN, !mIsDropEntering, SNAP_TO_2_50_50);
} 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
@@ -2899,7 +2928,8 @@
// updated layout will get applied in startAnimation pendingResize
mSplitTransitions.setEnterTransition(transition,
request.getRemoteTransition(),
- TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, true /*resizeAnim*/);
+ TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, true /*resizeAnim*/,
+ SNAP_TO_2_50_50);
}
} else if (inFullscreen && isSplitScreenVisible()) {
// If the trigger task is in fullscreen and in split, exit split and place
@@ -2977,14 +3007,15 @@
out = new WindowContainerTransaction();
prepareEnterSplitScreen(out);
mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(),
- TRANSIT_SPLIT_SCREEN_PAIR_OPEN, !mIsDropEntering);
+ TRANSIT_SPLIT_SCREEN_PAIR_OPEN, !mIsDropEntering, SNAP_TO_2_50_50);
return out;
}
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "handleRequest: transition=%d "
+ "restoring to split", request.getDebugId());
out = new WindowContainerTransaction();
mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(),
- TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, false /* resizeAnim */);
+ TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, false /* resizeAnim */,
+ SNAP_TO_2_50_50);
}
return out;
}
@@ -3171,7 +3202,8 @@
if (keepSplitWithPip) {
// Set an enter transition for when startAnimation gets called again
mSplitTransitions.setEnterTransition(transition, /*remoteTransition*/ null,
- TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, /*resizeAnim*/ false);
+ TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, /*resizeAnim*/ false,
+ SNAP_TO_2_50_50);
} else {
int finalClosingTaskId = closingSplitTaskId;
mRecentTasks.ifPresent(recentTasks ->
@@ -3556,6 +3588,9 @@
}
});
mPausingTasks.clear();
+ if (enableFlexibleTwoAppSplit()) {
+ grantFocusForSnapPosition(enterTransition.mEnteringPosition);
+ }
});
if (info.getType() == TRANSIT_CHANGE && !isSplitActive()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 30e5c2a..2a53157 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -1702,6 +1702,7 @@
mExclusionRegionListener.onExclusionRegionDismissed(mTaskInfo.taskId);
disposeResizeVeil();
disposeStatusBarInputLayer();
+ mWindowDecorViewHolder.close();
mWindowDecorViewHolder = null;
if (canEnterDesktopMode(mContext) && isEducationEnabled()) {
notifyNoCaptionHandle();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt
index bdde096d..e8aac39 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt
@@ -22,7 +22,7 @@
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color
-import android.graphics.drawable.RippleDrawable
+import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
@@ -30,11 +30,11 @@
import android.widget.FrameLayout
import android.widget.ImageButton
import android.widget.ProgressBar
+import android.window.DesktopModeFlags
import androidx.core.animation.doOnEnd
import androidx.core.animation.doOnStart
import androidx.core.content.ContextCompat
import com.android.wm.shell.R
-import android.window.DesktopModeFlags
private const val OPEN_MAXIMIZE_MENU_DELAY_ON_HOVER_MS = 350
private const val MAX_DRAWABLE_ALPHA = 255
@@ -109,14 +109,14 @@
darkMode: Boolean,
iconForegroundColor: ColorStateList? = null,
baseForegroundColor: Int? = null,
- rippleDrawable: RippleDrawable? = null
+ backgroundDrawable: Drawable? = null
) {
if (DesktopModeFlags.ENABLE_THEMED_APP_HEADERS.isTrue()) {
requireNotNull(iconForegroundColor) { "Icon foreground color must be non-null" }
requireNotNull(baseForegroundColor) { "Base foreground color must be non-null" }
- requireNotNull(rippleDrawable) { "Ripple drawable must be non-null" }
+ requireNotNull(backgroundDrawable) { "Background drawable must be non-null" }
maximizeWindow.imageTintList = iconForegroundColor
- maximizeWindow.background = rippleDrawable
+ maximizeWindow.background = backgroundDrawable
stubProgressBarContainer.setOnInflateListener { _, inflated ->
val progressBar = (inflated as FrameLayout)
.requireViewById(R.id.progress_bar) as ProgressBar
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/ButtonBackgroundDrawableUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/ButtonBackgroundDrawableUtils.kt
index e18239d..f44b15a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/ButtonBackgroundDrawableUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/ButtonBackgroundDrawableUtils.kt
@@ -16,14 +16,13 @@
package com.android.wm.shell.windowdecor.common
import android.annotation.ColorInt
+import android.content.res.ColorStateList
import android.graphics.Color
+import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.graphics.drawable.RippleDrawable
import android.graphics.drawable.ShapeDrawable
import android.graphics.drawable.shapes.RoundRectShape
-import com.android.wm.shell.windowdecor.common.OPACITY_11
-import com.android.wm.shell.windowdecor.common.OPACITY_15
-import android.content.res.ColorStateList
/**
* Represents drawable insets, specifying the number of pixels to inset a drawable from its bounds.
@@ -52,9 +51,9 @@
* Creates a RippleDrawable with specified color, corner radius, and insets.
*/
fun createRippleDrawable(
- @ColorInt color: Int,
- cornerRadius: Int,
- drawableInsets: DrawableInsets,
+ @ColorInt color: Int,
+ cornerRadius: Int,
+ drawableInsets: DrawableInsets,
): RippleDrawable {
return RippleDrawable(
ColorStateList(
@@ -86,3 +85,32 @@
}
)
}
+
+/**
+ * Creates a background drawable with specified color, corner radius, and insets.
+ */
+fun createBackgroundDrawable(
+ @ColorInt color: Int, cornerRadius: Int, drawableInsets: DrawableInsets
+): Drawable = LayerDrawable(arrayOf(
+ ShapeDrawable().apply {
+ shape = RoundRectShape(
+ FloatArray(8) { cornerRadius.toFloat() },
+ /* inset= */ null,
+ /* innerRadii= */ null
+ )
+ setTintList(ColorStateList(
+ arrayOf(
+ intArrayOf(android.R.attr.state_hovered),
+ intArrayOf(android.R.attr.state_pressed),
+ ),
+ intArrayOf(
+ replaceColorAlpha(color, OPACITY_11),
+ replaceColorAlpha(color, OPACITY_15),
+ )
+ ))
+ }
+)).apply {
+ require(numberOfLayers == 1) { "Must only contain one layer" }
+ setLayerInset(/* index= */ 0,
+ drawableInsets.l, drawableInsets.t, drawableInsets.r, drawableInsets.b)
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
index cb45c17..57f8046 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
@@ -16,6 +16,9 @@
package com.android.wm.shell.windowdecor.tiling
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
import android.content.Context
import android.content.res.Configuration
import android.graphics.Path
@@ -144,7 +147,6 @@
* @param relativeLeash the task leash that the TilingDividerView should be shown on top of.
*/
fun generateViewHost(relativeLeash: SurfaceControl) {
- val t = transactionSupplier.get()
val surfaceControlViewHost =
SurfaceControlViewHost(context, context.display, this, "DesktopTilingManager")
val dividerView =
@@ -155,22 +157,40 @@
val tmpDividerBounds = Rect()
getDividerBounds(tmpDividerBounds)
dividerView.setup(this, tmpDividerBounds, handleRegionSize, isDarkMode)
- t.setRelativeLayer(leash, relativeLeash, 1)
- .setPosition(
- leash,
- dividerBounds.left.toFloat() - maxRoundedCornerRadius,
- dividerBounds.top.toFloat(),
- )
- .show(leash)
- syncQueue.runInSync { transaction ->
- transaction.merge(t)
- t.close()
- }
- dividerShown = true
+ val dividerAnimatorT = transactionSupplier.get()
+ val dividerAnimator =
+ ValueAnimator.ofFloat(0f, 1f).apply {
+ duration = DIVIDER_FADE_IN_ALPHA_DURATION
+ addUpdateListener {
+ dividerAnimatorT.setAlpha(leash, animatedValue as Float).apply()
+ }
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationStart(animation: Animator) {
+ dividerAnimatorT
+ .setRelativeLayer(leash, relativeLeash, 1)
+ .setPosition(
+ leash,
+ dividerBounds.left.toFloat() - maxRoundedCornerRadius,
+ dividerBounds.top.toFloat(),
+ )
+ .setAlpha(leash, 0f)
+ .show(leash)
+ .apply()
+ }
+
+ override fun onAnimationEnd(animation: Animator) {
+ dividerAnimatorT.setAlpha(leash, 1f).apply()
+ dividerShown = true
+ }
+ }
+ )
+ }
+ dividerAnimator.start()
viewHost = surfaceControlViewHost
- dividerView.addOnLayoutChangeListener(this)
tilingDividerView = dividerView
updateTouchRegion()
+ dividerView.addOnLayoutChangeListener(this)
}
/** Changes divider colour if dark/light mode is toggled. */
@@ -311,4 +331,8 @@
)
.maxOf { position -> display.getRoundedCorner(position)?.getRadius() ?: 0 }
}
+
+ companion object {
+ private const val DIVIDER_FADE_IN_ALPHA_DURATION = 300L
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
index a45df04..c3d15df 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
@@ -133,10 +133,10 @@
isDarkMode = isTaskInDarkMode(taskInfo)
// Observe drag resizing to break tiling if a task is drag resized.
desktopModeWindowDecoration.addDragResizeListener(this)
-
+ val callback = { initTilingForDisplayIfNeeded(taskInfo.configuration, isFirstTiledApp) }
if (isTiled) {
val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds)
- toggleResizeDesktopTaskTransitionHandler.startTransition(wct, currentBounds)
+ toggleResizeDesktopTaskTransitionHandler.startTransition(wct, currentBounds, callback)
} else {
// Handle the case where we attempt to snap resize when already snap resized: the task
// position won't need to change but we want to animate the surface going back to the
@@ -147,10 +147,10 @@
resizeMetadata.getLeash(),
startBounds = currentBounds,
endBounds = destinationBounds,
+ callback,
)
}
}
- initTilingForDisplayIfNeeded(taskInfo.configuration, isFirstTiledApp)
return isTiled
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
index 2948fda..d9df899f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
@@ -274,4 +274,6 @@
}
animator.start()
}
+
+ override fun close() {}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
index eb8b617..c3f5b3f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
@@ -23,10 +23,6 @@
import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.Rect
-import android.graphics.drawable.LayerDrawable
-import android.graphics.drawable.RippleDrawable
-import android.graphics.drawable.ShapeDrawable
-import android.graphics.drawable.shapes.RoundRectShape
import android.os.Bundle
import android.view.View
import android.view.View.OnLongClickListener
@@ -55,14 +51,12 @@
import com.android.wm.shell.R
import com.android.wm.shell.windowdecor.MaximizeButtonView
import com.android.wm.shell.windowdecor.common.DecorThemeUtil
+import com.android.wm.shell.windowdecor.common.DrawableInsets
import com.android.wm.shell.windowdecor.common.OPACITY_100
-import com.android.wm.shell.windowdecor.common.OPACITY_11
-import com.android.wm.shell.windowdecor.common.OPACITY_15
import com.android.wm.shell.windowdecor.common.OPACITY_55
import com.android.wm.shell.windowdecor.common.OPACITY_65
import com.android.wm.shell.windowdecor.common.Theme
-import com.android.wm.shell.windowdecor.common.DrawableInsets
-import com.android.wm.shell.windowdecor.common.createRippleDrawable
+import com.android.wm.shell.windowdecor.common.createBackgroundDrawable
import com.android.wm.shell.windowdecor.extension.isLightCaptionBarAppearance
import com.android.wm.shell.windowdecor.extension.isTransparentCaptionBarAppearance
@@ -385,7 +379,7 @@
val colorStateList = ColorStateList.valueOf(foregroundColor).withAlpha(foregroundAlpha)
// App chip.
openMenuButton.apply {
- background = createRippleDrawable(
+ background = createBackgroundDrawable(
color = foregroundColor,
cornerRadius = headerButtonsRippleRadius,
drawableInsets = appChipDrawableInsets,
@@ -396,11 +390,12 @@
setTextColor(colorStateList)
}
appIconImageView.imageAlpha = foregroundAlpha
+ defaultFocusHighlightEnabled = false
}
// Minimize button.
minimizeWindowButton.apply {
imageTintList = colorStateList
- background = createRippleDrawable(
+ background = createBackgroundDrawable(
color = foregroundColor,
cornerRadius = headerButtonsRippleRadius,
drawableInsets = minimizeDrawableInsets
@@ -413,7 +408,7 @@
darkMode = header.appTheme == Theme.DARK,
iconForegroundColor = colorStateList,
baseForegroundColor = foregroundColor,
- rippleDrawable = createRippleDrawable(
+ backgroundDrawable = createBackgroundDrawable(
color = foregroundColor,
cornerRadius = headerButtonsRippleRadius,
drawableInsets = maximizeDrawableInsets
@@ -451,7 +446,7 @@
// Close button.
closeWindowButton.apply {
imageTintList = colorStateList
- background = createRippleDrawable(
+ background = createBackgroundDrawable(
color = foregroundColor,
cornerRadius = headerButtonsRippleRadius,
drawableInsets = closeDrawableInsets
@@ -725,6 +720,11 @@
Configuration.UI_MODE_NIGHT_YES
}
+ override fun close() {
+ // Should not fire long press events after closing the window decoration.
+ maximizeWindowButton.cancelLongPress()
+ }
+
companion object {
private const val TAG = "DesktopModeAppControlsWindowDecorationViewHolder"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/WindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/WindowDecorationViewHolder.kt
index 1fe743d..cd202bf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/WindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/WindowDecorationViewHolder.kt
@@ -24,7 +24,7 @@
* Encapsulates the root [View] of a window decoration and its children to facilitate looking up
* children (via findViewById) and updating to the latest data from [RunningTaskInfo].
*/
-abstract class WindowDecorationViewHolder<T : Data>(rootView: View) {
+abstract class WindowDecorationViewHolder<T : Data>(rootView: View) : AutoCloseable {
val context: Context = rootView.context
/**
@@ -39,6 +39,9 @@
/** Callback when the handle menu is closed. */
abstract fun onHandleMenuClosed()
+ /** Callback when the window decoration is destroyed. */
+ abstract override fun close()
+
/** Data clas that contains the information needed to update the view holder. */
abstract class Data
}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
index d73d08c..c3abe73 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
@@ -19,6 +19,7 @@
import android.tools.PlatformConsts.DESKTOP_MODE_MINIMUM_WINDOW_HEIGHT
import android.tools.PlatformConsts.DESKTOP_MODE_MINIMUM_WINDOW_WIDTH
import android.tools.flicker.AssertionInvocationGroup
+import android.tools.flicker.assertors.assertions.AppLayerCoversFullScreenAtEnd
import android.tools.flicker.assertors.assertions.ResizeVeilKeepsIncreasingInSize
import android.tools.flicker.assertors.assertions.AppLayerIsInvisibleAtEnd
import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAlways
@@ -99,6 +100,59 @@
.associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
)
+ val ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT =
+ FlickerConfigEntry(
+ scenarioId = ScenarioId("ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT"),
+ extractor =
+ ShellTransitionScenarioExtractor(
+ transitionMatcher =
+ object : ITransitionMatcher {
+ override fun findAll(
+ transitions: Collection<Transition>
+ ): Collection<Transition> =
+ transitions.filter {
+ it.type == TransitionType.ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT
+ }
+ }
+ ),
+ assertions =
+ AssertionTemplates.COMMON_ASSERTIONS +
+ listOf(
+ AppLayerIsVisibleAlways(DESKTOP_MODE_APP),
+ AppWindowOnTopAtEnd(DESKTOP_MODE_APP),
+ AppWindowHasDesktopModeInitialBoundsAtTheEnd(DESKTOP_MODE_APP),
+ AppWindowBecomesVisible(DESKTOP_WALLPAPER)
+ )
+ .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+ )
+
+ val EXIT_DESKTOP_FROM_KEYBOARD_SHORTCUT =
+ FlickerConfigEntry(
+ scenarioId = ScenarioId("EXIT_DESKTOP_FROM_KEYBOARD_SHORTCUT"),
+ extractor =
+ ShellTransitionScenarioExtractor(
+ transitionMatcher =
+ object : ITransitionMatcher {
+ override fun findAll(
+ transitions: Collection<Transition>
+ ): Collection<Transition> =
+ transitions.filter {
+ it.type == TransitionType.EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT
+ }
+ }
+ ),
+ assertions =
+ AssertionTemplates.COMMON_ASSERTIONS +
+ listOf(
+ AppLayerIsVisibleAlways(DESKTOP_MODE_APP),
+ AppLayerCoversFullScreenAtEnd(DESKTOP_MODE_APP),
+ AppWindowOnTopAtStart(DESKTOP_MODE_APP),
+ AppWindowOnTopAtEnd(DESKTOP_MODE_APP),
+ AppWindowBecomesInvisible(DESKTOP_WALLPAPER)
+ )
+ .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+ )
+
// Use this scenario for closing an app in desktop windowing, except the last app. For the
// last app use CLOSE_LAST_APP scenario
val CLOSE_APP =
@@ -361,11 +415,10 @@
object : ITransitionMatcher {
override fun findAll(
transitions: Collection<Transition>
- ): Collection<Transition> {
- return transitions.filter {
+ ): Collection<Transition> =
+ transitions.filter {
it.type == TransitionType.DESKTOP_MODE_TOGGLE_RESIZE
}
- }
}
),
assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
@@ -385,11 +438,10 @@
object : ITransitionMatcher {
override fun findAll(
transitions: Collection<Transition>
- ): Collection<Transition> {
- return transitions.filter {
+ ): Collection<Transition> =
+ transitions.filter {
it.type == TransitionType.DESKTOP_MODE_TOGGLE_RESIZE
}
- }
}
),
assertions =
@@ -410,11 +462,10 @@
object : ITransitionMatcher {
override fun findAll(
transitions: Collection<Transition>
- ): Collection<Transition> {
- return transitions.filter {
+ ): Collection<Transition> =
+ transitions.filter {
it.type == TransitionType.TO_FRONT
}
- }
}
),
assertions =
@@ -434,9 +485,8 @@
object : ITransitionMatcher {
override fun findAll(
transitions: Collection<Transition>
- ): Collection<Transition> {
- return transitions.filter { it.type == TransitionType.OPEN }
- }
+ ): Collection<Transition> =
+ transitions.filter { it.type == TransitionType.OPEN }
}
),
assertions =
@@ -512,9 +562,8 @@
object : ITransitionMatcher {
override fun findAll(
transitions: Collection<Transition>
- ): Collection<Transition> {
- return transitions.filter { it.type == TransitionType.OPEN }
- }
+ ): Collection<Transition> =
+ transitions.filter { it.type == TransitionType.OPEN }
}
),
assertions =
@@ -553,11 +602,10 @@
object : ITransitionMatcher {
override fun findAll(
transitions: Collection<Transition>
- ): Collection<Transition> {
- return listOf(transitions
+ ): Collection<Transition> =
+ listOf(transitions
.filter { it.type == TransitionType.OPEN }
.maxByOrNull { it.id }!!)
- }
}
),
assertions =
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/EnterDesktopWithKeyboardShortcut.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/EnterDesktopWithKeyboardShortcut.kt
new file mode 100644
index 0000000..4603e4e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/EnterDesktopWithKeyboardShortcut.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker
+
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT
+import com.android.wm.shell.scenarios.EnterDesktopFromKeyboardShortcut
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterDesktopWithKeyboardShortcut : EnterDesktopFromKeyboardShortcut() {
+ @ExpectedScenarios(["ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT"])
+ @Test
+ override fun enterDesktopFromKeyboardShortcut() = super.enterDesktopFromKeyboardShortcut()
+
+ companion object {
+
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ .use(ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ExitDesktopToFullScreenWithKeyboardShortcut.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ExitDesktopToFullScreenWithKeyboardShortcut.kt
new file mode 100644
index 0000000..d9fd339
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ExitDesktopToFullScreenWithKeyboardShortcut.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker
+
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.EXIT_DESKTOP_FROM_KEYBOARD_SHORTCUT
+import com.android.wm.shell.scenarios.ExitDesktopFromKeyboardShortcut
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class ExitDesktopToFullScreenWithKeyboardShortcut : ExitDesktopFromKeyboardShortcut() {
+ @ExpectedScenarios(["EXIT_DESKTOP_FROM_KEYBOARD_SHORTCUT"])
+ @Test
+ override fun exitDesktopToFullScreenFromKeyboardShortcut() =
+ super.exitDesktopToFullScreenFromKeyboardShortcut()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ .use(EXIT_DESKTOP_FROM_KEYBOARD_SHORTCUT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppDoubleTapAppHeaderLandscape.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppDoubleTapAppHeaderLandscape.kt
new file mode 100644
index 0000000..32df049
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppDoubleTapAppHeaderLandscape.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker
+
+import android.tools.Rotation.ROTATION_90
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper.MaximizeDesktopAppTrigger
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MAXIMIZE_APP
+import com.android.wm.shell.scenarios.MaximizeAppWindow
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Maximize app window by double tapping on the app header.
+ *
+ * Assert that the app window keeps the same increases in size, filling the vertical and horizontal
+ * stable display bounds.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class MaximizeAppDoubleTapAppHeaderLandscape : MaximizeAppWindow(
+ rotation = ROTATION_90,
+ trigger = MaximizeDesktopAppTrigger.DOUBLE_TAP_APP_HEADER
+) {
+ @ExpectedScenarios(["MAXIMIZE_APP"])
+ @Test
+ override fun maximizeAppWindow() = super.maximizeAppWindow()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(MAXIMIZE_APP)
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppDoubleTapAppHeaderPortrait.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppDoubleTapAppHeaderPortrait.kt
new file mode 100644
index 0000000..977846e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppDoubleTapAppHeaderPortrait.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker
+
+import android.tools.Rotation.ROTATION_0
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper.MaximizeDesktopAppTrigger
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MAXIMIZE_APP
+import com.android.wm.shell.scenarios.MaximizeAppWindow
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Maximize app window by double tapping on the app header in portrait mode.
+ *
+ * Assert that the app window keeps the same increases in size, filling the vertical and horizontal
+ * stable display bounds.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class MaximizeAppDoubleTapAppHeaderPortrait : MaximizeAppWindow(
+ rotation = ROTATION_0,
+ trigger = MaximizeDesktopAppTrigger.DOUBLE_TAP_APP_HEADER
+) {
+ @ExpectedScenarios(["MAXIMIZE_APP"])
+ @Test
+ override fun maximizeAppWindow() = super.maximizeAppWindow()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(MAXIMIZE_APP)
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppViaHeaderMenuLandscape.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppViaHeaderMenuLandscape.kt
new file mode 100644
index 0000000..7031926
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppViaHeaderMenuLandscape.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker
+
+import android.tools.Rotation.ROTATION_90
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper.MaximizeDesktopAppTrigger
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MAXIMIZE_APP
+import com.android.wm.shell.scenarios.MaximizeAppWindow
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Maximize app window by tapping on the maximize button within the app header maximize menu.
+ *
+ * Assert that the app window keeps the same increases in size, filling the vertical and horizontal
+ * stable display bounds.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class MaximizeAppViaHeaderMenuLandscape : MaximizeAppWindow(
+ rotation = ROTATION_90,
+ trigger = MaximizeDesktopAppTrigger.MAXIMIZE_BUTTON_IN_MENU
+) {
+ @ExpectedScenarios(["MAXIMIZE_APP"])
+ @Test
+ override fun maximizeAppWindow() = super.maximizeAppWindow()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(MAXIMIZE_APP)
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppViaHeaderMenuPortrait.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppViaHeaderMenuPortrait.kt
new file mode 100644
index 0000000..4c7eb8d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppViaHeaderMenuPortrait.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker
+
+import android.tools.Rotation.ROTATION_0
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper.MaximizeDesktopAppTrigger
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MAXIMIZE_APP
+import com.android.wm.shell.scenarios.MaximizeAppWindow
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Maximize app window by tapping on the maximize button within the app header maximize menu.
+ *
+ * Assert that the app window keeps the same increases in size, filling the vertical and horizontal
+ * stable display bounds.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class MaximizeAppViaHeaderMenuPortrait : MaximizeAppWindow(
+ rotation = ROTATION_0,
+ trigger = MaximizeDesktopAppTrigger.MAXIMIZE_BUTTON_IN_MENU
+) {
+ @ExpectedScenarios(["MAXIMIZE_APP"])
+ @Test
+ override fun maximizeAppWindow() = super.maximizeAppWindow()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(MAXIMIZE_APP)
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppWithKeyboard.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppWithKeyboard.kt
index b399e9b..3f8a28f 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppWithKeyboard.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MaximizeAppWithKeyboard.kt
@@ -23,6 +23,7 @@
import android.tools.flicker.config.FlickerConfig
import android.tools.flicker.config.FlickerServiceConfig
import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper.MaximizeDesktopAppTrigger
import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MAXIMIZE_APP
import com.android.wm.shell.scenarios.MaximizeAppWindow
import org.junit.Test
@@ -35,7 +36,10 @@
* stable display bounds.
*/
@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class MaximizeAppWithKeyboard : MaximizeAppWindow(rotation = ROTATION_90, usingKeyboard = true) {
+class MaximizeAppWithKeyboard : MaximizeAppWindow(
+ rotation = ROTATION_90,
+ trigger = MaximizeDesktopAppTrigger.KEYBOARD_SHORTCUT
+) {
@ExpectedScenarios(["MAXIMIZE_APP"])
@Test
override fun maximizeAppWindow() = super.maximizeAppWindow()
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopFromKeyboardShortcut.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopFromKeyboardShortcut.kt
new file mode 100644
index 0000000..9cbc46b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopFromKeyboardShortcut.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.scenarios
+
+import android.platform.test.annotations.Postsubmit
+import android.app.Instrumentation
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class EnterDesktopFromKeyboardShortcut {
+
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val simpleAppHelper = SimpleAppHelper(instrumentation)
+ private val testApp = DesktopModeAppHelper(simpleAppHelper)
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ }
+
+ @Test
+ open fun enterDesktopFromKeyboardShortcut() {
+ simpleAppHelper.launchViaIntent(wmHelper)
+ testApp.enterDesktopModeViaKeyboard(wmHelper)
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopFromKeyboardShortcut.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopFromKeyboardShortcut.kt
new file mode 100644
index 0000000..1b1f1cf
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopFromKeyboardShortcut.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.scenarios
+
+import android.platform.test.annotations.Postsubmit
+import android.app.Instrumentation
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class ExitDesktopFromKeyboardShortcut {
+
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val simpleAppHelper = SimpleAppHelper(instrumentation)
+ private val testApp = DesktopModeAppHelper(simpleAppHelper)
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ simpleAppHelper.launchViaIntent(wmHelper)
+ testApp.enterDesktopMode(wmHelper, device)
+ }
+
+ @Test
+ open fun exitDesktopToFullScreenFromKeyboardShortcut() {
+ testApp.exitDesktopModeToFullScreenViaKeyboard(wmHelper)
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt
index 7855698..92fe40d 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt
@@ -26,6 +26,7 @@
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper.MaximizeDesktopAppTrigger
import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.window.flags.Flags
@@ -38,11 +39,10 @@
import org.junit.Test
@Ignore("Test Base Class")
-abstract class MaximizeAppWindow
-constructor(
+abstract class MaximizeAppWindow(
private val rotation: Rotation = Rotation.ROTATION_0,
isResizable: Boolean = true,
- private val usingKeyboard: Boolean = false
+ private val trigger: MaximizeDesktopAppTrigger = MaximizeDesktopAppTrigger.MAXIMIZE_MENU,
) {
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
private val tapl = LauncherInstrumentation()
@@ -59,7 +59,7 @@
@Before
fun setup() {
Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
- if (usingKeyboard) {
+ if (trigger == MaximizeDesktopAppTrigger.KEYBOARD_SHORTCUT) {
Assume.assumeTrue(DesktopModeFlags.ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS.isTrue)
}
tapl.setEnableRotation(true)
@@ -70,7 +70,7 @@
@Test
open fun maximizeAppWindow() {
- testApp.maximiseDesktopApp(wmHelper, device, usingKeyboard = usingKeyboard)
+ testApp.maximiseDesktopApp(wmHelper, device, trigger)
}
@After
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
index 6a6aa1a..fa9864b 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
@@ -17,15 +17,15 @@
package com.android.wm.shell.flicker.splitscreen.benchmark
import android.tools.NavBar
-import android.tools.Rotation
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
-import android.tools.helpers.WindowUtils
import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.filters.RequiresDevice
+import androidx.test.uiautomator.UiDevice
import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils.isLeftRightSplit
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -37,6 +37,8 @@
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
abstract class SwitchAppByDoubleTapDividerBenchmark(override val flicker: LegacyFlickerTest) :
SplitScreenBase(flicker) {
+ private val device = UiDevice.getInstance(instrumentation)
+
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
setup {
@@ -73,7 +75,8 @@
}
?: return@add false
- if (isLandscape(flicker.scenario.endRotation)) {
+ if (isLeftRightSplit(instrumentation.context, flicker.scenario.endRotation,
+ device.displaySizeDp)) {
return@add if (flicker.scenario.isTablet) {
secondaryAppWindow.frame.right <= primaryAppWindow.frame.left
} else {
@@ -109,7 +112,8 @@
val secondaryVisibleRegion =
secondaryAppLayer.visibleRegion?.bounds ?: return@add false
- if (isLandscape(flicker.scenario.endRotation)) {
+ if (isLeftRightSplit(instrumentation.context, flicker.scenario.endRotation,
+ device.displaySizeDp)) {
return@add if (flicker.scenario.isTablet) {
secondaryVisibleRegion.right <= primaryVisibleRegion.left
} else {
@@ -126,11 +130,6 @@
.waitForAndVerify()
}
- private fun isLandscape(rotation: Rotation): Boolean {
- val displayBounds = WindowUtils.getDisplayBounds(rotation)
- return displayBounds.width() > displayBounds.height()
- }
-
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchAppByDoubleTapDivider.kt
index 26203d4..3fd93d3 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchAppByDoubleTapDivider.kt
@@ -17,16 +17,16 @@
package com.android.wm.shell.scenarios
import android.app.Instrumentation
-import android.graphics.Point
import android.tools.NavBar
import android.tools.Rotation
-import android.tools.helpers.WindowUtils
import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.wm.shell.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils.isLeftRightSplit
+import com.android.wm.shell.flicker.utils.SplitScreenUtils.isTablet
import org.junit.After
import org.junit.Before
import org.junit.Ignore
@@ -89,14 +89,14 @@
}
?: return@add false
- if (isLandscape(rotation)) {
- return@add if (isTablet()) {
+ if (isLeftRightSplit(instrumentation.context, rotation, device.displaySizeDp)) {
+ return@add if (isTablet(device.displaySizeDp)) {
secondaryAppWindow.frame.right <= primaryAppWindow.frame.left
} else {
primaryAppWindow.frame.right <= secondaryAppWindow.frame.left
}
} else {
- return@add if (isTablet()) {
+ return@add if (isTablet(device.displaySizeDp)) {
primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
} else {
primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
@@ -125,14 +125,14 @@
val secondaryVisibleRegion =
secondaryAppLayer.visibleRegion?.bounds ?: return@add false
- if (isLandscape(rotation)) {
- return@add if (isTablet()) {
+ if (isLeftRightSplit(instrumentation.context, rotation, device.displaySizeDp)) {
+ return@add if (isTablet(device.displaySizeDp)) {
secondaryVisibleRegion.right <= primaryVisibleRegion.left
} else {
primaryVisibleRegion.right <= secondaryVisibleRegion.left
}
} else {
- return@add if (isTablet()) {
+ return@add if (isTablet(device.displaySizeDp)) {
primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
} else {
primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
@@ -141,15 +141,4 @@
}
.waitForAndVerify()
}
-
- private fun isLandscape(rotation: Rotation): Boolean {
- val displayBounds = WindowUtils.getDisplayBounds(rotation)
- return displayBounds.width() > displayBounds.height()
- }
-
- private fun isTablet(): Boolean {
- val sizeDp: Point = device.displaySizeDp
- val LARGE_SCREEN_DP_THRESHOLD = 600
- return sizeDp.x >= LARGE_SCREEN_DP_THRESHOLD && sizeDp.y >= LARGE_SCREEN_DP_THRESHOLD
- }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
index feb3edc..49d6877 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
@@ -17,12 +17,14 @@
package com.android.wm.shell.flicker.utils
import android.app.Instrumentation
+import android.content.Context
import android.graphics.Point
import android.os.SystemClock
import android.tools.Rotation
import android.tools.device.apphelpers.IStandardAppHelper
import android.tools.device.apphelpers.StandardAppHelper
import android.tools.flicker.rules.ChangeDisplayOrientationRule
+import android.tools.helpers.WindowUtils
import android.tools.traces.component.ComponentNameMatcher
import android.tools.traces.component.IComponentMatcher
import android.tools.traces.component.IComponentNameMatcher
@@ -393,4 +395,24 @@
error("Fail to copy content in split")
}
}
+
+ fun isLeftRightSplit(context: Context, rotation: Rotation, displaySizeDp: Point): Boolean {
+ val allowLeftRightSplit = context.resources.getBoolean(
+ com.android.internal.R.bool.config_leftRightSplitInPortrait)
+ val displayBounds = WindowUtils.getDisplayBounds(rotation)
+ val isLandscape = displayBounds.width() > displayBounds.height()
+ if (allowLeftRightSplit && isTablet(displaySizeDp)) {
+ // Certain devices allow left/right split in portrait, so they end up with top/bottom
+ // split in landscape
+ return !isLandscape
+ } else {
+ return isLandscape
+ }
+ }
+
+ fun isTablet(displaySizeDp: Point): Boolean {
+ val LARGE_SCREEN_DP_THRESHOLD = 600
+ return displaySizeDp.x >= LARGE_SCREEN_DP_THRESHOLD
+ && displaySizeDp.y >= LARGE_SCREEN_DP_THRESHOLD
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java
index 42310ca..3c79ea7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java
@@ -25,6 +25,7 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
@@ -35,6 +36,7 @@
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.os.IBinder;
import android.view.SurfaceControl;
@@ -138,13 +140,14 @@
return taskInfo;
}
- private TransitionInfo setupFullscreenTaskTransition(ActivityManager.RunningTaskInfo taskInfo) {
+ private TransitionInfo setupFullscreenTaskTransition(ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl taskLeash, SurfaceControl snapshot) {
final TransitionInfo info = new TransitionInfo(TRANSIT_CONVERT_TO_BUBBLE, 0);
- final TransitionInfo.Change chg = new TransitionInfo.Change(taskInfo.token,
- mock(SurfaceControl.class));
+ final TransitionInfo.Change chg = new TransitionInfo.Change(taskInfo.token, taskLeash);
chg.setTaskInfo(taskInfo);
chg.setMode(TRANSIT_CHANGE);
chg.setStartAbsBounds(new Rect(0, 0, FULLSCREEN_TASK_WIDTH, FULLSCREEN_TASK_HEIGHT));
+ chg.setSnapshot(snapshot, /* luma= */ 0f);
info.addChange(chg);
info.addRoot(new TransitionInfo.Root(0, mock(SurfaceControl.class), 0, 0));
return info;
@@ -172,7 +175,9 @@
// Ensure we are communicating with the taskviewtransitions queue
assertTrue(mTaskViewTransitions.hasPending());
- final TransitionInfo info = setupFullscreenTaskTransition(taskInfo);
+ SurfaceControl taskLeash = new SurfaceControl.Builder().setName("taskLeash").build();
+ SurfaceControl snapshot = new SurfaceControl.Builder().setName("snapshot").build();
+ final TransitionInfo info = setupFullscreenTaskTransition(taskInfo, taskLeash, snapshot);
SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
final boolean[] finishCalled = new boolean[]{false};
@@ -183,7 +188,8 @@
ctb.startAnimation(ctb.mTransition, info, startT, finishT, finishCb);
assertFalse(mTaskViewTransitions.hasPending());
- verify(startT).setPosition(any(), eq(0f), eq(0f));
+ verify(startT).setPosition(taskLeash, 0, 0);
+ verify(startT).setPosition(snapshot, 0, 0);
verify(mBubbleData).notificationEntryUpdated(eq(mBubble), anyBoolean(), anyBoolean());
@@ -194,7 +200,7 @@
// Check that preparing transition is not reset before continueExpand is called
verify(mBubble, never()).setPreparingTransition(any());
ArgumentCaptor<Runnable> animCb = ArgumentCaptor.forClass(Runnable.class);
- verify(mLayerView).animateConvert(any(), any(), any(), any(), animCb.capture());
+ verify(mLayerView).animateConvert(any(), any(), anyFloat(), any(), any(), animCb.capture());
// continueExpand is now called, check that preparing transition is cleared
ctb.continueExpand();
@@ -209,14 +215,14 @@
public void testConvertToBubble_drag() {
ActivityManager.RunningTaskInfo taskInfo = setupBubble();
- Rect draggedTaskBounds = new Rect(10, 20, 30, 40);
WindowContainerTransaction pendingWct = new WindowContainerTransaction();
WindowContainerToken pendingDragOpToken = createMockToken();
pendingWct.reorder(pendingDragOpToken, /* onTop= */ false);
+ PointF dragPosition = new PointF(10f, 20f);
BubbleTransitions.DragData dragData = new BubbleTransitions.DragData(
- draggedTaskBounds, pendingWct, /* releasedOnLeft= */ false
- );
+ /* releasedOnLeft= */ false, /* taskScale= */ 0.5f, dragPosition,
+ pendingWct);
final BubbleTransitions.BubbleTransition bt = mBubbleTransitions.startConvertToBubble(
mBubble, taskInfo, mExpandedViewManager, mTaskViewFactory, mBubblePositioner,
@@ -234,15 +240,19 @@
== WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER
&& op.getContainer() == pendingDragOpToken.asBinder())).isTrue();
- final TransitionInfo info = setupFullscreenTaskTransition(taskInfo);
+ SurfaceControl taskLeash = new SurfaceControl.Builder().setName("taskLeash").build();
+ SurfaceControl snapshot = new SurfaceControl.Builder().setName("snapshot").build();
+ final TransitionInfo info = setupFullscreenTaskTransition(taskInfo, taskLeash, snapshot);
SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
Transitions.TransitionFinishCallback finishCb = wct -> {};
ctb.startAnimation(ctb.mTransition, info, startT, finishT, finishCb);
- // Verify that dragged task bounds are used for the position
- verify(startT).setPosition(any(), eq((float) draggedTaskBounds.left),
- eq((float) draggedTaskBounds.top));
+ // Verify that snapshot and task are placed at where the drag ended
+ verify(startT).setPosition(taskLeash, dragPosition.x, dragPosition.y);
+ verify(startT).setPosition(snapshot, dragPosition.x, dragPosition.y);
+ // Snapshot has the scale of the dragged task
+ verify(startT).setScale(snapshot, dragData.getTaskScale(), dragData.getTaskScale());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
index 60f1d271..d829c6a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
@@ -104,6 +104,7 @@
@Test
public void testExpansionAndCollapse() throws Exception {
expand();
+ waitForAnimation();
testBubblesInCorrectExpandedPositions();
waitForMainThread();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDesktopStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDesktopStateTest.java
index e85d30f..4cdb1e5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDesktopStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDesktopStateTest.java
@@ -41,7 +41,7 @@
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.desktopmode.DesktopUserRepositories;
-import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider;
+import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler;
import org.junit.Before;
import org.junit.Test;
@@ -62,11 +62,12 @@
public class PipDesktopStateTest {
@Mock private PipDisplayLayoutState mMockPipDisplayLayoutState;
@Mock private Optional<DesktopUserRepositories> mMockDesktopUserRepositoriesOptional;
- @Mock private Optional<DesktopWallpaperActivityTokenProvider>
- mMockDesktopWallpaperActivityTokenProviderOptional;
@Mock private DesktopUserRepositories mMockDesktopUserRepositories;
- @Mock private DesktopWallpaperActivityTokenProvider mMockDesktopWallpaperActivityTokenProvider;
@Mock private DesktopRepository mMockDesktopRepository;
+ @Mock
+ private Optional<DragToDesktopTransitionHandler> mMockDragToDesktopTransitionHandlerOptional;
+ @Mock private DragToDesktopTransitionHandler mMockDragToDesktopTransitionHandler;
+
@Mock private RootTaskDisplayAreaOrganizer mMockRootTaskDisplayAreaOrganizer;
@Mock private ActivityManager.RunningTaskInfo mMockTaskInfo;
@@ -78,11 +79,12 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mMockDesktopUserRepositoriesOptional.get()).thenReturn(mMockDesktopUserRepositories);
- when(mMockDesktopWallpaperActivityTokenProviderOptional.get()).thenReturn(
- mMockDesktopWallpaperActivityTokenProvider);
when(mMockDesktopUserRepositories.getCurrent()).thenReturn(mMockDesktopRepository);
when(mMockDesktopUserRepositoriesOptional.isPresent()).thenReturn(true);
- when(mMockDesktopWallpaperActivityTokenProviderOptional.isPresent()).thenReturn(true);
+
+ when(mMockDragToDesktopTransitionHandlerOptional.get()).thenReturn(
+ mMockDragToDesktopTransitionHandler);
+ when(mMockDragToDesktopTransitionHandlerOptional.isPresent()).thenReturn(true);
when(mMockTaskInfo.getDisplayId()).thenReturn(DISPLAY_ID);
when(mMockPipDisplayLayoutState.getDisplayId()).thenReturn(DISPLAY_ID);
@@ -93,7 +95,7 @@
mPipDesktopState = new PipDesktopState(mMockPipDisplayLayoutState,
mMockDesktopUserRepositoriesOptional,
- mMockDesktopWallpaperActivityTokenProviderOptional,
+ mMockDragToDesktopTransitionHandlerOptional,
mMockRootTaskDisplayAreaOrganizer);
}
@@ -110,8 +112,8 @@
}
@Test
- public void isDesktopWindowingPipEnabled_desktopWallpaperEmpty_returnsFalse() {
- when(mMockDesktopWallpaperActivityTokenProviderOptional.isPresent()).thenReturn(false);
+ public void isDesktopWindowingPipEnabled_dragToDesktopTransitionHandlerEmpty_returnsFalse() {
+ when(mMockDragToDesktopTransitionHandlerOptional.isPresent()).thenReturn(false);
assertFalse(mPipDesktopState.isDesktopWindowingPipEnabled());
}
@@ -123,59 +125,7 @@
}
@Test
- public void isPipEnteringInDesktopMode_visibleCountZero_minimizedPipPresent_returnsTrue() {
- when(mMockDesktopRepository.isAnyDeskActive(DISPLAY_ID)).thenReturn(false);
- when(mMockDesktopRepository.isMinimizedPipPresentInDisplay(DISPLAY_ID)).thenReturn(true);
-
- assertTrue(mPipDesktopState.isPipEnteringInDesktopMode(mMockTaskInfo));
- }
-
- @Test
- public void isPipEnteringInDesktopMode_visibleCountNonzero_minimizedPipAbsent_returnsTrue() {
- when(mMockDesktopRepository.isAnyDeskActive(DISPLAY_ID)).thenReturn(true);
- when(mMockDesktopRepository.isMinimizedPipPresentInDisplay(DISPLAY_ID)).thenReturn(false);
-
- assertTrue(mPipDesktopState.isPipEnteringInDesktopMode(mMockTaskInfo));
- }
-
- @Test
- public void isPipEnteringInDesktopMode_visibleCountZero_minimizedPipAbsent_returnsFalse() {
- when(mMockDesktopRepository.isAnyDeskActive(DISPLAY_ID)).thenReturn(false);
- when(mMockDesktopRepository.isMinimizedPipPresentInDisplay(DISPLAY_ID)).thenReturn(false);
-
- assertFalse(mPipDesktopState.isPipEnteringInDesktopMode(mMockTaskInfo));
- }
-
- @Test
- public void shouldExitPipExitDesktopMode_visibleCountZero_wallpaperInvisible_returnsFalse() {
- when(mMockDesktopRepository.isAnyDeskActive(DISPLAY_ID)).thenReturn(false);
- when(mMockDesktopWallpaperActivityTokenProvider.isWallpaperActivityVisible(
- DISPLAY_ID)).thenReturn(false);
-
- assertFalse(mPipDesktopState.shouldExitPipExitDesktopMode());
- }
-
- @Test
- public void shouldExitPipExitDesktopMode_visibleCountNonzero_wallpaperVisible_returnsFalse() {
- when(mMockDesktopRepository.isAnyDeskActive(DISPLAY_ID)).thenReturn(true);
- when(mMockDesktopWallpaperActivityTokenProvider.isWallpaperActivityVisible(
- DISPLAY_ID)).thenReturn(true);
-
- assertFalse(mPipDesktopState.shouldExitPipExitDesktopMode());
- }
-
- @Test
- public void shouldExitPipExitDesktopMode_visibleCountZero_wallpaperVisible_returnsTrue() {
- when(mMockDesktopRepository.isAnyDeskActive(DISPLAY_ID)).thenReturn(false);
- when(mMockDesktopWallpaperActivityTokenProvider.isWallpaperActivityVisible(
- DISPLAY_ID)).thenReturn(true);
-
- assertTrue(mPipDesktopState.shouldExitPipExitDesktopMode());
- }
-
- @Test
public void getOutPipWindowingMode_exitToDesktop_displayFreeform_returnsUndefined() {
- // Set visible task count to 1 so isPipExitingToDesktopMode returns true
when(mMockDesktopRepository.isAnyDeskActive(DISPLAY_ID)).thenReturn(true);
setDisplayWindowingMode(WINDOWING_MODE_FREEFORM);
@@ -184,7 +134,6 @@
@Test
public void getOutPipWindowingMode_exitToDesktop_displayFullscreen_returnsFreeform() {
- // Set visible task count to 1 so isPipExitingToDesktopMode returns true
when(mMockDesktopRepository.isAnyDeskActive(DISPLAY_ID)).thenReturn(true);
setDisplayWindowingMode(WINDOWING_MODE_FULLSCREEN);
@@ -198,6 +147,20 @@
assertEquals(WINDOWING_MODE_UNDEFINED, mPipDesktopState.getOutPipWindowingMode());
}
+ @Test
+ public void isDragToDesktopInProgress_inProgress_returnsTrue() {
+ when(mMockDragToDesktopTransitionHandler.getInProgress()).thenReturn(true);
+
+ assertTrue(mPipDesktopState.isDragToDesktopInProgress());
+ }
+
+ @Test
+ public void isDragToDesktopInProgress_notInProgress_returnsFalse() {
+ when(mMockDragToDesktopTransitionHandler.getInProgress()).thenReturn(false);
+
+ assertFalse(mPipDesktopState.isDragToDesktopInProgress());
+ }
+
private void setDisplayWindowingMode(int windowingMode) {
mDefaultTda.configuration.windowConfiguration.setWindowingMode(windowingMode);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
index 70a30a3..d58f8a3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
@@ -188,7 +188,7 @@
SCREEN_ORIENTATION_LANDSCAPE,
)
- verify(resizeTransitionHandler, never()).startTransition(any(), any())
+ verify(resizeTransitionHandler, never()).startTransition(any(), any(), any())
}
@Test
@@ -209,7 +209,7 @@
SCREEN_ORIENTATION_LANDSCAPE,
)
- verify(resizeTransitionHandler, never()).startTransition(any(), any())
+ verify(resizeTransitionHandler, never()).startTransition(any(), any(), any())
}
@Test
@@ -225,7 +225,7 @@
handler.handleActivityOrientationChange(task, newTask)
- verify(resizeTransitionHandler, never()).startTransition(any(), any())
+ verify(resizeTransitionHandler, never()).startTransition(any(), any(), any())
}
@Test
@@ -240,7 +240,7 @@
SCREEN_ORIENTATION_LANDSCAPE,
)
- verify(resizeTransitionHandler, never()).startTransition(any(), any())
+ verify(resizeTransitionHandler, never()).startTransition(any(), any(), any())
}
@Test
@@ -318,7 +318,7 @@
val arg: ArgumentCaptor<WindowContainerTransaction> =
ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
verify(resizeTransitionHandler, atLeastOnce())
- .startTransition(capture(arg), eq(currentBounds))
+ .startTransition(capture(arg), eq(currentBounds), isNull())
return arg.value
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandlerTest.kt
index 0d1c5722..3e6f688 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMinimizationTransitionHandlerTest.kt
@@ -33,6 +33,7 @@
import com.android.wm.shell.TestRunningTaskInfoBuilder
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.transition.Transitions
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
@@ -154,6 +155,24 @@
assertTrue("Should animate going to back freeform task close transition", animates)
}
+ @Test
+ fun startAnimation_minimizeTransitionToBackFreeformTask_returnsTrue() {
+ val animates =
+ handler.startAnimation(
+ transition = mock(),
+ info =
+ createTransitionInfo(
+ type = Transitions.TRANSIT_MINIMIZE,
+ task = createTask(WINDOWING_MODE_FREEFORM),
+ ),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ finishCallback = {},
+ )
+
+ assertTrue("Should animate going to back freeform task minimize transition", animates)
+ }
+
private fun createTransitionInfo(
type: Int = WindowManager.TRANSIT_TO_BACK,
changeMode: Int = WindowManager.TRANSIT_TO_BACK,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeMoveToDisplayTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeMoveToDisplayTransitionHandlerTest.kt
new file mode 100644
index 0000000..fbc9406
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeMoveToDisplayTransitionHandlerTest.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.wm.shell.desktopmode
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.WindowManager
+import android.window.TransitionInfo
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.util.StubTransaction
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+
+@SmallTest
+@RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+class DesktopModeMoveToDisplayTransitionHandlerTest : ShellTestCase() {
+ private lateinit var handler: DesktopModeMoveToDisplayTransitionHandler
+
+ @Before
+ fun setUp() {
+ handler = DesktopModeMoveToDisplayTransitionHandler(StubTransaction())
+ }
+
+ @Test
+ fun handleRequest_returnsNull() {
+ assertNull(handler.handleRequest(mock(), mock()))
+ }
+
+ @Test
+ fun startAnimation_changeWithinDisplay_returnsFalse() {
+ val animates =
+ handler.startAnimation(
+ transition = mock(),
+ info =
+ TransitionInfo(WindowManager.TRANSIT_CHANGE, /* flags= */ 0).apply {
+ addChange(
+ TransitionInfo.Change(mock(), mock()).apply { setDisplayId(1, 1) }
+ )
+ },
+ startTransaction = StubTransaction(),
+ finishTransaction = StubTransaction(),
+ finishCallback = mock(),
+ )
+
+ assertFalse("Should not animate open transition", animates)
+ }
+
+ @Test
+ fun startAnimation_changeMoveToDisplay_returnsTrue() {
+ val animates =
+ handler.startAnimation(
+ transition = mock(),
+ info =
+ TransitionInfo(WindowManager.TRANSIT_CHANGE, /* flags= */ 0).apply {
+ addChange(
+ TransitionInfo.Change(mock(), mock()).apply { setDisplayId(1, 2) }
+ )
+ },
+ startTransaction = StubTransaction(),
+ finishTransaction = StubTransaction(),
+ finishCallback = mock(),
+ )
+
+ assertTrue("Should animate display change transition", animates)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
index dcc9e24..fe1dc29 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
@@ -20,6 +20,7 @@
import android.app.ActivityManager.RunningTaskInfo
import android.graphics.PointF
import android.graphics.Rect
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
@@ -28,6 +29,7 @@
import androidx.test.filters.SmallTest
import com.android.internal.policy.SystemBarUtils
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
+import com.android.window.flags.Flags.FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX
import com.android.wm.shell.R
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTestCase
@@ -45,6 +47,8 @@
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.kotlin.any
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
/**
@@ -345,6 +349,38 @@
assertThat(visualIndicator.indicatorBounds).isEqualTo(dropTargetBounds)
}
+ @Test
+ @DisableFlags(FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX)
+ fun createIndicator_inTransitionFlagDisabled_isAttachedToDisplayArea() {
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)
+
+ verify(taskDisplayAreaOrganizer).attachToDisplayArea(anyInt(), any())
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX)
+ fun createIndicator_fromFreeform_inTransitionFlagEnabled_isAttachedToDisplayArea() {
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FREEFORM)
+
+ verify(taskDisplayAreaOrganizer).attachToDisplayArea(anyInt(), any())
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX)
+ fun createIndicator_fromFullscreen_inTransitionFlagEnabled_notAttachedToDisplayArea() {
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)
+
+ verify(taskDisplayAreaOrganizer, never()).attachToDisplayArea(anyInt(), any())
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX)
+ fun createIndicator_fromSplit_inTransitionFlagEnabled_notAttachedToDisplayArea() {
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_SPLIT)
+
+ verify(taskDisplayAreaOrganizer, never()).attachToDisplayArea(anyInt(), any())
+ }
+
private fun createVisualIndicator(dragStartState: DesktopModeVisualIndicator.DragStartState) {
visualIndicator =
DesktopModeVisualIndicator(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
index a43b4dd..2e74d43 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
@@ -26,7 +26,6 @@
import androidx.test.filters.SmallTest
import com.android.window.flags.Flags
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE
-import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP
import com.android.window.flags.Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestShellExecutor
@@ -1242,39 +1241,6 @@
}
@Test
- @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
- fun setPipShouldKeepDesktopActive_shouldKeepDesktopActive() {
- assertThat(repo.shouldDesktopBeActiveForPip(DEFAULT_DESKTOP_ID)).isFalse()
-
- repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = true)
- repo.setPipShouldKeepDesktopActive(DEFAULT_DESKTOP_ID, keepActive = true)
-
- assertThat(repo.shouldDesktopBeActiveForPip(DEFAULT_DESKTOP_ID)).isTrue()
- }
-
- @Test
- @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
- fun setPipShouldNotKeepDesktopActive_shouldNotKeepDesktopActive() {
- repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = true)
- assertThat(repo.shouldDesktopBeActiveForPip(DEFAULT_DESKTOP_ID)).isTrue()
-
- repo.setPipShouldKeepDesktopActive(DEFAULT_DESKTOP_ID, keepActive = false)
-
- assertThat(repo.shouldDesktopBeActiveForPip(DEFAULT_DESKTOP_ID)).isFalse()
- }
-
- @Test
- @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
- fun removeTaskInPip_shouldNotKeepDesktopActive() {
- repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = true)
- assertThat(repo.shouldDesktopBeActiveForPip(DEFAULT_DESKTOP_ID)).isTrue()
-
- repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = false)
-
- assertThat(repo.shouldDesktopBeActiveForPip(DEFAULT_DESKTOP_ID)).isFalse()
- }
-
- @Test
@DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun addTask_deskDoesNotExists_createsDesk() {
repo.addTask(displayId = 999, taskId = 6, isVisible = true)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index b0785df..8f499c9 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
@@ -87,7 +87,6 @@
import com.android.internal.jank.InteractionJankMonitor
import com.android.window.flags.Flags
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
-import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP
import com.android.window.flags.Flags.FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS
import com.android.window.flags.Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP
import com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT
@@ -264,6 +263,8 @@
@Mock private lateinit var packageManager: PackageManager
@Mock private lateinit var mockDisplayContext: Context
@Mock private lateinit var dragToDisplayTransitionHandler: DragToDisplayTransitionHandler
+ @Mock
+ private lateinit var moveToDisplayTransitionHandler: DesktopModeMoveToDisplayTransitionHandler
private lateinit var controller: DesktopTasksController
private lateinit var shellInit: ShellInit
@@ -446,6 +447,7 @@
userProfileContexts,
desktopModeCompatPolicy,
dragToDisplayTransitionHandler,
+ moveToDisplayTransitionHandler,
)
@After
@@ -656,38 +658,6 @@
}
@Test
- @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
- fun isDesktopModeShowing_minimizedPipTask_wallpaperVisible_returnsTrue() {
- val pipTask = setUpPipTask(autoEnterEnabled = true)
- whenever(desktopWallpaperActivityTokenProvider.isWallpaperActivityVisible())
- .thenReturn(true)
-
- taskRepository.setTaskInPip(DEFAULT_DISPLAY, pipTask.taskId, enterPip = true)
-
- assertThat(controller.isDesktopModeShowing(displayId = DEFAULT_DISPLAY)).isTrue()
- }
-
- @Test
- @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
- fun isDesktopModeShowing_minimizedPipTask_wallpaperNotVisible_returnsFalse() {
- val pipTask = setUpPipTask(autoEnterEnabled = true)
- whenever(desktopWallpaperActivityTokenProvider.isWallpaperActivityVisible())
- .thenReturn(false)
-
- taskRepository.setTaskInPip(DEFAULT_DISPLAY, pipTask.taskId, enterPip = true)
-
- assertThat(controller.isDesktopModeShowing(displayId = DEFAULT_DISPLAY)).isFalse()
- }
-
- @Test
- @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
- fun isDesktopModeShowing_pipTaskNotMinimizedNorVisible_returnsFalse() {
- setUpPipTask(autoEnterEnabled = true)
-
- assertThat(controller.isDesktopModeShowing(displayId = DEFAULT_DISPLAY)).isFalse()
- }
-
- @Test
@EnableFlags(
Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
@@ -1114,44 +1084,44 @@
}
@Test
- fun addMoveToDesktopChanges_gravityLeft_noBoundsApplied() {
+ fun addMoveToDeskTaskChanges_gravityLeft_noBoundsApplied() {
setUpLandscapeDisplay()
val task = setUpFullscreenTask(gravity = Gravity.LEFT)
val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
val finalBounds = findBoundsChange(wct, task)
assertThat(finalBounds).isEqualTo(Rect())
}
@Test
- fun addMoveToDesktopChanges_gravityRight_noBoundsApplied() {
+ fun addMoveToDeskTaskChanges_gravityRight_noBoundsApplied() {
setUpLandscapeDisplay()
val task = setUpFullscreenTask(gravity = Gravity.RIGHT)
val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
val finalBounds = findBoundsChange(wct, task)
assertThat(finalBounds).isEqualTo(Rect())
}
@Test
- fun addMoveToDesktopChanges_gravityTop_noBoundsApplied() {
+ fun addMoveToDeskTaskChanges_gravityTop_noBoundsApplied() {
setUpLandscapeDisplay()
val task = setUpFullscreenTask(gravity = Gravity.TOP)
val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
val finalBounds = findBoundsChange(wct, task)
assertThat(finalBounds).isEqualTo(Rect())
}
@Test
- fun addMoveToDesktopChanges_gravityBottom_noBoundsApplied() {
+ fun addMoveToDeskTaskChanges_gravityBottom_noBoundsApplied() {
setUpLandscapeDisplay()
val task = setUpFullscreenTask(gravity = Gravity.BOTTOM)
val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
val finalBounds = findBoundsChange(wct, task)
assertThat(finalBounds).isEqualTo(Rect())
@@ -1192,7 +1162,7 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
- fun addMoveToDesktopChanges_positionBottomRight() {
+ fun addMoveToDeskTaskChanges_positionBottomRight() {
setUpLandscapeDisplay()
val stableBounds = Rect()
displayLayout.getStableBoundsForDesktopMode(stableBounds)
@@ -1201,7 +1171,7 @@
val task = setUpFullscreenTask()
val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
val finalBounds = findBoundsChange(wct, task)
assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
@@ -1210,7 +1180,7 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
- fun addMoveToDesktopChanges_positionTopLeft() {
+ fun addMoveToDeskTaskChanges_positionTopLeft() {
setUpLandscapeDisplay()
val stableBounds = Rect()
displayLayout.getStableBoundsForDesktopMode(stableBounds)
@@ -1219,7 +1189,7 @@
val task = setUpFullscreenTask()
val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
val finalBounds = findBoundsChange(wct, task)
assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
@@ -1228,7 +1198,7 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
- fun addMoveToDesktopChanges_positionBottomLeft() {
+ fun addMoveToDeskTaskChanges_positionBottomLeft() {
setUpLandscapeDisplay()
val stableBounds = Rect()
displayLayout.getStableBoundsForDesktopMode(stableBounds)
@@ -1237,7 +1207,7 @@
val task = setUpFullscreenTask()
val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
val finalBounds = findBoundsChange(wct, task)
assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
@@ -1246,7 +1216,7 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
- fun addMoveToDesktopChanges_positionTopRight() {
+ fun addMoveToDeskTaskChanges_positionTopRight() {
setUpLandscapeDisplay()
val stableBounds = Rect()
displayLayout.getStableBoundsForDesktopMode(stableBounds)
@@ -1255,7 +1225,7 @@
val task = setUpFullscreenTask()
val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
val finalBounds = findBoundsChange(wct, task)
assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
@@ -1264,7 +1234,7 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
- fun addMoveToDesktopChanges_positionResetsToCenter() {
+ fun addMoveToDeskTaskChanges_positionResetsToCenter() {
setUpLandscapeDisplay()
val stableBounds = Rect()
displayLayout.getStableBoundsForDesktopMode(stableBounds)
@@ -1273,7 +1243,7 @@
val task = setUpFullscreenTask()
val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
val finalBounds = findBoundsChange(wct, task)
assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
@@ -1282,7 +1252,7 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
- fun addMoveToDesktopChanges_lastWindowSnapLeft_positionResetsToCenter() {
+ fun addMoveToDeskTaskChanges_lastWindowSnapLeft_positionResetsToCenter() {
setUpLandscapeDisplay()
val stableBounds = Rect()
displayLayout.getStableBoundsForDesktopMode(stableBounds)
@@ -1294,7 +1264,7 @@
val task = setUpFullscreenTask()
val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
val finalBounds = findBoundsChange(wct, task)
assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
@@ -1303,7 +1273,7 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
- fun addMoveToDesktopChanges_lastWindowSnapRight_positionResetsToCenter() {
+ fun addMoveToDeskTaskChanges_lastWindowSnapRight_positionResetsToCenter() {
setUpLandscapeDisplay()
val stableBounds = Rect()
displayLayout.getStableBoundsForDesktopMode(stableBounds)
@@ -1321,7 +1291,7 @@
val task = setUpFullscreenTask()
val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
val finalBounds = findBoundsChange(wct, task)
assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
@@ -1330,7 +1300,7 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
- fun addMoveToDesktopChanges_lastWindowMaximised_positionResetsToCenter() {
+ fun addMoveToDeskTaskChanges_lastWindowMaximised_positionResetsToCenter() {
setUpLandscapeDisplay()
val stableBounds = Rect()
displayLayout.getStableBoundsForDesktopMode(stableBounds)
@@ -1340,7 +1310,7 @@
val task = setUpFullscreenTask()
val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
val finalBounds = findBoundsChange(wct, task)
assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
@@ -1349,7 +1319,7 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
- fun addMoveToDesktopChanges_defaultToCenterIfFree() {
+ fun addMoveToDeskTaskChanges_defaultToCenterIfFree() {
setUpLandscapeDisplay()
val stableBounds = Rect()
displayLayout.getStableBoundsForDesktopMode(stableBounds)
@@ -1367,7 +1337,7 @@
val task = setUpFullscreenTask()
val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
val finalBounds = findBoundsChange(wct, task)
assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
@@ -1375,7 +1345,7 @@
}
@Test
- fun addMoveToDesktopChanges_excludeCaptionFromAppBounds_nonResizableLandscape() {
+ fun addMoveToDeskTaskChanges_excludeCaptionFromAppBounds_nonResizableLandscape() {
setUpLandscapeDisplay()
val task =
setUpFullscreenTask(
@@ -1385,7 +1355,7 @@
whenever(desktopModeCompatPolicy.shouldExcludeCaptionFromAppBounds(task)).thenReturn(true)
val initialAspectRatio = calculateAspectRatio(task)
val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
val finalBounds = findBoundsChange(wct, task)
val captionInsets = getAppHeaderHeight(context)
@@ -1397,7 +1367,7 @@
}
@Test
- fun addMoveToDesktopChanges_excludeCaptionFromAppBounds_nonResizablePortrait() {
+ fun addMoveToDeskTaskChanges_excludeCaptionFromAppBounds_nonResizablePortrait() {
setUpLandscapeDisplay()
val task =
setUpFullscreenTask(
@@ -1407,7 +1377,7 @@
whenever(desktopModeCompatPolicy.shouldExcludeCaptionFromAppBounds(task)).thenReturn(true)
val initialAspectRatio = calculateAspectRatio(task)
val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
val finalBounds = findBoundsChange(wct, task)
val captionInsets = getAppHeaderHeight(context)
@@ -1445,29 +1415,29 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun addMoveToDesktopChanges_landscapeDevice_userFullscreenOverride_defaultPortraitBounds() {
+ fun addMoveToDeskTaskChanges_landscapeDevice_userFullscreenOverride_defaultPortraitBounds() {
setUpLandscapeDisplay()
val task = setUpFullscreenTask(enableUserFullscreenOverride = true)
val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun addMoveToDesktopChanges_landscapeDevice_systemFullscreenOverride_defaultPortraitBounds() {
+ fun addMoveToDeskTaskChanges_landscapeDevice_systemFullscreenOverride_defaultPortraitBounds() {
setUpLandscapeDisplay()
val task = setUpFullscreenTask(enableSystemFullscreenOverride = true)
val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun addMoveToDesktopChanges_landscapeDevice_portraitResizableApp_aspectRatioOverridden() {
+ fun addMoveToDeskTaskChanges_landscapeDevice_portraitResizableApp_aspectRatioOverridden() {
setUpLandscapeDisplay()
val task =
setUpFullscreenTask(
@@ -1476,36 +1446,36 @@
aspectRatioOverrideApplied = true,
)
val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_PORTRAIT_BOUNDS)
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun addMoveToDesktopChanges_portraitDevice_userFullscreenOverride_defaultPortraitBounds() {
+ fun addMoveToDeskTaskChanges_portraitDevice_userFullscreenOverride_defaultPortraitBounds() {
setUpPortraitDisplay()
val task = setUpFullscreenTask(enableUserFullscreenOverride = true)
val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun addMoveToDesktopChanges_portraitDevice_systemFullscreenOverride_defaultPortraitBounds() {
+ fun addMoveToDeskTaskChanges_portraitDevice_systemFullscreenOverride_defaultPortraitBounds() {
setUpPortraitDisplay()
val task = setUpFullscreenTask(enableSystemFullscreenOverride = true)
val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun addMoveToDesktopChanges_portraitDevice_landscapeResizableApp_aspectRatioOverridden() {
+ fun addMoveToDeskTaskChanges_portraitDevice_landscapeResizableApp_aspectRatioOverridden() {
setUpPortraitDisplay()
val task =
setUpFullscreenTask(
@@ -1515,7 +1485,7 @@
aspectRatioOverrideApplied = true,
)
val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
+ controller.addMoveToDeskTaskChanges(wct, task, deskId = 0)
assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_LANDSCAPE_BOUNDS)
}
@@ -2554,7 +2524,7 @@
whenever(rootTaskDisplayAreaOrganizer.displayIds).thenReturn(intArrayOf(DEFAULT_DISPLAY))
val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
controller.moveToNextDisplay(task.taskId)
- verifyWCTNotExecuted()
+ verify(transitions, never()).startTransition(anyInt(), any(), anyOrNull())
}
@Test
@@ -2572,9 +2542,12 @@
controller.moveToNextDisplay(task.taskId)
val taskChange =
- getLatestWct(type = TRANSIT_CHANGE).hierarchyOps.find {
- it.container == task.token.asBinder() && it.isReparent
- }
+ getLatestWct(
+ type = TRANSIT_CHANGE,
+ handlerClass = DesktopModeMoveToDisplayTransitionHandler::class.java,
+ )
+ .hierarchyOps
+ .find { it.container == task.token.asBinder() && it.isReparent }
assertNotNull(taskChange)
assertThat(taskChange.newParent).isEqualTo(secondDisplayArea.token.asBinder())
assertThat(taskChange.toTop).isTrue()
@@ -2595,9 +2568,12 @@
controller.moveToNextDisplay(task.taskId)
val taskChange =
- getLatestWct(type = TRANSIT_CHANGE).hierarchyOps.find {
- it.container == task.token.asBinder() && it.isReparent
- }
+ getLatestWct(
+ type = TRANSIT_CHANGE,
+ handlerClass = DesktopModeMoveToDisplayTransitionHandler::class.java,
+ )
+ .hierarchyOps
+ .find { it.container == task.token.asBinder() && it.isReparent }
assertNotNull(taskChange)
assertThat(taskChange.newParent).isEqualTo(defaultDisplayArea.token.asBinder())
assertThat(taskChange.toTop).isTrue()
@@ -2622,7 +2598,12 @@
controller.moveToNextDisplay(task.taskId)
- with(getLatestWct(type = TRANSIT_CHANGE)) {
+ with(
+ getLatestWct(
+ type = TRANSIT_CHANGE,
+ handlerClass = DesktopModeMoveToDisplayTransitionHandler::class.java,
+ )
+ ) {
val wallpaperChange =
hierarchyOps.find { op -> op.container == wallpaperToken.asBinder() }
assertNotNull(wallpaperChange)
@@ -2648,9 +2629,12 @@
controller.moveToNextDisplay(task.taskId)
val wallpaperChange =
- getLatestWct(type = TRANSIT_CHANGE).hierarchyOps.find { op ->
- op.container == wallpaperToken.asBinder()
- }
+ getLatestWct(
+ type = TRANSIT_CHANGE,
+ handlerClass = DesktopModeMoveToDisplayTransitionHandler::class.java,
+ )
+ .hierarchyOps
+ .find { op -> op.container == wallpaperToken.asBinder() }
assertNotNull(wallpaperChange)
assertThat(wallpaperChange.type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK)
}
@@ -2682,7 +2666,12 @@
controller.moveToNextDisplay(task.taskId)
- val taskChange = getLatestWct(type = TRANSIT_CHANGE).changes[task.token.asBinder()]
+ val taskChange =
+ getLatestWct(
+ type = TRANSIT_CHANGE,
+ handlerClass = DesktopModeMoveToDisplayTransitionHandler::class.java,
+ )
+ .changes[task.token.asBinder()]
assertNotNull(taskChange)
// To preserve DP size, pixel size is changed to 320x240. The ratio of the left margin
// to the right margin and the ratio of the top margin to bottom margin are also
@@ -2719,7 +2708,12 @@
controller.moveToNextDisplay(task.taskId)
- val taskChange = getLatestWct(type = TRANSIT_CHANGE).changes[task.token.asBinder()]
+ val taskChange =
+ getLatestWct(
+ type = TRANSIT_CHANGE,
+ handlerClass = DesktopModeMoveToDisplayTransitionHandler::class.java,
+ )
+ .changes[task.token.asBinder()]
assertNotNull(taskChange)
assertThat(taskChange.configuration.windowConfiguration.bounds)
.isEqualTo(Rect(960, 480, 1280, 720))
@@ -2750,7 +2744,12 @@
controller.moveToNextDisplay(task.taskId)
- val taskChange = getLatestWct(type = TRANSIT_CHANGE).changes[task.token.asBinder()]
+ val taskChange =
+ getLatestWct(
+ type = TRANSIT_CHANGE,
+ handlerClass = DesktopModeMoveToDisplayTransitionHandler::class.java,
+ )
+ .changes[task.token.asBinder()]
assertNotNull(taskChange)
// DP size is preserved. The window is centered in the destination display.
assertThat(taskChange.configuration.windowConfiguration.bounds)
@@ -2788,7 +2787,12 @@
controller.moveToNextDisplay(task.taskId)
- val taskChange = getLatestWct(type = TRANSIT_CHANGE).changes[task.token.asBinder()]
+ val taskChange =
+ getLatestWct(
+ type = TRANSIT_CHANGE,
+ handlerClass = DesktopModeMoveToDisplayTransitionHandler::class.java,
+ )
+ .changes[task.token.asBinder()]
assertNotNull(taskChange)
assertThat(taskChange.configuration.windowConfiguration.bounds.left).isAtLeast(0)
assertThat(taskChange.configuration.windowConfiguration.bounds.top).isAtLeast(0)
@@ -2815,16 +2819,20 @@
controller.moveToNextDisplay(task.taskId)
val taskChange =
- getLatestWct(type = TRANSIT_CHANGE).hierarchyOps.find {
- it.container == task.token.asBinder() && it.type == HIERARCHY_OP_TYPE_REORDER
- }
+ getLatestWct(
+ type = TRANSIT_CHANGE,
+ handlerClass = DesktopModeMoveToDisplayTransitionHandler::class.java,
+ )
+ .hierarchyOps
+ .find {
+ it.container == task.token.asBinder() && it.type == HIERARCHY_OP_TYPE_REORDER
+ }
assertNotNull(taskChange)
assertThat(taskChange.toTop).isTrue()
assertThat(taskChange.includingParents()).isTrue()
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun moveToNextDisplay_toDesktopInOtherDisplay_bringsExistingTasksToFront() {
val transition = Binder()
val sourceDeskId = 0
@@ -2856,7 +2864,6 @@
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))
@@ -2920,11 +2927,10 @@
verify(desksOrganizer).activateDesk(any(), eq(targetDeskId))
verify(desksTransitionsObserver)
.addPendingTransition(
- DeskTransition.ActiveDeskWithTask(
+ DeskTransition.ActivateDesk(
token = transition,
displayId = SECOND_DISPLAY,
deskId = targetDeskId,
- enterTaskId = task.taskId,
)
)
}
@@ -3105,42 +3111,6 @@
}
@Test
- @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
- fun onDesktopWindowClose_minimizedPipPresent_doesNotExitDesktop() {
- val freeformTask = setUpFreeformTask().apply { isFocused = true }
- val pipTask = setUpPipTask(autoEnterEnabled = true)
-
- taskRepository.setTaskInPip(DEFAULT_DISPLAY, pipTask.taskId, enterPip = true)
- val wct = WindowContainerTransaction()
- controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, freeformTask)
-
- verifyExitDesktopWCTNotExecuted()
- }
-
- @Test
- @EnableFlags(
- FLAG_ENABLE_DESKTOP_WINDOWING_PIP,
- Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
- )
- fun onDesktopWindowClose_minimizedPipNotPresent_exitDesktop() {
- val freeformTask = setUpFreeformTask()
- val pipTask = setUpPipTask(autoEnterEnabled = true)
- val handler = mock(TransitionHandler::class.java)
- whenever(transitions.dispatchRequest(any(), any(), anyOrNull()))
- .thenReturn(android.util.Pair(handler, WindowContainerTransaction()))
-
- controller.minimizeTask(pipTask, MinimizeReason.MINIMIZE_BUTTON)
- verifyExitDesktopWCTNotExecuted()
-
- taskRepository.setTaskInPip(DEFAULT_DISPLAY, pipTask.taskId, enterPip = false)
- val wct = WindowContainerTransaction()
- controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, freeformTask)
-
- // Moves wallpaper activity to back when leaving desktop
- wct.assertReorder(wallpaperToken, toTop = false)
- }
-
- @Test
@EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun onDesktopWindowClose_lastWindow_deactivatesDesk() {
val task = setUpFreeformTask()
@@ -3269,26 +3239,6 @@
}
@Test
- fun onPipTaskMinimize_doesntRemoveWallpaper() {
- val task = setUpPipTask(autoEnterEnabled = true)
- val handler = mock(TransitionHandler::class.java)
- whenever(transitions.dispatchRequest(any(), any(), anyOrNull()))
- .thenReturn(android.util.Pair(handler, WindowContainerTransaction()))
-
- controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON)
-
- val captor = argumentCaptor<WindowContainerTransaction>()
- verify(freeformTaskTransitionStarter).startPipTransition(captor.capture())
- assertThat(
- captor.firstValue.hierarchyOps.none { hop ->
- hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK &&
- hop.container == wallpaperToken.asBinder()
- }
- )
- .isTrue()
- }
-
- @Test
fun onDesktopWindowMinimize_singleActiveTask_noWallpaperActivityToken_doesntRemoveWallpaper() {
val task = setUpFreeformTask(active = true)
val transition = Binder()
@@ -3506,6 +3456,39 @@
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_fullscreenTask_switchToDesktop_movesTaskToDesk() {
+ taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = 5)
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 5)
+ taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 5)
+
+ val fullscreenTask = createFullscreenTask()
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+ assertNotNull(wct, "should handle request")
+ verify(desksOrganizer).moveTaskToDesk(wct = wct, deskId = 5, task = fullscreenTask)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_fullscreenTaskThatWasInactiveInDesk_tracksDeskDeactivation() {
+ // Set up and existing desktop task in an active desk.
+ val inactiveInDeskTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0)
+ taskRepository.setDeskInactive(deskId = 0)
+
+ // Now the task is launching as fullscreen.
+ inactiveInDeskTask.configuration.windowConfiguration.windowingMode =
+ WINDOWING_MODE_FULLSCREEN
+ val transition = Binder()
+ val wct = controller.handleRequest(transition, createTransition(inactiveInDeskTask))
+
+ // Desk is deactivated.
+ assertNotNull(wct, "should handle request")
+ verify(desksTransitionsObserver)
+ .addPendingTransition(DeskTransition.DeactivateDesk(transition, deskId = 0))
+ }
+
+ @Test
fun handleRequest_fullscreenTask_freeformVisible_returnSwitchToFreeformWCT() {
val homeTask = setUpHomeTask()
val freeformTask = setUpFreeformTask()
@@ -3583,9 +3566,15 @@
val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
// Make sure we reorder the new task to top, and the back task to the bottom
- assertThat(wct!!.hierarchyOps.size).isEqualTo(9)
+ assertThat(wct!!.hierarchyOps.size).isEqualTo(8)
wct.assertReorderAt(0, fullscreenTask, toTop = true)
- wct.assertReorderAt(8, freeformTasks[0], toTop = false)
+ // Oldest task that needs to minimized is never reordered to top over Home.
+ val taskToMinimize = freeformTasks[0]
+ wct.assertWithoutHop { hop ->
+ hop.container == taskToMinimize.token &&
+ hop.type == HIERARCHY_OP_TYPE_REORDER &&
+ hop.toTop == true
+ }
}
@Test
@@ -3600,12 +3589,18 @@
val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
- assertThat(wct!!.hierarchyOps.size).isEqualTo(10)
+ assertThat(wct!!.hierarchyOps.size).isEqualTo(9)
wct.assertReorderAt(0, fullscreenTask, toTop = true)
// Make sure we reorder the home task to the top, desktop tasks to top of them and minimized
// task is under the home task.
wct.assertReorderAt(1, homeTask, toTop = true)
- wct.assertReorderAt(9, freeformTasks[0], toTop = false)
+ // Oldest task that needs to minimized is never reordered to top over Home.
+ val taskToMinimize = freeformTasks[0]
+ wct.assertWithoutHop { hop ->
+ hop.container == taskToMinimize.token &&
+ hop.type == HIERARCHY_OP_TYPE_REORDER &&
+ hop.toTop == true
+ }
}
@Test
@@ -3681,6 +3676,20 @@
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_freeformTaskFromInactiveDesk_tracksDeskDeactivation() {
+ val deskId = 0
+ val freeformTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ taskRepository.setDeskInactive(deskId = deskId)
+
+ val transition = Binder()
+ controller.handleRequest(transition, createTransition(freeformTask))
+
+ verify(desksTransitionsObserver)
+ .addPendingTransition(DeskTransition.DeactivateDesk(transition, deskId))
+ }
+
+ @Test
fun handleRequest_freeformTask_relaunchActiveTask_taskBecomesUndefined() {
val freeformTask = setUpFreeformTask()
markTaskHidden(freeformTask)
@@ -3928,6 +3937,24 @@
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun handleRequest_recentsAnimationRunning_relaunchActiveTask_tracksDeskDeactivation() {
+ // Set up a visible freeform task
+ val freeformTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0)
+ markTaskVisible(freeformTask)
+
+ // Mark recents animation running
+ recentsTransitionStateListener.onTransitionStateChanged(TRANSITION_STATE_ANIMATING)
+
+ val transition = Binder()
+ controller.handleRequest(transition, createTransition(freeformTask))
+
+ desksTransitionsObserver.addPendingTransition(
+ DeskTransition.DeactivateDesk(transition, deskId = 0)
+ )
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
fun handleRequest_topActivityTransparentWithoutDisplay_returnSwitchToFreeformWCT() {
val freeformTask = setUpFreeformTask()
@@ -4045,6 +4072,31 @@
}
@Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+ )
+ fun handleRequest_systemUIActivityWithDisplayInFreeformTask_inDesktop_tracksDeskDeactivation() {
+ val deskId = 5
+ taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = deskId)
+ val systemUIPackageName =
+ context.resources.getString(com.android.internal.R.string.config_systemUi)
+ val baseComponent = ComponentName(systemUIPackageName, /* cls= */ "")
+ val task =
+ setUpFreeformTask(displayId = DEFAULT_DISPLAY).apply {
+ baseActivity = baseComponent
+ isTopActivityNoDisplay = false
+ }
+
+ val transition = Binder()
+ controller.handleRequest(transition, createTransition(task))
+
+ verify(desksTransitionsObserver)
+ .addPendingTransition(DeskTransition.DeactivateDesk(transition, deskId))
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
fun handleRequest_systemUIActivityWithoutDisplay_returnSwitchToFreeformWCT() {
val freeformTask = setUpFreeformTask()
@@ -4647,32 +4699,6 @@
}
@Test
- @EnableFlags(
- FLAG_ENABLE_DESKTOP_WINDOWING_PIP,
- Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
- )
- fun moveFocusedTaskToFullscreen_minimizedPipPresent_removeWallpaperActivity() {
- val freeformTask = setUpFreeformTask()
- val pipTask = setUpPipTask(autoEnterEnabled = true)
- val handler = mock(TransitionHandler::class.java)
- whenever(transitions.dispatchRequest(any(), any(), anyOrNull()))
- .thenReturn(android.util.Pair(handler, WindowContainerTransaction()))
-
- controller.minimizeTask(pipTask, MinimizeReason.MINIMIZE_BUTTON)
- verifyExitDesktopWCTNotExecuted()
-
- freeformTask.isFocused = true
- controller.enterFullscreen(DEFAULT_DISPLAY, transitionSource = UNKNOWN)
-
- val wct = getLatestExitDesktopWct()
- val taskChange = assertNotNull(wct.changes[freeformTask.token.asBinder()])
- assertThat(taskChange.windowingMode)
- .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
- // Moves wallpaper activity to back when leaving desktop
- wct.assertReorder(wallpaperToken, toTop = false)
- }
-
- @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
@DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun removeDesk_multipleTasks_removesAll() {
@@ -5280,7 +5306,8 @@
)
// Assert that task is NOT updated via WCT
- verify(toggleResizeDesktopTaskTransitionHandler, never()).startTransition(any(), any())
+ verify(toggleResizeDesktopTaskTransitionHandler, never())
+ .startTransition(any(), any(), any())
// Assert that task leash is updated via Surface Animations
verify(mReturnToDragStartAnimator)
.start(
@@ -5765,7 +5792,8 @@
InputMethod.TOUCH,
)
// Assert that task is NOT updated via WCT
- verify(toggleResizeDesktopTaskTransitionHandler, never()).startTransition(any(), any())
+ verify(toggleResizeDesktopTaskTransitionHandler, never())
+ .startTransition(any(), any(), any())
// Assert that task leash is updated via Surface Animations
verify(mReturnToDragStartAnimator)
@@ -5862,7 +5890,8 @@
)
// Assert that task is NOT updated via WCT
- verify(toggleResizeDesktopTaskTransitionHandler, never()).startTransition(any(), any())
+ verify(toggleResizeDesktopTaskTransitionHandler, never())
+ .startTransition(any(), any(), any())
verify(mockToast).show()
}
@@ -6789,8 +6818,7 @@
}
private fun setUpPipTask(autoEnterEnabled: Boolean): RunningTaskInfo =
- // active = false marks the task as non-visible; PiP window doesn't count as visible tasks
- setUpFreeformTask(active = false).apply {
+ setUpFreeformTask().apply {
pictureInPictureParams =
PictureInPictureParams.Builder().setAutoEnterEnabled(autoEnterEnabled).build()
}
@@ -6931,7 +6959,7 @@
): WindowContainerTransaction {
val arg = argumentCaptor<WindowContainerTransaction>()
verify(toggleResizeDesktopTaskTransitionHandler, atLeastOnce())
- .startTransition(arg.capture(), eq(currentBounds))
+ .startTransition(arg.capture(), eq(currentBounds), isNull())
return arg.lastValue
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
index 5b0f94f..fd8842b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
@@ -340,51 +340,6 @@
}
@Test
- fun transitOpenWallpaper_wallpaperActivityVisibilitySaved() {
- val wallpaperTask = createWallpaperTaskInfo()
-
- transitionObserver.onTransitionReady(
- transition = mock(),
- info = createOpenChangeTransition(wallpaperTask),
- startTransaction = mock(),
- finishTransaction = mock(),
- )
-
- verify(desktopWallpaperActivityTokenProvider)
- .setWallpaperActivityIsVisible(isVisible = true, wallpaperTask.displayId)
- }
-
- @Test
- fun transitToFrontWallpaper_wallpaperActivityVisibilitySaved() {
- val wallpaperTask = createWallpaperTaskInfo()
-
- transitionObserver.onTransitionReady(
- transition = mock(),
- info = createToFrontTransition(wallpaperTask),
- startTransaction = mock(),
- finishTransaction = mock(),
- )
-
- verify(desktopWallpaperActivityTokenProvider)
- .setWallpaperActivityIsVisible(isVisible = true, wallpaperTask.displayId)
- }
-
- @Test
- fun transitToBackWallpaper_wallpaperActivityVisibilitySaved() {
- val wallpaperTask = createWallpaperTaskInfo()
-
- transitionObserver.onTransitionReady(
- transition = mock(),
- info = createToBackTransition(wallpaperTask),
- startTransaction = mock(),
- finishTransaction = mock(),
- )
-
- verify(desktopWallpaperActivityTokenProvider)
- .setWallpaperActivityIsVisible(isVisible = false, wallpaperTask.displayId)
- }
-
- @Test
fun transitCloseWallpaper_wallpaperActivityVisibilitySaved() {
val wallpaperTask = createWallpaperTaskInfo()
@@ -407,7 +362,7 @@
transitionObserver.onTransitionReady(
transition = pipTransition,
- info = createOpenChangeTransition(task, TRANSIT_PIP),
+ info = createOpenChangeTransition(task, type = TRANSIT_PIP),
startTransaction = mock(),
finishTransaction = mock(),
)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
index de55db8..9588a5c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
@@ -11,8 +11,11 @@
import android.graphics.Rect
import android.os.IBinder
import android.os.SystemProperties
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
+import android.view.Display.DEFAULT_DISPLAY
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_OPEN
import android.window.TransitionInfo
@@ -23,6 +26,7 @@
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD
import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE
import com.android.internal.jank.InteractionJankMonitor
+import com.android.window.flags.Flags
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestRunningTaskInfoBuilder
@@ -78,6 +82,7 @@
@Mock private lateinit var homeTaskLeash: SurfaceControl
@Mock private lateinit var desktopUserRepositories: DesktopUserRepositories
@Mock private lateinit var bubbleController: BubbleController
+ @Mock private lateinit var visualIndicator: DesktopModeVisualIndicator
private val transactionSupplier = Supplier { mock<SurfaceControl.Transaction>() }
@@ -740,11 +745,47 @@
assertThat(fraction).isWithin(TOLERANCE).of(0f)
}
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX)
+ fun startDrag_indicatorFlagEnabled_attachesIndicatorToTransitionRoot() {
+ val task = createTask()
+ val rootLeash = mock<SurfaceControl>()
+ val startTransaction = mock<SurfaceControl.Transaction>()
+ startDrag(
+ defaultHandler,
+ task,
+ startTransaction = startTransaction,
+ transitionRootLeash = rootLeash,
+ )
+
+ verify(visualIndicator).reparentLeash(startTransaction, rootLeash)
+ verify(visualIndicator).fadeInIndicator()
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX)
+ fun startDrag_indicatorFlagDisabled_doesNotAttachIndicatorToTransitionRoot() {
+ val task = createTask()
+ val rootLeash = mock<SurfaceControl>()
+ val startTransaction = mock<SurfaceControl.Transaction>()
+ startDrag(
+ defaultHandler,
+ task,
+ startTransaction = startTransaction,
+ transitionRootLeash = rootLeash,
+ )
+
+ verify(visualIndicator, never()).reparentLeash(any(), any())
+ verify(visualIndicator, never()).fadeInIndicator()
+ }
+
private fun startDrag(
handler: DragToDesktopTransitionHandler,
task: RunningTaskInfo = createTask(),
+ startTransaction: SurfaceControl.Transaction = mock(),
finishTransaction: SurfaceControl.Transaction = mock(),
homeChange: TransitionInfo.Change? = createHomeChange(),
+ transitionRootLeash: SurfaceControl? = null,
): IBinder {
whenever(dragAnimator.position).thenReturn(PointF())
// Simulate transition is started and is ready to animate.
@@ -756,8 +797,9 @@
type = TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP,
draggedTask = task,
homeChange = homeChange,
+ rootLeash = transitionRootLeash,
),
- startTransaction = mock(),
+ startTransaction = startTransaction,
finishTransaction = finishTransaction,
finishCallback = {},
)
@@ -778,7 +820,7 @@
)
)
.thenReturn(token)
- handler.startDragToDesktopTransition(task, dragAnimator)
+ handler.startDragToDesktopTransition(task, dragAnimator, visualIndicator)
return token
}
@@ -845,6 +887,7 @@
type: Int,
draggedTask: RunningTaskInfo,
homeChange: TransitionInfo.Change? = createHomeChange(),
+ rootLeash: SurfaceControl? = null,
) =
TransitionInfo(type, /* flags= */ 0).apply {
homeChange?.let { addChange(it) }
@@ -861,6 +904,9 @@
flags = flags or FLAG_IS_WALLPAPER
}
)
+ if (rootLeash != null) {
+ addRootLeash(DEFAULT_DISPLAY, rootLeash, /* offsetLeft= */ 0, /* offsetTop= */ 0)
+ }
}
private fun createHomeChange() =
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainerTest.kt
index 4c8cb38..c7518d5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainerTest.kt
@@ -25,6 +25,7 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.Display
+import android.view.Display.DEFAULT_DISPLAY
import android.view.SurfaceControl
import android.view.SurfaceControlViewHost
import android.view.View
@@ -49,6 +50,8 @@
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
import org.mockito.kotlin.spy
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyZeroInteractions
@@ -121,7 +124,7 @@
DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR,
)
desktopExecutor.flushAll()
- verify(spyViewContainer).fadeInIndicator(any(), any(), any(), any())
+ verify(spyViewContainer).fadeInIndicatorInternal(any(), any(), any(), any())
}
@Test
@@ -265,6 +268,35 @@
)
}
+ @Test
+ fun fadeInIndicator_callsFadeIn() {
+ val spyViewContainer = setupSpyViewContainer()
+
+ spyViewContainer.fadeInIndicator(
+ mock<DisplayLayout>(),
+ DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR,
+ DEFAULT_DISPLAY,
+ )
+ desktopExecutor.flushAll()
+
+ verify(spyViewContainer).fadeInIndicatorInternal(any(), any(), any(), any())
+ }
+
+ @Test
+ fun fadeInIndicator_alreadyReleased_doesntCallFadeIn() {
+ val spyViewContainer = setupSpyViewContainer()
+ spyViewContainer.releaseVisualIndicator()
+
+ spyViewContainer.fadeInIndicator(
+ mock<DisplayLayout>(),
+ DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR,
+ DEFAULT_DISPLAY,
+ )
+ desktopExecutor.flushAll()
+
+ verify(spyViewContainer, never()).fadeInIndicatorInternal(any(), any(), any(), any())
+ }
+
private fun setupSpyViewContainer(): VisualIndicatorViewContainer {
val viewContainer =
VisualIndicatorViewContainer(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipAlphaAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipAlphaAnimatorTest.java
index 607e6a4..14f9ffc 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipAlphaAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipAlphaAnimatorTest.java
@@ -22,6 +22,7 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
@@ -126,7 +127,7 @@
}
@Test
- public void onAnimationStart_setCornerAndShadowRadii() {
+ public void onAnimationStart_fadeInAnimator_setCornerAndShadowRadii() {
mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction,
mMockFinishTransaction, PipAlphaAnimator.FADE_IN);
mPipAlphaAnimator.setSurfaceControlTransactionFactory(mMockFactory);
@@ -143,7 +144,26 @@
}
@Test
- public void onAnimationUpdate_setCornerAndShadowRadii() {
+ public void onAnimationStart_fadeOutAnimator_setCornerNoShadowRadii() {
+ mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction,
+ mMockFinishTransaction, PipAlphaAnimator.FADE_OUT);
+ mPipAlphaAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mPipAlphaAnimator.start();
+ mPipAlphaAnimator.pause();
+ });
+
+ verify(mMockStartTransaction, atLeastOnce())
+ .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS));
+ verify(mMockStartTransaction, never())
+ .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS));
+ verify(mMockStartTransaction, atLeastOnce())
+ .setShadowRadius(eq(mTestLeash), eq(0f));
+ }
+
+ @Test
+ public void onAnimationUpdate_fadeInAnimator_setCornerAndShadowRadii() {
mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction,
mMockFinishTransaction, PipAlphaAnimator.FADE_IN);
mPipAlphaAnimator.setSurfaceControlTransactionFactory(mMockFactory);
@@ -160,7 +180,26 @@
}
@Test
- public void onAnimationEnd_setCornerAndShadowRadii() {
+ public void onAnimationUpdate_fadeOutAnimator_setCornerNoShadowRadii() {
+ mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction,
+ mMockFinishTransaction, PipAlphaAnimator.FADE_OUT);
+ mPipAlphaAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mPipAlphaAnimator.start();
+ mPipAlphaAnimator.pause();
+ });
+
+ verify(mMockAnimateTransaction, atLeastOnce())
+ .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS));
+ verify(mMockAnimateTransaction, never())
+ .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS));
+ verify(mMockAnimateTransaction, atLeastOnce())
+ .setShadowRadius(eq(mTestLeash), eq(0f));
+ }
+
+ @Test
+ public void onAnimationEnd_fadeInAnimator_setCornerAndShadowRadii() {
mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction,
mMockFinishTransaction, PipAlphaAnimator.FADE_IN);
mPipAlphaAnimator.setSurfaceControlTransactionFactory(mMockFactory);
@@ -177,6 +216,25 @@
}
@Test
+ public void onAnimationEnd_fadeOutAnimator_setCornerNoShadowRadii() {
+ mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction,
+ mMockFinishTransaction, PipAlphaAnimator.FADE_OUT);
+ mPipAlphaAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mPipAlphaAnimator.start();
+ mPipAlphaAnimator.end();
+ });
+
+ verify(mMockFinishTransaction, atLeastOnce())
+ .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS));
+ verify(mMockFinishTransaction, never())
+ .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS));
+ verify(mMockFinishTransaction, atLeastOnce())
+ .setShadowRadius(eq(mTestLeash), eq(0f));
+ }
+
+ @Test
public void onAnimationEnd_fadeInAnimator_leashVisibleAtEnd() {
mPipAlphaAnimator = new PipAlphaAnimator(mMockContext, mTestLeash, mMockStartTransaction,
mMockFinishTransaction, PipAlphaAnimator.FADE_IN);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipInteractionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipInteractionHandlerTest.java
new file mode 100644
index 0000000..9c0127e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipInteractionHandlerTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip2.phone;
+
+import static com.android.internal.jank.Cuj.CUJ_PIP_TRANSITION;
+import static com.android.wm.shell.pip2.phone.PipInteractionHandler.INTERACTION_EXIT_PIP;
+import static com.android.wm.shell.pip2.phone.PipInteractionHandler.INTERACTION_EXIT_PIP_TO_SPLIT;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.kotlin.VerificationKt.times;
+
+import android.content.Context;
+import android.os.Handler;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.SurfaceControl;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.jank.InteractionJankMonitor;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit test against {@link PipInteractionHandler}.
+ */
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
+public class PipInteractionHandlerTest {
+ @Mock private Context mMockContext;
+ @Mock private Handler mMockHandler;
+ @Mock private InteractionJankMonitor mMockInteractionJankMonitor;
+
+ private SurfaceControl mTestLeash;
+
+ private PipInteractionHandler mPipInteractionHandler;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mPipInteractionHandler = new PipInteractionHandler(mMockContext, mMockHandler,
+ mMockInteractionJankMonitor);
+ mTestLeash = new SurfaceControl.Builder()
+ .setContainerLayer()
+ .setName("PipInteractionHandlerTest")
+ .setCallsite("PipInteractionHandlerTest")
+ .build();
+ }
+
+ @Test
+ public void begin_expand_startsTracking() {
+ mPipInteractionHandler.begin(mTestLeash, INTERACTION_EXIT_PIP);
+
+ verify(mMockInteractionJankMonitor, times(1)).begin(eq(mTestLeash),
+ eq(mMockContext), eq(mMockHandler), eq(CUJ_PIP_TRANSITION),
+ eq(PipInteractionHandler.pipInteractionToString(INTERACTION_EXIT_PIP)));
+ }
+
+ @Test
+ public void begin_expandToSplit_startsTracking() {
+ mPipInteractionHandler.begin(mTestLeash, INTERACTION_EXIT_PIP_TO_SPLIT);
+
+ verify(mMockInteractionJankMonitor, times(1)).begin(eq(mTestLeash),
+ eq(mMockContext), eq(mMockHandler), eq(CUJ_PIP_TRANSITION),
+ eq(PipInteractionHandler.pipInteractionToString(INTERACTION_EXIT_PIP_TO_SPLIT)));
+ }
+
+ @Test
+ public void end_stopsTracking() {
+ mPipInteractionHandler.end();
+
+ verify(mMockInteractionJankMonitor, times(1)).end(CUJ_PIP_TRANSITION);
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTransitionStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTransitionStateTest.java
index fa9b590..66b8ef1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTransitionStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTransitionStateTest.java
@@ -16,12 +16,15 @@
package com.android.wm.shell.pip2.phone;
+import static org.mockito.Mockito.when;
+
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcelable;
import android.testing.AndroidTestingRunner;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.pip.PipDesktopState;
import junit.framework.Assert;
@@ -45,9 +48,12 @@
@Mock
private Handler mMainHandler;
+ @Mock
+ private PipDesktopState mMockPipDesktopState;
+
@Before
public void setUp() {
- mPipTransitionState = new PipTransitionState(mMainHandler);
+ mPipTransitionState = new PipTransitionState(mMainHandler, mMockPipDesktopState);
mPipTransitionState.setState(PipTransitionState.UNDEFINED);
mEmptyParcelable = new Bundle();
}
@@ -128,4 +134,29 @@
mPipTransitionState.setState(PipTransitionState.SCHEDULED_BOUNDS_CHANGE, extra);
Assert.assertEquals(PipTransitionState.EXITING_PIP, mPipTransitionState.getState());
}
+
+ @Test
+ public void testShouldTransitionToState_scheduledBoundsChange_inPip_returnsTrue() {
+ mPipTransitionState.setState(PipTransitionState.ENTERED_PIP);
+
+ Assert.assertTrue(mPipTransitionState.shouldTransitionToState(
+ PipTransitionState.SCHEDULED_BOUNDS_CHANGE));
+ }
+
+ @Test
+ public void testShouldTransitionToState_scheduledBoundsChange_notInPip_returnsFalse() {
+ mPipTransitionState.setState(PipTransitionState.EXITED_PIP);
+
+ Assert.assertFalse(mPipTransitionState.shouldTransitionToState(
+ PipTransitionState.SCHEDULED_BOUNDS_CHANGE));
+ }
+
+ @Test
+ public void testShouldTransitionToState_scheduledBoundsChange_dragToDesktop_returnsFalse() {
+ mPipTransitionState.setState(PipTransitionState.ENTERED_PIP);
+ when(mMockPipDesktopState.isDragToDesktopInProgress()).thenReturn(true);
+
+ Assert.assertFalse(mPipTransitionState.shouldTransitionToState(
+ PipTransitionState.SCHEDULED_BOUNDS_CHANGE));
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/transition/PipExpandHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/transition/PipExpandHandlerTest.java
index 2a22842..cc66f00 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/transition/PipExpandHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/transition/PipExpandHandlerTest.java
@@ -23,6 +23,7 @@
import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP_TO_SPLIT;
import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -48,11 +49,13 @@
import android.window.WindowContainerTransaction;
import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.pip2.animation.PipExpandAnimator;
+import com.android.wm.shell.pip2.phone.PipInteractionHandler;
import com.android.wm.shell.pip2.phone.PipTransitionState;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.transition.TransitionInfoBuilder;
@@ -61,6 +64,8 @@
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;
@@ -79,6 +84,7 @@
@Mock private PipBoundsAlgorithm mMockPipBoundsAlgorithm;
@Mock private PipTransitionState mMockPipTransitionState;
@Mock private PipDisplayLayoutState mMockPipDisplayLayoutState;
+ @Mock private PipInteractionHandler mMockPipInteractionHandler;
@Mock private SplitScreenController mMockSplitScreenController;
@Mock private IBinder mMockTransitionToken;
@@ -89,6 +95,8 @@
@Mock private PipExpandAnimator mMockPipExpandAnimator;
+ @Captor private ArgumentCaptor<Runnable> mAnimatorCallbackArgumentCaptor;
+
@Surface.Rotation
private static final int DISPLAY_ROTATION = Surface.ROTATION_0;
@@ -108,7 +116,7 @@
mPipExpandHandler = new PipExpandHandler(mMockContext, mMockPipBoundsState,
mMockPipBoundsAlgorithm, mMockPipTransitionState, mMockPipDisplayLayoutState,
- Optional.of(mMockSplitScreenController));
+ mMockPipInteractionHandler, Optional.of(mMockSplitScreenController));
mPipExpandHandler.setPipExpandAnimatorSupplier((context, leash, startTransaction,
finishTransaction, baseBounds, startBounds, endBounds,
sourceRectHint, rotation) -> mMockPipExpandAnimator);
@@ -138,6 +146,13 @@
verify(mMockPipExpandAnimator, times(1)).start();
verify(mMockPipBoundsState, times(1)).saveReentryState(SNAP_FRACTION);
+
+ verify(mMockPipExpandAnimator, times(1))
+ .setAnimationStartCallback(mAnimatorCallbackArgumentCaptor.capture());
+ InstrumentationRegistry.getInstrumentation()
+ .runOnMainSync(mAnimatorCallbackArgumentCaptor.getValue());
+ verify(mMockPipInteractionHandler, times(1)).begin(any(),
+ eq(PipInteractionHandler.INTERACTION_EXIT_PIP));
}
@Test
@@ -158,6 +173,13 @@
verify(mMockSplitScreenController, times(1)).finishEnterSplitScreen(eq(mFinishT));
verify(mMockPipExpandAnimator, times(1)).start();
verify(mMockPipBoundsState, times(1)).saveReentryState(SNAP_FRACTION);
+
+ verify(mMockPipExpandAnimator, times(1))
+ .setAnimationStartCallback(mAnimatorCallbackArgumentCaptor.capture());
+ InstrumentationRegistry.getInstrumentation()
+ .runOnMainSync(mAnimatorCallbackArgumentCaptor.getValue());
+ verify(mMockPipInteractionHandler, times(1)).begin(any(),
+ eq(PipInteractionHandler.INTERACTION_EXIT_PIP_TO_SPLIT));
}
private TransitionInfo getExpandFromPipTransitionInfo(@WindowManager.TransitionType int type,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DropTargetManagerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DropTargetManagerTest.kt
index 180a691..95498cb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DropTargetManagerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DropTargetManagerTest.kt
@@ -18,7 +18,6 @@
import android.content.Context
import android.graphics.Rect
-import android.view.View
import android.widget.FrameLayout
import androidx.core.animation.AnimatorTestRule
import androidx.test.core.app.ApplicationProvider.getApplicationContext
@@ -58,15 +57,15 @@
private val bubbleRightDragZone =
DragZone.Bubble.Right(bounds = Rect(200, 0, 300, 100), dropTarget = Rect(200, 0, 280, 150))
- private val dropTargetView: View
- get() = container.getChildAt(0)
+ private val dropTargetView: DropTargetView
+ get() = container.getChildAt(0) as DropTargetView
@Before
fun setUp() {
container = FrameLayout(context)
dragZoneChangedListener = FakeDragZoneChangedListener()
dropTargetManager =
- DropTargetManager(context, container, isLayoutRtl = false, dragZoneChangedListener)
+ DropTargetManager(context, container, dragZoneChangedListener)
}
@Test
@@ -229,6 +228,22 @@
}
@Test
+ fun onDragEnded_dropTargetNotifies() {
+ dropTargetManager.onDragStarted(
+ DraggedObject.Bubble(BubbleBarLocation.LEFT),
+ listOf(bubbleLeftDragZone, bubbleRightDragZone, dismissDragZone)
+ )
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ dropTargetManager.onDragUpdated(
+ bubbleRightDragZone.bounds.centerX(),
+ bubbleRightDragZone.bounds.centerY()
+ )
+ dropTargetManager.onDragEnded()
+ }
+ assertThat(dragZoneChangedListener.endedDragZone).isEqualTo(bubbleRightDragZone)
+ }
+
+ @Test
fun startNewDrag_beforeDropTargetRemoved() {
dropTargetManager.onDragStarted(
DraggedObject.Bubble(BubbleBarLocation.LEFT),
@@ -238,8 +253,9 @@
InstrumentationRegistry.getInstrumentation().runOnMainSync {
dropTargetManager.onDragEnded()
- // advance the timer by 100ms so the animation doesn't complete
- animatorTestRule.advanceTimeBy(100)
+ // advance the timer by 50ms so the animation doesn't complete
+ // needs to be < DropTargetManager.DROP_TARGET_ALPHA_OUT_DURATION
+ animatorTestRule.advanceTimeBy(50)
}
assertThat(container.childCount).isEqualTo(1)
@@ -320,16 +336,17 @@
}
private fun verifyDropTargetPosition(rect: Rect) {
- assertThat(dropTargetView.scaleX).isEqualTo(rect.width())
- assertThat(dropTargetView.scaleY).isEqualTo(rect.height())
- assertThat(dropTargetView.translationX).isEqualTo(rect.exactCenterX())
- assertThat(dropTargetView.translationY).isEqualTo(rect.exactCenterY())
+ assertThat(dropTargetView.getRect().left).isEqualTo(rect.left)
+ assertThat(dropTargetView.getRect().top).isEqualTo(rect.top)
+ assertThat(dropTargetView.getRect().right).isEqualTo(rect.right)
+ assertThat(dropTargetView.getRect().bottom).isEqualTo(rect.bottom)
}
private class FakeDragZoneChangedListener : DropTargetManager.DragZoneChangedListener {
var initialDragZone: DragZone? = null
var fromDragZone: DragZone? = null
var toDragZone: DragZone? = null
+ var endedDragZone: DragZone? = null
override fun onInitialDragZoneSet(dragZone: DragZone) {
initialDragZone = dragZone
@@ -339,5 +356,9 @@
fromDragZone = from
toDragZone = to
}
+
+ override fun onDragEnded(zone: DragZone) {
+ endedDragZone = zone
+ }
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index e5a6a6d..70603fa 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -27,6 +27,7 @@
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW;
@@ -213,7 +214,7 @@
IBinder transition = mSplitScreenTransitions.startEnterTransition(
TRANSIT_OPEN, new WindowContainerTransaction(),
new RemoteTransition(testRemote, "Test"), mStageCoordinator,
- TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false);
+ TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false, SNAP_TO_2_50_50);
mMainStage.onTaskAppeared(mMainChild, createMockSurface());
mSideStage.onTaskAppeared(mSideChild, createMockSurface());
boolean accepted = mStageCoordinator.startAnimation(transition, info,
@@ -239,7 +240,7 @@
IBinder transition = mSplitScreenTransitions.startEnterTransition(
TRANSIT_OPEN, new WindowContainerTransaction(),
new RemoteTransition(testRemote, "Test"), mStageCoordinator,
- TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false);
+ TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false, SNAP_TO_2_50_50);
mMainStage.onTaskAppeared(mMainChild, createMockSurface());
boolean accepted = mStageCoordinator.startAnimation(transition, info,
mock(SurfaceControl.Transaction.class),
@@ -262,7 +263,7 @@
IBinder transition = mSplitScreenTransitions.startEnterTransition(
TRANSIT_OPEN, new WindowContainerTransaction(),
new RemoteTransition(testRemote, "Test"), mStageCoordinator,
- TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false);
+ TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false, SNAP_TO_2_50_50);
mMainStage.onTaskAppeared(mMainChild, createMockSurface());
mStageCoordinator.startAnimation(transition, info,
mock(SurfaceControl.Transaction.class),
@@ -524,7 +525,7 @@
IBinder enterTransit = mSplitScreenTransitions.startEnterTransition(
TRANSIT_OPEN, new WindowContainerTransaction(),
new RemoteTransition(new TestRemoteTransition(), "Test"),
- mStageCoordinator, TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false);
+ mStageCoordinator, TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false, SNAP_TO_2_50_50);
mMainStage.onTaskAppeared(mMainChild, createMockSurface());
mSideStage.onTaskAppeared(mSideChild, createMockSurface());
mStageCoordinator.startAnimation(enterTransit, enterInfo,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index e9c4c31..e246329 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -475,7 +475,7 @@
mStageCoordinator.startTask(mTaskId, SPLIT_POSITION_TOP_OR_LEFT, null /*options*/,
null, SPLIT_INDEX_UNDEFINED);
verify(mSplitScreenTransitions).startEnterTransition(anyInt(),
- mWctCaptor.capture(), any(), any(), anyInt(), anyBoolean());
+ mWctCaptor.capture(), any(), any(), anyInt(), anyBoolean(), anyInt());
int windowingMode = mWctCaptor.getValue().getChanges().get(mBinder).getWindowingMode();
assertEquals(windowingMode, WINDOWING_MODE_UNDEFINED);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManagerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManagerTest.kt
index 8442056..42eab14 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManagerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManagerTest.kt
@@ -70,6 +70,13 @@
whenever(context.display).thenReturn(display)
whenever(display.getRoundedCorner(any())).thenReturn(roundedCorner)
whenever(roundedCorner.radius).thenReturn(CORNER_RADIUS)
+ whenever(transactionSupplierMock.get()).thenReturn(transaction)
+ whenever(transaction.show(any())).thenReturn(transaction)
+ whenever(transaction.setAlpha(any(), any())).thenReturn(transaction)
+ whenever(transaction.hide(any())).thenReturn(transaction)
+ whenever(transaction.setRelativeLayer(any(), any(), any())).thenReturn(transaction)
+ whenever(transaction.setPosition(any(), any(), any())).thenReturn(transaction)
+ whenever(transaction.remove(any())).thenReturn(transaction)
desktopTilingWindowManager =
DesktopTilingDividerWindowManager(
config,
@@ -88,12 +95,6 @@
@Test
@UiThreadTest
fun testWindowManager_isInitialisedAndReleased() {
- whenever(transactionSupplierMock.get()).thenReturn(transaction)
- whenever(transaction.hide(any())).thenReturn(transaction)
- whenever(transaction.setRelativeLayer(any(), any(), any())).thenReturn(transaction)
- whenever(transaction.setPosition(any(), any(), any())).thenReturn(transaction)
- whenever(transaction.remove(any())).thenReturn(transaction)
-
desktopTilingWindowManager.generateViewHost(surfaceControl)
// Ensure a surfaceControl transaction runs to show the divider.
@@ -102,18 +103,11 @@
desktopTilingWindowManager.release()
verify(transaction, times(1)).hide(any())
verify(transaction, times(1)).remove(any())
- verify(transaction, times(1)).apply()
}
@Test
@UiThreadTest
fun testWindowManager_accountsForRoundedCornerDimensions() {
- whenever(transactionSupplierMock.get()).thenReturn(transaction)
- whenever(transaction.setRelativeLayer(any(), any(), any())).thenReturn(transaction)
- whenever(transaction.setRelativeLayer(any(), any(), any())).thenReturn(transaction)
- whenever(transaction.setPosition(any(), any(), any())).thenReturn(transaction)
- whenever(transaction.show(any())).thenReturn(transaction)
-
desktopTilingWindowManager.generateViewHost(surfaceControl)
// Ensure a surfaceControl transaction runs to show the divider.
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt
index 399a51e..bc8faed 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt
@@ -114,6 +114,7 @@
private val split_divider_width = 10
@Captor private lateinit var wctCaptor: ArgumentCaptor<WindowContainerTransaction>
+ @Captor private lateinit var callbackCaptor: ArgumentCaptor<(() -> Unit)>
@Before
fun setUp() {
@@ -134,7 +135,7 @@
userRepositories,
desktopModeEventLogger,
focusTransitionObserver,
- mainExecutor
+ mainExecutor,
)
whenever(context.createContextAsUser(any(), any())).thenReturn(context)
whenever(userRepositories.current).thenReturn(desktopRepository)
@@ -158,7 +159,8 @@
BOUNDS,
)
- verify(toggleResizeDesktopTaskTransitionHandler).startTransition(capture(wctCaptor), any())
+ verify(toggleResizeDesktopTaskTransitionHandler)
+ .startTransition(capture(wctCaptor), any(), any())
for (change in wctCaptor.value.changes) {
val bounds = change.value.configuration.windowConfiguration.bounds
val leftBounds = getLeftTaskBounds()
@@ -185,7 +187,8 @@
BOUNDS,
)
- verify(toggleResizeDesktopTaskTransitionHandler).startTransition(capture(wctCaptor), any())
+ verify(toggleResizeDesktopTaskTransitionHandler)
+ .startTransition(capture(wctCaptor), any(), any())
for (change in wctCaptor.value.changes) {
val bounds = change.value.configuration.windowConfiguration.bounds
val leftBounds = getRightTaskBounds()
@@ -220,7 +223,7 @@
)
verify(toggleResizeDesktopTaskTransitionHandler, times(1))
- .startTransition(capture(wctCaptor), any())
+ .startTransition(capture(wctCaptor), any(), any())
verify(returnToDragStartAnimator, times(1)).start(any(), any(), any(), any(), anyOrNull())
for (change in wctCaptor.value.changes) {
val bounds = change.value.configuration.windowConfiguration.bounds
@@ -308,9 +311,13 @@
DesktopTasksController.SnapPosition.LEFT,
BOUNDS,
)
+ verify(toggleResizeDesktopTaskTransitionHandler, times(2))
+ .startTransition(capture(wctCaptor), any(), capture(callbackCaptor))
+ (callbackCaptor.value).invoke()
task1.isFocused = true
- assertThat(tilingDecoration.moveTiledPairToFront(task1.taskId, isFocusedOnDisplay = true)).isTrue()
+ assertThat(tilingDecoration.moveTiledPairToFront(task1.taskId, isFocusedOnDisplay = true))
+ .isTrue()
verify(transitions, times(1)).startTransition(eq(TRANSIT_TO_FRONT), any(), eq(null))
}
@@ -341,6 +348,9 @@
)
task1.isFocused = true
task3.isFocused = true
+ verify(toggleResizeDesktopTaskTransitionHandler, times(2))
+ .startTransition(capture(wctCaptor), any(), capture(callbackCaptor))
+ (callbackCaptor.value).invoke()
assertThat(tilingDecoration.moveTiledPairToFront(task3.taskId, true)).isFalse()
assertThat(tilingDecoration.moveTiledPairToFront(task1.taskId, true)).isTrue()
@@ -372,9 +382,14 @@
DesktopTasksController.SnapPosition.LEFT,
BOUNDS,
)
+ verify(toggleResizeDesktopTaskTransitionHandler, times(2))
+ .startTransition(capture(wctCaptor), any(), capture(callbackCaptor))
+ (callbackCaptor.value).invoke()
- assertThat(tilingDecoration.moveTiledPairToFront(task3.taskId, isFocusedOnDisplay = true)).isFalse()
- assertThat(tilingDecoration.moveTiledPairToFront(task1.taskId, isFocusedOnDisplay = true)).isTrue()
+ assertThat(tilingDecoration.moveTiledPairToFront(task3.taskId, isFocusedOnDisplay = true))
+ .isFalse()
+ assertThat(tilingDecoration.moveTiledPairToFront(task1.taskId, isFocusedOnDisplay = true))
+ .isTrue()
verify(transitions, times(1)).startTransition(eq(TRANSIT_TO_FRONT), any(), eq(null))
}
@@ -482,27 +497,29 @@
tilingDecoration.onDividerHandleDragStart(motionEvent)
// Log start event for task1 and task2, but the tasks are the same in
// this test, so we verify the same log twice.
- verify(desktopModeEventLogger, times(2)).logTaskResizingStarted(
- ResizeTrigger.TILING_DIVIDER,
- DesktopModeEventLogger.Companion.InputMethod.UNKNOWN_INPUT_METHOD,
- task1,
- BOUNDS.width() / 2,
- BOUNDS.height(),
- displayController,
- )
+ verify(desktopModeEventLogger, times(2))
+ .logTaskResizingStarted(
+ ResizeTrigger.TILING_DIVIDER,
+ DesktopModeEventLogger.Companion.InputMethod.UNKNOWN_INPUT_METHOD,
+ task1,
+ BOUNDS.width() / 2,
+ BOUNDS.height(),
+ displayController,
+ )
tilingDecoration.onDividerHandleMoved(BOUNDS, transaction)
tilingDecoration.onDividerHandleDragEnd(BOUNDS, transaction, motionEvent)
// Log end event for task1 and task2, but the tasks are the same in
// this test, so we verify the same log twice.
- verify(desktopModeEventLogger, times(2)).logTaskResizingEnded(
- ResizeTrigger.TILING_DIVIDER,
- DesktopModeEventLogger.Companion.InputMethod.UNKNOWN_INPUT_METHOD,
- task1,
- BOUNDS.width(),
- BOUNDS.height(),
- displayController,
- )
+ verify(desktopModeEventLogger, times(2))
+ .logTaskResizingEnded(
+ ResizeTrigger.TILING_DIVIDER,
+ DesktopModeEventLogger.Companion.InputMethod.UNKNOWN_INPUT_METHOD,
+ task1,
+ BOUNDS.width(),
+ BOUNDS.height(),
+ displayController,
+ )
}
@Test
diff --git a/libs/hwui/CanvasTransform.cpp b/libs/hwui/CanvasTransform.cpp
index 30e7a62..6f60d01 100644
--- a/libs/hwui/CanvasTransform.cpp
+++ b/libs/hwui/CanvasTransform.cpp
@@ -55,12 +55,20 @@
}
}
+SkColor invert(SkColor color) {
+ Lab lab = sRGBToLab(color);
+ lab.L = 100 - lab.L;
+ return LabToSRGB(lab, SkColorGetA(color));
+}
+
SkColor transformColor(ColorTransform transform, SkColor color) {
switch (transform) {
case ColorTransform::Light:
return makeLight(color);
case ColorTransform::Dark:
return makeDark(color);
+ case ColorTransform::Invert:
+ return invert(color);
default:
return color;
}
@@ -80,19 +88,6 @@
static void applyColorTransform(ColorTransform transform, SkPaint& paint) {
if (transform == ColorTransform::None) return;
- if (transform == ColorTransform::Invert) {
- auto filter = SkHighContrastFilter::Make(
- {/* grayscale= */ false, SkHighContrastConfig::InvertStyle::kInvertLightness,
- /* contrast= */ 0.0f});
-
- if (paint.getColorFilter()) {
- paint.setColorFilter(SkColorFilters::Compose(filter, paint.refColorFilter()));
- } else {
- paint.setColorFilter(filter);
- }
- return;
- }
-
SkColor newColor = transformColor(transform, paint.getColor());
paint.setColor(newColor);
@@ -112,6 +107,22 @@
paint.setShader(SkGradientShader::MakeLinear(
info.fPoints, info.fColors, info.fColorOffsets, info.fColorCount,
info.fTileMode, info.fGradientFlags, nullptr));
+ } else {
+ if (transform == ColorTransform::Invert) {
+ // Since we're trying to invert every thing around this draw call, we invert
+ // the color of the draw call if we don't know what it is.
+ auto filter = SkHighContrastFilter::Make(
+ {/* grayscale= */ false,
+ SkHighContrastConfig::InvertStyle::kInvertLightness,
+ /* contrast= */ 0.0f});
+
+ if (paint.getColorFilter()) {
+ paint.setColorFilter(SkColorFilters::Compose(filter, paint.refColorFilter()));
+ } else {
+ paint.setColorFilter(filter);
+ }
+ return;
+ }
}
}
@@ -150,8 +161,13 @@
}
bool transformPaint(ColorTransform transform, SkPaint* paint, BitmapPalette palette) {
- palette = filterPalette(paint, palette);
bool shouldInvert = false;
+ if (transform == ColorTransform::Invert && palette != BitmapPalette::Colorful) {
+ // When the transform is Invert we invert any image that is not deemed "colorful",
+ // regardless of calculated image brightness.
+ shouldInvert = true;
+ }
+ palette = filterPalette(paint, palette);
if (palette == BitmapPalette::Light && transform == ColorTransform::Dark) {
shouldInvert = true;
}
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 4801bd1..8b4e59a 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -27,6 +27,7 @@
#include "DamageAccumulator.h"
#include "Debug.h"
+#include "FeatureFlags.h"
#include "Properties.h"
#include "TreeInfo.h"
#include "VectorDrawable.h"
@@ -398,26 +399,32 @@
deleteDisplayList(observer, info);
mDisplayList = std::move(mStagingDisplayList);
if (mDisplayList) {
- WebViewSyncData syncData{.applyForceDark = shouldEnableForceDark(info)};
+ WebViewSyncData syncData{.applyForceDark = shouldEnableForceDark(info) ||
+ (info && isForceInvertDark(*info))};
mDisplayList.syncContents(syncData);
handleForceDark(info);
}
}
+// Return true if the tree should use the force invert feature that inverts
+// the entire tree to darken it.
inline bool RenderNode::isForceInvertDark(TreeInfo& info) {
- return CC_UNLIKELY(
- info.forceDarkType == android::uirenderer::ForceDarkType::FORCE_INVERT_COLOR_DARK);
+ return CC_UNLIKELY(info.forceDarkType ==
+ android::uirenderer::ForceDarkType::FORCE_INVERT_COLOR_DARK);
}
+// Return true if the tree should use the force dark feature that selectively
+// darkens light nodes on the tree.
inline bool RenderNode::shouldEnableForceDark(TreeInfo* info) {
- return CC_UNLIKELY(
- info &&
- (!info->disableForceDark || isForceInvertDark(*info)));
+ return CC_UNLIKELY(info && !info->disableForceDark);
}
-
-
-void RenderNode::handleForceDark(android::uirenderer::TreeInfo *info) {
+void RenderNode::handleForceDark(TreeInfo *info) {
+ if (CC_UNLIKELY(view_accessibility_flags::force_invert_color() && info &&
+ isForceInvertDark(*info))) {
+ mDisplayList.applyColorTransform(ColorTransform::Invert);
+ return;
+ }
if (!shouldEnableForceDark(info)) {
return;
}
@@ -427,13 +434,7 @@
children.push_back(node);
});
if (mDisplayList.hasText()) {
- if (isForceInvertDark(*info) && mDisplayList.hasFill()) {
- // Handle a special case for custom views that draw both text and background in the
- // same RenderNode, which would otherwise be altered to white-on-white text.
- usage = UsageHint::Container;
- } else {
- usage = UsageHint::Foreground;
- }
+ usage = UsageHint::Foreground;
}
if (usage == UsageHint::Unknown) {
if (children.size() > 1) {
diff --git a/libs/hwui/apex/LayoutlibLoader.cpp b/libs/hwui/apex/LayoutlibLoader.cpp
index 56191c0..87a43fc 100644
--- a/libs/hwui/apex/LayoutlibLoader.cpp
+++ b/libs/hwui/apex/LayoutlibLoader.cpp
@@ -29,6 +29,7 @@
extern int register_android_graphics_Bitmap(JNIEnv*);
extern int register_android_graphics_BitmapFactory(JNIEnv*);
extern int register_android_graphics_BitmapRegionDecoder(JNIEnv*);
+extern int register_android_graphics_RuntimeXfermode(JNIEnv*);
extern int register_android_graphics_ByteBufferStreamAdaptor(JNIEnv* env);
extern int register_android_graphics_Camera(JNIEnv* env);
extern int register_android_graphics_CreateJavaOutputStreamAdaptor(JNIEnv* env);
@@ -131,6 +132,7 @@
{"android.graphics.RenderNode", REG_JNI(register_android_view_RenderNode)},
{"android.graphics.Shader", REG_JNI(register_android_graphics_Shader)},
{"android.graphics.RenderEffect", REG_JNI(register_android_graphics_RenderEffect)},
+ {"android.graphics.RuntimeXfermode", REG_JNI(register_android_graphics_RuntimeXfermode)},
{"android.graphics.Typeface", REG_JNI(register_android_graphics_Typeface)},
{"android.graphics.YuvImage", REG_JNI(register_android_graphics_YuvImage)},
{"android.graphics.animation.NativeInterpolatorFactory",
diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp
index 63a024b..3ef9708 100644
--- a/libs/hwui/hwui/Bitmap.cpp
+++ b/libs/hwui/hwui/Bitmap.cpp
@@ -16,6 +16,8 @@
#include "Bitmap.h"
#include <android-base/file.h>
+
+#include "FeatureFlags.h"
#include "HardwareBitmapUploader.h"
#include "Properties.h"
#ifdef __ANDROID__ // Layoutlib does not support render thread
@@ -547,9 +549,16 @@
}
ALOGV("samples = %d, hue [min = %f, max = %f, avg = %f]; saturation [min = %f, max = %f, avg = "
- "%f]",
+ "%f] %d x %d",
sampledCount, hue.min(), hue.max(), hue.average(), saturation.min(), saturation.max(),
- saturation.average());
+ saturation.average(), info.width(), info.height());
+
+ if (CC_UNLIKELY(view_accessibility_flags::force_invert_color())) {
+ if (saturation.delta() > 0.1f ||
+ (hue.delta() > 20 && saturation.average() > 0.2f && value.average() < 0.9f)) {
+ return BitmapPalette::Colorful;
+ }
+ }
if (hue.delta() <= 20 && saturation.delta() <= .1f) {
if (value.average() >= .5f) {
diff --git a/libs/hwui/hwui/Bitmap.h b/libs/hwui/hwui/Bitmap.h
index 4e9bcf2..0fe5fe8 100644
--- a/libs/hwui/hwui/Bitmap.h
+++ b/libs/hwui/hwui/Bitmap.h
@@ -49,6 +49,7 @@
Unknown,
Light,
Dark,
+ Colorful,
};
namespace uirenderer {
diff --git a/libs/hwui/jni/GIFMovie.cpp b/libs/hwui/jni/GIFMovie.cpp
index 6c82aa1..476b6fd 100644
--- a/libs/hwui/jni/GIFMovie.cpp
+++ b/libs/hwui/jni/GIFMovie.cpp
@@ -63,7 +63,7 @@
}
fCurrIndex = -1;
fLastDrawIndex = -1;
- fPaintingColor = SkPackARGB32(0, 0, 0, 0);
+ fPaintingColor = SK_AlphaTRANSPARENT;
}
GIFMovie::~GIFMovie()
@@ -127,7 +127,7 @@
for (; width > 0; width--, src++, dst++) {
if (*src != transparent && *src < cmap->ColorCount) {
const GifColorType& col = cmap->Colors[*src];
- *dst = SkPackARGB32(0xFF, col.Red, col.Green, col.Blue);
+ *dst = SkColorSetRGB(col.Red, col.Green, col.Blue);
}
}
}
@@ -395,10 +395,10 @@
lastIndex = fGIF->ImageCount - 1;
}
- SkColor bgColor = SkPackARGB32(0, 0, 0, 0);
+ SkColor bgColor = SK_ColorTRANSPARENT;
if (gif->SColorMap != nullptr && gif->SBackGroundColor < gif->SColorMap->ColorCount) {
const GifColorType& col = gif->SColorMap->Colors[gif->SBackGroundColor];
- bgColor = SkColorSetARGB(0xFF, col.Red, col.Green, col.Blue);
+ bgColor = SkColorSetRGB(col.Red, col.Green, col.Blue);
}
// draw each frames - not intelligent way
@@ -411,7 +411,7 @@
if (!trans && gif->SColorMap != nullptr) {
fPaintingColor = bgColor;
} else {
- fPaintingColor = SkColorSetARGB(0, 0, 0, 0);
+ fPaintingColor = SK_ColorTRANSPARENT;
}
bm->eraseColor(fPaintingColor);
diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java
index 80b55e2..5a993bf 100644
--- a/location/java/android/location/LocationRequest.java
+++ b/location/java/android/location/LocationRequest.java
@@ -33,6 +33,7 @@
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
import android.content.pm.PackageManager;
+import android.location.flags.Flags;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
@@ -181,7 +182,8 @@
public static final int POWER_HIGH = 203;
private static final long IMPLICIT_MIN_UPDATE_INTERVAL = -1;
- private static final double IMPLICIT_MIN_UPDATE_INTERVAL_FACTOR = 1D / 6D;
+ private static final double LEGACY_IMPLICIT_MIN_UPDATE_INTERVAL_FACTOR = 1D / 6D;
+ private static final double IMPLICIT_MIN_UPDATE_INTERVAL_FACTOR = 1D / 2D;
private @Nullable String mProvider;
private @Quality int mQuality;
@@ -553,7 +555,10 @@
*/
public @IntRange(from = 0) long getMinUpdateIntervalMillis() {
if (mMinUpdateIntervalMillis == IMPLICIT_MIN_UPDATE_INTERVAL) {
- return (long) (mIntervalMillis * IMPLICIT_MIN_UPDATE_INTERVAL_FACTOR);
+ if (Flags.updateMinLocationRequestInterval()) {
+ return (long) (mIntervalMillis * IMPLICIT_MIN_UPDATE_INTERVAL_FACTOR);
+ }
+ return (long) (mIntervalMillis * LEGACY_IMPLICIT_MIN_UPDATE_INTERVAL_FACTOR);
} else {
// the min is only necessary in case someone use a deprecated function to mess with the
// interval or min update interval
diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig
index 1b38982..83b1778 100644
--- a/location/java/android/location/flags/location.aconfig
+++ b/location/java/android/location/flags/location.aconfig
@@ -168,3 +168,14 @@
description: "Flag for GNSS assistance interface"
bug: "209078566"
}
+
+flag {
+ name: "update_min_location_request_interval"
+ namespace: "location"
+ description: "Flag for updating the default logic for the minimal interval for location request"
+ bug: "397444378"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index 4ae8daa..6a33b37 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -759,12 +759,29 @@
/**
* Updates routes of the provider and notifies the system media router service.
+ *
+ * @throws IllegalArgumentException If {@code routes} contains a route that {@link
+ * MediaRoute2Info#getSupportedRoutingTypes() supports} both system media routing and remote
+ * routing but doesn't contain any {@link MediaRoute2Info#getDeduplicationIds()
+ * deduplication ids}.
*/
public final void notifyRoutes(@NonNull Collection<MediaRoute2Info> routes) {
requireNonNull(routes, "routes must not be null");
List<MediaRoute2Info> sanitizedRoutes = new ArrayList<>(routes.size());
for (MediaRoute2Info route : routes) {
+ if (Flags.enableMirroringInMediaRouter2()
+ && route.supportsRemoteRouting()
+ && route.supportsSystemMediaRouting()
+ && route.getDeduplicationIds().isEmpty()) {
+ String errorMessage =
+ TextUtils.formatSimple(
+ "Route with id='%s' name='%s' supports both system media and remote"
+ + " type routing, but doesn't contain a deduplication id, which"
+ + " it needs. You can add the route id as a deduplication id.",
+ route.getOriginalId(), route.getName());
+ throw new IllegalArgumentException(errorMessage);
+ }
if (route.isSystemRouteType()) {
Log.w(
TAG,
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index 2759724..f6dc41e 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -89,7 +89,7 @@
CHECK_NOT_NULL(window);
CHECK_NOT_NULL(debug_name);
- sp<SurfaceComposerClient> client = new SurfaceComposerClient();
+ sp<SurfaceComposerClient> client = sp<SurfaceComposerClient>::make();
if (client->initCheck() != NO_ERROR) {
return nullptr;
}
diff --git a/native/android/thermal.cpp b/native/android/thermal.cpp
index cefcaf7..93e6ed8 100644
--- a/native/android/thermal.cpp
+++ b/native/android/thermal.cpp
@@ -139,7 +139,12 @@
mStatusListeners.clear();
if (mServiceStatusListener != nullptr) {
bool success = false;
- mThermalSvc->unregisterThermalStatusListener(mServiceStatusListener, &success);
+ auto ret =
+ mThermalSvc->unregisterThermalStatusListener(mServiceStatusListener, &success);
+ if (!success || !ret.isOk()) {
+ ALOGE("Failed in unregisterThermalStatusListener when AThermalManager is being "
+ "destroyed %d", success);
+ }
mServiceStatusListener = nullptr;
}
}
@@ -148,7 +153,12 @@
mHeadroomListeners.clear();
if (mServiceHeadroomListener != nullptr) {
bool success = false;
- mThermalSvc->unregisterThermalHeadroomListener(mServiceHeadroomListener, &success);
+ auto ret = mThermalSvc->unregisterThermalHeadroomListener(mServiceHeadroomListener,
+ &success);
+ if (!success || !ret.isOk()) {
+ ALOGE("Failed in unregisterThermalHeadroomListener when AThermalManager is being "
+ "destroyed %d", success);
+ }
mServiceHeadroomListener = nullptr;
}
}
diff --git a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
index 0ad7f5f..3d011bc 100644
--- a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
+++ b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
@@ -76,6 +76,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
+ android:accessibilityLiveRegion="polite"
style="@style/TimeoutMessage" />
<androidx.recyclerview.widget.RecyclerView
diff --git a/packages/CompanionDeviceManager/res/values-ar/strings.xml b/packages/CompanionDeviceManager/res/values-ar/strings.xml
index f4e1721..40c7dd1 100644
--- a/packages/CompanionDeviceManager/res/values-ar/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ar/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"عدم السماح"</string>
<string name="consent_cancel" msgid="5655005528379285841">"إلغاء"</string>
<string name="consent_back" msgid="2560683030046918882">"رجوع"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"الانتقال لأسفل القائمة"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"سهم متّجه للأسفل"</string>
<string name="permission_expand" msgid="893185038020887411">"توسيع <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"تصغير <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"هل تريد منح التطبيقات على <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> نفس الأذونات على <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>؟"</string>
diff --git a/packages/CompanionDeviceManager/res/values-be/strings.xml b/packages/CompanionDeviceManager/res/values-be/strings.xml
index 14878c1..8ef80d34 100644
--- a/packages/CompanionDeviceManager/res/values-be/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-be/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Не дазваляць"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Скасаваць"</string>
<string name="consent_back" msgid="2560683030046918882">"Назад"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Прагартаць спіс уніз"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Стрэлка ўніз"</string>
<string name="permission_expand" msgid="893185038020887411">"Разгарнуць <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Згарнуць <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Даць праграмам на прыладзе <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> такія самыя дазволы, што і на прыладзе <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-bg/strings.xml b/packages/CompanionDeviceManager/res/values-bg/strings.xml
index 6e5b323..ebc2cf5 100644
--- a/packages/CompanionDeviceManager/res/values-bg/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-bg/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Забраняване"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Отказ"</string>
<string name="consent_back" msgid="2560683030046918882">"Назад"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Превъртете списъка надолу"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Стрелка за надолу"</string>
<string name="permission_expand" msgid="893185038020887411">"Разгъване на <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Свиване на <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Искате ли да дадете на приложенията на <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> същите разрешения както на <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-el/strings.xml b/packages/CompanionDeviceManager/res/values-el/strings.xml
index 1dc00dc..9ede684 100644
--- a/packages/CompanionDeviceManager/res/values-el/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-el/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Να μην επιτρέπεται"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Ακύρωση"</string>
<string name="consent_back" msgid="2560683030046918882">"Πίσω"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Κύλιση προς τα κάτω στη λίστα"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Βέλος που δείχνει προς τα κάτω"</string>
<string name="permission_expand" msgid="893185038020887411">"Ανάπτυξη <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Σύμπτυξη <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Παραχώρηση των ίδιων αδειών στις εφαρμογές στη συσκευή <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> όπως στη συσκευή <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>;"</string>
diff --git a/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml b/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml
index 8d73380..15d3042 100644
--- a/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Don\'t allow"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Cancel"</string>
<string name="consent_back" msgid="2560683030046918882">"Back"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Scroll down the list"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Downward arrow"</string>
<string name="permission_expand" msgid="893185038020887411">"Expand <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Collapse <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Give apps on <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> the same permissions as on <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml b/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml
index 50f7654..edcc4bd 100644
--- a/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"No permitir"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Cancelar"</string>
<string name="consent_back" msgid="2560683030046918882">"Atrás"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Bajar por la lista"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Flecha hacia abajo"</string>
<string name="permission_expand" msgid="893185038020887411">"Expandir <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Contraer <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"¿Dar a las apps de <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> los mismos permisos que tienen en <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-et/strings.xml b/packages/CompanionDeviceManager/res/values-et/strings.xml
index 0f390e1..3c83758 100644
--- a/packages/CompanionDeviceManager/res/values-et/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-et/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Ära luba"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Tühista"</string>
<string name="consent_back" msgid="2560683030046918882">"Tagasi"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Kerige loendis alla"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Allapoole suunatud nool"</string>
<string name="permission_expand" msgid="893185038020887411">"Laienda: <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Ahenda: <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Kas anda rakendustele seadmes <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> samad load, mis seadmes <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-hi/strings.xml b/packages/CompanionDeviceManager/res/values-hi/strings.xml
index f7086c6..f204e94 100644
--- a/packages/CompanionDeviceManager/res/values-hi/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-hi/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"अनुमति न दें"</string>
<string name="consent_cancel" msgid="5655005528379285841">"रद्द करें"</string>
<string name="consent_back" msgid="2560683030046918882">"वापस जाएं"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"सूची को नीचे की ओर स्क्रोल करें"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"डाउनवर्ड ऐरो"</string>
<string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> को बड़ा करें"</string>
<string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> को छोटा करें"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"क्या <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> पर ऐप्लिकेशन को वही अनुमतियां देनी हैं जो <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong> पर दी हैं?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-hu/strings.xml b/packages/CompanionDeviceManager/res/values-hu/strings.xml
index 5d3b313..42702ca 100644
--- a/packages/CompanionDeviceManager/res/values-hu/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-hu/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Tiltás"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Mégse"</string>
<string name="consent_back" msgid="2560683030046918882">"Vissza"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Görgetés a listában lefelé"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Lefelé mutató nyíl"</string>
<string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> kibontása"</string>
<string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> összecsukása"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Ugyanolyan engedélyeket ad a(z) <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> eszközön található alkalmazásoknak, mint a(z) <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong> eszköz esetén?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-it/strings.xml b/packages/CompanionDeviceManager/res/values-it/strings.xml
index 3496718..b744542 100644
--- a/packages/CompanionDeviceManager/res/values-it/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-it/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Non consentire"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Annulla"</string>
<string name="consent_back" msgid="2560683030046918882">"Indietro"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Scorri l\'elenco verso il basso"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Freccia verso il basso"</string>
<string name="permission_expand" msgid="893185038020887411">"Espandi <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Comprimi <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Vuoi dare alle app su <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> le stesse autorizzazioni che hai dato su <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-ja/strings.xml b/packages/CompanionDeviceManager/res/values-ja/strings.xml
index a8dc756..664d536 100644
--- a/packages/CompanionDeviceManager/res/values-ja/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ja/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"許可しない"</string>
<string name="consent_cancel" msgid="5655005528379285841">"キャンセル"</string>
<string name="consent_back" msgid="2560683030046918882">"戻る"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"リストを下にスクロール"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"下矢印"</string>
<string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>を開く"</string>
<string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>を閉じる"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"<strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> のアプリに <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong> の場合と同じ権限を付与しますか?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-km/strings.xml b/packages/CompanionDeviceManager/res/values-km/strings.xml
index 9047b05..ddd4144 100644
--- a/packages/CompanionDeviceManager/res/values-km/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-km/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"មិនអនុញ្ញាត"</string>
<string name="consent_cancel" msgid="5655005528379285841">"បោះបង់"</string>
<string name="consent_back" msgid="2560683030046918882">"ថយក្រោយ"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"រំកិលបញ្ជីចុះក្រោម"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"ព្រួញចុះក្រោម"</string>
<string name="permission_expand" msgid="893185038020887411">"ពង្រីក <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"បង្រួម <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"ផ្ដល់ការអនុញ្ញាតឱ្យកម្មវិធីនៅលើ <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> ដូចនៅលើ <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong> ឬ?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-lt/strings.xml b/packages/CompanionDeviceManager/res/values-lt/strings.xml
index a3ac47b..09183bb 100644
--- a/packages/CompanionDeviceManager/res/values-lt/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-lt/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Neleisti"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Atšaukti"</string>
<string name="consent_back" msgid="2560683030046918882">"Atgal"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Slinkti sąrašu žemyn"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Rodyklė žemyn"</string>
<string name="permission_expand" msgid="893185038020887411">"Išskleisti „<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>“"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Sutraukti „<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>“"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Suteikti <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> esančioms programoms tuos pačius leidimus kaip <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong> esančioms programoms?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-nl/strings.xml b/packages/CompanionDeviceManager/res/values-nl/strings.xml
index 2f0fc9a..2bb4b9a 100644
--- a/packages/CompanionDeviceManager/res/values-nl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-nl/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Niet toestaan"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Annuleren"</string>
<string name="consent_back" msgid="2560683030046918882">"Terug"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Omlaag scrollen in de lijst"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Pijl-omlaag"</string>
<string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> uitvouwen"</string>
<string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> samenvouwen"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Apps op de <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> dezelfde rechten geven als op de <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml b/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml
index bee8e91..ec9beeb 100644
--- a/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Não permitir"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Cancelar"</string>
<string name="consent_back" msgid="2560683030046918882">"Voltar"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Deslocar para baixo na lista"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Seta para baixo"</string>
<string name="permission_expand" msgid="893185038020887411">"Expandir <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"Reduzir <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Dar às apps no dispositivo <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> as mesmas autorizações de <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-tl/strings.xml b/packages/CompanionDeviceManager/res/values-tl/strings.xml
index 90e5eb7..fbc12f2 100644
--- a/packages/CompanionDeviceManager/res/values-tl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-tl/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"Huwag payagan"</string>
<string name="consent_cancel" msgid="5655005528379285841">"Kanselahin"</string>
<string name="consent_back" msgid="2560683030046918882">"Bumalik"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"Mag-scroll pababa sa listahan"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"Pababang arrow"</string>
<string name="permission_expand" msgid="893185038020887411">"I-expand ang <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_collapse" msgid="3320833884220844084">"I-collapse ang <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Bigyan ang mga app sa <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> ng mga pahintulot na mayroon din sa <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string>
diff --git a/packages/CompanionDeviceManager/res/values-ur/strings.xml b/packages/CompanionDeviceManager/res/values-ur/strings.xml
index 690e439..895dfb0 100644
--- a/packages/CompanionDeviceManager/res/values-ur/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ur/strings.xml
@@ -51,10 +51,8 @@
<string name="consent_no" msgid="2640796915611404382">"اجازت نہ دیں"</string>
<string name="consent_cancel" msgid="5655005528379285841">"منسوخ کریں"</string>
<string name="consent_back" msgid="2560683030046918882">"پیچھے"</string>
- <!-- no translation found for downward_arrow_action (2327165938832076333) -->
- <skip />
- <!-- no translation found for downward_arrow (2292427714411156088) -->
- <skip />
+ <string name="downward_arrow_action" msgid="2327165938832076333">"فہرست نیچے سکرول کریں"</string>
+ <string name="downward_arrow" msgid="2292427714411156088">"نیچے کی طرف تیر کا نشان"</string>
<string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> کو پھیلائیں"</string>
<string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> کو سکیڑیں"</string>
<string name="permission_sync_confirmation_title" msgid="4409622174437248702">"ایپس کو <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> پر وہی اجازتیں دیں جو <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong> پر دی گئی ہیں؟"</string>
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
index dd77c61..b2c1e60 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
@@ -339,7 +339,7 @@
private void onDiscoveryStateChanged(DiscoveryState newState) {
switch (newState) {
case IN_PROGRESS: {
- mTimeoutMessage.setText(null);
+ mTimeoutMessage.setVisibility(View.GONE);
mProgressBar.setIndeterminate(true);
break;
}
@@ -351,6 +351,7 @@
R.string.message_discovery_soft_timeout,
deviceType, discoveryType, profile);
mTimeoutMessage.setText(message);
+ mTimeoutMessage.setVisibility(View.VISIBLE);
break;
}
case FINISHED_STOPPED: {
@@ -363,6 +364,7 @@
}
}
mTimeoutMessage.setText(getString(R.string.message_discovery_hard_timeout));
+ mTimeoutMessage.setVisibility(View.VISIBLE);
}
mProgressBar.setIndeterminate(false);
break;
@@ -528,6 +530,7 @@
mVendorHeader.setVisibility(View.VISIBLE);
mProfileIcon.setVisibility(View.GONE);
mDeviceListRecyclerView.setVisibility(View.GONE);
+ mTimeoutMessage.setVisibility(View.GONE);
mProgressBar.setVisibility(View.GONE);
mBorderBottom.setVisibility(View.GONE);
}
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java
index d8348d1..8f12cb4 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java
@@ -101,7 +101,7 @@
void setDevices(List<DeviceFilterPair<?>> devices) {
mDevices = devices;
- notifyDataSetChanged();
+ notifyItemRangeInserted(devices.size(), mDevices.size());
}
static class ViewHolder extends RecyclerView.ViewHolder {
diff --git a/packages/DynamicSystemInstallationService/res/values-mr/strings.xml b/packages/DynamicSystemInstallationService/res/values-mr/strings.xml
index 3b6741d..b18ce0e 100644
--- a/packages/DynamicSystemInstallationService/res/values-mr/strings.xml
+++ b/packages/DynamicSystemInstallationService/res/values-mr/strings.xml
@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="keyguard_description" msgid="8582605799129954556">"कृपया तुमचा पासवर्ड एंटर करा आणि डायनॅमिक सिस्टम अपडेट वर जा"</string>
- <string name="notification_install_completed" msgid="6252047868415172643">"डायनॅमिक सिस्टम तयार आहे. ती वापरणे सुरू करण्यासाठी, तुमचे डिव्हाइस रीस्टार्ट करा."</string>
+ <string name="keyguard_description" msgid="8582605799129954556">"कृपया तुमचा पासवर्ड एंटर करा आणि डायनॅमिक सिस्टीम अपडेट वर जा"</string>
+ <string name="notification_install_completed" msgid="6252047868415172643">"डायनॅमिक सिस्टीम तयार आहे. ती वापरणे सुरू करण्यासाठी, तुमचे डिव्हाइस रीस्टार्ट करा."</string>
<string name="notification_install_inprogress" msgid="7383334330065065017">"इंस्टॉल प्रगतीपथावर आहे"</string>
<string name="notification_install_failed" msgid="4066039210317521404">"इंस्टॉल करता आली नाही"</string>
<string name="notification_image_validation_failed" msgid="2720357826403917016">"इमेज प्रमाणित करता आली नाही. इंस्टॉलेशन रद्द करा."</string>
- <string name="notification_dynsystem_in_use" msgid="1053194595682188396">"सध्या डायनॅमिक सिस्टम रन करत आहे. मूळ Android आवृत्ती वापरण्यासाठी रीस्टार्ट करा."</string>
+ <string name="notification_dynsystem_in_use" msgid="1053194595682188396">"सध्या डायनॅमिक सिस्टीम रन करत आहे. मूळ Android आवृत्ती वापरण्यासाठी रीस्टार्ट करा."</string>
<string name="notification_action_cancel" msgid="5929299408545961077">"रद्द करा"</string>
<string name="notification_action_discard" msgid="1817481003134947493">"काढून टाका"</string>
<string name="notification_action_reboot_to_dynsystem" msgid="4015817159115912479">"रीस्टार्ट करा"</string>
<string name="notification_action_reboot_to_origin" msgid="4013901243271889897">"रीस्टार्ट करा"</string>
- <string name="toast_dynsystem_discarded" msgid="1733249860276017050">"डायनॅमिक सिस्टम काढून टाकली"</string>
- <string name="toast_failed_to_reboot_to_dynsystem" msgid="6336737274625452067">"डायनॅमिक सिस्टम रीस्टार्ट किंवा लोड करू शकत नाही"</string>
- <string name="toast_failed_to_disable_dynsystem" msgid="3285742944977744413">"डायनॅमिक सिस्टम बंद करता आली नाही"</string>
+ <string name="toast_dynsystem_discarded" msgid="1733249860276017050">"डायनॅमिक सिस्टीम काढून टाकली"</string>
+ <string name="toast_failed_to_reboot_to_dynsystem" msgid="6336737274625452067">"डायनॅमिक सिस्टीम रीस्टार्ट किंवा लोड करू शकत नाही"</string>
+ <string name="toast_failed_to_disable_dynsystem" msgid="3285742944977744413">"डायनॅमिक सिस्टीम बंद करता आली नाही"</string>
</resources>
diff --git a/packages/EasterEgg/src/com/android/egg/landroid/Autopilot.kt b/packages/EasterEgg/src/com/android/egg/landroid/Autopilot.kt
index 8214c54..a4803d2 100644
--- a/packages/EasterEgg/src/com/android/egg/landroid/Autopilot.kt
+++ b/packages/EasterEgg/src/com/android/egg/landroid/Autopilot.kt
@@ -44,7 +44,7 @@
if (enabled)
listOf(
"---- AUTOPILOT ENGAGED ----",
- "TGT: " + (target?.name?.toUpperCase() ?: "SELECTING..."),
+ "TGT: " + (target?.name?.uppercase() ?: "SELECTING..."),
"EXE: $strategy" + if (debug.isNotEmpty()) " ($debug)" else "",
)
.joinToString("\n")
diff --git a/packages/EasterEgg/src/com/android/egg/landroid/MainActivity.kt b/packages/EasterEgg/src/com/android/egg/landroid/MainActivity.kt
index 95a60c7..61ea08e 100644
--- a/packages/EasterEgg/src/com/android/egg/landroid/MainActivity.kt
+++ b/packages/EasterEgg/src/com/android/egg/landroid/MainActivity.kt
@@ -236,8 +236,8 @@
((closest.pos - pos).mag() - closest.radius).toInt()
listOfNotNull(
landing?.let {
- "LND: ${it.planet.name.toUpperCase()}\n" +
- "JOB: ${it.text.toUpperCase()}"
+ "LND: ${it.planet.name.uppercase()}\n" +
+ "JOB: ${it.text.uppercase()}"
}
?: if (distToClosest < 10_000) {
"ALT: $distToClosest"
diff --git a/packages/EasterEgg/src/com/android/egg/landroid/UniverseProgressNotifier.kt b/packages/EasterEgg/src/com/android/egg/landroid/UniverseProgressNotifier.kt
index bb3a04d..705d9e1 100644
--- a/packages/EasterEgg/src/com/android/egg/landroid/UniverseProgressNotifier.kt
+++ b/packages/EasterEgg/src/com/android/egg/landroid/UniverseProgressNotifier.kt
@@ -127,7 +127,7 @@
val eta = if (speed > 0) "%1.0fs".format(distToTarget / speed) else "???"
builder.setContentTitle("headed to: ${target.name}")
builder.setContentText(
- "autopilot is ${autopilot.strategy.toLowerCase()}" +
+ "autopilot is ${autopilot.strategy.lowercase()}" +
"\ndist: ${distToTarget}u // eta: $eta"
)
// fun fact: ProgressStyle was originally EnRouteStyle
diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout-v36/settingslib_expressive_main_switch_layout.xml b/packages/SettingsLib/MainSwitchPreference/res/layout-v36/settingslib_expressive_main_switch_layout.xml
index 94c6924..7adbfbf 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/layout-v36/settingslib_expressive_main_switch_layout.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/layout-v36/settingslib_expressive_main_switch_layout.xml
@@ -19,14 +19,14 @@
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:importantForAccessibility="no">
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
<com.android.settingslib.widget.MainSwitchBar
android:id="@+id/settingslib_main_switch_bar"
android:visibility="gone"
android:layout_height="wrap_content"
- android:layout_width="match_parent" />
+ android:layout_width="match_parent"
+ android:importantForAccessibility="no" />
</FrameLayout>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_layout.xml b/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_layout.xml
index bef6e35..c5dd24c 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_layout.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_layout.xml
@@ -17,14 +17,14 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
- android:layout_width="match_parent"
- android:importantForAccessibility="no">
+ android:layout_width="match_parent">
<com.android.settingslib.widget.MainSwitchBar
android:id="@+id/settingslib_main_switch_bar"
android:visibility="gone"
android:layout_height="wrap_content"
- android:layout_width="match_parent" />
+ android:layout_width="match_parent"
+ android:importantForAccessibility="no" />
</FrameLayout>
diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
index d883fb0..5170581 100644
--- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
+++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
@@ -80,7 +80,14 @@
mainSwitchBar.setIconSpaceReserved(isIconSpaceReserved());
// To support onPreferenceChange callback, it needs to call callChangeListener() when
// MainSwitchBar is clicked.
- mainSwitchBar.setOnClickListener(view -> callChangeListener(isChecked()));
+ mainSwitchBar.setOnClickListener(view -> {
+ boolean isChecked = isChecked();
+ if (!callChangeListener(isChecked)) {
+ // Change checked state back if listener doesn't like it.
+ // Note that CompoundButton maintains internal state to avoid infinite recursion.
+ mainSwitchBar.setChecked(!isChecked);
+ }
+ });
// Remove all listeners to 1. avoid triggering listener when update UI 2. prevent potential
// listener leaking when the view holder is reused by RecyclerView
@@ -88,7 +95,11 @@
mainSwitchBar.setChecked(isChecked());
mainSwitchBar.addOnSwitchChangeListener(this);
- mainSwitchBar.show();
+ if (isVisible()) {
+ mainSwitchBar.show();
+ } else {
+ mainSwitchBar.hide();
+ }
}
@Override
@@ -101,7 +112,10 @@
/**
* Adds a listener for switch changes
+ *
+ * @deprecated Use {@link #setOnPreferenceChangeListener(OnPreferenceChangeListener)}
*/
+ @Deprecated
public void addOnSwitchChangeListener(OnCheckedChangeListener listener) {
if (!mSwitchChangeListeners.contains(listener)) {
mSwitchChangeListeners.add(listener);
@@ -110,7 +124,10 @@
/**
* Remove a listener for switch changes
+ *
+ * @deprecated Use {@link #setOnPreferenceChangeListener(OnPreferenceChangeListener)}
*/
+ @Deprecated
public void removeOnSwitchChangeListener(OnCheckedChangeListener listener) {
mSwitchChangeListeners.remove(listener);
}
diff --git a/packages/SettingsLib/SelectorWithWidgetPreference/res/layout-v36/preference_selector_with_widget.xml b/packages/SettingsLib/SelectorWithWidgetPreference/res/layout-v36/settingslib_expressive_preference_selector_with_widget.xml
similarity index 100%
rename from packages/SettingsLib/SelectorWithWidgetPreference/res/layout-v36/preference_selector_with_widget.xml
rename to packages/SettingsLib/SelectorWithWidgetPreference/res/layout-v36/settingslib_expressive_preference_selector_with_widget.xml
diff --git a/packages/SettingsLib/SelectorWithWidgetPreference/src/com/android/settingslib/widget/SelectorWithWidgetPreference.java b/packages/SettingsLib/SelectorWithWidgetPreference/src/com/android/settingslib/widget/SelectorWithWidgetPreference.java
index cde8b33..465b6cc 100644
--- a/packages/SettingsLib/SelectorWithWidgetPreference/src/com/android/settingslib/widget/SelectorWithWidgetPreference.java
+++ b/packages/SettingsLib/SelectorWithWidgetPreference/src/com/android/settingslib/widget/SelectorWithWidgetPreference.java
@@ -238,7 +238,10 @@
} else {
setWidgetLayoutResource(R.layout.settingslib_preference_widget_radiobutton);
}
- setLayoutResource(R.layout.preference_selector_with_widget);
+ int resID = SettingsThemeHelper.isExpressiveTheme(context)
+ ? R.layout.settingslib_expressive_preference_selector_with_widget
+ : R.layout.preference_selector_with_widget;
+ setLayoutResource(resID);
setIconSpaceReserved(false);
final TypedArray a =
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 0446873..9aa0bc3 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_round_background.xml
@@ -19,12 +19,16 @@
android:color="?android:colorControlHighlight">
<item
android:start="?android:attr/listPreferredItemPaddingStart"
- android:end="?android:attr/listPreferredItemPaddingEnd">
+ android:end="?android:attr/listPreferredItemPaddingEnd"
+ android:top="2dp"
+ android:bottom="16dp">
<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>
+</ripple>
\ No newline at end of file
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 25a936d..554cba5 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,7 +19,9 @@
android:color="?android:colorControlHighlight">
<item
android:start="?android:attr/listPreferredItemPaddingStart"
- android:end="?android:attr/listPreferredItemPaddingEnd">
+ android:end="?android:attr/listPreferredItemPaddingEnd"
+ android:top="2dp"
+ android:bottom="16dp">
<shape android:shape="rectangle">
<solid
android:color="@color/settingslib_materialColorSurfaceBright" />
@@ -28,6 +30,8 @@
android:bottomLeftRadius="@dimen/settingslib_preference_corner_radius"
android:topRightRadius="4dp"
android:bottomRightRadius="@dimen/settingslib_preference_corner_radius" />
+ <padding
+ android:bottom="16dp"/>
</shape>
</item>
-</ripple>
+</ripple>
\ No newline at end of file
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 db2800e..c0c0869 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,7 +19,8 @@
<item
android:bottom="16dp"
android:end="?android:attr/listPreferredItemPaddingEnd"
- android:start="?android:attr/listPreferredItemPaddingStart">
+ android:start="?android:attr/listPreferredItemPaddingStart"
+ android:top="2dp">
<shape
android:shape="rectangle"
android:tint="?android:attr/colorAccent">
@@ -28,7 +29,8 @@
android:bottomRightRadius="@dimen/settingslib_preference_corner_radius"
android:topLeftRadius="4dp"
android:topRightRadius="4dp" />
+ <padding android:bottom="16dp" />
<solid android:color="#42000000" />
</shape>
</item>
-</ripple>
+</ripple>
\ No newline at end of file
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 98f95d92..543b237 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,7 +19,9 @@
android:color="?android:colorControlHighlight">
<item
android:start="?android:attr/listPreferredItemPaddingStart"
- android:end="?android:attr/listPreferredItemPaddingEnd">
+ android:end="?android:attr/listPreferredItemPaddingEnd"
+ android:top="2dp"
+ android:bottom="16dp">
<shape android:shape="rectangle">
<solid
android:color="@color/settingslib_materialColorSurfaceContainer" />
@@ -28,6 +30,8 @@
android:bottomLeftRadius="@dimen/settingslib_preference_corner_radius"
android:topRightRadius="4dp"
android:bottomRightRadius="@dimen/settingslib_preference_corner_radius" />
+ <padding
+ android:bottom="16dp"/>
</shape>
</item>
-</ripple>
+</ripple>
\ No newline at end of file
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 c4286fd..b89a0dd 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,7 +19,8 @@
android:color="?android:colorControlHighlight">
<item
android:start="?android:attr/listPreferredItemPaddingStart"
- android:end="?android:attr/listPreferredItemPaddingEnd">
+ android:end="?android:attr/listPreferredItemPaddingEnd"
+ android:top="2dp">
<shape android:shape="rectangle">
<solid
android:color="@color/settingslib_materialColorSurfaceBright" />
@@ -27,4 +28,4 @@
android:radius="4dp" />
</shape>
</item>
-</ripple>
+</ripple>
\ No newline at end of file
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 194cdb0..8099d9b 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,7 +18,8 @@
android:color="?android:colorControlHighlight">
<item
android:end="?android:attr/listPreferredItemPaddingEnd"
- android:start="?android:attr/listPreferredItemPaddingStart">
+ android:start="?android:attr/listPreferredItemPaddingStart"
+ android:top="2dp">
<shape
android:shape="rectangle"
android:tint="?android:attr/colorAccent">
@@ -26,4 +27,4 @@
<solid android:color="#42000000" />
</shape>
</item>
-</ripple>
+</ripple>
\ No newline at end of file
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 8bc2f2f..6d2cd1a 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,7 +19,8 @@
android:color="?android:colorControlHighlight">
<item
android:start="?android:attr/listPreferredItemPaddingStart"
- android:end="?android:attr/listPreferredItemPaddingEnd">
+ android:end="?android:attr/listPreferredItemPaddingEnd"
+ android:top="2dp">
<shape android:shape="rectangle">
<solid
android:color="@color/settingslib_materialColorSurfaceContainer" />
@@ -27,4 +28,4 @@
android:radius="4dp" />
</shape>
</item>
-</ripple>
+</ripple>
\ No newline at end of file
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 2341661..a119a4a 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,12 +19,14 @@
<item
android:bottom="16dp"
android:end="?android:attr/listPreferredItemPaddingEnd"
- android:start="?android:attr/listPreferredItemPaddingStart">
+ android:start="?android:attr/listPreferredItemPaddingStart"
+ android:top="2dp">
<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>
+</ripple>
\ No newline at end of file
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 99704f2d..bcdbf1d 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,12 +19,16 @@
android:color="?android:colorControlHighlight">
<item
android:start="?android:attr/listPreferredItemPaddingStart"
- android:end="?android:attr/listPreferredItemPaddingEnd">
+ android:end="?android:attr/listPreferredItemPaddingEnd"
+ android:top="2dp"
+ android:bottom="16dp">
<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>
+</ripple>
\ No newline at end of file
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 3a59386..7955e44 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,7 +19,8 @@
android:color="?android:colorControlHighlight">
<item
android:start="?android:attr/listPreferredItemPaddingStart"
- android:end="?android:attr/listPreferredItemPaddingEnd">
+ android:end="?android:attr/listPreferredItemPaddingEnd"
+ android:top="2dp">
<shape android:shape="rectangle">
<solid
android:color="@color/settingslib_materialColorSurfaceBright" />
@@ -30,4 +31,4 @@
android:bottomRightRadius="4dp" />
</shape>
</item>
-</ripple>
+</ripple>
\ No newline at end of file
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 edace29..052eb01 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,7 +19,8 @@
<item
android:color="?android:attr/colorAccent"
android:end="?android:attr/listPreferredItemPaddingEnd"
- android:start="?android:attr/listPreferredItemPaddingStart">
+ android:start="?android:attr/listPreferredItemPaddingStart"
+ android:top="2dp">
<shape
android:shape="rectangle"
android:tint="?android:attr/colorAccent">
@@ -31,4 +32,4 @@
<solid android:color="#42000000" />
</shape>
</item>
-</ripple>
+</ripple>
\ No newline at end of file
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 b2d6d9d..d4b658c 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,7 +19,8 @@
android:color="?android:colorControlHighlight">
<item
android:start="?android:attr/listPreferredItemPaddingStart"
- android:end="?android:attr/listPreferredItemPaddingEnd">
+ android:end="?android:attr/listPreferredItemPaddingEnd"
+ android:top="2dp">
<shape android:shape="rectangle">
<solid
android:color="@color/settingslib_materialColorSurfaceContainer" />
@@ -30,4 +31,4 @@
android:bottomRightRadius="4dp" />
</shape>
</item>
-</ripple>
+</ripple>
\ No newline at end of file
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 22cd873..8d12f01 100644
--- a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsBasePreferenceFragment.kt
+++ b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsBasePreferenceFragment.kt
@@ -16,7 +16,6 @@
package com.android.settingslib.widget
-import android.graphics.Rect
import android.os.Bundle
import android.view.LayoutInflater;
import android.view.View
@@ -25,7 +24,6 @@
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() {
@@ -45,7 +43,6 @@
if (SettingsThemeHelper.isExpressiveTheme(requireContext())) {
// Don't allow any divider in between the preferences in expressive design.
setDivider(null)
- this.listView.addItemDecoration(MarginItemDecoration())
}
}
@@ -54,18 +51,4 @@
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/SpaPrivileged/tests/Android.bp b/packages/SettingsLib/SpaPrivileged/tests/unit/Android.bp
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/Android.bp
rename to packages/SettingsLib/SpaPrivileged/tests/unit/Android.bp
diff --git a/packages/SettingsLib/SpaPrivileged/tests/AndroidManifest.xml b/packages/SettingsLib/SpaPrivileged/tests/unit/AndroidManifest.xml
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/AndroidManifest.xml
rename to packages/SettingsLib/SpaPrivileged/tests/unit/AndroidManifest.xml
diff --git a/packages/SettingsLib/SpaPrivileged/tests/res/values/strings.xml b/packages/SettingsLib/SpaPrivileged/tests/unit/res/values/strings.xml
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/res/values/strings.xml
rename to packages/SettingsLib/SpaPrivileged/tests/unit/res/values/strings.xml
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverAsUserFlowTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverAsUserFlowTest.kt
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverAsUserFlowTest.kt
rename to packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverAsUserFlowTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt
rename to packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BytesFormatterTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/framework/common/BytesFormatterTest.kt
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BytesFormatterTest.kt
rename to packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/framework/common/BytesFormatterTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt
rename to packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
rename to packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
rename to packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt
rename to packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsPermissionControllerTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/AppOpsPermissionControllerTest.kt
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsPermissionControllerTest.kt
rename to packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/AppOpsPermissionControllerTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/AppOpsRepositoryTest.kt
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsRepositoryTest.kt
rename to packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/AppOpsRepositoryTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt
rename to packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppStorageRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/AppStorageRepositoryTest.kt
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppStorageRepositoryTest.kt
rename to packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/AppStorageRepositoryTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfosTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfosTest.kt
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfosTest.kt
rename to packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfosTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt
rename to packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagersTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/PackageManagersTest.kt
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagersTest.kt
rename to packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/PackageManagersTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PermissionsChangedFlowTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/PermissionsChangedFlowTest.kt
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PermissionsChangedFlowTest.kt
rename to packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/app/PermissionsChangedFlowTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedModeTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedModeTest.kt
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedModeTest.kt
rename to packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedModeTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalBooleanTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalBooleanTest.kt
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalBooleanTest.kt
rename to packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalBooleanTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalChangeFlowTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalChangeFlowTest.kt
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalChangeFlowTest.kt
rename to packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalChangeFlowTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSecureBooleanTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSecureBooleanTest.kt
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSecureBooleanTest.kt
rename to packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSecureBooleanTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSecureStringTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSecureStringTest.kt
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSecureStringTest.kt
rename to packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSecureStringTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt
rename to packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt
rename to packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItemTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItemTest.kt
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItemTest.kt
rename to packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItemTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
rename to packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItemTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItemTest.kt
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItemTest.kt
rename to packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItemTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
rename to packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
rename to packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt
rename to packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
rename to packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt
rename to packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/common/UserProfilePagerTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/common/UserProfilePagerTest.kt
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/common/UserProfilePagerTest.kt
rename to packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/common/UserProfilePagerTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedMainSwitchPreferenceTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/preference/RestrictedMainSwitchPreferenceTest.kt
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedMainSwitchPreferenceTest.kt
rename to packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/preference/RestrictedMainSwitchPreferenceTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreferenceTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreferenceTest.kt
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreferenceTest.kt
rename to packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreferenceTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceTest.kt
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceTest.kt
rename to packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreferenceTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreferenceTest.kt
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreferenceTest.kt
rename to packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreferenceTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt
rename to packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/RestrictedTestUtils.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/tests/testutils/RestrictedTestUtils.kt
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/RestrictedTestUtils.kt
rename to packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/tests/testutils/RestrictedTestUtils.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt
rename to packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt
rename to packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListProvider.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListProvider.kt
similarity index 100%
rename from packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListProvider.kt
rename to packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListProvider.kt
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index 2fc946b..07849e0 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -81,7 +81,7 @@
<string name="speed_label_fast" msgid="2677719134596044051">"Élevée"</string>
<string name="speed_label_very_fast" msgid="8215718029533182439">"Très rapide"</string>
<string name="wifi_passpoint_expired" msgid="6540867261754427561">"Arrivé à expiration"</string>
- <string name="preference_summary_default_combination" msgid="2644094566845577901">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
+ <string name="preference_summary_default_combination" msgid="2644094566845577901">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
<string name="bluetooth_disconnected" msgid="7739366554710388701">"Déconnecté"</string>
<string name="bluetooth_disconnecting" msgid="7638892134401574338">"Déconnexion…"</string>
<string name="bluetooth_connecting" msgid="5871702668260192755">"Connexion…"</string>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index 826d601..8c77b08 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -561,7 +561,7 @@
<string name="retail_demo_reset_next" msgid="3688129033843885362">"पुढील"</string>
<string name="retail_demo_reset_title" msgid="1866911701095959800">"पासवर्ड आवश्यक"</string>
<string name="active_input_method_subtypes" msgid="4232680535471633046">"सक्रिय इनपुट पद्धती"</string>
- <string name="use_system_language_to_select_input_method_subtypes" msgid="4865195835541387040">"सिस्टम भाषा वापरा"</string>
+ <string name="use_system_language_to_select_input_method_subtypes" msgid="4865195835541387040">"सिस्टीम भाषा वापरा"</string>
<string name="failed_to_open_app_settings_toast" msgid="764897252657692092">"<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> साठी सेटिंग्ज उघडण्यात अयशस्वी"</string>
<string name="ime_security_warning" msgid="6547562217880551450">"ही इनपुट पद्धत पासवर्ड आणि क्रेडिट कार्ड नंबर यासह, तुम्ही टाइप करता तो सर्व मजकूर संकलित करण्यात सक्षम होऊ शकते. ही <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> ॲपवरून येते. ही इनपुट पद्धत वापरायची?"</string>
<string name="direct_boot_unaware_dialog_message" msgid="7845398276735021548">"टीप: रीबूट केल्यानंतर, तुम्ही तुमचा फोन अनलॉक करेपर्यंत हे अॅप सुरू होऊ शकत नाही"</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index e4381d6..11f410f 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -530,7 +530,7 @@
<string name="battery_info_status_charging_dock" msgid="8573274094093364791">"A carregar"</string>
<string name="battery_info_status_discharging" msgid="6962689305413556485">"Não está a carregar"</string>
<string name="battery_info_status_not_charging" msgid="1103084691314264664">"Ligado, mas não está a carregar"</string>
- <string name="battery_info_status_full" msgid="1339002294876531312">"Carregada"</string>
+ <string name="battery_info_status_full" msgid="1339002294876531312">"Bateria carregada"</string>
<string name="battery_info_status_full_charged" msgid="3536054261505567948">"Totalmente carregada"</string>
<string name="battery_info_status_charging_on_hold" msgid="6364355145521694438">"Carregamento em espera"</string>
<string name="battery_info_status_charging_v2" msgid="6118522107222245505">"Carregamento"</string>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index 6095e8c..c2c035c 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -109,7 +109,7 @@
<string name="bluetooth_saved_device" msgid="4895871321722311428">"Đã lưu"</string>
<string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Đang hoạt động (chỉ tai trái)"</string>
<string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Đang hoạt động (chỉ tai phải)"</string>
- <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Đang hoạt động (cả tai phải và tai trái)"</string>
+ <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Đang hoạt động (trái và phải)"</string>
<string name="bluetooth_hearing_device_ambient_error" msgid="6035857289108813878">"Không cập nhật được âm lượng xung quanh"</string>
<string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Đang hoạt động (chỉ phát nội dung đa phương tiện). Còn <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> pin."</string>
<string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Đang hoạt động (chỉ phát nội dung đa phương tiện). Bên trái: Còn <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> pin. Bên phải: Còn <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> pin."</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BatteryLevelsInfo.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BatteryLevelsInfo.kt
new file mode 100644
index 0000000..b52a901
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BatteryLevelsInfo.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.settingslib.bluetooth
+
+/**
+ * BatteryLevelsInfo contains the battery levels of different components of a bluetooth device.
+ * The range of a valid battery level is [0-100], and -1 if the battery level is not applicable.
+ */
+data class BatteryLevelsInfo(
+ val leftBatteryLevel: Int,
+ val rightBatteryLevel: Int,
+ val caseBatteryLevel: Int,
+ val overallBatteryLevel: Int,
+)
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index bb96041..3646842 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -44,10 +44,12 @@
import android.util.Log;
import android.util.LruCache;
import android.util.Pair;
+import android.view.InputDevice;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import androidx.annotation.WorkerThread;
import com.android.internal.util.ArrayUtils;
import com.android.settingslib.R;
@@ -62,6 +64,7 @@
import java.sql.Timestamp;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
@@ -150,6 +153,9 @@
private boolean mIsHearingAidProfileConnectedFail = false;
private boolean mIsLeAudioProfileConnectedFail = false;
private boolean mUnpairing;
+ @Nullable
+ private final InputDevice mInputDevice;
+ private final boolean mIsDeviceStylus;
// Group second device for Hearing Aid
private CachedBluetoothDevice mSubDevice;
@@ -193,6 +199,8 @@
mGroupId = BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
initDrawableCache();
mUnpairing = false;
+ mInputDevice = BluetoothUtils.getInputDevice(mContext, getAddress());
+ mIsDeviceStylus = BluetoothUtils.isDeviceStylus(mInputDevice, this);
}
/** Clears any pending messages in the message queue. */
@@ -1622,6 +1630,86 @@
}
}
+ /**
+ * Returns the battery levels of all components of the bluetooth device. If no battery info is
+ * available then returns null.
+ */
+ @WorkerThread
+ @Nullable
+ public BatteryLevelsInfo getBatteryLevelsInfo() {
+ // Try getting the battery information from metadata.
+ BatteryLevelsInfo metadataSourceBattery = getBatteryFromMetadata();
+ if (metadataSourceBattery != null) {
+ return metadataSourceBattery;
+ }
+ // Get the battery information from Bluetooth service.
+ return getBatteryFromBluetoothService();
+ }
+
+ @Nullable
+ private BatteryLevelsInfo getBatteryFromMetadata() {
+ if (BluetoothUtils.getBooleanMetaData(mDevice,
+ BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) {
+ // The device is untethered headset, containing both earbuds and case.
+ int leftBattery =
+ BluetoothUtils.getIntMetaData(
+ mDevice, BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY);
+ int rightBattery =
+ BluetoothUtils.getIntMetaData(
+ mDevice, BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY);
+ int caseBattery =
+ BluetoothUtils.getIntMetaData(
+ mDevice, BluetoothDevice.METADATA_UNTETHERED_CASE_BATTERY);
+
+ if (leftBattery <= BluetoothDevice.BATTERY_LEVEL_UNKNOWN
+ && rightBattery <= BluetoothDevice.BATTERY_LEVEL_UNKNOWN
+ && caseBattery <= BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
+ Log.d(TAG, "No battery info from metadata is available for untethered device "
+ + mDevice.getAnonymizedAddress());
+ return null;
+ } else {
+ int overallBattery =
+ Arrays.stream(new int[]{leftBattery, rightBattery, caseBattery})
+ .filter(battery -> battery > BluetoothDevice.BATTERY_LEVEL_UNKNOWN)
+ .min()
+ .orElse(BluetoothDevice.BATTERY_LEVEL_UNKNOWN);
+ Log.d(TAG, "Acquired battery info from metadata for untethered device "
+ + mDevice.getAnonymizedAddress()
+ + " left earbud battery: " + leftBattery
+ + " right earbud battery: " + rightBattery
+ + " case battery: " + caseBattery
+ + " overall battery: " + overallBattery);
+ return new BatteryLevelsInfo(
+ leftBattery, rightBattery, caseBattery, overallBattery);
+ }
+ } else if (mInputDevice != null || mIsDeviceStylus) {
+ // The device is input device, using METADATA_MAIN_BATTERY field to get battery info.
+ int overallBattery = BluetoothUtils.getIntMetaData(
+ mDevice, BluetoothDevice.METADATA_MAIN_BATTERY);
+ if (overallBattery <= BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
+ Log.d(TAG, "No battery info from metadata is available for input device "
+ + mDevice.getAnonymizedAddress());
+ return null;
+ } else {
+ Log.d(TAG, "Acquired battery info from metadata for input device "
+ + mDevice.getAnonymizedAddress()
+ + " overall battery: " + overallBattery);
+ return new BatteryLevelsInfo(
+ BluetoothDevice.BATTERY_LEVEL_UNKNOWN,
+ BluetoothDevice.BATTERY_LEVEL_UNKNOWN,
+ BluetoothDevice.BATTERY_LEVEL_UNKNOWN,
+ overallBattery);
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ private BatteryLevelsInfo getBatteryFromBluetoothService() {
+ // TODO(b/397847825): Implement the logic to get battery from Bluetooth service.
+ return null;
+ }
+
private CharSequence getTvBatterySummary(int mainBattery, int leftBattery, int rightBattery,
int lowBatteryColorRes) {
// Since there doesn't seem to be a way to use format strings to add the
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index f6e26a7..ed53d8d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -40,12 +40,14 @@
import android.bluetooth.BluetoothStatusCodes;
import android.content.Context;
import android.graphics.drawable.BitmapDrawable;
+import android.hardware.input.InputManager;
import android.media.AudioManager;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.text.Spannable;
import android.text.style.ForegroundColorSpan;
import android.util.LruCache;
+import android.view.InputDevice;
import com.android.settingslib.R;
import com.android.settingslib.media.flags.Flags;
@@ -77,8 +79,10 @@
private static final String DEVICE_ALIAS_NEW = "TestAliasNew";
private static final String TWS_BATTERY_LEFT = "15";
private static final String TWS_BATTERY_RIGHT = "25";
+ private static final String TWS_BATTERY_CASE = "10";
private static final String TWS_LOW_BATTERY_THRESHOLD_LOW = "10";
private static final String TWS_LOW_BATTERY_THRESHOLD_HIGH = "25";
+ private static final String MAIN_BATTERY = "80";
private static final String TEMP_BOND_METADATA =
"<TEMP_BOND_TYPE>le_audio_sharing</TEMP_BOND_TYPE>";
private static final short RSSI_1 = 10;
@@ -87,6 +91,8 @@
private static final boolean JUSTDISCOVERED_2 = false;
private static final int LOW_BATTERY_COLOR = android.R.color.holo_red_dark;
private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25;
+ private static final int TEST_DEVICE_ID = 123;
+ private final InputDevice mInputDevice = mock(InputDevice.class);
@Mock
private LocalBluetoothProfileManager mProfileManager;
@Mock
@@ -116,6 +122,8 @@
private LocalBluetoothLeBroadcastAssistant mAssistant;
@Mock
private BluetoothLeBroadcastReceiveState mLeBroadcastReceiveState;
+ @Mock
+ private InputManager mInputManager;
private CachedBluetoothDevice mCachedDevice;
private CachedBluetoothDevice mSubCachedDevice;
private AudioManager mAudioManager;
@@ -2175,6 +2183,74 @@
assertThat(mCachedDevice.isHearingDevice()).isFalse();
}
+ @Test
+ public void getBatteryLevelsInfo_untetheredHeadsetWithBattery_returnBatteryLevelsInfo() {
+ when(mDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
+ "true".getBytes());
+ when(mDevice.getMetadata(BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY)).thenReturn(
+ TWS_BATTERY_LEFT.getBytes());
+ when(mDevice.getMetadata(BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY)).thenReturn(
+ TWS_BATTERY_RIGHT.getBytes());
+ when(mDevice.getMetadata(BluetoothDevice.METADATA_UNTETHERED_CASE_BATTERY)).thenReturn(
+ TWS_BATTERY_CASE.getBytes());
+
+ BatteryLevelsInfo batteryLevelsInfo = mCachedDevice.getBatteryLevelsInfo();
+
+ assertThat(batteryLevelsInfo.getLeftBatteryLevel()).isEqualTo(
+ Integer.parseInt(TWS_BATTERY_LEFT));
+ assertThat(batteryLevelsInfo.getRightBatteryLevel()).isEqualTo(
+ Integer.parseInt(TWS_BATTERY_RIGHT));
+ assertThat(batteryLevelsInfo.getCaseBatteryLevel()).isEqualTo(
+ Integer.parseInt(TWS_BATTERY_CASE));
+ assertThat(batteryLevelsInfo.getOverallBatteryLevel()).isEqualTo(
+ Integer.parseInt(TWS_BATTERY_CASE));
+ }
+
+ @Test
+ public void getBatteryLevelsInfo_inputDeviceWithBattery_returnBatteryLevelsInfo() {
+ when(mDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
+ "false".getBytes());
+ when(mDevice.getMetadata(BluetoothDevice.METADATA_MAIN_BATTERY)).thenReturn(
+ MAIN_BATTERY.getBytes());
+ when(mContext.getSystemService(InputManager.class)).thenReturn(mInputManager);
+ when(mInputManager.getInputDeviceIds()).thenReturn(new int[]{TEST_DEVICE_ID});
+ when(mInputManager.getInputDeviceBluetoothAddress(TEST_DEVICE_ID)).thenReturn(
+ DEVICE_ADDRESS);
+ when(mInputManager.getInputDevice(TEST_DEVICE_ID)).thenReturn(mInputDevice);
+
+ BatteryLevelsInfo batteryLevelsInfo = mCachedDevice.getBatteryLevelsInfo();
+
+ assertThat(batteryLevelsInfo.getLeftBatteryLevel()).isEqualTo(
+ BluetoothDevice.BATTERY_LEVEL_UNKNOWN);
+ assertThat(batteryLevelsInfo.getRightBatteryLevel()).isEqualTo(
+ BluetoothDevice.BATTERY_LEVEL_UNKNOWN);
+ assertThat(batteryLevelsInfo.getCaseBatteryLevel()).isEqualTo(
+ BluetoothDevice.BATTERY_LEVEL_UNKNOWN);
+ assertThat(batteryLevelsInfo.getOverallBatteryLevel()).isEqualTo(
+ Integer.parseInt(MAIN_BATTERY));
+ }
+
+ @Test
+ public void getBatteryLevelsInfo_stylusDeviceWithBattery_returnBatteryLevelsInfo() {
+ when(mDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
+ "false".getBytes());
+ when(mDevice.getMetadata(BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
+ BluetoothDevice.DEVICE_TYPE_STYLUS.getBytes());
+ when(mDevice.getMetadata(BluetoothDevice.METADATA_MAIN_BATTERY)).thenReturn(
+ MAIN_BATTERY.getBytes());
+
+ BatteryLevelsInfo batteryLevelsInfo = mCachedDevice.getBatteryLevelsInfo();
+
+ assertThat(batteryLevelsInfo.getLeftBatteryLevel()).isEqualTo(
+ BluetoothDevice.BATTERY_LEVEL_UNKNOWN);
+ assertThat(batteryLevelsInfo.getRightBatteryLevel()).isEqualTo(
+ BluetoothDevice.BATTERY_LEVEL_UNKNOWN);
+ assertThat(batteryLevelsInfo.getCaseBatteryLevel()).isEqualTo(
+ BluetoothDevice.BATTERY_LEVEL_UNKNOWN);
+ assertThat(batteryLevelsInfo.getOverallBatteryLevel()).isEqualTo(
+ Integer.parseInt(MAIN_BATTERY));
+ }
+
private void updateProfileStatus(LocalBluetoothProfile profile, int status) {
doReturn(status).when(profile).getConnectionStatus(mDevice);
mCachedDevice.onProfileStateChanged(profile, status);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/MainSwitchPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/MainSwitchPreferenceTest.java
index a47b4d5..093833e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/MainSwitchPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/MainSwitchPreferenceTest.java
@@ -18,13 +18,22 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
import android.content.Context;
import android.view.View;
import android.widget.TextView;
+import androidx.preference.PreferenceDataStore;
import androidx.preference.PreferenceViewHolder;
import androidx.test.core.app.ApplicationProvider;
+import com.android.settingslib.preference.PreferenceScreenFactory;
import com.android.settingslib.widget.mainswitch.R;
import org.junit.Before;
@@ -67,4 +76,31 @@
assertThat(mRootView.<MainSwitchBar>requireViewById(
R.id.settingslib_main_switch_bar).isChecked()).isTrue();
}
+
+ @Test
+ public void setOnPreferenceChangeListener() {
+ // Attach preference to preference screen, otherwise `Preference.performClick` does not
+ // interact with underlying datastore
+ new PreferenceScreenFactory(mContext).getOrCreatePreferenceScreen().addPreference(
+ mPreference);
+
+ PreferenceDataStore preferenceDataStore = mock(PreferenceDataStore.class);
+ // always return the provided default value
+ when(preferenceDataStore.getBoolean(any(), anyBoolean())).thenAnswer(
+ invocation -> invocation.getArguments()[1]);
+ mPreference.setPreferenceDataStore(preferenceDataStore);
+
+ String key = "key";
+ mPreference.setKey(key);
+ mPreference.setOnPreferenceChangeListener((preference, newValue) -> false);
+ mPreference.onBindViewHolder(mHolder);
+
+ mPreference.performClick();
+ verify(preferenceDataStore, never()).putBoolean(any(), anyBoolean());
+
+ mPreference.setOnPreferenceChangeListener((preference, newValue) -> true);
+
+ mPreference.performClick();
+ verify(preferenceDataStore).putBoolean(any(), anyBoolean());
+ }
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index f692601e..c010529 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -96,6 +96,7 @@
Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED,
Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY,
Settings.Secure.ACCESSIBILITY_AUTOCLICK_PANEL_POSITION,
+ Settings.Secure.ACCESSIBILITY_AUTOCLICK_REVERT_TO_LEFT_CLICK,
Settings.Secure.ACCESSIBILITY_LARGE_POINTER_ICON,
Settings.Secure.PREFERRED_TTY_MODE,
Settings.Secure.ENHANCED_VOICE_PRIVACY_ENABLED,
@@ -270,8 +271,6 @@
Settings.Secure.DEFAULT_NOTE_TASK_PROFILE,
Settings.Secure.CREDENTIAL_SERVICE,
Settings.Secure.CREDENTIAL_SERVICE_PRIMARY,
- Settings.Secure.EVEN_DIMMER_ACTIVATED,
- Settings.Secure.EVEN_DIMMER_MIN_NITS,
Settings.Secure.STYLUS_POINTER_ICON_ENABLED,
Settings.Secure.CAMERA_EXTENSIONS_FALLBACK,
Settings.Secure.VISUAL_QUERY_ACCESSIBILITY_DETECTION_ENABLED,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index e42d3fb..0ffdf53 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -114,9 +114,6 @@
VALIDATORS.put(Secure.FONT_WEIGHT_ADJUSTMENT, ANY_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.REDUCE_BRIGHT_COLORS_LEVEL, PERCENTAGE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.REDUCE_BRIGHT_COLORS_PERSIST_ACROSS_REBOOTS, BOOLEAN_VALIDATOR);
- VALIDATORS.put(Secure.EVEN_DIMMER_ACTIVATED, BOOLEAN_VALIDATOR);
- VALIDATORS.put(Secure.EVEN_DIMMER_MIN_NITS,
- new InclusiveFloatRangeValidator(0.0f, Float.MAX_VALUE));
VALIDATORS.put(Secure.TTS_DEFAULT_RATE, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.TTS_DEFAULT_PITCH, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.TTS_DEFAULT_SYNTH, PACKAGE_NAME_VALIDATOR);
@@ -147,6 +144,7 @@
VALIDATORS.put(Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ACCESSIBILITY_AUTOCLICK_DELAY, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.ACCESSIBILITY_AUTOCLICK_PANEL_POSITION, ANY_STRING_VALIDATOR);
+ VALIDATORS.put(Secure.ACCESSIBILITY_AUTOCLICK_REVERT_TO_LEFT_CLICK, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ACCESSIBILITY_LARGE_POINTER_ICON, BOOLEAN_VALIDATOR);
VALIDATORS.put(
Secure.PREFERRED_TTY_MODE,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index c29a5a2..e07832e 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1722,6 +1722,9 @@
Settings.Secure.ACCESSIBILITY_AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT,
SecureSettingsProto.Accessibility.AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT);
dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_AUTOCLICK_REVERT_TO_LEFT_CLICK,
+ SecureSettingsProto.Accessibility.AUTOCLICK_REVERT_TO_LEFT_CLICK);
+ dumpSetting(s, p,
Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED,
SecureSettingsProto.Accessibility.AUTOCLICK_ENABLED);
dumpSetting(s, p,
@@ -2186,15 +2189,6 @@
Settings.Secure.ENHANCED_VOICE_PRIVACY_ENABLED,
SecureSettingsProto.ENHANCED_VOICE_PRIVACY_ENABLED);
- final long evenDimmerToken = p.start(SecureSettingsProto.EVEN_DIMMER);
- dumpSetting(s, p,
- Settings.Secure.EVEN_DIMMER_ACTIVATED,
- SecureSettingsProto.EvenDimmer.EVEN_DIMMER_ACTIVATED);
- dumpSetting(s, p,
- Settings.Secure.EVEN_DIMMER_MIN_NITS,
- SecureSettingsProto.EvenDimmer.EVEN_DIMMER_MIN_NITS);
- p.end(evenDimmerToken);
-
dumpSetting(s, p,
Settings.Secure.EM_VALUE,
SecureSettingsProto.Accessibility.EM_VALUE);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index bc281ee..4a225bd 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -396,6 +396,8 @@
private volatile SystemConfigManager mSysConfigManager;
+ private PackageMonitor mPackageMonitor;
+
@GuardedBy("mLock")
private boolean mSyncConfigDisabledUntilReboot;
@@ -403,6 +405,7 @@
@EnabledSince(targetSdkVersion=android.os.Build.VERSION_CODES.S)
private static final long ENFORCE_READ_PERMISSION_FOR_MULTI_SIM_DATA_CALL = 172670679L;
+
@Override
public boolean onCreate() {
Settings.setInSystemServer();
@@ -1036,7 +1039,7 @@
}
}, userFilter);
- PackageMonitor monitor = new PackageMonitor() {
+ mPackageMonitor = new PackageMonitor() {
@Override
public void onPackageRemoved(String packageName, int uid) {
synchronized (mLock) {
@@ -1062,7 +1065,7 @@
};
// package changes
- monitor.register(getContext(), BackgroundThread.getHandler().getLooper(),
+ mPackageMonitor.register(getContext(), BackgroundThread.getHandler().getLooper(),
UserHandle.ALL, true);
}
diff --git a/packages/Shell/OWNERS b/packages/Shell/OWNERS
index 2fa707e..897c1fe 100644
--- a/packages/Shell/OWNERS
+++ b/packages/Shell/OWNERS
@@ -4,7 +4,6 @@
jsharkey@android.com
felipeal@google.com
nandana@google.com
-svetoslavganov@google.com
hackbod@google.com
yamasani@google.com
patb@google.com
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index b53cb27..5b48566 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -727,6 +727,7 @@
"TraceurCommon",
"Traceur-res",
"aconfig_settings_flags_lib",
+ "kairos",
],
}
diff --git a/packages/SystemUI/BUILD_OWNERS b/packages/SystemUI/BUILD_OWNERS
new file mode 100644
index 0000000..4aadee1
--- /dev/null
+++ b/packages/SystemUI/BUILD_OWNERS
@@ -0,0 +1,22 @@
+# Build file owners for System UI. Owners should consider the following:
+#
+# - Does the change negatively affect developer builds? Will it make
+# the build slower?
+#
+# - Does the change add unnecessary dependencies or compilation steps
+# that will be difficult to refactor?
+#
+# For more information, see http://go/sysui-build-owners
+
+dsandler@android.com
+
+caitlinshk@google.com
+ccross@android.com
+cinek@google.com
+jernej@google.com
+mankoff@google.com
+nicomazz@google.com
+peskal@google.com
+pixel@google.com
+saff@google.com
+vadimt@google.com
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index f5c0233..ab3fa1b 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -125,3 +125,6 @@
uwaisashraf@google.com
vinayjoglekar@google.com
willosborn@google.com
+
+per-file *.mk,{**/,}Android.bp = set noparent
+per-file *.mk,{**/,}Android.bp = file:BUILD_OWNERS
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 7e8d549..a900817 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1434,16 +1434,6 @@
}
flag {
- name: "notification_media_manager_background_execution"
- namespace: "systemui"
- description: "Decide whether to execute binder calls in background thread"
- bug: "336612071"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "dozeui_scheduling_alarms_background_execution"
namespace: "systemui"
description: "Decide whether to execute binder calls to schedule alarms in background thread"
@@ -2103,3 +2093,13 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "always_compose_qs_ui_fragment"
+ namespace: "systemui"
+ description: "Have QQS and QS scenes in the Compose fragment always composed, not just when it should be visible."
+ bug: "389985793"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GSFAxes.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GSFAxes.kt
index f4e0361..96feeed 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GSFAxes.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GSFAxes.kt
@@ -91,8 +91,8 @@
private val AXIS_MAP =
listOf(WEIGHT, WIDTH, SLANT, ROUND, GRADE, OPTICAL_SIZE, ITALIC)
- .map { def -> def.tag.toLowerCase() to def }
+ .map { def -> def.tag.lowercase() to def }
.toMap()
- fun getAxis(axis: String): AxisDefinition? = AXIS_MAP[axis.toLowerCase()]
+ fun getAxis(axis: String): AxisDefinition? = AXIS_MAP[axis.lowercase()]
}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DoNotDirectlyConstructKosmosDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DoNotDirectlyConstructKosmosDetector.kt
new file mode 100644
index 0000000..40fac0d
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DoNotDirectlyConstructKosmosDetector.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 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.internal.systemui.lint
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiMethod
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.getContainingUClass
+
+/**
+ * Detects direct construction of `Kosmos()` in subclasses of SysuiTestCase, which can and should
+ * use `testKosmos`. See go/thetiger
+ */
+class DoNotDirectlyConstructKosmosDetector : Detector(), SourceCodeScanner {
+ override fun getApplicableConstructorTypes() = listOf("com.android.systemui.kosmos.Kosmos")
+
+ override fun visitConstructor(
+ context: JavaContext,
+ node: UCallExpression,
+ constructor: PsiMethod,
+ ) {
+ val superClassNames =
+ node.getContainingUClass()?.superTypes.orEmpty().map { it.resolve()?.qualifiedName }
+ if (superClassNames.contains("com.android.systemui.SysuiTestCase")) {
+ context.report(
+ issue = ISSUE,
+ scope = node,
+ location = context.getLocation(node.methodIdentifier),
+ message = "Prefer testKosmos to direct Kosmos() in sysui tests. go/testkosmos",
+ )
+ }
+ super.visitConstructor(context, node, constructor)
+ }
+
+ companion object {
+ @JvmStatic
+ val ISSUE =
+ Issue.create(
+ id = "DoNotDirectlyConstructKosmos",
+ briefDescription =
+ "Prefer testKosmos to direct Kosmos() in sysui tests. go/testkosmos",
+ explanation =
+ """
+ SysuiTestCase.testKosmos allows us to pre-populate a Kosmos instance with
+ team-standard fixture values, and makes it easier to make centralized changes
+ when necessary. See go/testkosmos
+ """,
+ category = Category.TESTING,
+ priority = 8,
+ severity = Severity.WARNING,
+ implementation =
+ Implementation(
+ DoNotDirectlyConstructKosmosDetector::class.java,
+ Scope.JAVA_FILE_SCOPE,
+ ),
+ )
+ }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RunTestShouldUseKosmosDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RunTestShouldUseKosmosDetector.kt
index 4927fb9..13ffa6c 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RunTestShouldUseKosmosDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RunTestShouldUseKosmosDetector.kt
@@ -29,16 +29,12 @@
import org.jetbrains.uast.UCallExpression
import org.jetbrains.uast.getContainingUFile
-/**
- * Detects test function naming violations regarding use of the backtick-wrapped space-allowed
- * feature of Kotlin functions.
- */
+/** Detects use of `TestScope.runTest` when we should use `Kosmos.runTest` by go/kosmos-runtest */
class RunTestShouldUseKosmosDetector : Detector(), SourceCodeScanner {
override fun getApplicableMethodNames() = listOf("runTest")
override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
if (method.getReceiver()?.qualifiedName == "kotlinx.coroutines.test.TestScope") {
-
val imports =
node.getContainingUFile()?.imports.orEmpty().mapNotNull {
it.importReference?.asSourceString()
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DoNotDirectlyConstructKosmosTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DoNotDirectlyConstructKosmosTest.kt
new file mode 100644
index 0000000..20f6bcb
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DoNotDirectlyConstructKosmosTest.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2023 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.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.checks.infrastructure.TestLintResult
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+class DoNotDirectlyConstructKosmosTest : SystemUILintDetectorTest() {
+ override fun getDetector(): Detector = DoNotDirectlyConstructKosmosDetector()
+
+ override fun getIssues(): List<Issue> = listOf(DoNotDirectlyConstructKosmosDetector.ISSUE)
+
+ @Test
+ fun wronglyTriesToDirectlyConstructKosmos() {
+ val runOnSource =
+ runOnSource(
+ """
+ package test.pkg.name
+
+ import com.android.systemui.kosmos.Kosmos
+ import com.android.systemui.SysuiTestCase
+
+ class MyTest: SysuiTestCase {
+ val kosmos = Kosmos()
+ }
+ """
+ )
+
+ runOnSource
+ .expectWarningCount(1)
+ .expect(
+ """
+ src/test/pkg/name/MyTest.kt:7: Warning: Prefer testKosmos to direct Kosmos() in sysui tests. go/testkosmos [DoNotDirectlyConstructKosmos]
+ val kosmos = Kosmos()
+ ~~~~~~
+ 0 errors, 1 warnings
+ """
+ )
+ }
+
+ @Test
+ fun okToConstructKosmosIfNotInSysuiTestCase() {
+ val runOnSource =
+ runOnSource(
+ """
+ package test.pkg.name
+
+ import com.android.systemui.kosmos.Kosmos
+
+ class MyTest {
+ val kosmos = Kosmos()
+ }
+ """
+ )
+
+ runOnSource.expectWarningCount(0)
+ }
+
+ private fun runOnSource(source: String): TestLintResult {
+ return lint()
+ .files(TestFiles.kotlin(source).indented(), kosmosStub, sysuiTestCaseStub)
+ .issues(DoNotDirectlyConstructKosmosDetector.ISSUE)
+ .run()
+ }
+
+ companion object {
+ private val kosmosStub: TestFile =
+ kotlin(
+ """
+ package com.android.systemui.kosmos
+
+ class Kosmos
+ """
+ )
+
+ private val sysuiTestCaseStub: TestFile =
+ kotlin(
+ """
+ package com.android.systemui
+
+ class SysuiTestCase
+ """
+ )
+ }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt b/packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt
index 20efea5..df50eb8 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt
@@ -31,6 +31,7 @@
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
@@ -40,12 +41,15 @@
modifier: Modifier = Modifier,
enabled: Boolean = true,
colors: ButtonColors = filledButtonColors(),
+ contentPadding: PaddingValues = ButtonPaddings,
+ shape: Shape = ButtonDefaults.shape,
content: @Composable RowScope.() -> Unit,
) {
androidx.compose.material3.Button(
modifier = modifier.heightIn(min = 36.dp),
colors = colors,
- contentPadding = ButtonPaddings,
+ contentPadding = contentPadding,
+ shape = shape,
onClick = onClick,
enabled = enabled,
) {
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/Bounceable.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/Bounceable.kt
index 3f2f84b..8270969 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/Bounceable.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/Bounceable.kt
@@ -17,13 +17,20 @@
package com.android.compose.animation
import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
import androidx.compose.ui.layout.layout
+import androidx.compose.ui.node.LayoutModifierNode
+import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp
import kotlin.math.roundToInt
/** A component that can bounce in one dimension, for instance when it is tapped. */
+@Stable
interface Bounceable {
val bounce: Dp
}
@@ -46,6 +53,7 @@
* RTL layouts) side. This can be used for grids for which the last item does not align perfectly
* with the end of the grid.
*/
+@Stable
fun Modifier.bounceable(
bounceable: Bounceable,
previousBounceable: Bounceable?,
@@ -53,7 +61,47 @@
orientation: Orientation,
bounceEnd: Boolean = nextBounceable != null,
): Modifier {
- return layout { measurable, constraints ->
+ return this then
+ BounceableElement(bounceable, previousBounceable, nextBounceable, orientation, bounceEnd)
+}
+
+private data class BounceableElement(
+ private val bounceable: Bounceable,
+ private val previousBounceable: Bounceable?,
+ private val nextBounceable: Bounceable?,
+ private val orientation: Orientation,
+ private val bounceEnd: Boolean,
+) : ModifierNodeElement<BounceableNode>() {
+ override fun create(): BounceableNode {
+ return BounceableNode(
+ bounceable,
+ previousBounceable,
+ nextBounceable,
+ orientation,
+ bounceEnd,
+ )
+ }
+
+ override fun update(node: BounceableNode) {
+ node.bounceable = bounceable
+ node.previousBounceable = previousBounceable
+ node.nextBounceable = nextBounceable
+ node.orientation = orientation
+ node.bounceEnd = bounceEnd
+ }
+}
+
+private class BounceableNode(
+ var bounceable: Bounceable,
+ var previousBounceable: Bounceable?,
+ var nextBounceable: Bounceable?,
+ var orientation: Orientation,
+ var bounceEnd: Boolean = nextBounceable != null,
+) : Modifier.Node(), LayoutModifierNode {
+ override fun MeasureScope.measure(
+ measurable: Measurable,
+ constraints: Constraints,
+ ): MeasureResult {
// The constraints in the orientation should be fixed, otherwise there is no way to know
// what the size of our child node will be without this animation code.
checkFixedSize(constraints, orientation)
@@ -61,10 +109,12 @@
var sizePrevious = 0f
var sizeNext = 0f
+ val previousBounceable = previousBounceable
if (previousBounceable != null) {
sizePrevious += bounceable.bounce.toPx() - previousBounceable.bounce.toPx()
}
+ val nextBounceable = nextBounceable
if (nextBounceable != null) {
sizeNext += bounceable.bounce.toPx() - nextBounceable.bounce.toPx()
} else if (bounceEnd) {
@@ -84,7 +134,7 @@
// constraints, otherwise the parent will automatically center this node given the
// size that it expects us to be. This allows us to then place the element where we
// want it to be.
- layout(idleWidth, placeable.height) {
+ return layout(idleWidth, placeable.height) {
placeable.placeRelative(-sizePrevious.roundToInt(), 0)
}
}
@@ -95,7 +145,7 @@
constraints.copy(minHeight = animatedHeight, maxHeight = animatedHeight)
val placeable = measurable.measure(animatedConstraints)
- layout(placeable.width, idleHeight) {
+ return layout(placeable.width, idleHeight) {
placeable.placeRelative(0, -sizePrevious.roundToInt())
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/SysuiTestTag.kt b/packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/SysuiTestTag.kt
index 9eb78e1..b1afb161 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/SysuiTestTag.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/SysuiTestTag.kt
@@ -16,7 +16,7 @@
package com.android.systemui.compose.modifiers
-import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.semantics
@@ -26,7 +26,11 @@
* Set a test tag on this node so that it is associated with [resId]. This node will then be
* accessible by integration tests using `sysuiResSelector(resId)`.
*/
-@OptIn(ExperimentalComposeUiApi::class)
+@Stable
fun Modifier.sysuiResTag(resId: String): Modifier {
- return this.semantics { testTagsAsResourceId = true }.testTag("com.android.systemui:id/$resId")
+ // TODO(b/372412931): Only compose the semantics modifier once, at the root of the SystemUI
+ // window.
+ return this.then(TestTagAsResourceIdModifier).testTag("com.android.systemui:id/$resId")
}
+
+private val TestTagAsResourceIdModifier = Modifier.semantics { testTagsAsResourceId = true }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/row/BundleHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/row/BundleHeader.kt
new file mode 100644
index 0000000..d7740a4
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/row/BundleHeader.kt
@@ -0,0 +1,193 @@
+/*
+ * 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.notifications.ui.composable.row
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.constrainHeight
+import androidx.compose.ui.unit.constrainWidth
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.fastForEach
+import androidx.compose.ui.util.fastMap
+import androidx.compose.ui.util.fastMaxOfOrDefault
+import androidx.compose.ui.util.fastSumBy
+import com.android.compose.animation.scene.ContentScope
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.SceneTransitionLayout
+import com.android.compose.theme.PlatformTheme
+import com.android.compose.ui.graphics.painter.rememberDrawablePainter
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.BundleHeaderViewModel
+
+object BundleHeader {
+ object Scenes {
+ val Collapsed = SceneKey("Collapsed")
+ val Expanded = SceneKey("Expanded")
+ }
+
+ object Elements {
+ val PreviewIcon1 = ElementKey("PreviewIcon1")
+ val PreviewIcon2 = ElementKey("PreviewIcon2")
+ val PreviewIcon3 = ElementKey("PreviewIcon3")
+ val TitleText = ElementKey("TitleText")
+ }
+}
+
+fun createComposeView(viewModel: BundleHeaderViewModel, context: Context): ComposeView {
+ // TODO(b/399588047): Check if we can init PlatformTheme once instead of once per ComposeView
+ return ComposeView(context).apply { setContent { PlatformTheme { BundleHeader(viewModel) } } }
+}
+
+@Composable
+fun BundleHeader(viewModel: BundleHeaderViewModel, modifier: Modifier = Modifier) {
+ Box(modifier) {
+ Background(background = viewModel.backgroundDrawable, modifier = Modifier.matchParentSize())
+ val scope = rememberCoroutineScope()
+ SceneTransitionLayout(
+ state = viewModel.state,
+ modifier =
+ Modifier.clickable(
+ onClick = { viewModel.onHeaderClicked(scope) },
+ interactionSource = null,
+ indication = null,
+ ),
+ ) {
+ scene(BundleHeader.Scenes.Collapsed) {
+ BundleHeaderContent(viewModel, collapsed = true)
+ }
+ scene(BundleHeader.Scenes.Expanded) {
+ BundleHeaderContent(viewModel, collapsed = false)
+ }
+ }
+ }
+}
+
+@Composable
+private fun Background(background: Drawable?, modifier: Modifier = Modifier) {
+ if (background != null) {
+ val painter = rememberDrawablePainter(drawable = background)
+ Image(
+ painter = painter,
+ contentDescription = null,
+ contentScale = ContentScale.Crop,
+ modifier = modifier,
+ )
+ }
+}
+
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+@Composable
+private fun ContentScope.BundleHeaderContent(
+ viewModel: BundleHeaderViewModel,
+ collapsed: Boolean,
+ modifier: Modifier = Modifier,
+) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = modifier.padding(vertical = 16.dp),
+ ) {
+ BundleIcon(viewModel.bundleIcon, modifier = Modifier.padding(horizontal = 16.dp))
+ Text(
+ text = viewModel.titleText,
+ style = MaterialTheme.typography.titleMediumEmphasized,
+ color = MaterialTheme.colorScheme.primary,
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ modifier = Modifier.element(BundleHeader.Elements.TitleText).weight(1f),
+ )
+
+ if (collapsed && viewModel.previewIcons.isNotEmpty()) {
+ BundlePreviewIcons(
+ previewDrawables = viewModel.previewIcons,
+ modifier = Modifier.padding(start = 8.dp),
+ )
+ }
+
+ ExpansionControl(
+ collapsed = collapsed,
+ hasUnread = viewModel.hasUnreadMessages,
+ numberToShow = viewModel.numberOfChildren,
+ modifier = Modifier.padding(start = 8.dp, end = 16.dp),
+ )
+ }
+}
+
+@Composable
+private fun ContentScope.BundlePreviewIcons(
+ previewDrawables: List<Drawable>,
+ modifier: Modifier = Modifier,
+) {
+ check(previewDrawables.isNotEmpty())
+ val iconSize = 32.dp
+ HalfOverlappingReversedRow(modifier = modifier) {
+ PreviewIcon(
+ drawable = previewDrawables[0],
+ modifier = Modifier.element(BundleHeader.Elements.PreviewIcon1).size(iconSize),
+ )
+ if (previewDrawables.size < 2) return@HalfOverlappingReversedRow
+ PreviewIcon(
+ drawable = previewDrawables[1],
+ modifier = Modifier.element(BundleHeader.Elements.PreviewIcon2).size(iconSize),
+ )
+ if (previewDrawables.size < 3) return@HalfOverlappingReversedRow
+ PreviewIcon(
+ drawable = previewDrawables[2],
+ modifier = Modifier.element(BundleHeader.Elements.PreviewIcon3).size(iconSize),
+ )
+ }
+}
+
+@Composable
+private fun HalfOverlappingReversedRow(
+ modifier: Modifier = Modifier,
+ content: @Composable () -> Unit,
+) {
+ Layout(modifier = modifier, content = content) { measurables, constraints ->
+ val placeables = measurables.fastMap { measurable -> measurable.measure(constraints) }
+
+ if (placeables.isEmpty())
+ return@Layout layout(constraints.minWidth, constraints.minHeight) {}
+ val width = placeables.fastSumBy { it.width / 2 } + placeables.first().width / 2
+ val childHeight = placeables.fastMaxOfOrDefault(0) { it.height }
+
+ layout(constraints.constrainWidth(width), constraints.constrainHeight(childHeight)) {
+ // Start in the middle of the right-most placeable
+ var currentXPosition = placeables.fastSumBy { it.width / 2 }
+ placeables.fastForEach { placeable ->
+ currentXPosition -= placeable.width / 2
+ placeable.placeRelative(x = currentXPosition, y = 0)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/row/NotificationRowPrimitives.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/row/NotificationRowPrimitives.kt
new file mode 100644
index 0000000..c9ffa40
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/row/NotificationRowPrimitives.kt
@@ -0,0 +1,201 @@
+/*
+ * 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.notifications.ui.composable.row
+
+import android.graphics.drawable.Drawable
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ExpandMore
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.drawBehind
+import androidx.compose.ui.geometry.CornerRadius
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.android.compose.animation.scene.ContentScope
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.LowestZIndexContentPicker
+import com.android.compose.animation.scene.ValueKey
+import com.android.compose.animation.scene.animateElementColorAsState
+import com.android.compose.animation.scene.animateElementFloatAsState
+import com.android.compose.ui.graphics.painter.rememberDrawablePainter
+
+object NotificationRowPrimitives {
+ object Elements {
+ val PillBackground = ElementKey("PillBackground", contentPicker = LowestZIndexContentPicker)
+ val NotificationIconBackground = ElementKey("NotificationIconBackground")
+ val Chevron = ElementKey("Chevron")
+ }
+
+ object Values {
+ val ChevronRotation = ValueKey("NotificationChevronRotation")
+ val PillBackgroundColor = ValueKey("PillBackgroundColor")
+ }
+}
+
+/** The Icon displayed at the start of any notification row. */
+@Composable
+fun ContentScope.BundleIcon(drawable: Drawable?, modifier: Modifier = Modifier) {
+ val surfaceColor = notificationElementSurfaceColor()
+ Box(
+ modifier =
+ modifier
+ // Has to be a shared element because we may have semi-transparent background color
+ .element(NotificationRowPrimitives.Elements.NotificationIconBackground)
+ .size(40.dp)
+ .background(color = surfaceColor, shape = CircleShape)
+ ) {
+ if (drawable == null) return@Box
+ val painter = rememberDrawablePainter(drawable)
+ Image(
+ painter = painter,
+ contentDescription = null,
+ modifier = Modifier.padding(10.dp).fillMaxSize(),
+ contentScale = ContentScale.Fit,
+ colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.primary),
+ )
+ }
+}
+
+/** The Icon used to display a preview of contained child notifications in a Bundle. */
+@Composable
+fun PreviewIcon(drawable: Drawable, modifier: Modifier = Modifier) {
+ val surfaceColor = notificationElementSurfaceColor()
+ Box(
+ modifier =
+ modifier
+ .background(color = surfaceColor, shape = CircleShape)
+ .border(0.5.dp, surfaceColor, CircleShape)
+ ) {
+ val painter = rememberDrawablePainter(drawable)
+ Image(
+ painter = painter,
+ contentDescription = null,
+ modifier = Modifier.fillMaxSize().clip(CircleShape),
+ contentScale = ContentScale.Fit,
+ )
+ }
+}
+
+/** The ExpansionControl of any expandable notification row, containing a Chevron. */
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
+@Composable
+fun ContentScope.ExpansionControl(
+ collapsed: Boolean,
+ hasUnread: Boolean,
+ numberToShow: Int?,
+ modifier: Modifier = Modifier,
+) {
+ val textColor =
+ if (hasUnread) MaterialTheme.colorScheme.onTertiary else MaterialTheme.colorScheme.onSurface
+ Box(modifier = modifier) {
+ // The background is a shared Element and therefore can't be the parent of a different
+ // shared Element (the chevron), otherwise the child can't be animated.
+ PillBackground(hasUnread, modifier = Modifier.matchParentSize())
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.padding(vertical = 2.dp, horizontal = 6.dp),
+ ) {
+ val iconSizeDp = with(LocalDensity.current) { 16.sp.toDp() }
+
+ if (numberToShow != null) {
+ Text(
+ text = numberToShow.toString(),
+ style = MaterialTheme.typography.labelSmallEmphasized,
+ color = textColor,
+ modifier = Modifier.padding(end = 2.dp),
+ )
+ }
+ Chevron(collapsed = collapsed, modifier = Modifier.size(iconSizeDp), color = textColor)
+ }
+ }
+}
+
+@Composable
+private fun ContentScope.PillBackground(hasUnread: Boolean, modifier: Modifier = Modifier) {
+ ElementWithValues(NotificationRowPrimitives.Elements.PillBackground, modifier) {
+ val bgColorNoUnread = notificationElementSurfaceColor()
+ val surfaceColor by
+ animateElementColorAsState(
+ if (hasUnread) MaterialTheme.colorScheme.tertiary else bgColorNoUnread,
+ NotificationRowPrimitives.Values.PillBackgroundColor,
+ )
+ content {
+ Box(
+ modifier =
+ Modifier.drawBehind {
+ drawRoundRect(
+ color = surfaceColor,
+ cornerRadius = CornerRadius(100.dp.toPx(), 100.dp.toPx()),
+ )
+ }
+ )
+ }
+ }
+}
+
+@Composable
+@ReadOnlyComposable
+private fun notificationElementSurfaceColor(): Color {
+ return if (isSystemInDarkTheme()) {
+ Color.White.copy(alpha = 0.15f)
+ } else {
+ MaterialTheme.colorScheme.surfaceContainerHighest
+ }
+}
+
+@Composable
+private fun ContentScope.Chevron(collapsed: Boolean, color: Color, modifier: Modifier = Modifier) {
+ val key = NotificationRowPrimitives.Elements.Chevron
+ ElementWithValues(key, modifier) {
+ val rotation by
+ animateElementFloatAsState(
+ if (collapsed) 0f else 180f,
+ NotificationRowPrimitives.Values.ChevronRotation,
+ )
+ content {
+ Icon(
+ imageVector = Icons.Default.ExpandMore,
+ contentDescription = null,
+ modifier = Modifier.graphicsLayer { rotationZ = rotation },
+ tint = color,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
index 60c01722..216f0a7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
@@ -141,7 +141,7 @@
mutableStateOf<FooterActionsForegroundServicesButtonViewModel?>(null)
}
var userSwitcher by remember { mutableStateOf<FooterActionsButtonViewModel?>(null) }
- var power by remember { mutableStateOf<FooterActionsButtonViewModel?>(null) }
+ var power by remember { mutableStateOf(viewModel.initialPower()) }
LaunchedEffect(
context,
@@ -218,23 +218,19 @@
}
val useModifierBasedExpandable = remember { QSComposeFragment.isEnabled }
- security?.let { SecurityButton(it, useModifierBasedExpandable, Modifier.weight(1f)) }
- foregroundServices?.let { ForegroundServicesButton(it, useModifierBasedExpandable) }
- userSwitcher?.let {
- IconButton(
- it,
- useModifierBasedExpandable,
- Modifier.sysuiResTag("multi_user_switch"),
- )
- }
+ SecurityButton({ security }, useModifierBasedExpandable, Modifier.weight(1f))
+ ForegroundServicesButton({ foregroundServices }, useModifierBasedExpandable)
IconButton(
- viewModel.settings,
+ { userSwitcher },
+ useModifierBasedExpandable,
+ Modifier.sysuiResTag("multi_user_switch"),
+ )
+ IconButton(
+ { viewModel.settings },
useModifierBasedExpandable,
Modifier.sysuiResTag("settings_button_container"),
)
- power?.let {
- IconButton(it, useModifierBasedExpandable, Modifier.sysuiResTag("pm_lite"))
- }
+ IconButton({ power }, useModifierBasedExpandable, Modifier.sysuiResTag("pm_lite"))
}
}
}
@@ -242,10 +238,11 @@
/** The security button. */
@Composable
private fun SecurityButton(
- model: FooterActionsSecurityButtonViewModel,
+ model: () -> FooterActionsSecurityButtonViewModel?,
useModifierBasedExpandable: Boolean,
modifier: Modifier = Modifier,
) {
+ val model = model() ?: return
val onClick: ((Expandable) -> Unit)? =
model.onClick?.let { onClick ->
val context = LocalContext.current
@@ -265,9 +262,10 @@
/** The foreground services button. */
@Composable
private fun RowScope.ForegroundServicesButton(
- model: FooterActionsForegroundServicesButtonViewModel,
+ model: () -> FooterActionsForegroundServicesButtonViewModel?,
useModifierBasedExpandable: Boolean,
) {
+ val model = model() ?: return
if (model.displayText) {
TextButton(
Icon.Resource(R.drawable.ic_info_outline, contentDescription = null),
@@ -291,6 +289,17 @@
/** A button with an icon. */
@Composable
fun IconButton(
+ model: () -> FooterActionsButtonViewModel?,
+ useModifierBasedExpandable: Boolean,
+ modifier: Modifier = Modifier,
+) {
+ val model = model() ?: return
+ IconButton(model, useModifierBasedExpandable, modifier)
+}
+
+/** A button with an icon. */
+@Composable
+fun IconButton(
model: FooterActionsButtonViewModel,
useModifierBasedExpandable: Boolean,
modifier: Modifier = Modifier,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
index 0f1cb40..547461e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
@@ -26,16 +26,19 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.systemGestureExclusion
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.onPlaced
@@ -59,6 +62,7 @@
import com.android.systemui.qs.panels.ui.compose.TileDetails
import com.android.systemui.qs.panels.ui.compose.TileGrid
import com.android.systemui.qs.panels.ui.compose.toolbar.Toolbar
+import com.android.systemui.qs.ui.composable.QuickSettingsShade.systemGestureExclusionInShade
import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel
import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeOverlayActionsViewModel
import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeOverlayContentViewModel
@@ -254,9 +258,7 @@
BrightnessSliderContainer(
viewModel = viewModel.brightnessSliderViewModel,
containerColor = OverlayShade.Colors.PanelBackground,
- modifier =
- Modifier.fillMaxWidth()
- .height(QuickSettingsShade.Dimensions.BrightnessSliderHeight),
+ modifier = Modifier.systemGestureExclusionInShade().fillMaxWidth(),
)
Box {
@@ -280,6 +282,25 @@
object Dimensions {
val Padding = 16.dp
val ToolbarHeight = 48.dp
- val BrightnessSliderHeight = 64.dp
+ }
+
+ /**
+ * Applies system gesture exclusion to a component adding [Dimensions.Padding] to left and
+ * right.
+ */
+ @Composable
+ fun Modifier.systemGestureExclusionInShade(): Modifier {
+ val density = LocalDensity.current
+ return systemGestureExclusion { layoutCoordinates ->
+ val sidePadding = with(density) { Dimensions.Padding.toPx() }
+ Rect(
+ offset = Offset(x = -sidePadding, y = 0f),
+ size =
+ Size(
+ width = layoutCoordinates.size.width.toFloat() + 2 * sidePadding,
+ height = layoutCoordinates.size.height.toFloat(),
+ ),
+ )
+ }
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 404f1b2..22688d3 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -102,6 +102,7 @@
key: SceneKey,
userActions: Map<UserAction, UserActionResult> = emptyMap(),
effectFactory: OverscrollFactory? = null,
+ alwaysCompose: Boolean = false,
content: @Composable CS.() -> Unit,
)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index e3c4eb0..4da83c3 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -207,6 +207,9 @@
private val nestedScrollDispatcher = NestedScrollDispatcher()
private val nestedScrollConnection = object : NestedScrollConnection {}
+ // TODO(b/399825091): Remove this.
+ private var scenesToAlwaysCompose: MutableList<Scene>? = null
+
init {
updateContents(builder, layoutDirection, defaultEffectFactory)
@@ -312,6 +315,7 @@
key: SceneKey,
userActions: Map<UserAction, UserActionResult>,
effectFactory: OverscrollFactory?,
+ alwaysCompose: Boolean,
content: @Composable InternalContentScope.() -> Unit,
) {
require(!overlaysDefined) { "all scenes must be defined before overlays" }
@@ -324,6 +328,10 @@
Content.calculateGlobalZIndex(parentZIndex, ++zIndex, ancestors.size)
val factory = effectFactory ?: defaultEffectFactory
if (scene != null) {
+ check(alwaysCompose == scene.alwaysCompose) {
+ "scene.alwaysCompose can not change"
+ }
+
// Update an existing scene.
scene.content = content
scene.userActions = resolvedUserActions
@@ -332,7 +340,7 @@
scene.maybeUpdateEffects(factory)
} else {
// New scene.
- scenes[key] =
+ val scene =
Scene(
key,
this@SceneTransitionLayoutImpl,
@@ -341,7 +349,16 @@
zIndex.toFloat(),
globalZIndex,
factory,
+ alwaysCompose,
)
+
+ scenes[key] = scene
+
+ if (alwaysCompose) {
+ (scenesToAlwaysCompose
+ ?: mutableListOf<Scene>().also { scenesToAlwaysCompose = it })
+ .add(scene)
+ }
}
}
@@ -470,22 +487,24 @@
@Composable
private fun Scenes() {
- scenesToCompose().fastForEach { scene -> key(scene.key) { scene.Content() } }
+ scenesToCompose().fastForEach { (scene, isInvisible) ->
+ key(scene.key) { scene.Content(isInvisible = isInvisible) }
+ }
}
- private fun scenesToCompose(): List<Scene> {
+ private fun scenesToCompose(): List<SceneToCompose> {
val transitions = state.currentTransitions
- return if (transitions.isEmpty()) {
- listOf(scene(state.transitionState.currentScene))
- } else {
- buildList {
- val visited = mutableSetOf<SceneKey>()
- fun maybeAdd(sceneKey: SceneKey) {
- if (visited.add(sceneKey)) {
- add(scene(sceneKey))
- }
+ return buildList {
+ val visited = mutableSetOf<SceneKey>()
+ fun maybeAdd(sceneKey: SceneKey, isInvisible: Boolean = false) {
+ if (visited.add(sceneKey)) {
+ add(SceneToCompose(scene(sceneKey), isInvisible))
}
+ }
+ if (transitions.isEmpty()) {
+ maybeAdd(state.transitionState.currentScene)
+ } else {
// Compose the new scene we are going to first.
transitions.fastForEachReversed { transition ->
when (transition) {
@@ -504,9 +523,13 @@
// Make sure that the current scene is always composed.
maybeAdd(transitions.last().currentScene)
}
+
+ scenesToAlwaysCompose?.fastForEach { maybeAdd(it.key, isInvisible = true) }
}
}
+ private data class SceneToCompose(val scene: Scene, val isInvisible: Boolean)
+
@Composable
private fun BoxScope.Overlays() {
val overlaysOrderedByZIndex = overlaysToComposeOrderedByZIndex()
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
index 149a9e7..72ee75a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
@@ -34,6 +34,7 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.LookaheadScope
import androidx.compose.ui.layout.approachLayout
+import androidx.compose.ui.layout.layout
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.zIndex
@@ -154,11 +155,12 @@
@SuppressLint("NotConstructor")
@Composable
- fun Content(modifier: Modifier = Modifier) {
+ fun Content(modifier: Modifier = Modifier, isInvisible: Boolean = false) {
// If this content has a custom factory, provide it to the content so that the factory is
// automatically used when calling rememberOverscrollEffect().
Box(
modifier
+ .thenIf(isInvisible) { InvisibleModifier }
.zIndex(zIndex)
.approachLayout(
isMeasurementApproachInProgress = { layoutImpl.state.isTransitioning() }
@@ -305,3 +307,8 @@
)
}
}
+
+private val InvisibleModifier =
+ Modifier.layout { measurable, constraints ->
+ measurable.measure(constraints).run { layout(width, height) {} }
+ }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Scene.kt
index 7f57798..38acd4b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Scene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Scene.kt
@@ -35,6 +35,7 @@
zIndex: Float,
globalZIndex: Long,
effectFactory: OverscrollFactory,
+ val alwaysCompose: Boolean,
) : Content(key, layoutImpl, content, actions, zIndex, globalZIndex, effectFactory) {
override fun toString(): String {
return "Scene(key=$key)"
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
index e23e234..312dd77 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
@@ -22,6 +22,8 @@
import androidx.compose.runtime.Stable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
import com.android.compose.animation.scene.OverlayKey
@@ -241,6 +243,15 @@
/** Additional gesture context whenever the transition is driven by a user gesture. */
abstract val gestureContext: GestureContext?
+ /**
+ * True when the transition reached the end and the progress won't be updated anymore.
+ *
+ * [isProgressStable] will be `true` before this [Transition] is completed while there are
+ * still custom transition animations settling.
+ */
+ var isProgressStable: Boolean by mutableStateOf(false)
+ private set
+
/** The CUJ covered by this transition. */
@CujType
val cuj: Int?
@@ -372,7 +383,11 @@
check(_coroutineScope == null) { "A Transition can be started only once." }
coroutineScope {
_coroutineScope = this
- run()
+ try {
+ run()
+ } finally {
+ isProgressStable = true
+ }
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/mechanics/TransitionScopedMechanicsAdapter.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/mechanics/TransitionScopedMechanicsAdapter.kt
new file mode 100644
index 0000000..ac8a8c0
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/mechanics/TransitionScopedMechanicsAdapter.kt
@@ -0,0 +1,135 @@
+/*
+ * 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.compose.animation.scene.mechanics
+
+import androidx.annotation.VisibleForTesting
+import androidx.compose.runtime.mutableFloatStateOf
+import com.android.compose.animation.scene.ContentKey
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.ElementStateScope
+import com.android.compose.animation.scene.content.state.TransitionState
+import com.android.compose.animation.scene.transformation.CustomPropertyTransformation
+import com.android.compose.animation.scene.transformation.PropertyTransformationScope
+import com.android.mechanics.MotionValue
+import com.android.mechanics.ProvidedGestureContext
+import com.android.mechanics.spec.InputDirection
+import com.android.mechanics.spec.MotionSpec
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * Callback to create a [MotionSpec] on the first call to [CustomPropertyTransformation.transform]
+ */
+typealias SpecFactory =
+ PropertyTransformationScope.(content: ContentKey, element: ElementKey) -> MotionSpec
+
+/** Callback to compute the [MotionValue] per frame */
+typealias MotionValueInput =
+ PropertyTransformationScope.(progress: Float, content: ContentKey, element: ElementKey) -> Float
+
+/**
+ * Adapter to create a [MotionValue] and `keepRunning()` it temporarily while a
+ * [CustomPropertyTransformation] is in progress and until the animation settles.
+ *
+ * The [MotionValue]'s input is by default the transition progress.
+ */
+internal class TransitionScopedMechanicsAdapter(
+ private val computeInput: MotionValueInput = { progress, _, _ -> progress },
+ private val stableThreshold: Float = MotionValue.StableThresholdEffect,
+ private val label: String? = null,
+ private val createSpec: SpecFactory,
+) {
+
+ private val input = mutableFloatStateOf(0f)
+ private var motionValue: MotionValue? = null
+
+ fun PropertyTransformationScope.update(
+ content: ContentKey,
+ element: ElementKey,
+ transition: TransitionState.Transition,
+ transitionScope: CoroutineScope,
+ ): Float {
+ val progress = transition.progressTo(content)
+ input.floatValue = computeInput(progress, content, element)
+ var motionValue = motionValue
+
+ if (motionValue == null) {
+ motionValue =
+ MotionValue(
+ input::floatValue,
+ transition.gestureContext
+ ?: ProvidedGestureContext(
+ 0f,
+ appearDirection(content, element, transition),
+ ),
+ createSpec(content, element),
+ stableThreshold = stableThreshold,
+ label = label,
+ )
+ this@TransitionScopedMechanicsAdapter.motionValue = motionValue
+
+ transitionScope.launch {
+ motionValue.keepRunningWhile { !transition.isProgressStable || !isStable }
+ }
+ }
+
+ return motionValue.output
+ }
+
+ companion object {
+ /**
+ * Computes the InputDirection for a triggered transition of an element appearing /
+ * disappearing.
+ *
+ * Since [CustomPropertyTransformation] are only supported for non-shared elements, the
+ * [TransitionScopedMechanicsAdapter] is only used in the context of an element appearing /
+ * disappearing. This helper computes the direction to result in [InputDirection.Max] for an
+ * appear transition, and [InputDirection.Min] for a disappear transition.
+ */
+ @VisibleForTesting
+ internal fun ElementStateScope.appearDirection(
+ content: ContentKey,
+ element: ElementKey,
+ transition: TransitionState.Transition,
+ ): InputDirection {
+ check(!transition.isInitiatedByUserInput)
+
+ val inMaxDirection =
+ when (transition) {
+ is TransitionState.Transition.ChangeScene -> {
+ val transitionTowardsContent = content == transition.toContent
+ val elementInContent = element.targetSize(content) != null
+ val isReversed = transition.currentScene != transition.toScene
+ (transitionTowardsContent xor elementInContent) xor !isReversed
+ }
+
+ is TransitionState.Transition.ShowOrHideOverlay -> {
+ val transitioningTowardsOverlay = transition.overlay == transition.toContent
+ val isReversed =
+ transitioningTowardsOverlay xor transition.isEffectivelyShown
+ transitioningTowardsOverlay xor isReversed
+ }
+
+ is TransitionState.Transition.ReplaceOverlay -> {
+ transition.effectivelyShownOverlay == content
+ }
+ }
+
+ return if (inMaxDirection) InputDirection.Max else InputDirection.Min
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/scene/tests/goldens/motionValue_interruptedAnimation_completes.json b/packages/SystemUI/compose/scene/tests/goldens/motionValue_interruptedAnimation_completes.json
new file mode 100644
index 0000000..ce62ac3
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/goldens/motionValue_interruptedAnimation_completes.json
@@ -0,0 +1,70 @@
+{
+ "frame_ids": [
+ 0,
+ 16,
+ 32,
+ 48,
+ 64,
+ 80,
+ 96,
+ 112,
+ 128,
+ 144,
+ 160,
+ 176,
+ 192,
+ 208,
+ 224,
+ 240,
+ 256,
+ 272,
+ 288,
+ 304,
+ 320,
+ 336,
+ 352,
+ 368,
+ 384,
+ "after"
+ ],
+ "features": [
+ {
+ "name": "Foo_yOffset",
+ "type": "float",
+ "data_points": [
+ {
+ "type": "not_found"
+ },
+ {
+ "type": "not_found"
+ },
+ 175,
+ 175,
+ 174.00105,
+ 149.84001,
+ 114.73702,
+ 0,
+ 0,
+ 0,
+ 0,
+ 10.212692,
+ 42.525528,
+ 77.174965,
+ 106.322296,
+ 128.37651,
+ 144.09671,
+ 154.88022,
+ 162.08202,
+ 166.79778,
+ 169.83923,
+ 171.77742,
+ 173.00056,
+ 173.76627,
+ 174.24236,
+ {
+ "type": "not_found"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/SystemUI/compose/scene/tests/goldens/motionValue_withAnimation_prolongsTransition.json b/packages/SystemUI/compose/scene/tests/goldens/motionValue_withAnimation_prolongsTransition.json
new file mode 100644
index 0000000..ac09ff3
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/goldens/motionValue_withAnimation_prolongsTransition.json
@@ -0,0 +1,48 @@
+{
+ "frame_ids": [
+ 0,
+ 16,
+ 32,
+ 48,
+ 64,
+ 80,
+ 96,
+ 112,
+ 128,
+ 144,
+ 160,
+ 176,
+ 192,
+ 208,
+ 224,
+ 240,
+ 256,
+ "after"
+ ],
+ "features": [
+ {
+ "name": "Foo_yOffset",
+ "type": "float",
+ "data_points": [
+ 175,
+ 175,
+ 175,
+ 175,
+ 156.26086,
+ 121.784874,
+ 88.35684,
+ 61.32686,
+ 41.302353,
+ 27.215454,
+ 17.638702,
+ 11.284393,
+ 7.144104,
+ 4.4841614,
+ 2.7943878,
+ 1.7307587,
+ 1.0663452,
+ 0
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/SystemUI/compose/scene/tests/goldens/motionValue_withoutAnimation_terminatesImmediately.json b/packages/SystemUI/compose/scene/tests/goldens/motionValue_withoutAnimation_terminatesImmediately.json
new file mode 100644
index 0000000..5cf66a4
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/goldens/motionValue_withoutAnimation_terminatesImmediately.json
@@ -0,0 +1,26 @@
+{
+ "frame_ids": [
+ 0,
+ 16,
+ 32,
+ 48,
+ 64,
+ 80,
+ "after"
+ ],
+ "features": [
+ {
+ "name": "Foo_yOffset",
+ "type": "float",
+ "data_points": [
+ 175,
+ 145.83333,
+ 116.666664,
+ 87.5,
+ 58.33333,
+ 29.166672,
+ 0
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
index fa7661b6..6538d43 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
@@ -45,6 +45,7 @@
import androidx.compose.ui.test.SemanticsNodeInteraction
import androidx.compose.ui.test.assertHeightIsEqualTo
import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.assertPositionInRootIsEqualTo
import androidx.compose.ui.test.assertWidthIsEqualTo
import androidx.compose.ui.test.junit4.createComposeRule
@@ -64,10 +65,13 @@
import com.android.compose.animation.scene.TestScenes.SceneC
import com.android.compose.animation.scene.subjects.assertThat
import com.android.compose.test.assertSizeIsEqualTo
+import com.android.compose.test.setContentAndCreateMainScope
import com.android.compose.test.subjects.DpOffsetSubject
import com.android.compose.test.subjects.assertThat
+import com.android.compose.test.transition
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
import org.junit.Assert.assertThrows
import org.junit.Rule
import org.junit.Test
@@ -582,4 +586,34 @@
assertThat((state2 as MutableSceneTransitionLayoutStateImpl).motionScheme)
.isEqualTo(motionScheme2)
}
+
+ @Test
+ fun alwaysCompose() {
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
+ val scope =
+ rule.setContentAndCreateMainScope {
+ SceneTransitionLayoutForTesting(state) {
+ scene(SceneA) { Box(Modifier.element(TestElements.Foo).size(20.dp)) }
+ scene(SceneB, alwaysCompose = true) {
+ Box(Modifier.element(TestElements.Bar).size(40.dp))
+ }
+ }
+ }
+
+ // Idle(A): Foo is displayed and Bar exists given that SceneB is always composed but it is
+ // not displayed.
+ rule.onNode(isElement(TestElements.Foo)).assertIsDisplayed().assertSizeIsEqualTo(20.dp)
+ rule.onNode(isElement(TestElements.Bar)).assertExists().assertIsNotDisplayed()
+
+ // Transition(A => B): Foo and Bar are both displayed
+ val aToB = transition(SceneA, SceneB)
+ scope.launch { state.startTransition(aToB) }
+ rule.onNode(isElement(TestElements.Foo)).assertIsDisplayed().assertSizeIsEqualTo(20.dp)
+ rule.onNode(isElement(TestElements.Bar)).assertIsDisplayed().assertSizeIsEqualTo(40.dp)
+
+ // Idle(B): Foo does not exist and Bar is displayed.
+ aToB.finish()
+ rule.onNode(isElement(TestElements.Foo)).assertDoesNotExist()
+ rule.onNode(isElement(TestElements.Bar)).assertIsDisplayed().assertSizeIsEqualTo(40.dp)
+ }
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/mechanics/TransitionScopedMechanicsAdapterTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/mechanics/TransitionScopedMechanicsAdapterTest.kt
new file mode 100644
index 0000000..b9bd115
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/mechanics/TransitionScopedMechanicsAdapterTest.kt
@@ -0,0 +1,519 @@
+/*
+ * 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.compose.animation.scene.mechanics
+
+import android.platform.test.annotations.MotionTest
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.semantics.SemanticsNode
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.ContentKey
+import com.android.compose.animation.scene.ContentScope
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.MutableSceneTransitionLayoutStateForTests
+import com.android.compose.animation.scene.MutableSceneTransitionLayoutStateImpl
+import com.android.compose.animation.scene.OverlayKey
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.SceneTransitionLayout
+import com.android.compose.animation.scene.SceneTransitionLayoutForTesting
+import com.android.compose.animation.scene.SceneTransitionsBuilder
+import com.android.compose.animation.scene.TestElements
+import com.android.compose.animation.scene.TestOverlays
+import com.android.compose.animation.scene.TestScenes
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.compose.animation.scene.TransitionRecordingSpec
+import com.android.compose.animation.scene.content.state.TransitionState
+import com.android.compose.animation.scene.featureOfElement
+import com.android.compose.animation.scene.mechanics.TransitionScopedMechanicsAdapter.Companion.appearDirection
+import com.android.compose.animation.scene.recordTransition
+import com.android.compose.animation.scene.testing.lastOffsetForTesting
+import com.android.compose.animation.scene.transformation.CustomPropertyTransformation
+import com.android.compose.animation.scene.transformation.PropertyTransformation
+import com.android.compose.animation.scene.transformation.PropertyTransformationScope
+import com.android.compose.animation.scene.transitions
+import com.android.mechanics.spec.InputDirection
+import com.android.mechanics.spec.Mapping
+import com.android.mechanics.spec.MotionSpec
+import com.android.mechanics.spec.buildDirectionalMotionSpec
+import com.android.mechanics.spring.SpringParameters
+import com.google.common.truth.Truth
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.test.TestScope
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import platform.test.motion.compose.ComposeRecordingSpec
+import platform.test.motion.compose.MotionControl
+import platform.test.motion.compose.createComposeMotionTestRule
+import platform.test.motion.compose.recordMotion
+import platform.test.motion.compose.runTest
+import platform.test.motion.golden.DataPoint
+import platform.test.motion.golden.DataPointTypes
+import platform.test.motion.golden.FeatureCapture
+import platform.test.motion.testing.createGoldenPathManager
+
+@RunWith(AndroidJUnit4::class)
+@MotionTest
+class TransitionScopedMechanicsAdapterTest {
+
+ private val goldenPaths =
+ createGoldenPathManager("frameworks/base/packages/SystemUI/compose/scene/tests/goldens")
+
+ private val testScope = TestScope()
+ @get:Rule val motionRule = createComposeMotionTestRule(goldenPaths, testScope)
+ private val composeRule = motionRule.toolkit.composeContentTestRule
+
+ @Test
+ fun motionValue_withoutAnimation_terminatesImmediately() =
+ motionRule.runTest {
+ val specFactory: SpecFactory = { _, _ ->
+ MotionSpec(
+ // Linearly animate from 10 down to 0
+ buildDirectionalMotionSpec(TestSpring, Mapping.Fixed(50.dp.toPx())) {
+ targetFromCurrent(breakpoint = 0f, to = 0f)
+ constantValueFromCurrent(breakpoint = 1f)
+ }
+ )
+ }
+
+ assertOffsetMatchesGolden(
+ transition = {
+ spec = tween(16 * 6, easing = LinearEasing)
+ transformation(TestElements.Foo) { TestTransformation(specFactory) }
+ }
+ )
+ }
+
+ @Test
+ fun motionValue_withAnimation_prolongsTransition() =
+ motionRule.runTest {
+ val specFactory: SpecFactory = { _, _ ->
+ MotionSpec(
+ // Use a spring to toggle 10f -> 0f at a progress of 0.5
+ buildDirectionalMotionSpec(TestSpring, Mapping.Fixed(50.dp.toPx())) {
+ constantValue(breakpoint = 0.5f, value = 0f)
+ }
+ )
+ }
+
+ assertOffsetMatchesGolden(
+ transition = {
+ spec = tween(16 * 6, easing = LinearEasing)
+ transformation(TestElements.Foo) { TestTransformation(specFactory) }
+ }
+ )
+ }
+
+ @Test
+ fun motionValue_interruptedAnimation_completes() =
+ motionRule.runTest {
+ val transitions = transitions {
+ from(TestScenes.SceneA, to = TestScenes.SceneB) {
+ spec = tween(16 * 6, easing = LinearEasing)
+
+ transformation(TestElements.Foo) {
+ TestTransformation { _, _ ->
+ MotionSpec(
+ buildDirectionalMotionSpec(
+ TestSpring,
+ Mapping.Fixed(50.dp.toPx()),
+ ) {
+ constantValue(breakpoint = 0.3f, value = 0f)
+ }
+ )
+ }
+ }
+ }
+ }
+
+ val state =
+ composeRule.runOnUiThread {
+ MutableSceneTransitionLayoutStateForTests(TestScenes.SceneA, transitions)
+ }
+ lateinit var coroutineScope: CoroutineScope
+
+ val motionControl =
+ MotionControl(delayRecording = { awaitFrames(4) }) {
+ awaitFrames(1)
+ val (transitionToB, firstTransitionJob) =
+ toolkit.composeContentTestRule.runOnUiThread {
+ checkNotNull(
+ state.setTargetScene(
+ TestScenes.SceneB,
+ animationScope = coroutineScope,
+ )
+ )
+ }
+
+ awaitCondition { transitionToB.progress > 0.5f }
+ val (transitionBackToA, secondTransitionJob) =
+ toolkit.composeContentTestRule.runOnUiThread {
+ checkNotNull(
+ state.setTargetScene(
+ TestScenes.SceneA,
+ animationScope = coroutineScope,
+ )
+ )
+ }
+
+ Truth.assertThat(transitionBackToA.replacedTransition)
+ .isSameInstanceAs(transitionToB)
+
+ awaitCondition { !state.isTransitioning() }
+
+ Truth.assertThat(firstTransitionJob.isCompleted).isTrue()
+ Truth.assertThat(secondTransitionJob.isCompleted).isTrue()
+ }
+
+ val motion =
+ recordMotion(
+ content = {
+ coroutineScope = rememberCoroutineScope()
+ SceneTransitionLayoutForTesting(state, modifier = Modifier.size(50.dp)) {
+ scene(TestScenes.SceneA) { SceneAContent() }
+ scene(TestScenes.SceneB) { SceneBContent() }
+ }
+ },
+ ComposeRecordingSpec(motionControl, recordBefore = false) {
+ featureOfElement(TestElements.Foo, yOffsetFeature)
+ },
+ )
+
+ assertThat(motion).timeSeriesMatchesGolden()
+ }
+
+ @Test
+ fun animationDirection_sceneTransition_forward() {
+ val transitionDirection =
+ composeRule.getAppearDirectionOnTransition(
+ initialScene = TestScenes.SceneA,
+ transitionBuilder = {
+ from(TestScenes.SceneA, to = TestScenes.SceneB) { it(TestElements.Foo) }
+ },
+ ) { state, animationScope, _ ->
+ state.setTargetScene(TestScenes.SceneB, animationScope)
+ false
+ }
+
+ Truth.assertThat(transitionDirection).isEqualTo(InputDirection.Max)
+ }
+
+ @Test
+ fun animationDirection_sceneTransition_backwards() {
+ val transitionDirection =
+ composeRule.getAppearDirectionOnTransition(
+ initialScene = TestScenes.SceneB,
+ transitionBuilder = {
+ from(TestScenes.SceneA, to = TestScenes.SceneB) { it(TestElements.Foo) }
+ },
+ ) { state, animationScope, _ ->
+ state.setTargetScene(TestScenes.SceneA, animationScope)
+ false
+ }
+
+ Truth.assertThat(transitionDirection).isEqualTo(InputDirection.Min)
+ }
+
+ @Test
+ fun animationDirection_interruptedTransition_flipsDirection() {
+ val transitionDirection =
+ composeRule.getAppearDirectionOnTransition(
+ initialScene = TestScenes.SceneA,
+ transitionBuilder = {
+ from(TestScenes.SceneA, to = TestScenes.SceneB) { it(TestElements.Foo) }
+ },
+ ) { state, animationScope, iteration ->
+ when (iteration) {
+ 0 -> {
+ state.setTargetScene(TestScenes.SceneB, animationScope)
+ true
+ }
+ 1 -> {
+ state.setTargetScene(TestScenes.SceneA, animationScope)
+ false
+ }
+ else -> throw AssertionError()
+ }
+ }
+
+ Truth.assertThat(transitionDirection).isEqualTo(InputDirection.Min)
+ }
+
+ @Test
+ fun animationDirection_showOverlay_animatesInMaxDirection() {
+ val transitionDirection =
+ composeRule.getAppearDirectionOnTransition(
+ initialScene = TestScenes.SceneA,
+ transitionBuilder = { this.to(TestOverlays.OverlayA) { it(TestElements.Bar) } },
+ ) { state, animationScope, _ ->
+ state.showOverlay(TestOverlays.OverlayA, animationScope)
+ false
+ }
+
+ Truth.assertThat(transitionDirection).isEqualTo(InputDirection.Max)
+ }
+
+ @Test
+ fun animationDirection_hideOverlay_animatesInMinDirection() {
+ val transitionDirection =
+ composeRule.getAppearDirectionOnTransition(
+ initialScene = TestScenes.SceneA,
+ initialOverlays = setOf(TestOverlays.OverlayA),
+ transitionBuilder = { this.to(TestOverlays.OverlayA) { it(TestElements.Bar) } },
+ ) { state, animationScope, _ ->
+ state.hideOverlay(TestOverlays.OverlayA, animationScope)
+ false
+ }
+
+ Truth.assertThat(transitionDirection).isEqualTo(InputDirection.Min)
+ }
+
+ @Test
+ fun animationDirection_hideOverlayMidTransition_animatesInMinDirection() {
+ val transitionDirection =
+ composeRule.getAppearDirectionOnTransition(
+ initialScene = TestScenes.SceneA,
+ transitionBuilder = { this.to(TestOverlays.OverlayA) { it(TestElements.Bar) } },
+ ) { state, animationScope, iteration ->
+ when (iteration) {
+ 0 -> {
+ state.showOverlay(TestOverlays.OverlayA, animationScope)
+ true
+ }
+ 1 -> {
+ state.hideOverlay(TestOverlays.OverlayA, animationScope)
+ false
+ }
+ else -> throw AssertionError()
+ }
+ }
+
+ Truth.assertThat(transitionDirection).isEqualTo(InputDirection.Min)
+ }
+
+ @Test
+ fun animationDirection_replaceOverlay_showingContent_animatesInMaxDirection() {
+ val transitionDirection =
+ composeRule.getAppearDirectionOnTransition(
+ initialScene = TestScenes.SceneA,
+ initialOverlays = setOf(TestOverlays.OverlayB),
+ transitionBuilder = { this.to(TestOverlays.OverlayA) { it(TestElements.Bar) } },
+ ) { state, animationScope, _ ->
+ state.replaceOverlay(TestOverlays.OverlayB, TestOverlays.OverlayA, animationScope)
+ false
+ }
+
+ Truth.assertThat(transitionDirection).isEqualTo(InputDirection.Max)
+ }
+
+ @Test
+ fun animationDirection_replaceOverlay_hidingContent_animatesInMinDirection() {
+ val transitionDirection =
+ composeRule.getAppearDirectionOnTransition(
+ initialScene = TestScenes.SceneA,
+ initialOverlays = setOf(TestOverlays.OverlayA),
+ transitionBuilder = { this.to(TestOverlays.OverlayA) { it(TestElements.Bar) } },
+ ) { state, animationScope, _ ->
+ state.replaceOverlay(TestOverlays.OverlayA, TestOverlays.OverlayB, animationScope)
+ false
+ }
+
+ Truth.assertThat(transitionDirection).isEqualTo(InputDirection.Min)
+ }
+
+ @Test
+ fun animationDirection_replaceOverlay_revertMidTransition_animatesInMinDirection() {
+ val transitionDirection =
+ composeRule.getAppearDirectionOnTransition(
+ initialScene = TestScenes.SceneA,
+ initialOverlays = setOf(TestOverlays.OverlayB),
+ transitionBuilder = { this.to(TestOverlays.OverlayA) { it(TestElements.Bar) } },
+ ) { state, animationScope, iteration ->
+ when (iteration) {
+ 0 -> {
+ state.replaceOverlay(
+ TestOverlays.OverlayB,
+ TestOverlays.OverlayA,
+ animationScope,
+ )
+ true
+ }
+ 1 -> {
+ state.replaceOverlay(
+ TestOverlays.OverlayA,
+ TestOverlays.OverlayB,
+ animationScope,
+ )
+ false
+ }
+ else -> throw AssertionError()
+ }
+ }
+
+ Truth.assertThat(transitionDirection).isEqualTo(InputDirection.Min)
+ }
+
+ private fun ComposeContentTestRule.getAppearDirectionOnTransition(
+ initialScene: SceneKey,
+ transitionBuilder: SceneTransitionsBuilder.(foo: DirectionAssertionTransition) -> Unit,
+ initialOverlays: Set<OverlayKey> = emptySet(),
+ runTransition:
+ (
+ state: MutableSceneTransitionLayoutStateImpl,
+ animationScope: CoroutineScope,
+ iteration: Int,
+ ) -> Boolean,
+ ): InputDirection {
+
+ lateinit var result: InputDirection
+
+ val x: DirectionAssertionTransition = {
+ transformation(it) {
+ object : CustomPropertyTransformation<IntSize> {
+ override val property = PropertyTransformation.Property.Size
+
+ override fun PropertyTransformationScope.transform(
+ content: ContentKey,
+ element: ElementKey,
+ transition: TransitionState.Transition,
+ transitionScope: CoroutineScope,
+ ): IntSize {
+ result = appearDirection(content, element, transition)
+ return IntSize.Zero
+ }
+ }
+ }
+ }
+
+ val state = runOnUiThread {
+ MutableSceneTransitionLayoutStateForTests(
+ initialScene,
+ transitions { transitionBuilder(x) },
+ initialOverlays,
+ )
+ }
+ lateinit var coroutineScope: CoroutineScope
+
+ setContent {
+ coroutineScope = rememberCoroutineScope()
+ SceneTransitionLayout(state) {
+ scene(TestScenes.SceneA) { SceneAContent() }
+ scene(TestScenes.SceneB) { SceneBContent() }
+ overlay(TestOverlays.OverlayA) { OverlayAContent() }
+ overlay(TestOverlays.OverlayB) {}
+ }
+ }
+
+ waitForIdle()
+ mainClock.autoAdvance = false
+ var keepOnAnimating = true
+ var iterationCount = 0
+ while (keepOnAnimating) {
+ runOnUiThread { keepOnAnimating = runTransition(state, coroutineScope, iterationCount) }
+ composeRule.mainClock.advanceTimeByFrame()
+ waitForIdle()
+ iterationCount++
+ }
+ waitForIdle()
+
+ return result
+ }
+
+ private class TestTransformation(specFactory: SpecFactory) :
+ CustomPropertyTransformation<Offset> {
+ override val property = PropertyTransformation.Property.Offset
+
+ val motionValue =
+ TransitionScopedMechanicsAdapter(createSpec = specFactory, stableThreshold = 1f)
+
+ override fun PropertyTransformationScope.transform(
+ content: ContentKey,
+ element: ElementKey,
+ transition: TransitionState.Transition,
+ transitionScope: CoroutineScope,
+ ): Offset {
+ val yOffset =
+ with(motionValue) { update(content, element, transition, transitionScope) }
+
+ return Offset(x = 0f, y = yOffset)
+ }
+ }
+
+ private fun assertOffsetMatchesGolden(transition: TransitionBuilder.() -> Unit) {
+ val recordingSpec =
+ TransitionRecordingSpec(recordBefore = false, recordAfter = true) {
+ featureOfElement(TestElements.Foo, yOffsetFeature)
+ }
+
+ val motion =
+ motionRule.recordTransition(
+ fromSceneContent = { SceneAContent() },
+ toSceneContent = { SceneBContent() },
+ transition = transition,
+ recordingSpec = recordingSpec,
+ layoutModifier = Modifier.size(50.dp),
+ )
+
+ motionRule.assertThat(motion).timeSeriesMatchesGolden()
+ }
+
+ companion object {
+
+ @Composable
+ fun ContentScope.SceneAContent() {
+ Box(modifier = Modifier.fillMaxSize())
+ }
+
+ @Composable
+ fun ContentScope.SceneBContent() {
+ Box(modifier = Modifier.fillMaxSize()) {
+ Box(Modifier.element(TestElements.Foo).size(50.dp).background(Color.Red))
+ }
+ }
+
+ @Composable
+ fun ContentScope.OverlayAContent() {
+ Box(Modifier.element(TestElements.Bar).size(50.dp).background(Color.Red))
+ }
+
+ @Composable
+ fun ContentScope.OverlayBContent() {
+ Box(modifier = Modifier.size(50.dp).background(Color.Green))
+ }
+
+ val TestSpring = SpringParameters(1200f, 1f)
+
+ val yOffsetFeature =
+ FeatureCapture<SemanticsNode, Float>("yOffset") {
+ DataPoint.of(it.lastOffsetForTesting?.y, DataPointTypes.float)
+ }
+ }
+}
+
+typealias DirectionAssertionTransition = TransitionBuilder.(container: ElementKey) -> Unit
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index b59b4ab..0648412 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -103,6 +103,7 @@
fun onAvailableClocksChanged() {}
}
+ private val replacementMap = ConcurrentHashMap<ClockId, ClockId>()
private val availableClocks = ConcurrentHashMap<ClockId, ClockInfo>()
private val clockChangeListeners = mutableListOf<ClockChangeListener>()
private val settingObserver =
@@ -209,6 +210,7 @@
continue
}
+ clock.replacementTarget?.let { replacementMap[id] = it }
info.provider = plugin
onLoaded(info)
}
@@ -393,10 +395,11 @@
// TODO: Merge w/ CurrentClockId when we convert to a flow. We shouldn't need both behaviors.
val activeClockId: String
get() {
- if (!availableClocks.containsKey(currentClockId)) {
+ var id = currentClockId
+ if (!availableClocks.containsKey(id)) {
return DEFAULT_CLOCK_ID
}
- return currentClockId
+ return replacementMap[id] ?: id
}
init {
@@ -404,6 +407,7 @@
defaultClockProvider.initialize(clockBuffers)
for (clock in defaultClockProvider.getClocks()) {
availableClocks[clock.clockId] = ClockInfo(clock, defaultClockProvider, null)
+ clock.replacementTarget?.let { replacementMap[clock.clockId] = it }
}
// Something has gone terribly wrong if the default clock isn't present
@@ -562,9 +566,12 @@
}
}
- fun getClocks(): List<ClockMetadata> {
- if (!isEnabled) return listOf(availableClocks[DEFAULT_CLOCK_ID]!!.metadata)
- return availableClocks.map { (_, clock) -> clock.metadata }
+ fun getClocks(includeDeprecated: Boolean = false): List<ClockMetadata> {
+ return when {
+ !isEnabled -> listOf(availableClocks[DEFAULT_CLOCK_ID]!!.metadata)
+ includeDeprecated -> availableClocks.map { (_, clock) -> clock.metadata }
+ else -> availableClocks.map { (_, clock) -> clock.metadata }.filter { !it.isDeprecated }
+ }
}
fun getClockPickerConfig(clockId: ClockId): ClockPickerConfig? {
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 b9200c1..e9e61a7 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
@@ -44,6 +44,7 @@
val dozeState = DefaultClockController.AnimationState(1F)
override val view = FlexClockView(clockCtx)
+ override var onViewBoundsChanged by view::onViewBoundsChanged
init {
fun createController(cfg: LayerConfig) {
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index f24ad1c..9bb3bac 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -17,6 +17,7 @@
import android.content.res.Resources
import android.graphics.Color
import android.graphics.Rect
+import android.graphics.RectF
import android.icu.text.NumberFormat
import android.util.TypedValue
import android.view.LayoutInflater
@@ -97,7 +98,12 @@
events.onLocaleChanged(Locale.getDefault())
}
- override fun initialize(isDarkTheme: Boolean, dozeFraction: Float, foldFraction: Float) {
+ override fun initialize(
+ isDarkTheme: Boolean,
+ dozeFraction: Float,
+ foldFraction: Float,
+ onBoundsChanged: (RectF) -> Unit,
+ ) {
largeClock.recomputePadding(null)
largeClock.animations = LargeClockAnimations(largeClock.view, dozeFraction, foldFraction)
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index 654478a..c3935e6 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -61,7 +61,14 @@
override fun getClocks(): List<ClockMetadata> {
var clocks = listOf(ClockMetadata(DEFAULT_CLOCK_ID))
- if (isClockReactiveVariantsEnabled) clocks += ClockMetadata(FLEX_CLOCK_ID)
+ if (isClockReactiveVariantsEnabled) {
+ clocks +=
+ ClockMetadata(
+ FLEX_CLOCK_ID,
+ isDeprecated = true,
+ replacementTarget = DEFAULT_CLOCK_ID,
+ )
+ }
return clocks
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt
index 1a1033b..6dfd226 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt
@@ -16,6 +16,7 @@
package com.android.systemui.shared.clocks
+import android.graphics.RectF
import com.android.systemui.animation.GSFAxes
import com.android.systemui.customization.R
import com.android.systemui.plugins.clocks.AlarmData
@@ -102,9 +103,15 @@
}
}
- override fun initialize(isDarkTheme: Boolean, dozeFraction: Float, foldFraction: Float) {
+ override fun initialize(
+ isDarkTheme: Boolean,
+ dozeFraction: Float,
+ foldFraction: Float,
+ onBoundsChanged: (RectF) -> Unit,
+ ) {
events.onFontAxesChanged(clockCtx.settings.axes)
smallClock.run {
+ layerController.onViewBoundsChanged = onBoundsChanged
events.onThemeChanged(theme.copy(isDarkTheme = isDarkTheme))
animations.doze(dozeFraction)
animations.fold(foldFraction)
@@ -112,6 +119,7 @@
}
largeClock.run {
+ layerController.onViewBoundsChanged = onBoundsChanged
events.onThemeChanged(theme.copy(isDarkTheme = isDarkTheme))
animations.doze(dozeFraction)
animations.fold(foldFraction)
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
index 2282863..578a489 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
@@ -247,7 +247,7 @@
timespec = DigitalTimespec.TIME_FULL_FORMAT,
style = FontTextStyle(fontSizeScale = 0.98f),
aodStyle = FontTextStyle(),
- alignment = DigitalAlignment(HorizontalAlignment.LEFT, VerticalAlignment.CENTER),
+ alignment = DigitalAlignment(HorizontalAlignment.START, VerticalAlignment.CENTER),
dateTimeFormat = "h:mm",
)
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockLayerController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockLayerController.kt
index af00cc2..336c66e 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockLayerController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockLayerController.kt
@@ -16,6 +16,7 @@
package com.android.systemui.shared.clocks
+import android.graphics.RectF
import android.view.View
import androidx.annotation.VisibleForTesting
import com.android.systemui.plugins.clocks.ClockAnimations
@@ -31,4 +32,5 @@
val config: ClockFaceConfig
@VisibleForTesting var fakeTimeMills: Long?
+ var onViewBoundsChanged: ((RectF) -> Unit)?
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt
index 0b7ea1a..97004ef 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt
@@ -85,6 +85,7 @@
override val view = SimpleDigitalClockTextView(clockCtx, isLargeClock)
private val logger = Logger(clockCtx.messageBuffer, TAG)
val timespec = DigitalTimespecHandler(layerCfg.timespec, layerCfg.dateTimeFormat)
+ override var onViewBoundsChanged by view::onViewBoundsChanged
@VisibleForTesting
override var fakeTimeMills: Long?
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 2d0ca53..2dc3e2b 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
@@ -33,6 +33,7 @@
import com.android.systemui.shared.clocks.CanvasUtil.use
import com.android.systemui.shared.clocks.ClockContext
import com.android.systemui.shared.clocks.DigitTranslateAnimator
+import com.android.systemui.shared.clocks.VPoint
import com.android.systemui.shared.clocks.VPointF
import com.android.systemui.shared.clocks.VPointF.Companion.max
import com.android.systemui.shared.clocks.VPointF.Companion.times
@@ -100,6 +101,7 @@
updateLocale(Locale.getDefault())
}
+ var onViewBoundsChanged: ((RectF) -> Unit)? = null
private val digitOffsets = mutableMapOf<Int, Float>()
protected fun calculateSize(
@@ -108,13 +110,11 @@
shouldMeasureChildren: Boolean,
): VPointF {
maxChildSize = VPointF(-1, -1)
- fun SimpleDigitalClockTextView.getSize() = VPointF(measuredWidth, measuredHeight)
-
childViews.forEach { textView ->
if (shouldMeasureChildren) {
textView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
}
- maxChildSize = max(maxChildSize, textView.getSize())
+ maxChildSize = max(maxChildSize, textView.measuredSize)
}
aodTranslate = VPointF.ZERO
// TODO(b/364680879): Cleanup
@@ -179,8 +179,8 @@
)
private fun updateMeasuredSize(
- widthMeasureSpec: Int = measuredWidthAndState,
- heightMeasureSpec: Int = measuredHeightAndState,
+ widthMeasureSpec: Int,
+ heightMeasureSpec: Int,
shouldMeasureChildren: Boolean,
) {
val size = calculateSize(widthMeasureSpec, heightMeasureSpec, shouldMeasureChildren)
@@ -189,13 +189,21 @@
fun updateLocation() {
val layoutBounds = this.layoutBounds ?: return
+ val bounds =
+ RectF(
+ layoutBounds.centerX() - measuredWidth / 2f,
+ layoutBounds.centerY() - measuredHeight / 2f,
+ layoutBounds.centerX() + measuredWidth / 2f,
+ layoutBounds.centerY() + measuredHeight / 2f,
+ )
setFrame(
- (layoutBounds.centerX() - measuredWidth / 2f).roundToInt(),
- (layoutBounds.centerY() - measuredHeight / 2f).roundToInt(),
- (layoutBounds.centerX() + measuredWidth / 2f).roundToInt(),
- (layoutBounds.centerY() + measuredHeight / 2f).roundToInt(),
+ bounds.left.roundToInt(),
+ bounds.top.roundToInt(),
+ bounds.right.roundToInt(),
+ bounds.bottom.roundToInt(),
)
updateChildFrames(isLayout = false)
+ onViewBoundsChanged?.let { it(bounds) }
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
@@ -348,7 +356,18 @@
}
fun animateFidget(x: Float, y: Float) {
- childViews.forEach { view -> view.animateFidget(x, y) }
+ val touchPt = VPointF(x, y)
+ val ints = intArrayOf(0, 0)
+ childViews
+ .sortedBy { view ->
+ view.getLocationInWindow(ints)
+ val loc = VPoint(ints[0], ints[1])
+ val center = loc + view.measuredSize / 2f
+ (center - touchPt).length()
+ }
+ .forEachIndexed { i, view ->
+ view.animateFidget(FIDGET_DELAYS[min(i, FIDGET_DELAYS.size - 1)])
+ }
}
private fun updateLocale(locale: Locale) {
@@ -432,6 +451,8 @@
val AOD_HORIZONTAL_TRANSLATE_RATIO = -0.15F
val AOD_VERTICAL_TRANSLATE_RATIO = 0.075F
+ val FIDGET_DELAYS = listOf(0L, 75L, 150L, 225L)
+
// Delays. Each digit's animation should have a slight delay, so we get a nice
// "stepping" effect. When moving right, the second digit of the hour should move first.
// When moving left, the first digit of the hour should move first. The lists encode
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 015a827..fae17a5 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
@@ -31,6 +31,7 @@
import android.util.Log
import android.util.MathUtils.lerp
import android.util.TypedValue
+import android.view.View
import android.view.View.MeasureSpec.EXACTLY
import android.view.animation.Interpolator
import android.view.animation.PathInterpolator
@@ -74,14 +75,38 @@
enum class VerticalAlignment {
TOP,
BOTTOM,
- BASELINE, // default
+ BASELINE,
CENTER,
}
enum class HorizontalAlignment {
+ LEFT {
+ override fun resolveXAlignment(view: View) = XAlignment.LEFT
+ },
+ RIGHT {
+ override fun resolveXAlignment(view: View) = XAlignment.RIGHT
+ },
+ START {
+ override fun resolveXAlignment(view: View): XAlignment {
+ return if (view.isLayoutRtl()) XAlignment.RIGHT else XAlignment.LEFT
+ }
+ },
+ END {
+ override fun resolveXAlignment(view: View): XAlignment {
+ return if (view.isLayoutRtl()) XAlignment.LEFT else XAlignment.RIGHT
+ }
+ },
+ CENTER {
+ override fun resolveXAlignment(view: View) = XAlignment.CENTER
+ };
+
+ abstract fun resolveXAlignment(view: View): XAlignment
+}
+
+enum class XAlignment {
LEFT,
RIGHT,
- CENTER, // default
+ CENTER,
}
@SuppressLint("AppCompatCustomView")
@@ -117,6 +142,7 @@
fidgetFontVariation = buildFidgetVariation(lsFontAxes).toFVar()
}
+ var onViewBoundsChanged: ((RectF) -> Unit)? = null
private val parser = DimensionParser(clockCtx.context)
var maxSingleDigitHeight = -1f
var maxSingleDigitWidth = -1f
@@ -154,7 +180,11 @@
}
var verticalAlignment: VerticalAlignment = VerticalAlignment.BASELINE
- var horizontalAlignment: HorizontalAlignment = HorizontalAlignment.LEFT
+ var horizontalAlignment: HorizontalAlignment = HorizontalAlignment.CENTER
+
+ val xAlignment: XAlignment
+ get() = horizontalAlignment.resolveXAlignment(this)
+
var isAnimationEnabled = true
var dozeFraction: Float = 0f
set(value) {
@@ -256,6 +286,7 @@
canvas.use {
digitTranslateAnimator?.apply { canvas.translate(currentTranslation) }
canvas.translate(getDrawTranslation(interpBounds))
+ if (isLayoutRtl()) canvas.translate(interpBounds.width() - textBounds.width(), 0f)
textAnimator.draw(canvas)
}
}
@@ -341,7 +372,9 @@
updateTextBoundsForTextAnimator()
}
- fun animateFidget(x: Float, y: Float) {
+ fun animateFidget(x: Float, y: Float) = animateFidget(0L)
+
+ fun animateFidget(delay: Long) {
if (!this::textAnimator.isInitialized || textAnimator.isRunning) {
// Skip fidget animation if other animation is already playing.
return
@@ -350,13 +383,13 @@
logger.animateFidget(x, y)
clockCtx.vibrator?.vibrate(FIDGET_HAPTICS)
- // TODO(b/374306512): Delay each glyph's animation based on x/y position
textAnimator.setTextStyle(
TextAnimator.Style(fVar = fidgetFontVariation),
TextAnimator.Animation(
animate = isAnimationEnabled,
duration = FIDGET_ANIMATION_DURATION,
interpolator = FIDGET_INTERPOLATOR,
+ startDelay = delay,
onAnimationEnd = {
textAnimator.setTextStyle(
TextAnimator.Style(fVar = lsFontVariation),
@@ -399,8 +432,10 @@
/** Returns the interpolated text bounding rect based on interpolation progress */
private fun getInterpolatedTextBounds(progress: Float = getInterpolatedProgress()): RectF {
- if (!textAnimator.isRunning || progress >= 1f) {
- return RectF(targetTextBounds)
+ if (progress <= 0f) {
+ return prevTextBounds
+ } else if (!textAnimator.isRunning || progress >= 1f) {
+ return targetTextBounds
}
return RectF().apply {
@@ -456,16 +491,16 @@
private fun setInterpolatedLocation(measureSize: VPointF): RectF {
val targetRect = RectF()
targetRect.apply {
- when (horizontalAlignment) {
- HorizontalAlignment.LEFT -> {
+ when (xAlignment) {
+ XAlignment.LEFT -> {
left = layoutBounds.left
right = layoutBounds.left + measureSize.x
}
- HorizontalAlignment.CENTER -> {
+ XAlignment.CENTER -> {
left = layoutBounds.centerX() - measureSize.x / 2f
right = layoutBounds.centerX() + measureSize.x / 2f
}
- HorizontalAlignment.RIGHT -> {
+ XAlignment.RIGHT -> {
left = layoutBounds.right - measureSize.x
right = layoutBounds.right
}
@@ -497,6 +532,7 @@
targetRect.right.roundToInt(),
targetRect.bottom.roundToInt(),
)
+ onViewBoundsChanged?.let { it(targetRect) }
return targetRect
}
@@ -504,10 +540,10 @@
val sizeDiff = this.measuredSize - interpBounds.size
val alignment =
VPointF(
- when (horizontalAlignment) {
- HorizontalAlignment.LEFT -> 0f
- HorizontalAlignment.CENTER -> 0.5f
- HorizontalAlignment.RIGHT -> 1f
+ when (xAlignment) {
+ XAlignment.LEFT -> 0f
+ XAlignment.CENTER -> 0.5f
+ XAlignment.RIGHT -> 1f
},
when (verticalAlignment) {
VerticalAlignment.TOP -> 0f
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 189d554..f4d4b1e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -25,6 +25,8 @@
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
+import android.view.ViewTreeObserver
+import android.view.ViewTreeObserver.OnPreDrawListener
import android.view.WindowInsetsController
import android.widget.FrameLayout
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -720,6 +722,37 @@
}
@Test
+ fun startAppearAnimation_ifDelayed() {
+ val argumentCaptor = ArgumentCaptor.forClass(OnPreDrawListener::class.java)
+ whenever(view.isAppearAnimationDelayed).thenReturn(true)
+ val viewTreeObserver: ViewTreeObserver = mock()
+ whenever(view.viewTreeObserver).thenReturn(viewTreeObserver)
+
+ underTest.startAppearAnimationIfDelayed()
+
+ verify(view).alpha = 1f
+ verify(viewTreeObserver).addOnPreDrawListener(argumentCaptor.capture())
+ argumentCaptor.value.onPreDraw()
+
+ verify(view).startAppearAnimation(any(SecurityMode::class.java))
+ verify(view).setIsAppearAnimationDelayed(false)
+ }
+
+ @Test
+ fun appearAnimation_willNotStart_ifNotDelayed() {
+ whenever(view.isAppearAnimationDelayed).thenReturn(false)
+ val viewTreeObserver: ViewTreeObserver = mock()
+ whenever(view.viewTreeObserver).thenReturn(viewTreeObserver)
+
+ underTest.startAppearAnimationIfDelayed()
+
+ verify(view, never()).alpha
+ verify(viewTreeObserver, never()).addOnPreDrawListener(any())
+
+ verify(view, never()).startAppearAnimation(any(SecurityMode::class.java))
+ }
+
+ @Test
fun gravityReappliedOnConfigurationChange() {
// Set initial gravity
testableResources.addOverride(R.integer.keyguard_host_view_gravity, Gravity.CENTER)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index 176824f..2845f6a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -452,6 +452,14 @@
verify(keyguardPasswordView).setDisappearAnimationListener(any());
}
+ @Test
+ public void setupForDelayedAppear() {
+ mKeyguardSecurityContainer.setupForDelayedAppear();
+ assertThat(mKeyguardSecurityContainer.getTranslationY()).isEqualTo(0f);
+ assertThat(mKeyguardSecurityContainer.getAlpha()).isEqualTo(0f);
+ assertThat(mKeyguardSecurityContainer.isAppearAnimationDelayed()).isTrue();
+ }
+
private BackEvent createBackEvent(float touchX, float progress) {
return new BackEvent(0, 0, progress, BackEvent.EDGE_LEFT);
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java
index f53f964..191eccc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java
@@ -28,6 +28,8 @@
import android.animation.ValueAnimator;
import android.content.pm.UserInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.Rect;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
@@ -54,6 +56,7 @@
import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.wm.shell.animation.FlingAnimationUtils;
import org.junit.Before;
@@ -127,10 +130,16 @@
@Mock
WindowRootView mWindowRootView;
+ @Mock
+ Resources mResources;
+
private SceneInteractor mSceneInteractor;
+ private KeyguardStateController mKeyguardStateController;
+
private static final float TOUCH_REGION = .3f;
private static final float MIN_BOUNCER_HEIGHT = .05f;
+ private final Configuration mConfiguration = new Configuration();
private static final Rect SCREEN_BOUNDS = new Rect(0, 0, 1024, 100);
private static final UserInfo CURRENT_USER_INFO = new UserInfo(
@@ -153,6 +162,8 @@
public void setup() {
mKosmos = new KosmosJavaAdapter(this);
mSceneInteractor = spy(mKosmos.getSceneInteractor());
+ mKeyguardStateController = mKosmos.getKeyguardStateController();
+ mConfiguration.orientation = Configuration.ORIENTATION_PORTRAIT;
MockitoAnnotations.initMocks(this);
mTouchHandler = new BouncerSwipeTouchHandler(
@@ -172,7 +183,9 @@
mKeyguardInteractor,
mSceneInteractor,
mKosmos.getShadeRepository(),
- Optional.of(() -> mWindowRootView));
+ Optional.of(() -> mWindowRootView),
+ mKeyguardStateController,
+ mKosmos.getCommunalSettingsInteractor());
when(mScrimManager.getCurrentController()).thenReturn(mScrimController);
when(mValueAnimatorCreator.create(anyFloat(), anyFloat())).thenReturn(mValueAnimator);
@@ -180,6 +193,9 @@
when(mFlingAnimationUtils.getMinVelocityPxPerSecond()).thenReturn(Float.MAX_VALUE);
when(mTouchSession.getBounds()).thenReturn(SCREEN_BOUNDS);
when(mKeyguardInteractor.isKeyguardDismissible()).thenReturn(MutableStateFlow(false));
+ when(mKeyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(true);
+ when(mWindowRootView.getResources()).thenReturn(mResources);
+ when(mResources.getConfiguration()).thenReturn(mConfiguration);
}
/**
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
index dd43d81..e8dc6762 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
@@ -16,6 +16,10 @@
package com.android.systemui.ambient.touch;
+import static android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf;
+
+import static com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2;
+
import static com.google.common.truth.Truth.assertThat;
import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
@@ -34,6 +38,8 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.pm.UserInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.Rect;
import android.graphics.Region;
import android.platform.test.annotations.DisableFlags;
@@ -63,6 +69,7 @@
import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.wm.shell.animation.FlingAnimationUtils;
import org.junit.Before;
@@ -132,12 +139,16 @@
@Mock
WindowRootView mWindowRootView;
+ Resources mResources;
+
@Mock
CommunalViewModel mCommunalViewModel;
@Mock
KeyguardInteractor mKeyguardInteractor;
+ private KeyguardStateController mKeyguardStateController;
+
@Captor
ArgumentCaptor<Rect> mRectCaptor;
@@ -147,6 +158,7 @@
private static final int SCREEN_WIDTH_PX = 1024;
private static final int SCREEN_HEIGHT_PX = 100;
private static final float MIN_BOUNCER_HEIGHT = .05f;
+ private final Configuration mConfiguration = new Configuration();
private static final Rect SCREEN_BOUNDS = new Rect(0, 0, 1024, 100);
private static final UserInfo CURRENT_USER_INFO = new UserInfo(
@@ -157,7 +169,8 @@
@Parameters(name = "{0}")
public static List<FlagsParameterization> getParams() {
- return SceneContainerFlagParameterizationKt.parameterizeSceneContainerFlag();
+ return SceneContainerFlagParameterizationKt
+ .andSceneContainer(allCombinationsOf(Flags.FLAG_GLANCEABLE_HUB_V2));
}
public BouncerSwipeTouchHandlerTest(FlagsParameterization flags) {
@@ -168,7 +181,13 @@
@Before
public void setup() {
mKosmos = new KosmosJavaAdapter(this);
+ mContext.ensureTestableResources();
+ mResources = mContext.getResources();
+ overrideConfiguration(mConfiguration);
+ mConfiguration.orientation = Configuration.ORIENTATION_PORTRAIT;
+
mSceneInteractor = spy(mKosmos.getSceneInteractor());
+ mKeyguardStateController = mKosmos.getKeyguardStateController();
MockitoAnnotations.initMocks(this);
mTouchHandler = new BouncerSwipeTouchHandler(
@@ -188,7 +207,9 @@
mKeyguardInteractor,
mSceneInteractor,
mKosmos.getShadeRepository(),
- Optional.of(() -> mWindowRootView)
+ Optional.of(() -> mWindowRootView),
+ mKeyguardStateController,
+ mKosmos.getCommunalSettingsInteractor()
);
when(mScrimManager.getCurrentController()).thenReturn(mScrimController);
@@ -197,6 +218,9 @@
when(mFlingAnimationUtils.getMinVelocityPxPerSecond()).thenReturn(Float.MAX_VALUE);
when(mTouchSession.getBounds()).thenReturn(SCREEN_BOUNDS);
when(mKeyguardInteractor.isKeyguardDismissible()).thenReturn(MutableStateFlow(false));
+ when(mKeyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(true);
+ when(mWindowRootView.getResources()).thenReturn(mResources);
+ setCommunalV2ConfigEnabled(true);
}
/**
@@ -586,6 +610,43 @@
verify(mUiEventLogger).log(BouncerSwipeTouchHandler.DreamEvent.DREAM_BOUNCER_FULLY_VISIBLE);
}
+ @Test
+ @DisableFlags(Flags.FLAG_SCENE_CONTAINER)
+ @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+ public void swipeUpAboveThresholdInLandscape_keyguardRotationNotAllowed_showsBouncer() {
+ when(mKeyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(false);
+ mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
+
+ final float swipeUpPercentage = .1f;
+ // The upward velocity is ignored.
+ final float velocityY = -1;
+ swipeToPosition(swipeUpPercentage, velocityY);
+
+ // Ensure show bouncer scrimmed
+ verify(mScrimController).show(true);
+ verify(mValueAnimatorCreator, never()).create(anyFloat(), anyFloat());
+ verify(mValueAnimator, never()).start();
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_SCENE_CONTAINER)
+ @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+ public void swipeUpBelowThreshold_inLandscapeKeyguardRotationNotAllowed_noBouncer() {
+ mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
+
+ final float swipeUpPercentage = .02f;
+ // The upward velocity is ignored.
+ final float velocityY = -1;
+ swipeToPosition(swipeUpPercentage, velocityY);
+
+ // no bouncer shown scrimmed
+ verify(mScrimController, never()).show(true);
+ // on touch end, bouncer hidden
+ verify(mValueAnimatorCreator).create(eq(1 - swipeUpPercentage),
+ eq(KeyguardBouncerConstants.EXPANSION_HIDDEN));
+ verify(mValueAnimator, never()).addListener(any());
+ }
+
/**
* Tests that swiping up with a speed above the set threshold will continue the expansion.
*/
@@ -672,4 +733,15 @@
inputEventListenerCaptor.getValue().onInputEvent(upEvent);
}
+
+ private void setCommunalV2ConfigEnabled(boolean enabled) {
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.bool.config_glanceableHubEnabled,
+ enabled);
+ }
+
+ private void overrideConfiguration(Configuration configuration) {
+ mContext.getOrCreateTestableResources().overrideConfiguration(
+ configuration);
+ }
}
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 0cfb36d..446891a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -531,20 +531,23 @@
@Test
fun testLayoutParams_hasSecureWindowFlag() {
- val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
+ val layoutParams =
+ AuthContainerView.getLayoutParams(windowToken, "", false /* isCredentialView */)
assertThat((layoutParams.flags and WindowManager.LayoutParams.FLAG_SECURE) != 0).isTrue()
}
@Test
fun testLayoutParams_hasShowWhenLockedFlag() {
- val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
+ val layoutParams =
+ AuthContainerView.getLayoutParams(windowToken, "", false /* isCredentialView */)
assertThat((layoutParams.flags and WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED) != 0)
.isTrue()
}
@Test
fun testLayoutParams_hasDimbehindWindowFlag() {
- val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
+ val layoutParams =
+ AuthContainerView.getLayoutParams(windowToken, "", false /* isCredentialView */)
val lpFlags = layoutParams.flags
val lpDimAmount = layoutParams.dimAmount
@@ -554,7 +557,8 @@
@Test
fun testLayoutParams_excludesImeInsets() {
- val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
+ val layoutParams =
+ AuthContainerView.getLayoutParams(windowToken, "", false /* isCredentialView */)
assertThat((layoutParams.fitInsetsTypes and WindowInsets.Type.ime()) == 0).isTrue()
}
@@ -706,7 +710,8 @@
@Test
fun testLayoutParams_hasCutoutModeAlwaysFlag() {
- val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
+ val layoutParams =
+ AuthContainerView.getLayoutParams(windowToken, "", false /* isCredentialView */)
val lpFlags = layoutParams.flags
assertThat(
@@ -717,7 +722,8 @@
@Test
fun testLayoutParams_excludesSystemBarInsets() {
- val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
+ val layoutParams =
+ AuthContainerView.getLayoutParams(windowToken, "", false /* isCredentialView */)
assertThat((layoutParams.fitInsetsTypes and WindowInsets.Type.systemBars()) == 0).isTrue()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index aeea99b..a2f5a30 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -193,8 +193,6 @@
@Mock
private UdfpsOverlayInteractor mUdfpsOverlayInteractor;
@Mock
- private UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
- @Mock
private SelectedUserInteractor mSelectedUserInteractor;
// Capture listeners so that they can be used to send events
@@ -321,7 +319,6 @@
mAlternateBouncerInteractor,
mInputManager,
mock(DeviceEntryFaceAuthInteractor.class),
- mUdfpsKeyguardAccessibilityDelegate,
mSelectedUserInteractor,
mKeyguardTransitionInteractor,
mDeviceEntryUdfpsTouchOverlayViewModel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegateTest.kt
deleted file mode 100644
index 921ff09..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegateTest.kt
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2023 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.biometrics
-
-import android.testing.TestableLooper
-import android.view.View
-import android.view.accessibility.AccessibilityNodeInfo
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
-import com.android.systemui.util.mockito.argumentCaptor
-import org.junit.Assert.assertEquals
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.anyBoolean
-import org.mockito.Mock
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@RunWith(AndroidJUnit4::class)
-@SmallTest
-@TestableLooper.RunWithLooper
-class UdfpsKeyguardAccessibilityDelegateTest : SysuiTestCase() {
-
- @Mock private lateinit var keyguardViewManager: StatusBarKeyguardViewManager
- @Mock private lateinit var hostView: View
- private lateinit var underTest: UdfpsKeyguardAccessibilityDelegate
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- underTest =
- UdfpsKeyguardAccessibilityDelegate(
- context.resources,
- keyguardViewManager,
- )
- }
-
- @Test
- fun onInitializeAccessibilityNodeInfo_clickActionAdded() {
- // WHEN node is initialized
- val mockedNodeInfo = mock(AccessibilityNodeInfo::class.java)
- underTest.onInitializeAccessibilityNodeInfo(hostView, mockedNodeInfo)
-
- // THEN a11y action is added
- val argumentCaptor = argumentCaptor<AccessibilityNodeInfo.AccessibilityAction>()
- verify(mockedNodeInfo).addAction(argumentCaptor.capture())
-
- // AND the a11y action is a click action
- assertEquals(
- AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id,
- argumentCaptor.value.id
- )
- }
-
- @Test
- fun performAccessibilityAction_actionClick_showsPrimaryBouncer() {
- // WHEN click action is performed
- val mockedNodeInfo = mock(AccessibilityNodeInfo::class.java)
- underTest.performAccessibilityAction(
- hostView,
- AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id,
- null
- )
-
- // THEN primary bouncer shows
- verify(keyguardViewManager).showPrimaryBouncer(anyBoolean())
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
index 4d02708..5249bbe 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
@@ -26,7 +26,10 @@
import androidx.test.filters.SmallTest
import com.airbnb.lottie.LottieAnimationView
import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.biometricStatusRepository
import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.AuthenticationReason
+import com.android.systemui.biometrics.shared.model.AuthenticationReason.SettingsOperations
import com.android.systemui.biometrics.shared.model.DisplayRotation
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.SensorStrength
@@ -50,7 +53,6 @@
import org.mockito.Mock
import org.mockito.Mockito.any
import org.mockito.Mockito.inOrder
-import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
@@ -67,6 +69,7 @@
@JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule()
@Mock private lateinit var layoutInflater: LayoutInflater
@Mock private lateinit var sideFpsView: View
+ @Mock private lateinit var lottieAnimationView: LottieAnimationView
@Captor private lateinit var viewCaptor: ArgumentCaptor<View>
@Before
@@ -76,7 +79,7 @@
context.addMockSystemService(WindowManager::class.java, kosmos.windowManager)
`when`(layoutInflater.inflate(R.layout.sidefps_view, null, false)).thenReturn(sideFpsView)
`when`(sideFpsView.requireViewById<LottieAnimationView>(eq(R.id.sidefps_animation)))
- .thenReturn(mock(LottieAnimationView::class.java))
+ .thenReturn(lottieAnimationView)
}
@Test
@@ -184,6 +187,20 @@
}
}
+ @Test
+ fun verifyToggleAnimation_onSideFpsIndicatorViewClickedWhileEnrolling() {
+ kosmos.testScope.runTest {
+ kosmos.biometricStatusRepository.setFingerprintAuthenticationReason(
+ AuthenticationReason.SettingsAuthentication(SettingsOperations.ENROLL_ENROLLING)
+ )
+ setupTestConfiguration(isInRearDisplayMode = false)
+ val clickListenerCaptor = ArgumentCaptor.forClass(View.OnClickListener::class.java)
+ verify(sideFpsView).setOnClickListener(clickListenerCaptor.capture())
+ clickListenerCaptor.value.onClick(sideFpsView)
+ verify(lottieAnimationView).toggleAnimation()
+ }
+ }
+
private suspend fun TestScope.setupTestConfiguration(isInRearDisplayMode: Boolean) {
kosmos.fingerprintPropertyRepository.setProperties(
sensorId = 1,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt
index 682ad0c5..754011b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt
@@ -125,7 +125,9 @@
bluetoothTileDialogAudioSharingRepository.emitAudioSourceStateUpdate()
runCurrent()
- assertThat(value).isNull()
+ assertThat(value).isEqualTo(Unit)
+ verify(bluetoothTileDialogLogger).logAudioSharingStateChanged(true)
+ verify(bluetoothTileDialogLogger).logAudioSourceStateUpdate()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepositoryTest.kt
index 587f3cc8..159c15b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepositoryTest.kt
@@ -62,6 +62,7 @@
AudioSharingRepositoryImpl(
kosmos.localBluetoothManager,
kosmos.audioSharingRepository,
+ kosmos.bluetoothTileDialogLogger,
kosmos.testDispatcher,
)
}
@@ -95,6 +96,8 @@
audioSharingRepository.setAudioSharingAvailable(true)
underTest.startAudioSharing()
verify(leAudioBroadcastProfile).startPrivateBroadcast()
+ verify(bluetoothTileDialogLogger)
+ .logAudioSharingRequest(AudioSharingRequest.START_BROADCAST)
}
}
@@ -105,6 +108,8 @@
audioSharingRepository.setAudioSharingAvailable(false)
underTest.startAudioSharing()
verify(leAudioBroadcastProfile, never()).startPrivateBroadcast()
+ verify(bluetoothTileDialogLogger, never())
+ .logAudioSharingRequest(AudioSharingRequest.START_BROADCAST)
}
}
@@ -117,6 +122,8 @@
audioSharingRepository.setAudioSharingAvailable(true)
underTest.stopAudioSharing()
verify(leAudioBroadcastProfile).stopLatestBroadcast()
+ verify(bluetoothTileDialogLogger)
+ .logAudioSharingRequest(AudioSharingRequest.STOP_BROADCAST)
}
}
@@ -140,6 +147,7 @@
runCurrent()
verify(leAudioBroadcastAssistant, never()).allConnectedDevices
+ verify(bluetoothTileDialogLogger, never()).logAudioSharingRequest(any())
}
}
@@ -157,6 +165,7 @@
runCurrent()
verify(leAudioBroadcastAssistant, never()).allConnectedDevices
+ verify(bluetoothTileDialogLogger, never()).logAudioSharingRequest(any())
}
}
@@ -177,6 +186,7 @@
runCurrent()
verify(leAudioBroadcastAssistant, never()).addSource(any(), any(), anyBoolean())
+ verify(bluetoothTileDialogLogger, never()).logAudioSharingRequest(any())
}
}
@@ -198,6 +208,8 @@
runCurrent()
verify(leAudioBroadcastAssistant).addSource(bluetoothDevice, metadata, false)
+ verify(bluetoothTileDialogLogger)
+ .logAudioSharingRequest(AudioSharingRequest.ADD_SOURCE)
}
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
index dd4af7b..53ddcfa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
@@ -20,6 +20,7 @@
import android.provider.Settings.Global.ONE_HANDED_KEYGUARD_SIDE
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
import com.android.internal.logging.uiEventLoggerFake
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
@@ -39,13 +40,22 @@
import com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runCurrent
+import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.res.R
+import com.android.systemui.scene.data.repository.sceneContainerRepository
+import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.transitionState
import com.android.systemui.testKosmos
import com.android.systemui.util.settings.fakeGlobalSettings
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -368,6 +378,76 @@
testableResources.removeOverride(R.bool.can_use_one_handed_bouncer)
}
+ @Test
+ fun bouncerExpansion_lockscreenToBouncer() =
+ kosmos.runTest {
+ val bouncerExpansion by collectLastValue(underTest.bouncerExpansion)
+
+ val progress = MutableStateFlow(0f)
+ kosmos.sceneContainerRepository.setTransitionState(transitionState)
+ transitionState.value =
+ ObservableTransitionState.Transition.showOverlay(
+ overlay = Overlays.Bouncer,
+ fromScene = Scenes.Lockscreen,
+ currentOverlays = flowOf(emptySet()),
+ progress = progress,
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+
+ assertThat(bouncerExpansion).isEqualTo(0f)
+
+ progress.value = 1f
+ assertThat(bouncerExpansion).isEqualTo(1f)
+ }
+
+ @Test
+ fun bouncerExpansion_BouncerToLockscreen() =
+ kosmos.runTest {
+ val bouncerExpansion by collectLastValue(underTest.bouncerExpansion)
+
+ val progress = MutableStateFlow(0f)
+ kosmos.sceneContainerRepository.setTransitionState(transitionState)
+ transitionState.value =
+ ObservableTransitionState.Transition.hideOverlay(
+ overlay = Overlays.Bouncer,
+ toScene = Scenes.Lockscreen,
+ currentOverlays = flowOf(emptySet()),
+ progress = progress,
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+
+ assertThat(bouncerExpansion).isEqualTo(1f)
+
+ progress.value = 1f
+ assertThat(bouncerExpansion).isEqualTo(0f)
+ }
+
+ @Test
+ fun bouncerExpansion_shadeToLockscreenUnderBouncer() =
+ kosmos.runTest {
+ val bouncerExpansion by collectLastValue(underTest.bouncerExpansion)
+
+ val progress = MutableStateFlow(0f)
+ kosmos.sceneContainerRepository.setTransitionState(transitionState)
+ transitionState.value =
+ ObservableTransitionState.Transition(
+ fromScene = Scenes.Shade,
+ toScene = Scenes.Lockscreen,
+ currentScene = flowOf(Scenes.Lockscreen),
+ progress = progress,
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ currentOverlays = setOf(Overlays.Bouncer),
+ )
+
+ assertThat(bouncerExpansion).isEqualTo(1f)
+
+ progress.value = 1f
+ assertThat(bouncerExpansion).isEqualTo(1f)
+ }
+
companion object {
private const val MESSAGE_ENTER_YOUR_PIN = "Enter your PIN"
private const val MESSAGE_ENTER_YOUR_PASSWORD = "Enter your password"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/domain/interactor/SysUIStatePerDisplayInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/domain/interactor/SysUIStatePerDisplayInteractorTest.kt
new file mode 100644
index 0000000..f64f13d
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/domain/interactor/SysUIStatePerDisplayInteractorTest.kt
@@ -0,0 +1,122 @@
+/*
+ * 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.common.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.model.StateChange
+import com.android.systemui.model.fakeSysUIStatePerDisplayRepository
+import com.android.systemui.model.sysUiStateFactory
+import com.android.systemui.model.sysuiStateInteractor
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SysUIStatePerDisplayInteractorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+
+ val stateRepository = kosmos.fakeSysUIStatePerDisplayRepository
+ val state0 = kosmos.sysUiStateFactory.create(0)
+ val state1 = kosmos.sysUiStateFactory.create(1)
+ val state2 = kosmos.sysUiStateFactory.create(2)
+
+ val underTest = kosmos.sysuiStateInteractor
+
+ @Before
+ fun setup() {
+ stateRepository.apply {
+ add(0, state0)
+ add(1, state1)
+ add(2, state2)
+ }
+ runBlocking {
+ kosmos.displayRepository.apply {
+ addDisplay(0)
+ addDisplay(1)
+ addDisplay(2)
+ }
+ }
+ }
+
+ @Test
+ fun setFlagsExclusivelyToDisplay_setsFlagsOnTargetStateAndClearsTheOthers() {
+ val targetDisplayId = 0
+ val stateChange = StateChange().setFlag(1L, true)
+
+ underTest.setFlagsExclusivelyToDisplay(targetDisplayId, stateChange)
+
+ assertThat(state0.isFlagEnabled(1)).isTrue()
+ assertThat(state1.isFlagEnabled(1)).isFalse()
+ assertThat(state2.isFlagEnabled(1)).isFalse()
+
+ underTest.setFlagsExclusivelyToDisplay(1, stateChange)
+
+ assertThat(state0.isFlagEnabled(1)).isFalse()
+ assertThat(state1.isFlagEnabled(1)).isTrue()
+ assertThat(state2.isFlagEnabled(1)).isFalse()
+
+ underTest.setFlagsExclusivelyToDisplay(2, stateChange)
+
+ assertThat(state0.isFlagEnabled(1)).isFalse()
+ assertThat(state1.isFlagEnabled(1)).isFalse()
+ assertThat(state2.isFlagEnabled(1)).isTrue()
+
+ underTest.setFlagsExclusivelyToDisplay(3, stateChange)
+
+ assertThat(state0.isFlagEnabled(1)).isFalse()
+ assertThat(state1.isFlagEnabled(1)).isFalse()
+ assertThat(state2.isFlagEnabled(1)).isFalse()
+ }
+
+ @Test
+ fun setFlagsExclusivelyToDisplay_multipleFlags_setsFlagsOnTargetStateAndClearsTheOthers() {
+ val stateChange = StateChange().setFlag(1L, true).setFlag(2L, true)
+
+ underTest.setFlagsExclusivelyToDisplay(1, stateChange)
+
+ assertThat(state0.isFlagEnabled(1)).isFalse()
+ assertThat(state0.isFlagEnabled(2)).isFalse()
+ assertThat(state1.isFlagEnabled(1)).isTrue()
+ assertThat(state1.isFlagEnabled(2)).isTrue()
+ assertThat(state2.isFlagEnabled(1)).isFalse()
+ assertThat(state2.isFlagEnabled(1)).isFalse()
+ }
+
+ @Test
+ fun setFlagsExclusivelyToDisplay_clearsFlags() {
+ state0.setFlag(1, true).setFlag(2, true).commitUpdate()
+ state1.setFlag(1, true).setFlag(2, true).commitUpdate()
+ state2.setFlag(1, true).setFlag(2, true).commitUpdate()
+
+ val stateChange = StateChange().setFlag(1L, false)
+
+ underTest.setFlagsExclusivelyToDisplay(1, stateChange)
+
+ // Sets it as false in display 1, but also the others.
+ assertThat(state0.isFlagEnabled(1)).isFalse()
+ assertThat(state1.isFlagEnabled(1)).isFalse()
+ assertThat(state2.isFlagEnabled(1)).isFalse()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
index 7051f81..f583914 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -22,6 +22,7 @@
import android.platform.test.flag.junit.FlagsParameterization
import android.provider.Settings
import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
import com.android.internal.logging.uiEventLoggerFake
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
@@ -38,8 +39,13 @@
import com.android.systemui.flags.andSceneContainer
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+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.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.collectLastValue
@@ -56,11 +62,15 @@
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.advanceTimeBy
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito
+import org.mockito.kotlin.verify
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
@@ -93,10 +103,12 @@
communalInteractor = communalInteractor,
communalSettingsInteractor = communalSettingsInteractor,
communalSceneInteractor = communalSceneInteractor,
+ keyguardTransitionInteractor = keyguardTransitionInteractor,
keyguardInteractor = keyguardInteractor,
systemSettings = fakeSettings,
notificationShadeWindowController = notificationShadeWindowController,
bgScope = applicationCoroutineScope,
+ applicationScope = applicationCoroutineScope,
mainDispatcher = testDispatcher,
uiEventLogger = uiEventLoggerFake,
)
@@ -111,13 +123,13 @@
UserHandle.USER_CURRENT,
)
fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
+ setCommunalV2ConfigEnabled(true)
underTest.start()
// Make communal available so that communalInteractor.desiredScene accurately reflects
// scene changes instead of just returning Blank.
runBlocking { setCommunalAvailable(true) }
- setCommunalV2ConfigEnabled(true)
}
}
@@ -414,6 +426,107 @@
assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
}
+ @Test
+ @DisableFlags(FLAG_SCENE_CONTAINER)
+ @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+ fun glanceableHubOrientationAware_idleOnCommunal() =
+ kosmos.runTest {
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
+
+ val scene by collectLastValue(communalSceneInteractor.currentScene)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+ verify(notificationShadeWindowController).setGlanceableHubOrientationAware(true)
+ }
+
+ @Test
+ @DisableFlags(FLAG_SCENE_CONTAINER)
+ @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+ fun glanceableHubOrientationAware_transitioningToCommunal() =
+ kosmos.runTest {
+ val progress = MutableStateFlow(0f)
+ val targetScene = CommunalScenes.Communal
+ val currentScene = CommunalScenes.Blank
+ val transitionState =
+ MutableStateFlow(
+ ObservableTransitionState.Transition(
+ fromScene = currentScene,
+ toScene = targetScene,
+ currentScene = flowOf(targetScene),
+ progress = progress,
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+ )
+ communalSceneInteractor.setTransitionState(transitionState)
+
+ // Partially transition.
+ progress.value = .4f
+
+ val scene by collectLastValue(communalSceneInteractor.currentScene)
+ assertThat(scene).isEqualTo(CommunalScenes.Blank)
+
+ verify(notificationShadeWindowController).setGlanceableHubOrientationAware(true)
+ }
+
+ @Test
+ @DisableFlags(FLAG_SCENE_CONTAINER)
+ @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+ fun glanceableHubOrientationAware_communalToDreaming() =
+ kosmos.runTest {
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
+
+ verify(notificationShadeWindowController).setGlanceableHubOrientationAware(true)
+ Mockito.clearInvocations(notificationShadeWindowController)
+
+ val progress = MutableStateFlow(0f)
+ val currentScene = CommunalScenes.Communal
+ val targetScene = CommunalScenes.Blank
+ val transitionState =
+ MutableStateFlow(
+ ObservableTransitionState.Transition(
+ fromScene = currentScene,
+ toScene = targetScene,
+ currentScene = flowOf(targetScene),
+ progress = progress,
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+ )
+ communalSceneInteractor.setTransitionState(transitionState)
+
+ // Partially transitioned out of Communal scene
+ progress.value = .4f
+
+ // Started keyguard transitioning from hub -> dreaming.
+ fakeKeyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.DREAMING,
+ transitionState = TransitionState.STARTED,
+ )
+ )
+ verify(notificationShadeWindowController).setGlanceableHubOrientationAware(true)
+ Mockito.clearInvocations(notificationShadeWindowController)
+
+ fakeKeyguardTransitionRepository.sendTransitionStep(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.DREAMING,
+ transitionState = TransitionState.RUNNING,
+ value = 0.5f,
+ )
+
+ // Transitioned to dreaming.
+ fakeKeyguardTransitionRepository.sendTransitionStep(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.DREAMING,
+ transitionState = TransitionState.FINISHED,
+ value = 1f,
+ )
+ // Not on hub anymore, let other states take control
+ verify(notificationShadeWindowController).setGlanceableHubOrientationAware(false)
+ }
+
/**
* Advances time by duration + 1 millisecond, to ensure that tasks scheduled to run at
* currentTime + duration are scheduled.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt
index 77d7091..dc21f06 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt
@@ -134,7 +134,7 @@
underTest.snapToScene(
CommunalScenes.Communal,
"test",
- ActivityTransitionAnimator.TIMINGS.totalDuration
+ ActivityTransitionAnimator.TIMINGS.totalDuration,
)
assertThat(currentScene).isEqualTo(CommunalScenes.Blank)
advanceTimeBy(ActivityTransitionAnimator.TIMINGS.totalDuration)
@@ -269,6 +269,48 @@
@DisableFlags(FLAG_SCENE_CONTAINER)
@Test
+ fun isTransitioningToOrIdleOnCommunal() =
+ testScope.runTest {
+ // isIdleOnCommunal is false when not on communal.
+ val isTransitioningToOrIdleOnCommunal by
+ collectLastValue(underTest.isTransitioningToOrIdleOnCommunal)
+ assertThat(isTransitioningToOrIdleOnCommunal).isEqualTo(false)
+
+ val transitionState: MutableStateFlow<ObservableTransitionState> =
+ MutableStateFlow(
+ ObservableTransitionState.Transition(
+ fromScene = CommunalScenes.Blank,
+ toScene = CommunalScenes.Communal,
+ currentScene = flowOf(CommunalScenes.Communal),
+ progress = flowOf(0f),
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+ )
+
+ // Start transition to communal.
+ repository.setTransitionState(transitionState)
+ assertThat(isTransitioningToOrIdleOnCommunal).isEqualTo(true)
+
+ // Finish transition to communal
+ transitionState.value = ObservableTransitionState.Idle(CommunalScenes.Communal)
+ assertThat(isTransitioningToOrIdleOnCommunal).isEqualTo(true)
+
+ // Start transition away from communal.
+ transitionState.value =
+ ObservableTransitionState.Transition(
+ fromScene = CommunalScenes.Communal,
+ toScene = CommunalScenes.Blank,
+ currentScene = flowOf(CommunalScenes.Blank),
+ progress = flowOf(.1f),
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+ assertThat(isTransitioningToOrIdleOnCommunal).isEqualTo(false)
+ }
+
+ @DisableFlags(FLAG_SCENE_CONTAINER)
+ @Test
fun isCommunalVisible() =
testScope.runTest {
// isCommunalVisible is false when not on communal.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt
new file mode 100644
index 0000000..299105e
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayInstanceRepositoryImplTest.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.display.data.repository
+
+import android.view.Display
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class PerDisplayInstanceRepositoryImplTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+ private val testScope = kosmos.testScope
+ private val fakeDisplayRepository = kosmos.displayRepository
+ private val fakePerDisplayInstanceProviderWithTeardown =
+ kosmos.fakePerDisplayInstanceProviderWithTeardown
+
+ private val underTest: PerDisplayInstanceRepositoryImpl<TestPerDisplayInstance> =
+ kosmos.fakePerDisplayInstanceRepository
+
+ @Before
+ fun addDisplays() = runBlocking {
+ fakeDisplayRepository += createDisplay(DEFAULT_DISPLAY_ID)
+ fakeDisplayRepository += createDisplay(NON_DEFAULT_DISPLAY_ID)
+ }
+
+ @Test
+ fun forDisplay_defaultDisplay_multipleCalls_returnsSameInstance() =
+ testScope.runTest {
+ val instance = underTest[DEFAULT_DISPLAY_ID]
+
+ assertThat(underTest[DEFAULT_DISPLAY_ID]).isSameInstanceAs(instance)
+ }
+
+ @Test
+ fun forDisplay_nonDefaultDisplay_multipleCalls_returnsSameInstance() =
+ testScope.runTest {
+ val instance = underTest[NON_DEFAULT_DISPLAY_ID]
+
+ assertThat(underTest[NON_DEFAULT_DISPLAY_ID]).isSameInstanceAs(instance)
+ }
+
+ @Test
+ fun forDisplay_nonDefaultDisplay_afterDisplayRemoved_returnsNewInstance() =
+ testScope.runTest {
+ val instance = underTest[NON_DEFAULT_DISPLAY_ID]
+
+ fakeDisplayRepository -= NON_DEFAULT_DISPLAY_ID
+ fakeDisplayRepository += createDisplay(NON_DEFAULT_DISPLAY_ID)
+
+ assertThat(underTest[NON_DEFAULT_DISPLAY_ID]).isNotSameInstanceAs(instance)
+ }
+
+ @Test
+ fun forDisplay_nonExistingDisplayId_returnsNull() =
+ testScope.runTest { assertThat(underTest[NON_EXISTING_DISPLAY_ID]).isNull() }
+
+ @Test
+ fun forDisplay_afterDisplayRemoved_destroyInstanceInvoked() =
+ testScope.runTest {
+ val instance = underTest[NON_DEFAULT_DISPLAY_ID]
+
+ fakeDisplayRepository -= NON_DEFAULT_DISPLAY_ID
+
+ assertThat(fakePerDisplayInstanceProviderWithTeardown.destroyed)
+ .containsExactly(instance)
+ }
+
+ @Test
+ fun forDisplay_withoutDisplayRemoval_destroyInstanceIsNotInvoked() =
+ testScope.runTest {
+ underTest[NON_DEFAULT_DISPLAY_ID]
+
+ assertThat(fakePerDisplayInstanceProviderWithTeardown.destroyed).isEmpty()
+ }
+
+ private fun createDisplay(displayId: Int): Display =
+ display(type = Display.TYPE_INTERNAL, id = displayId)
+
+ companion object {
+ private const val DEFAULT_DISPLAY_ID = Display.DEFAULT_DISPLAY
+ private const val NON_DEFAULT_DISPLAY_ID = DEFAULT_DISPLAY_ID + 1
+ private const val NON_EXISTING_DISPLAY_ID = DEFAULT_DISPLAY_ID + 2
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelTest.kt
index 6b9e23a..135e9a5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelTest.kt
@@ -16,28 +16,46 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.content.res.mainResources
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_BLURRED_BACKGROUND
+import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.communal.domain.interactor.setCommunalV2ConfigEnabled
+import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.ui.transitions.blurConfig
+import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.collectValues
+import com.android.systemui.kosmos.runCurrent
import com.android.systemui.kosmos.runTest
+import com.android.systemui.statusbar.policy.keyguardStateController
import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
class GlanceableHubToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() {
- private val kosmos = testKosmos()
+ private val kosmos =
+ testKosmos().apply { mainResources = mContext.orCreateTestableResources.resources }
private val underTest by lazy { kosmos.glanceableHubToPrimaryBouncerTransitionViewModel }
+ @Before
+ fun setUp() {
+ with(kosmos) { setCommunalV2ConfigEnabled(true) }
+ }
+
@Test
@DisableSceneContainer
@DisableFlags(FLAG_GLANCEABLE_HUB_BLURRED_BACKGROUND)
@@ -84,4 +102,81 @@
},
)
}
+
+ @Test
+ @DisableSceneContainer
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
+ fun willDelayBouncerAppearAnimation_flagDisabled_isFalse() =
+ kosmos.runTest {
+ // keyguard rotation is not allowed on device.
+ whenever(keyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(false)
+
+ val isIdleOnCommunal by collectLastValue(communalInteractor.isIdleOnCommunal)
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
+ runCurrent()
+ // Device is idle on communal.
+ assertThat(isIdleOnCommunal).isTrue()
+
+ // in landscape
+ assertThat(underTest.willDelayAppearAnimation(isLandscape = true)).isFalse()
+ // in portrait
+ assertThat(underTest.willDelayAppearAnimation(isLandscape = false)).isFalse()
+ }
+
+ @Test
+ @DisableSceneContainer
+ @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+ fun willDelayBouncerAppearAnimation_keyguardRotationAllowed_isFalse() =
+ kosmos.runTest {
+ // Keyguard rotation is allowed on device.
+ whenever(keyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(true)
+
+ val isIdleOnCommunal by collectLastValue(communalInteractor.isIdleOnCommunal)
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
+ runCurrent()
+ // Device is idle on communal.
+ assertThat(isIdleOnCommunal).isTrue()
+
+ // in landscape
+ assertThat(underTest.willDelayAppearAnimation(isLandscape = true)).isFalse()
+ // in portrait
+ assertThat(underTest.willDelayAppearAnimation(isLandscape = false)).isFalse()
+ }
+
+ @Test
+ @DisableSceneContainer
+ @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+ fun willDelayBouncerAppearAnimation_isNotIdleOnCommunal_isFalse() =
+ kosmos.runTest {
+ whenever(keyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(false)
+
+ val isIdleOnCommunal by collectLastValue(communalInteractor.isIdleOnCommunal)
+ communalSceneInteractor.changeScene(CommunalScenes.Blank, "test")
+ runCurrent()
+ // Device is not on communal.
+ assertThat(isIdleOnCommunal).isFalse()
+
+ // in landscape
+ assertThat(underTest.willDelayAppearAnimation(isLandscape = true)).isFalse()
+ // in portrait
+ assertThat(underTest.willDelayAppearAnimation(isLandscape = false)).isFalse()
+ }
+
+ @Test
+ @DisableSceneContainer
+ @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+ fun willDelayBouncerAppearAnimation_isIdleOnCommunalAndKeyguardRotationIsNotAllowed() =
+ kosmos.runTest {
+ whenever(keyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(false)
+ val isIdleOnCommunal by collectLastValue(communalInteractor.isIdleOnCommunal)
+ communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
+ runCurrent()
+ // Device is idle on communal.
+ assertThat(isIdleOnCommunal).isTrue()
+
+ // Will delay in landscape
+ assertThat(underTest.willDelayAppearAnimation(isLandscape = true)).isTrue()
+ // Won't delay in portrait
+ assertThat(underTest.willDelayAppearAnimation(isLandscape = false)).isFalse()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
index 0197a1e..c72afc7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
@@ -16,26 +16,17 @@
package com.android.systemui.media.controls.domain.interactor
-import android.R
-import android.graphics.drawable.Icon
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.internal.logging.InstanceId
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
-import com.android.systemui.media.controls.MediaTestHelper
import com.android.systemui.media.controls.data.repository.MediaFilterRepository
import com.android.systemui.media.controls.data.repository.mediaFilterRepository
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
-import com.android.systemui.media.controls.domain.pipeline.interactor.MediaRecommendationsInteractor
import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
-import com.android.systemui.media.controls.domain.pipeline.interactor.mediaRecommendationsInteractor
-import com.android.systemui.media.controls.shared.model.MediaCommonModel
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
-import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
-import com.android.systemui.media.controls.shared.model.SmartspaceMediaLoadingModel
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
@@ -52,16 +43,6 @@
private val mediaFilterRepository: MediaFilterRepository =
with(kosmos) { mediaFilterRepository }
- private val mediaRecommendationsInteractor: MediaRecommendationsInteractor =
- kosmos.mediaRecommendationsInteractor
- val icon = Icon.createWithResource(context, R.drawable.ic_media_play)
- private val mediaRecommendation =
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = true,
- packageName = PACKAGE_NAME,
- recommendations = MediaTestHelper.getValidRecommendationList(icon),
- )
private val underTest: MediaCarouselInteractor = kosmos.mediaCarouselInteractor
@@ -119,81 +100,6 @@
}
@Test
- fun addActiveRecommendation_inactiveMedia() =
- testScope.runTest {
- val hasActiveMediaOrRecommendation by
- collectLastValue(underTest.hasActiveMediaOrRecommendation)
- val hasAnyMediaOrRecommendation by
- collectLastValue(underTest.hasAnyMediaOrRecommendation)
- val currentMedia by collectLastValue(underTest.currentMedia)
-
- val userMedia = MediaData(active = false)
- val recsLoadingModel = SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE, true)
- val mediaLoadingModel = MediaDataLoadingModel.Loaded(userMedia.instanceId)
-
- mediaFilterRepository.setRecommendation(mediaRecommendation)
- mediaFilterRepository.setRecommendationsLoadingState(recsLoadingModel)
-
- assertThat(hasActiveMediaOrRecommendation).isTrue()
- assertThat(hasAnyMediaOrRecommendation).isTrue()
- assertThat(currentMedia)
- .containsExactly(MediaCommonModel.MediaRecommendations(recsLoadingModel))
-
- mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
- mediaFilterRepository.addMediaDataLoadingState(mediaLoadingModel)
- mediaFilterRepository.setOrderedMedia()
-
- assertThat(hasActiveMediaOrRecommendation).isTrue()
- assertThat(hasAnyMediaOrRecommendation).isTrue()
- assertThat(currentMedia)
- .containsExactly(
- MediaCommonModel.MediaRecommendations(recsLoadingModel),
- MediaCommonModel.MediaControl(mediaLoadingModel, true),
- )
- .inOrder()
- }
-
- @Test
- fun addActiveRecommendation_thenInactive() =
- testScope.runTest {
- val hasActiveMediaOrRecommendation by
- collectLastValue(underTest.hasActiveMediaOrRecommendation)
- val hasAnyMediaOrRecommendation by
- collectLastValue(underTest.hasAnyMediaOrRecommendation)
-
- mediaFilterRepository.setRecommendation(mediaRecommendation)
-
- assertThat(hasActiveMediaOrRecommendation).isTrue()
- assertThat(hasAnyMediaOrRecommendation).isTrue()
-
- mediaFilterRepository.setRecommendation(mediaRecommendation.copy(isActive = false))
-
- assertThat(hasActiveMediaOrRecommendation).isFalse()
- assertThat(hasAnyMediaOrRecommendation).isFalse()
- }
-
- @Test
- fun addActiveRecommendation_thenInvalid() =
- testScope.runTest {
- val hasActiveMediaOrRecommendation by
- collectLastValue(underTest.hasActiveMediaOrRecommendation)
- val hasAnyMediaOrRecommendation by
- collectLastValue(underTest.hasAnyMediaOrRecommendation)
-
- mediaFilterRepository.setRecommendation(mediaRecommendation)
-
- assertThat(hasActiveMediaOrRecommendation).isTrue()
- assertThat(hasAnyMediaOrRecommendation).isTrue()
-
- mediaFilterRepository.setRecommendation(
- mediaRecommendation.copy(recommendations = listOf())
- )
-
- assertThat(hasActiveMediaOrRecommendation).isFalse()
- assertThat(hasAnyMediaOrRecommendation).isFalse()
- }
-
- @Test
fun hasAnyMedia_noMediaSet_returnsFalse() =
testScope.runTest { assertThat(underTest.hasAnyMedia()).isFalse() }
@@ -208,47 +114,4 @@
@Test
fun hasActiveMediaOrRecommendation_nothingSet_returnsFalse() =
testScope.runTest { assertThat(underTest.hasActiveMediaOrRecommendation.value).isFalse() }
-
- @Test
- fun loadMediaFromRec() =
- testScope.runTest {
- val currentMedia by collectLastValue(underTest.currentMedia)
- val instanceId = InstanceId.fakeInstanceId(123)
- val data =
- MediaData(
- active = true,
- instanceId = instanceId,
- packageName = PACKAGE_NAME,
- notificationKey = KEY,
- )
- val smartspaceLoadingModel = SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE)
- val mediaLoadingModel = MediaDataLoadingModel.Loaded(instanceId)
-
- mediaFilterRepository.setRecommendation(mediaRecommendation)
- mediaFilterRepository.setRecommendationsLoadingState(smartspaceLoadingModel)
- mediaRecommendationsInteractor.switchToMediaControl(PACKAGE_NAME)
- mediaFilterRepository.addSelectedUserMediaEntry(data)
- mediaFilterRepository.addMediaDataLoadingState(mediaLoadingModel)
-
- assertThat(currentMedia)
- .containsExactly(MediaCommonModel.MediaRecommendations(smartspaceLoadingModel))
- .inOrder()
-
- mediaFilterRepository.addSelectedUserMediaEntry(data.copy(isPlaying = true))
- mediaFilterRepository.addMediaDataLoadingState(mediaLoadingModel)
-
- assertThat(currentMedia)
- .containsExactly(
- MediaCommonModel.MediaControl(mediaLoadingModel, isMediaFromRec = true),
- MediaCommonModel.MediaRecommendations(smartspaceLoadingModel),
- )
- .inOrder()
- }
-
- companion object {
- private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID"
- private const val PACKAGE_NAME = "com.android.example"
- private const val KEY = "key"
- private const val SURFACE = 4
- }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaRecommendationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaRecommendationsInteractorTest.kt
deleted file mode 100644
index 2265c01..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaRecommendationsInteractorTest.kt
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Copyright (C) 2024 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.media.controls.domain.interactor
-
-import android.R
-import android.content.ComponentName
-import android.content.Intent
-import android.content.applicationContext
-import android.graphics.drawable.Icon
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.Expandable
-import com.android.systemui.broadcast.broadcastSender
-import com.android.systemui.broadcast.mockBroadcastSender
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.media.controls.MediaTestHelper
-import com.android.systemui.media.controls.domain.pipeline.MediaDataFilterImpl
-import com.android.systemui.media.controls.domain.pipeline.interactor.MediaRecommendationsInteractor
-import com.android.systemui.media.controls.domain.pipeline.interactor.MediaRecommendationsInteractor.Companion.EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME
-import com.android.systemui.media.controls.domain.pipeline.interactor.mediaRecommendationsInteractor
-import com.android.systemui.media.controls.domain.pipeline.mediaDataFilter
-import com.android.systemui.media.controls.shared.model.MediaRecModel
-import com.android.systemui.media.controls.shared.model.MediaRecommendationsModel
-import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
-import com.android.systemui.plugins.activityStarter
-import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mockito.doNothing
-import org.mockito.Mockito.spy
-import org.mockito.Mockito.verify
-import org.mockito.kotlin.eq
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class MediaRecommendationsInteractorTest : SysuiTestCase() {
-
- private val spyContext = spy(context)
- private val kosmos = testKosmos().apply { applicationContext = spyContext }
- private val testScope = kosmos.testScope
-
- private val mediaDataFilter: MediaDataFilterImpl = with(kosmos) { mediaDataFilter }
- private val activityStarter = kosmos.activityStarter
- private val icon: Icon = Icon.createWithResource(context, R.drawable.ic_media_play)
- private val smartspaceMediaData: SmartspaceMediaData =
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = true,
- packageName = PACKAGE_NAME,
- recommendations = MediaTestHelper.getValidRecommendationList(icon),
- )
-
- private val underTest: MediaRecommendationsInteractor =
- with(kosmos) {
- broadcastSender = mockBroadcastSender
- kosmos.mediaRecommendationsInteractor
- }
-
- @Test
- fun addRecommendation_smartspaceMediaDataUpdate() =
- testScope.runTest {
- val recommendations by collectLastValue(underTest.recommendations)
-
- val model =
- MediaRecommendationsModel(
- key = KEY_MEDIA_SMARTSPACE,
- packageName = PACKAGE_NAME,
- areRecommendationsValid = true,
- mediaRecs =
- listOf(
- MediaRecModel(icon = icon),
- MediaRecModel(icon = icon),
- MediaRecModel(icon = icon),
- ),
- )
-
- mediaDataFilter.onSmartspaceMediaDataLoaded(KEY_MEDIA_SMARTSPACE, smartspaceMediaData)
-
- assertThat(recommendations).isEqualTo(model)
- }
-
- @Test
- fun addInvalidRecommendation() =
- testScope.runTest {
- val recommendations by collectLastValue(underTest.recommendations)
- val inValidData = smartspaceMediaData.copy(recommendations = listOf())
-
- mediaDataFilter.onSmartspaceMediaDataLoaded(KEY_MEDIA_SMARTSPACE, smartspaceMediaData)
- assertThat(recommendations?.areRecommendationsValid).isTrue()
-
- mediaDataFilter.onSmartspaceMediaDataLoaded(KEY_MEDIA_SMARTSPACE, inValidData)
- assertThat(recommendations?.areRecommendationsValid).isFalse()
- assertThat(recommendations?.mediaRecs?.isEmpty()).isTrue()
- }
-
- @Test
- fun removeRecommendation_noTrampolineActivity() {
- val intent = Intent()
-
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-
- mediaDataFilter.onSmartspaceMediaDataLoaded(KEY_MEDIA_SMARTSPACE, smartspaceMediaData)
- underTest.removeMediaRecommendations(KEY_MEDIA_SMARTSPACE, intent, 0)
-
- verify(kosmos.mockBroadcastSender).sendBroadcast(eq(intent))
- }
-
- @Test
- fun removeRecommendation_usingTrampolineActivity() {
- doNothing().whenever(spyContext).startActivity(any())
- val intent = Intent()
-
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- intent.component = ComponentName(PACKAGE_NAME, EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME)
-
- underTest.removeMediaRecommendations(KEY_MEDIA_SMARTSPACE, intent, 0)
-
- verify(spyContext).startActivity(eq(intent))
- }
-
- @Test
- fun startSettings() {
- underTest.startSettings()
-
- verify(activityStarter).startActivity(any(), eq(true))
- }
-
- @Test
- fun startClickIntent() {
- doNothing().whenever(spyContext).startActivity(any())
- val intent = Intent()
- val expandable = mock<Expandable>()
-
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-
- mediaDataFilter.onSmartspaceMediaDataLoaded(KEY_MEDIA_SMARTSPACE, smartspaceMediaData)
- underTest.startClickIntent(expandable, intent)
-
- verify(spyContext).startActivity(eq(intent))
- }
-
- companion object {
- private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID"
- private const val PACKAGE_NAME = "com.example.app"
- private const val SURFACE = 4
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/util/MediaDiffUtilTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/util/MediaDiffUtilTest.kt
index 005424b..faa62c2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/util/MediaDiffUtilTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/util/MediaDiffUtilTest.kt
@@ -23,7 +23,6 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.media.controls.ui.viewmodel.MediaCommonViewModel
import com.android.systemui.media.controls.ui.viewmodel.mediaControlViewModel
-import com.android.systemui.media.controls.ui.viewmodel.mediaRecommendationsViewModel
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.fail
@@ -56,25 +55,6 @@
}
@Test
- fun newMediaRecommendationsAdded() {
- val mediaRecs = createMediaRecommendations(KEY_MEDIA_SMARTSPACE, true)
- val oldList = listOf<MediaCommonViewModel>()
- val newList = listOf(mediaRecs)
- val mediaLoadedCallback = MediaViewModelCallback(oldList, newList)
- val mediaLoadedListUpdateCallback =
- MediaViewModelListUpdateCallback(
- oldList,
- newList,
- { commonViewModel, _ -> assertThat(commonViewModel).isEqualTo(mediaRecs) },
- { commonViewModel, _ -> fail("Unexpected to update $commonViewModel") },
- { fail("Unexpected to remove $it") },
- { commonViewModel, _, _ -> fail("Unexpected to move $commonViewModel ") },
- )
-
- DiffUtil.calculateDiff(mediaLoadedCallback).dispatchUpdatesTo(mediaLoadedListUpdateCallback)
- }
-
- @Test
fun updateMediaControl_contentChanged() {
val mediaControl = createMediaControl(InstanceId.fakeInstanceId(123), true)
val oldList = listOf(mediaControl)
@@ -94,25 +74,6 @@
}
@Test
- fun updateMediaRecommendations_contentChanged() {
- val mediaRecs = createMediaRecommendations(KEY_MEDIA_SMARTSPACE, true)
- val oldList = listOf(mediaRecs)
- val newList = listOf(mediaRecs.copy(key = KEY_MEDIA_SMARTSPACE_2))
- val mediaLoadedCallback = MediaViewModelCallback(oldList, newList)
- val mediaLoadedListUpdateCallback =
- MediaViewModelListUpdateCallback(
- oldList,
- newList,
- { commonViewModel, _ -> fail("Unexpected to add $commonViewModel") },
- { commonViewModel, _ -> assertThat(commonViewModel).isNotEqualTo(mediaRecs) },
- { fail("Unexpected to remove $it") },
- { commonViewModel, _, _ -> fail("Unexpected to move $commonViewModel ") },
- )
-
- DiffUtil.calculateDiff(mediaLoadedCallback).dispatchUpdatesTo(mediaLoadedListUpdateCallback)
- }
-
- @Test
fun mediaControlMoved() {
val mediaControl1 = createMediaControl(InstanceId.fakeInstanceId(123), true)
val mediaControl2 = createMediaControl(InstanceId.fakeInstanceId(456), false)
@@ -133,27 +94,6 @@
}
@Test
- fun mediaRecommendationsMoved() {
- val mediaControl1 = createMediaControl(InstanceId.fakeInstanceId(123), true)
- val mediaControl2 = createMediaControl(InstanceId.fakeInstanceId(456), false)
- val mediaRecs = createMediaRecommendations(KEY_MEDIA_SMARTSPACE, true)
- val oldList = listOf(mediaRecs, mediaControl1, mediaControl2)
- val newList = listOf(mediaControl1, mediaControl2, mediaRecs)
- val mediaLoadedCallback = MediaViewModelCallback(oldList, newList)
- val mediaLoadedListUpdateCallback =
- MediaViewModelListUpdateCallback(
- oldList,
- newList,
- { commonViewModel, _ -> fail("Unexpected to add $commonViewModel") },
- { commonViewModel, _ -> fail("Unexpected to update $commonViewModel") },
- { fail("Unexpected to remove $it") },
- { commonViewModel, _, _ -> assertThat(commonViewModel).isEqualTo(mediaRecs) },
- )
-
- DiffUtil.calculateDiff(mediaLoadedCallback).dispatchUpdatesTo(mediaLoadedListUpdateCallback)
- }
-
- @Test
fun mediaControlRemoved() {
val mediaControl = createMediaControl(InstanceId.fakeInstanceId(123), true)
val oldList = listOf(mediaControl)
@@ -172,25 +112,6 @@
DiffUtil.calculateDiff(mediaLoadedCallback).dispatchUpdatesTo(mediaLoadedListUpdateCallback)
}
- @Test
- fun mediaRecommendationsRemoved() {
- val mediaRecs = createMediaRecommendations(KEY_MEDIA_SMARTSPACE_2, false)
- val oldList = listOf(mediaRecs)
- val newList = listOf<MediaCommonViewModel>()
- val mediaLoadedCallback = MediaViewModelCallback(oldList, newList)
- val mediaLoadedListUpdateCallback =
- MediaViewModelListUpdateCallback(
- oldList,
- newList,
- { commonViewModel, _ -> fail("Unexpected to add $commonViewModel") },
- { commonViewModel, _ -> fail("Unexpected to update $commonViewModel") },
- { commonViewModel -> assertThat(commonViewModel).isEqualTo(mediaRecs) },
- { commonViewModel, _, _ -> fail("Unexpected to move $commonViewModel ") },
- )
-
- DiffUtil.calculateDiff(mediaLoadedCallback).dispatchUpdatesTo(mediaLoadedListUpdateCallback)
- }
-
private fun createMediaControl(
instanceId: InstanceId,
immediatelyUpdateUi: Boolean,
@@ -201,26 +122,7 @@
controlViewModel = kosmos.mediaControlViewModel,
onAdded = {},
onRemoved = {},
- onUpdated = {}
+ onUpdated = {},
)
}
-
- private fun createMediaRecommendations(
- key: String,
- loadingEnabled: Boolean,
- ): MediaCommonViewModel.MediaRecommendations {
- return MediaCommonViewModel.MediaRecommendations(
- key = key,
- loadingEnabled = loadingEnabled,
- recsViewModel = kosmos.mediaRecommendationsViewModel,
- onAdded = {},
- onRemoved = {},
- onUpdated = {}
- )
- }
-
- companion object {
- private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID"
- private const val KEY_MEDIA_SMARTSPACE_2 = "MEDIA_SMARTSPACE_ID_2"
- }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt
index fb5bbf4..e56b114 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt
@@ -19,23 +19,18 @@
import android.R
import android.content.packageManager
import android.content.pm.ApplicationInfo
-import android.graphics.drawable.Icon
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
-import com.android.systemui.media.controls.MediaTestHelper
import com.android.systemui.media.controls.domain.pipeline.MediaDataFilterImpl
import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
-import com.android.systemui.media.controls.domain.pipeline.interactor.mediaRecommendationsInteractor
import com.android.systemui.media.controls.domain.pipeline.mediaDataFilter
import com.android.systemui.media.controls.shared.mediaLogger
import com.android.systemui.media.controls.shared.mockMediaLogger
import com.android.systemui.media.controls.shared.model.MediaData
-import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
-import com.android.systemui.statusbar.notification.collection.provider.visualStabilityProvider
import com.android.systemui.statusbar.notificationLockscreenUserManager
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
@@ -62,15 +57,7 @@
private val mediaDataFilter: MediaDataFilterImpl = kosmos.mediaDataFilter
private val notificationLockscreenUserManager = kosmos.notificationLockscreenUserManager
private val packageManager = kosmos.packageManager
- private val icon = Icon.createWithResource(context, R.drawable.ic_media_play)
private val drawable = context.getDrawable(R.drawable.ic_media_play)
- private val smartspaceMediaData: SmartspaceMediaData =
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = true,
- packageName = PACKAGE_NAME,
- recommendations = MediaTestHelper.getValidRecommendationList(icon),
- )
private val underTest: MediaCarouselViewModel = kosmos.mediaCarouselViewModel
@@ -121,53 +108,6 @@
}
@Test
- fun loadMediaControlsAndRecommendations_mediaItemsAreUpdated() =
- testScope.runTest {
- val sortedMedia by collectLastValue(underTest.mediaItems)
- val instanceId1 = InstanceId.fakeInstanceId(123)
- val instanceId2 = InstanceId.fakeInstanceId(456)
-
- loadMediaControl(KEY, instanceId1)
- loadMediaControl(KEY_2, instanceId2)
- loadMediaRecommendations()
-
- val firstMediaControl = sortedMedia?.get(0) as MediaCommonViewModel.MediaControl
- val secondMediaControl = sortedMedia?.get(1) as MediaCommonViewModel.MediaControl
- val recsCard = sortedMedia?.get(2) as MediaCommonViewModel.MediaRecommendations
- assertThat(firstMediaControl.instanceId).isEqualTo(instanceId2)
- assertThat(secondMediaControl.instanceId).isEqualTo(instanceId1)
- assertThat(recsCard.key).isEqualTo(KEY_MEDIA_SMARTSPACE)
- }
-
- @Test
- fun recommendationClicked_switchToPlayer() =
- testScope.runTest {
- val sortedMedia by collectLastValue(underTest.mediaItems)
- kosmos.visualStabilityProvider.isReorderingAllowed = false
- val instanceId = InstanceId.fakeInstanceId(123)
-
- loadMediaRecommendations()
- kosmos.mediaRecommendationsInteractor.switchToMediaControl(PACKAGE_NAME)
-
- var recsCard = sortedMedia?.get(0) as MediaCommonViewModel.MediaRecommendations
- assertThat(sortedMedia).hasSize(1)
- assertThat(recsCard.key).isEqualTo(KEY_MEDIA_SMARTSPACE)
-
- loadMediaControl(KEY, instanceId, false)
-
- recsCard = sortedMedia?.get(0) as MediaCommonViewModel.MediaRecommendations
- assertThat(sortedMedia).hasSize(1)
- assertThat(recsCard.key).isEqualTo(KEY_MEDIA_SMARTSPACE)
-
- loadMediaControl(KEY, instanceId, true)
-
- val mediaControl = sortedMedia?.get(0) as MediaCommonViewModel.MediaControl
- assertThat(sortedMedia).hasSize(2)
- assertThat(mediaControl.instanceId).isEqualTo(instanceId)
- assertThat(mediaControl.isMediaFromRec).isTrue()
- }
-
- @Test
fun addMediaControlThenRemove_mediaEventsAreLogged() =
testScope.runTest {
val sortedMedia by collectLastValue(underTest.mediaItems)
@@ -199,31 +139,6 @@
verify(kosmos.mediaLogger).logMediaCardRemoved(eq(instanceId))
}
- @Test
- fun addMediaRecommendationThenRemove_mediaEventsAreLogged() =
- testScope.runTest {
- val sortedMedia by collectLastValue(underTest.mediaItems)
-
- loadMediaRecommendations()
-
- val mediaRecommendations =
- sortedMedia?.get(0) as MediaCommonViewModel.MediaRecommendations
- assertThat(mediaRecommendations.key).isEqualTo(KEY_MEDIA_SMARTSPACE)
-
- // when media recommendation is added to carousel
- mediaRecommendations.onAdded(mediaRecommendations)
-
- verify(kosmos.mediaLogger).logMediaRecommendationCardAdded(eq(KEY_MEDIA_SMARTSPACE))
-
- mediaDataFilter.onSmartspaceMediaDataRemoved(KEY, true)
- assertThat(sortedMedia).isEmpty()
-
- // when media recommendation is removed from carousel
- mediaRecommendations.onRemoved(true)
-
- verify(kosmos.mediaLogger).logMediaRecommendationCardRemoved(eq(KEY_MEDIA_SMARTSPACE))
- }
-
private fun loadMediaControl(key: String, instanceId: InstanceId, isPlaying: Boolean = true) {
whenever(notificationLockscreenUserManager.isCurrentProfile(USER_ID)).thenReturn(true)
whenever(notificationLockscreenUserManager.isProfileAvailable(USER_ID)).thenReturn(true)
@@ -239,15 +154,10 @@
mediaDataFilter.onMediaDataLoaded(key, key, mediaData)
}
- private fun loadMediaRecommendations(key: String = KEY_MEDIA_SMARTSPACE) {
- mediaDataFilter.onSmartspaceMediaDataLoaded(key, smartspaceMediaData)
- }
-
companion object {
private const val USER_ID = 0
private const val KEY = "key"
private const val KEY_2 = "key2"
private const val PACKAGE_NAME = "com.example.app"
- private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID"
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModelTest.kt
deleted file mode 100644
index 51b1911..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModelTest.kt
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2024 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.media.controls.ui.viewmodel
-
-import android.R
-import android.content.packageManager
-import android.content.pm.ApplicationInfo
-import android.graphics.drawable.Icon
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.media.controls.MediaTestHelper
-import com.android.systemui.media.controls.domain.pipeline.MediaDataFilterImpl
-import com.android.systemui.media.controls.domain.pipeline.mediaDataFilter
-import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
-import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers
-import org.mockito.Mockito
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class MediaRecommendationsViewModelTest : SysuiTestCase() {
-
- private val kosmos = testKosmos()
- private val testScope = kosmos.testScope
-
- private val mediaDataFilter: MediaDataFilterImpl = kosmos.mediaDataFilter
- private val packageManager = kosmos.packageManager
- private val icon: Icon = Icon.createWithResource(context, R.drawable.ic_media_play)
- private val drawable = context.getDrawable(R.drawable.ic_media_play)
- private val smartspaceMediaData: SmartspaceMediaData =
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = true,
- packageName = PACKAGE_NAME,
- recommendations = MediaTestHelper.getValidRecommendationList(icon),
- )
-
- private val underTest: MediaRecommendationsViewModel = kosmos.mediaRecommendationsViewModel
-
- @Test
- fun loadRecommendations_recsCardViewModelIsLoaded() =
- testScope.runTest {
- whenever(packageManager.getApplicationIcon(Mockito.anyString())).thenReturn(drawable)
- whenever(packageManager.getApplicationIcon(any(ApplicationInfo::class.java)))
- .thenReturn(drawable)
- whenever(packageManager.getApplicationInfo(eq(PACKAGE_NAME), ArgumentMatchers.anyInt()))
- .thenReturn(ApplicationInfo())
- whenever(packageManager.getApplicationLabel(any())).thenReturn(PACKAGE_NAME)
- val recsCardViewModel by collectLastValue(underTest.mediaRecsCard)
-
- context.setMockPackageManager(packageManager)
-
- mediaDataFilter.onSmartspaceMediaDataLoaded(KEY_MEDIA_SMARTSPACE, smartspaceMediaData)
-
- assertThat(recsCardViewModel).isNotNull()
- assertThat(recsCardViewModel?.mediaRecs?.size)
- .isEqualTo(smartspaceMediaData.recommendations.size)
- }
-
- companion object {
- private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID"
- private const val PACKAGE_NAME = "com.example.app"
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SceneContainerPluginTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SceneContainerPluginTest.kt
new file mode 100644
index 0000000..b2e29cf
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SceneContainerPluginTest.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.model
+
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.fakeSceneDataSource
+import com.android.systemui.shade.data.repository.fakeShadeDisplaysRepository
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
+class SceneContainerPluginTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
+ private val shadeDisplayRepository = kosmos.fakeShadeDisplaysRepository
+ private val sceneDataSource = kosmos.fakeSceneDataSource
+
+ private val underTest = kosmos.sceneContainerPlugin
+
+ @Test
+ @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ fun flagValueOverride_differentDisplayId_alwaysFalse() {
+ sceneDataSource.changeScene(Scenes.Shade)
+
+ shadeDisplayRepository.setDisplayId(1)
+
+ assertThat(
+ underTest.flagValueOverride(
+ flag = SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE,
+ displayId = 2,
+ )
+ )
+ .isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ fun flagValueOverride_sameDisplayId_returnsTrue() {
+ sceneDataSource.changeScene(Scenes.Shade)
+
+ shadeDisplayRepository.setDisplayId(1)
+
+ assertThat(
+ underTest.flagValueOverride(
+ flag = SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE,
+ displayId = 1,
+ )
+ )
+ .isTrue()
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ fun flagValueOverride_differentDisplayId_shadeGoesAroundFlagOff_returnsTrue() {
+ sceneDataSource.changeScene(Scenes.Shade)
+
+ shadeDisplayRepository.setDisplayId(1)
+
+ assertThat(
+ underTest.flagValueOverride(
+ flag = SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE,
+ displayId = 2,
+ )
+ )
+ .isTrue()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUIStateDispatcherTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUIStateDispatcherTest.kt
new file mode 100644
index 0000000..b82f5fc
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUIStateDispatcherTest.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.model
+
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.view.Display
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SysUIStateDispatcherTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+
+ private val stateFactory = kosmos.sysUiStateFactory
+ private val state0 = stateFactory.create(Display.DEFAULT_DISPLAY)
+ private val state1 = stateFactory.create(DISPLAY_1)
+ private val state2 = stateFactory.create(DISPLAY_2)
+ private val underTest = kosmos.sysUIStateDispatcher
+
+ private val flagsChanges = mutableMapOf<Int, Long>() // display id -> flag value
+ private val callback =
+ SysUiState.SysUiStateCallback { sysUiFlags, displayId ->
+ flagsChanges[displayId] = sysUiFlags
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ fun registerUnregisterListener_notifiedOfChanges_receivedForAllDisplayIdsWithOneCallback() {
+ underTest.registerListener(callback)
+
+ state1.setFlag(FLAG_1, true).commitUpdate()
+ state2.setFlag(FLAG_2, true).commitUpdate()
+
+ assertThat(flagsChanges).containsExactly(DISPLAY_1, FLAG_1, DISPLAY_2, FLAG_2)
+
+ underTest.unregisterListener(callback)
+
+ state1.setFlag(0, true).commitUpdate()
+
+ // Didn't change
+ assertThat(flagsChanges).containsExactly(DISPLAY_1, FLAG_1, DISPLAY_2, FLAG_2)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ fun registerUnregisterListener_notifiedOfChangesForNonDefaultDisplay_NotPropagated() {
+ underTest.registerListener(callback)
+
+ state1.setFlag(FLAG_1, true).commitUpdate()
+
+ assertThat(flagsChanges).isEmpty()
+
+ state0.setFlag(FLAG_1, true).commitUpdate()
+
+ assertThat(flagsChanges).containsExactly(Display.DEFAULT_DISPLAY, FLAG_1)
+ }
+
+ private companion object {
+ const val DISPLAY_1 = 1
+ const val DISPLAY_2 = 2
+ const val FLAG_1 = 10L
+ const val FLAG_2 = 20L
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateExtTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateExtTest.kt
index a3be9e3..09588f9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateExtTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateExtTest.kt
@@ -20,7 +20,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.settings.FakeDisplayTracker
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import org.junit.Test
@@ -32,20 +31,11 @@
private val kosmos = testKosmos()
- private val underTest =
- SysUiState(
- FakeDisplayTracker(context),
- kosmos.sceneContainerPlugin,
- )
+ private val underTest = kosmos.sysUiState
@Test
fun updateFlags() {
- underTest.updateFlags(
- Display.DEFAULT_DISPLAY,
- 1L to true,
- 2L to false,
- 3L to true,
- )
+ underTest.updateFlags(Display.DEFAULT_DISPLAY, 1L to true, 2L to false, 3L to true)
assertThat(underTest.flags and 1L).isNotEqualTo(0L)
assertThat(underTest.flags and 2L).isEqualTo(0L)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateTest.java
index 9a78bd9..f6de629 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateTest.java
@@ -19,16 +19,23 @@
import static android.view.Display.DEFAULT_DISPLAY;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import android.view.Display;
+
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.kosmos.KosmosJavaAdapter;
-import com.android.systemui.settings.FakeDisplayTracker;
import org.junit.Before;
import org.junit.Test;
@@ -46,21 +53,33 @@
private KosmosJavaAdapter mKosmos;
private SysUiState.SysUiStateCallback mCallback;
private SysUiState mFlagsContainer;
+ private SceneContainerPlugin mSceneContainerPlugin;
+ private DumpManager mDumpManager;
+ private SysUIStateDispatcher mSysUIStateDispatcher;
+
+ private SysUiState createInstance(int displayId) {
+ var sysuiState = new SysUiStateImpl(displayId, mSceneContainerPlugin, mDumpManager,
+ mSysUIStateDispatcher);
+ sysuiState.addCallback(mCallback);
+ return sysuiState;
+ }
@Before
public void setup() {
- FakeDisplayTracker displayTracker = new FakeDisplayTracker(mContext);
mKosmos = new KosmosJavaAdapter(this);
- mFlagsContainer = new SysUiState(displayTracker, mKosmos.getSceneContainerPlugin());
+ mFlagsContainer = mKosmos.getSysuiState();
+ mSceneContainerPlugin = mKosmos.getSceneContainerPlugin();
mCallback = mock(SysUiState.SysUiStateCallback.class);
- mFlagsContainer.addCallback(mCallback);
+ mDumpManager = mock(DumpManager.class);
+ mSysUIStateDispatcher = mKosmos.getSysUIStateDispatcher();
+ mFlagsContainer = createInstance(DEFAULT_DISPLAY);
}
@Test
public void addSingle_setFlag() {
setFlags(FLAG_1);
- verify(mCallback, times(1)).onSystemUiStateChanged(FLAG_1);
+ verify(mCallback, times(1)).onSystemUiStateChanged(FLAG_1, DEFAULT_DISPLAY);
}
@Test
@@ -68,22 +87,19 @@
setFlags(FLAG_1);
setFlags(FLAG_2);
- verify(mCallback, times(1)).onSystemUiStateChanged(FLAG_1);
- verify(mCallback, times(1))
- .onSystemUiStateChanged(FLAG_1 | FLAG_2);
+ verify(mCallback, times(1)).onSystemUiStateChanged(FLAG_1, DEFAULT_DISPLAY);
+ verify(mCallback, times(1)).onSystemUiStateChanged(FLAG_1 | FLAG_2, DEFAULT_DISPLAY);
}
@Test
public void addMultipleRemoveOne_setFlag() {
setFlags(FLAG_1);
setFlags(FLAG_2);
- mFlagsContainer.setFlag(FLAG_1, false)
- .commitUpdate(DISPLAY_ID);
+ mFlagsContainer.setFlag(FLAG_1, false).commitUpdate(DISPLAY_ID);
- verify(mCallback, times(1)).onSystemUiStateChanged(FLAG_1);
- verify(mCallback, times(1))
- .onSystemUiStateChanged(FLAG_1 | FLAG_2);
- verify(mCallback, times(1)).onSystemUiStateChanged(FLAG_2);
+ verify(mCallback, times(1)).onSystemUiStateChanged(FLAG_1, DEFAULT_DISPLAY);
+ verify(mCallback, times(1)).onSystemUiStateChanged(FLAG_1 | FLAG_2, DEFAULT_DISPLAY);
+ verify(mCallback, times(1)).onSystemUiStateChanged(FLAG_2, DEFAULT_DISPLAY);
}
@Test
@@ -91,19 +107,18 @@
setFlags(FLAG_1, FLAG_2, FLAG_3, FLAG_4);
int expected = FLAG_1 | FLAG_2 | FLAG_3 | FLAG_4;
- verify(mCallback, times(1)).onSystemUiStateChanged(expected);
+ verify(mCallback, times(1)).onSystemUiStateChanged(expected, DEFAULT_DISPLAY);
}
@Test
public void addMultipleRemoveOne_setFlags() {
setFlags(FLAG_1, FLAG_2, FLAG_3, FLAG_4);
- mFlagsContainer.setFlag(FLAG_2, false)
- .commitUpdate(DISPLAY_ID);
+ mFlagsContainer.setFlag(FLAG_2, false).commitUpdate(DISPLAY_ID);
int expected1 = FLAG_1 | FLAG_2 | FLAG_3 | FLAG_4;
- verify(mCallback, times(1)).onSystemUiStateChanged(expected1);
+ verify(mCallback, times(1)).onSystemUiStateChanged(expected1, DEFAULT_DISPLAY);
int expected2 = FLAG_1 | FLAG_3 | FLAG_4;
- verify(mCallback, times(1)).onSystemUiStateChanged(expected2);
+ verify(mCallback, times(1)).onSystemUiStateChanged(expected2, DEFAULT_DISPLAY);
}
@Test
@@ -112,13 +127,37 @@
setFlags(FLAG_1, FLAG_2, FLAG_3, FLAG_4);
int expected = FLAG_1 | FLAG_2 | FLAG_3 | FLAG_4;
- verify(mCallback, times(0)).onSystemUiStateChanged(expected);
+ verify(mCallback, times(0)).onSystemUiStateChanged(expected, DEFAULT_DISPLAY);
+ }
+
+ @Test
+ public void setFlag_receivedForDefaultDisplay() {
+ setFlags(FLAG_1);
+
+ verify(mCallback, times(1)).onSystemUiStateChanged(FLAG_1, DEFAULT_DISPLAY);
+ }
+
+
+ @Test
+ public void init_registersWithDumpManager() {
+ verify(mDumpManager).registerNormalDumpable(any(), eq(mFlagsContainer));
+ }
+
+ @Test
+ public void destroy_unregistersWithDumpManager() {
+ mFlagsContainer.destroy();
+
+ verify(mDumpManager).unregisterDumpable(anyString());
}
private void setFlags(int... flags) {
- for (int i = 0; i < flags.length; i++) {
- mFlagsContainer.setFlag(flags[i], true);
+ setFlags(mFlagsContainer, flags);
+ }
+
+ private void setFlags(SysUiState instance, int... flags) {
+ for (int flag : flags) {
+ instance.setFlag(flag, true);
}
- mFlagsContainer.commitUpdate(DISPLAY_ID);
+ instance.commitUpdate();
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/view/WindowRootViewKeyEventHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/view/WindowRootViewKeyEventHandlerTest.kt
new file mode 100644
index 0000000..18ebd4d
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/view/WindowRootViewKeyEventHandlerTest.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.scene.ui.view
+
+import android.view.KeyEvent
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.fakeFalsingCollector
+import com.android.systemui.keyevent.domain.interactor.mockSysUIKeyEventHandler
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.testKosmos
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class WindowRootViewKeyEventHandlerTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val underTest: WindowRootViewKeyEventHandler = kosmos.windowRootViewKeyEventHandler
+
+ @Test
+ fun dispatchKeyEvent_forwardsDispatchKeyEvent() =
+ kosmos.runTest {
+ val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_B)
+ underTest.dispatchKeyEvent(keyEvent)
+ verify(mockSysUIKeyEventHandler).dispatchKeyEvent(keyEvent)
+ }
+
+ @Test
+ fun dispatchKeyEventPreIme_forwardsDispatchKeyEventPreIme() =
+ kosmos.runTest {
+ val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_B)
+ underTest.dispatchKeyEventPreIme(keyEvent)
+ verify(mockSysUIKeyEventHandler).dispatchKeyEventPreIme(keyEvent)
+ }
+
+ @Test
+ fun interceptMediaKey_forwardsInterceptMediaKey() =
+ kosmos.runTest {
+ val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_UP)
+ underTest.interceptMediaKey(keyEvent)
+ verify(mockSysUIKeyEventHandler).interceptMediaKey(keyEvent)
+ }
+
+ @Test
+ fun collectKeyEvent_forwardsCollectKeyEvent() =
+ kosmos.runTest {
+ val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_A)
+ underTest.collectKeyEvent(keyEvent)
+ assertEquals(keyEvent, fakeFalsingCollector.lastKeyEvent)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
index 0448ad5..0a82f72 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
@@ -30,6 +30,9 @@
import com.google.common.truth.Truth.assertThat
import java.util.UUID
import kotlin.test.Test
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
import org.junit.runner.RunWith
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.kotlin.any
@@ -43,10 +46,19 @@
@RunWith(AndroidJUnit4::class)
class DefaultScreenshotActionsProviderTest : SysuiTestCase() {
+ private val scheduler = TestCoroutineScheduler()
+ private val mainDispatcher = UnconfinedTestDispatcher(scheduler)
+ private val testScope = TestScope(mainDispatcher)
private val actionExecutor = mock<ActionExecutor>()
private val uiEventLogger = mock<UiEventLogger>()
private val actionsCallback = mock<ScreenshotActionsController.ActionsCallback>()
- private val actionIntentCreator = ActionIntentCreator(context, context.packageManager)
+ private val actionIntentCreator =
+ ActionIntentCreator(
+ context,
+ context.packageManager,
+ testScope.backgroundScope,
+ mainDispatcher,
+ )
private val request = ScreenshotData.forTesting(userHandle = UserHandle.OWNER)
private val validResult = ScreenshotSavedResult(Uri.EMPTY, Process.myUserHandle(), 0)
@@ -198,6 +210,7 @@
context,
uiEventLogger,
actionIntentCreator,
+ testScope,
UUID.randomUUID(),
request,
actionExecutor,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index d852a9d..2c852c3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -17,7 +17,9 @@
package com.android.systemui.shade;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.view.Display.TYPE_INTERNAL;
+import static com.android.systemui.display.data.repository.FakeDisplayRepositoryKt.display;
import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;
import static com.google.common.truth.Truth.assertThat;
@@ -47,6 +49,7 @@
import android.os.PowerManager;
import android.os.UserManager;
import android.util.DisplayMetrics;
+import android.view.Display;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@@ -70,6 +73,7 @@
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.FalsingManagerFake;
+import com.android.systemui.common.domain.interactor.SysUIStateDisplaysInteractor;
import com.android.systemui.common.ui.view.TouchHandlingView;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
@@ -291,6 +295,7 @@
@Mock private NaturalScrollingSettingObserver mNaturalScrollingSettingObserver;
@Mock private LargeScreenHeaderHelper mLargeScreenHeaderHelper;
@Mock private StatusBarLongPressGestureDetector mStatusBarLongPressGestureDetector;
+ @Mock protected SysUIStateDisplaysInteractor mSysUIStateDisplaysInteractor;
protected final int mMaxUdfpsBurnInOffsetY = 5;
protected FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic();
protected KeyguardClockInteractor mKeyguardClockInteractor;
@@ -435,6 +440,9 @@
return null;
}).when(mView).setOnTouchListener(any(NotificationPanelViewController.TouchHandler.class));
+ var displayMock = display(TYPE_INTERNAL, /* flags= */ 0, /* id= */Display.DEFAULT_DISPLAY,
+ /* state= */ null);
+ when(mView.getDisplay()).thenReturn(displayMock);
// Any edge transition
when(mKeyguardTransitionInteractor.transition(any()))
.thenReturn(emptyFlow());
@@ -565,6 +573,7 @@
mShadeRepository,
mSysUIUnfoldComponent,
mSysUiState,
+ mSysUIStateDisplaysInteractor,
mKeyguardUnlockAnimationController,
mKeyguardIndicationController,
mNotificationListContainer,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 354c23d..f54c367 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -16,10 +16,16 @@
package com.android.systemui.shade;
+import static android.view.Display.TYPE_INTERNAL;
+
+import static com.android.systemui.display.data.repository.FakeDisplayRepositoryKt.display;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -28,6 +34,7 @@
import static org.mockito.Mockito.when;
import android.os.Build;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.testing.TestableLooper;
import android.view.HapticFeedbackConstants;
@@ -38,6 +45,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.DejankUtils;
+import com.android.systemui.Flags;
import com.android.systemui.flags.DisableSceneContainer;
import com.google.android.msdl.data.model.MSDLToken;
@@ -182,4 +190,27 @@
assertThat(mMSDLPlayer.getLatestTokenPlayed()).isEqualTo(MSDLToken.FAILURE);
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ public void updateSystemUiStateFlags_updatesSysuiStateInteractor() {
+ var DISPLAY_ID = 10;
+ var displayMock = display(TYPE_INTERNAL, /* flags= */ 0, /* id= */DISPLAY_ID,
+ /* state= */ null);
+ when(mView.getDisplay()).thenReturn(displayMock);
+
+ mNotificationPanelViewController.updateSystemUiStateFlags();
+
+ verify(mSysUIStateDisplaysInteractor).setFlagsExclusivelyToDisplay(eq(DISPLAY_ID), any());
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ public void updateSystemUiStateFlags_flagOff_doesNotUpdateSysuiStateInteractor() {
+ mNotificationPanelViewController.updateSystemUiStateFlags();
+
+ verify(mSysUIStateDisplaysInteractor, never()).setFlagsExclusivelyToDisplay(anyInt(),
+ any());
+ }
+
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 764068e..3407cd5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -84,12 +84,12 @@
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
-import java.util.List;
-import java.util.concurrent.Executor;
-
import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
import platform.test.runner.parameterized.Parameters;
+import java.util.List;
+import java.util.concurrent.Executor;
+
@RunWith(ParameterizedAndroidJunit4.class)
@RunWithLooper(setAsMainLooper = true)
@SmallTest
@@ -410,6 +410,19 @@
}
@Test
+ public void hubOrientationAware_layoutParamsUpdated() {
+ mNotificationShadeWindowController.setKeyguardShowing(false);
+ mNotificationShadeWindowController.setBouncerShowing(false);
+ mNotificationShadeWindowController.setGlanceableHubOrientationAware(true);
+ when(mKeyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(false);
+ mNotificationShadeWindowController.onConfigChanged(new Configuration());
+
+ verify(mWindowManager, atLeastOnce()).updateViewLayout(any(), mLayoutParameters.capture());
+ assertThat(mLayoutParameters.getValue().screenOrientation)
+ .isEqualTo(ActivityInfo.SCREEN_ORIENTATION_USER);
+ }
+
+ @Test
public void batchApplyWindowLayoutParams_doesNotDispatchEvents() {
mNotificationShadeWindowController.setForceDozeBrightness(true);
verify(mWindowManager).updateViewLayout(any(), any());
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 59e3a2e..4398359 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -42,6 +42,7 @@
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.res.R
+import com.android.systemui.scene.ui.view.WindowRootViewKeyEventHandler
import com.android.systemui.settings.brightness.data.repository.BrightnessMirrorShowingRepository
import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractorPassThrough
import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
@@ -69,7 +70,6 @@
import com.android.systemui.unfold.SysUIUnfoldComponent
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.window.ui.viewmodel.WindowRootViewModel
@@ -85,12 +85,12 @@
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
-import org.mockito.Mockito
import org.mockito.Mockito.never
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
@RunWith(AndroidJUnit4::class)
@RunWithLooper(setAsMainLooper = true)
@@ -175,13 +175,14 @@
featureFlags.set(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
mSetFlagsRule.enableFlags(AConfigFlags.FLAG_REVAMPED_BOUNCER_MESSAGES)
testScope = TestScope()
+ val falsingCollector = FalsingCollectorFake()
controller =
NotificationShadeWindowViewController(
blurUtils,
windowRootViewModelFactory,
choreographer,
lockscreenShadeTransitionController,
- FalsingCollectorFake(),
+ falsingCollector,
statusBarStateController,
dockManager,
notificationShadeDepthController,
@@ -212,7 +213,7 @@
NotificationLaunchAnimationInteractor(NotificationLaunchAnimationRepository()),
featureFlags,
FakeSystemClock(),
- Mockito.mock(SysUIKeyEventHandler::class.java),
+ WindowRootViewKeyEventHandler({ mock<SysUIKeyEventHandler>() }, falsingCollector),
quickSettingsController,
primaryBouncerInteractor,
alternateBouncerInteractor,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index f4a43a4..ddad230 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -151,9 +151,17 @@
create: (ClockId) -> ClockController = ::failFactory,
getPickerConfig: (ClockSettings) -> ClockPickerConfig = ::failPickerConfig,
): FakeClockPlugin {
- metadata.add(ClockMetadata(id))
- createCallbacks[id] = create
- pickerConfigs[id] = getPickerConfig
+ return addClock(ClockMetadata(id), create, getPickerConfig)
+ }
+
+ fun addClock(
+ metadata: ClockMetadata,
+ create: (ClockId) -> ClockController = ::failFactory,
+ getPickerConfig: (ClockSettings) -> ClockPickerConfig = ::failPickerConfig,
+ ): FakeClockPlugin {
+ this.metadata.add(metadata)
+ createCallbacks[metadata.clockId] = create
+ pickerConfigs[metadata.clockId] = getPickerConfig
return this
}
}
@@ -203,28 +211,40 @@
val plugin1 = FakeClockPlugin().addClock("clock_1").addClock("clock_2")
val lifecycle1 = FakeLifecycle("1", plugin1)
- val plugin2 = FakeClockPlugin().addClock("clock_3").addClock("clock_4")
+ val plugin2 =
+ FakeClockPlugin()
+ .addClock(ClockMetadata("clock_3", isDeprecated = false))
+ .addClock(ClockMetadata("clock_4", isDeprecated = true))
val lifecycle2 = FakeLifecycle("2", plugin2)
pluginListener.onPluginLoaded(plugin1, mockContext, lifecycle1)
pluginListener.onPluginLoaded(plugin2, mockContext, lifecycle2)
- val list = registry.getClocks()
assertEquals(
- list.toSet(),
setOf(
ClockMetadata(DEFAULT_CLOCK_ID),
ClockMetadata("clock_1"),
ClockMetadata("clock_2"),
ClockMetadata("clock_3"),
- ClockMetadata("clock_4"),
),
+ registry.getClocks().toSet(),
+ )
+
+ assertEquals(
+ setOf(
+ ClockMetadata(DEFAULT_CLOCK_ID),
+ ClockMetadata("clock_1"),
+ ClockMetadata("clock_2"),
+ ClockMetadata("clock_3"),
+ ClockMetadata("clock_4", isDeprecated = true),
+ ),
+ registry.getClocks(includeDeprecated = true).toSet(),
)
}
@Test
fun noPlugins_createDefaultClock() {
val clock = registry.createCurrentClock()
- assertEquals(clock, mockDefaultClock)
+ assertEquals(mockDefaultClock, clock)
}
@Test
@@ -242,18 +262,18 @@
pluginListener.onPluginLoaded(plugin2, mockContext, lifecycle2)
val list = registry.getClocks()
assertEquals(
- list.toSet(),
setOf(
ClockMetadata(DEFAULT_CLOCK_ID),
ClockMetadata("clock_1"),
ClockMetadata("clock_2"),
),
+ list.toSet(),
)
- assertEquals(registry.createExampleClock("clock_1"), mockClock)
- assertEquals(registry.createExampleClock("clock_2"), mockClock)
- assertEquals(registry.getClockPickerConfig("clock_1"), pickerConfig)
- assertEquals(registry.getClockPickerConfig("clock_2"), pickerConfig)
+ assertEquals(mockClock, registry.createExampleClock("clock_1"))
+ assertEquals(mockClock, registry.createExampleClock("clock_2"))
+ assertEquals(pickerConfig, registry.getClockPickerConfig("clock_1"))
+ assertEquals(pickerConfig, registry.getClockPickerConfig("clock_2"))
verify(lifecycle1, never()).unloadPlugin()
verify(lifecycle2, times(2)).unloadPlugin()
}
@@ -305,7 +325,7 @@
pluginListener.onPluginUnloaded(plugin2, lifecycle2)
val clock = registry.createCurrentClock()
- assertEquals(clock, mockDefaultClock)
+ assertEquals(mockDefaultClock, clock)
}
@Test
@@ -482,13 +502,13 @@
// Verify all plugins were correctly loaded into the registry
assertEquals(
- registry.getClocks().toSet(),
setOf(
ClockMetadata("DEFAULT"),
ClockMetadata("clock_2"),
ClockMetadata("clock_3"),
ClockMetadata("clock_4"),
),
+ registry.getClocks().toSet(),
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
index 9eba410..4f30103 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
@@ -108,7 +108,7 @@
verify(mockSmallClockView).setColors(DOZE_COLOR, Color.MAGENTA)
verify(mockLargeClockView).setColors(DOZE_COLOR, Color.MAGENTA)
- clock.initialize(true, 0f, 0f)
+ clock.initialize(true, 0f, 0f, {})
val expectedColor = 0
verify(mockSmallClockView).setColors(DOZE_COLOR, expectedColor)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 3eec1cd..f54c28f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -200,6 +200,7 @@
private NotificationEntry mSecondaryUserNotif;
private NotificationEntry mWorkProfileNotif;
private NotificationEntry mSensitiveContentNotif;
+ private NotificationEntry mSensitiveNotifWithOldCreationTime;
private long mSensitiveNotifPostTime;
private final FakeFeatureFlagsClassic mFakeFeatureFlags = new FakeFeatureFlagsClassic();
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
@@ -274,6 +275,20 @@
.setSensitiveContent(true)
.setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build());
mSensitiveNotifPostTime = mSensitiveContentNotif.getSbn().getNotification().getWhen();
+
+ mSensitiveNotifWithOldCreationTime = new NotificationEntryBuilder()
+ .setNotification(notifWithPrivateVisibility)
+ .setUser(new UserHandle(mCurrentUser.id))
+ .setPostTime(System.currentTimeMillis())
+ // creation time of at least -2 hours, no matter what the current value of
+ // SystemClock.currentTimeMillis
+ .setCreationTime(-1 * TimeUnit.HOURS.toMillis(2))
+ .build();
+ mSensitiveNotifWithOldCreationTime.setRanking(
+ new RankingBuilder(mCurrentUserNotif.getRanking())
+ .setChannel(channel)
+ .setSensitiveContent(true)
+ .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build());
when(mNotifCollection.getEntry(mWorkProfileNotif.getKey())).thenReturn(mWorkProfileNotif);
when(mKeyguardInteractorLazy.get()).thenReturn(mKeyguardInteractor);
when(mKeyguardInteractor.isKeyguardDismissible())
@@ -653,6 +668,23 @@
@Test
@EnableFlags(LockscreenOtpRedaction.FLAG_NAME)
+ public void testNewSensitiveNotification_notRedactedIfOldCreationTime() {
+ // Allow private notifications for this user
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+ mCurrentUser.id);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+ mLockscreenUserManager.mLocked.set(true);
+ // Claim the device was last unlocked 1 hour ago. Old enough to redact, but newer than the
+ // old creation time in the notification (which is -2 hours)
+ mLockscreenUserManager.mLastLockTime
+ .set(mSensitiveNotifPostTime - TimeUnit.HOURS.toMillis(1));
+ mLockscreenUserManager.mConnectedToWifi.set(false);
+ assertEquals(REDACTION_TYPE_NONE,
+ mLockscreenUserManager.getRedactionType(mSensitiveNotifWithOldCreationTime));
+ }
+
+ @Test
+ @EnableFlags(LockscreenOtpRedaction.FLAG_NAME)
public void testHasSensitiveContent_redacted() {
// Allow private notifications for this user
mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt
index fed6131..a7fe586 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt
@@ -125,8 +125,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_NOTIFICATION_MEDIA_MANAGER_BACKGROUND_EXECUTION)
- fun clearMediaNotification_flagOn_resetMediaMetadata() {
+ fun clearMediaNotification_resetMediaMetadata() {
// set up media metadata.
notificationMediaManager.mMediaListener.onMetadataChanged(MediaMetadata.Builder().build())
backgroundExecutor.runAllReady()
@@ -138,17 +137,4 @@
assertThat(notificationMediaManager.mediaMetadata).isNull()
assertThat(notificationMediaManager.mMediaController).isNull()
}
-
- @Test
- @DisableFlags(Flags.FLAG_NOTIFICATION_MEDIA_MANAGER_BACKGROUND_EXECUTION)
- fun clearMediaNotification_flagOff_resetMediaMetadata() {
- // set up media metadata.
- notificationMediaManager.mMediaListener.onMetadataChanged(MediaMetadata.Builder().build())
-
- // clear media notification.
- notificationMediaManager.clearCurrentMediaNotification()
-
- assertThat(notificationMediaManager.mediaMetadata).isNull()
- assertThat(notificationMediaManager.mMediaController).isNull()
- }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index 326d8ff..8165d45 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -34,6 +34,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
import com.android.systemui.shade.ShadeExpansionChangeEvent
+import com.android.systemui.shared.Flags as SharedFlags
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.ScrimController
@@ -43,10 +44,11 @@
import com.android.systemui.testKosmos
import com.android.systemui.util.WallpaperController
import com.android.systemui.util.mockito.eq
-import com.android.systemui.wallpapers.domain.interactor.WallpaperInteractor
import com.android.systemui.window.domain.interactor.WindowRootViewBlurInteractor
import com.android.wm.shell.appzoomout.AppZoomOut
import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import java.util.function.Consumer
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -67,8 +69,6 @@
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.junit.MockitoJUnit
-import java.util.Optional
-import java.util.function.Consumer
@RunWith(AndroidJUnit4::class)
@RunWithLooper
@@ -76,7 +76,6 @@
class NotificationShadeDepthControllerTest : SysuiTestCase() {
private val kosmos = testKosmos()
- private val applicationScope = kosmos.testScope.backgroundScope
@Mock private lateinit var windowRootViewBlurInteractor: WindowRootViewBlurInteractor
@Mock private lateinit var statusBarStateController: StatusBarStateController
@Mock private lateinit var blurUtils: BlurUtils
@@ -85,7 +84,6 @@
@Mock private lateinit var keyguardInteractor: KeyguardInteractor
@Mock private lateinit var choreographer: Choreographer
@Mock private lateinit var wallpaperController: WallpaperController
- @Mock private lateinit var wallpaperInteractor: WallpaperInteractor
@Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
@Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var appZoomOutOptional: Optional<AppZoomOut>
@@ -130,14 +128,12 @@
keyguardInteractor,
choreographer,
wallpaperController,
- wallpaperInteractor,
notificationShadeWindowController,
dozeParameters,
context,
ResourcesSplitShadeStateController(),
windowRootViewBlurInteractor,
appZoomOutOptional,
- applicationScope,
dumpManager,
configurationController,
)
@@ -314,24 +310,22 @@
}
@Test
- fun onDozeAmountChanged_doesNotApplyBlurWithAmbientAod() {
- notificationShadeDepthController.wallpaperSupportsAmbientMode = false
-
- statusBarStateListener.onDozeAmountChanged(1f, 1f)
- notificationShadeDepthController.updateBlurCallback.doFrame(0)
- verify(blurUtils).applyBlur(any(), eq(0), eq(false))
- }
-
- @Test
- fun onDozeAmountChanged_appliesBlurWithAmbientAod() {
- notificationShadeDepthController.wallpaperSupportsAmbientMode = true
-
+ @DisableFlags(SharedFlags.FLAG_AMBIENT_AOD)
+ fun onDozeAmountChanged_appliesBlur() {
statusBarStateListener.onDozeAmountChanged(1f, 1f)
notificationShadeDepthController.updateBlurCallback.doFrame(0)
verify(blurUtils).applyBlur(any(), eq(maxBlur), eq(false))
}
@Test
+ @EnableFlags(SharedFlags.FLAG_AMBIENT_AOD)
+ fun onDozeAmountChanged_doesNotApplyBlurWithAmbientAod() {
+ statusBarStateListener.onDozeAmountChanged(1f, 1f)
+ notificationShadeDepthController.updateBlurCallback.doFrame(0)
+ verify(blurUtils).applyBlur(any(), eq(0), eq(false))
+ }
+
+ @Test
fun setFullShadeTransition_appliesBlur_onlyIfSupported() {
reset(blurUtils)
`when`(blurUtils.blurRadiusOfRatio(anyFloat())).then { answer ->
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
index 2887de3..e3f93f2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
@@ -48,6 +48,7 @@
import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.chips.notification.ui.viewmodel.NotifChipsViewModelTest.Companion.assertIsNotifChip
+import com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel.ScreenRecordChipViewModel
import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel
import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModelLegacy
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
@@ -172,6 +173,18 @@
}
@Test
+ fun visibleChipKeys_allInactive() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.visibleChipKeys)
+
+ screenRecordState.value = ScreenRecordModel.DoingNothing
+ mediaProjectionState.value = MediaProjectionState.NotProjecting
+ setNotifs(emptyList())
+
+ assertThat(latest).isEmpty()
+ }
+
+ @Test
fun primaryChip_screenRecordShow_restHidden_screenRecordShown() =
kosmos.runTest {
screenRecordState.value = ScreenRecordModel.Recording
@@ -245,6 +258,20 @@
assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModel())
}
+ @Test
+ fun visibleChipKeys_screenRecordShowAndCallShow_hasBothKeys() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.visibleChipKeys)
+
+ val callNotificationKey = "call"
+ screenRecordState.value = ScreenRecordModel.Recording
+ addOngoingCallState(callNotificationKey)
+
+ assertThat(latest)
+ .containsExactly(ScreenRecordChipViewModel.KEY, callNotificationKey)
+ .inOrder()
+ }
+
@EnableChipsModernization
@Test
fun chips_screenRecordAndCallActive_inThatOrder() =
@@ -864,6 +891,37 @@
assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModelLegacy())
}
+ @Test
+ fun visibleChipKeys_threePromotedNotifs_topTwoInList() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.visibleChipKeys)
+
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "firstNotif",
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
+ promotedContent =
+ PromotedNotificationContentModel.Builder("firstNotif").build(),
+ ),
+ activeNotificationModel(
+ key = "secondNotif",
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
+ promotedContent =
+ PromotedNotificationContentModel.Builder("secondNotif").build(),
+ ),
+ activeNotificationModel(
+ key = "thirdNotif",
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
+ promotedContent =
+ PromotedNotificationContentModel.Builder("thirdNotif").build(),
+ ),
+ )
+ )
+
+ assertThat(latest).containsExactly("firstNotif", "secondNotif").inOrder()
+ }
+
@DisableChipsModernization
@Test
fun chipsLegacy_callAndPromotedNotifs_primaryIsCallSecondaryIsNotif() =
@@ -957,6 +1015,27 @@
assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModel())
}
+ @Test
+ fun visibleChipKeys_screenRecordAndCallAndPromotedNotifs_topTwoInList() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.visibleChipKeys)
+
+ val callNotificationKey = "call"
+ addOngoingCallState(callNotificationKey)
+ screenRecordState.value = ScreenRecordModel.Recording
+ activeNotificationListRepository.addNotif(
+ activeNotificationModel(
+ key = "notif",
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
+ promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ )
+ )
+
+ assertThat(latest)
+ .containsExactly(ScreenRecordChipViewModel.KEY, callNotificationKey)
+ .inOrder()
+ }
+
@EnableChipsModernization
@Test
fun chips_screenRecordAndCallAndPromotedNotif_notifInOverflow() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarOrchestratorStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarOrchestratorStoreTest.kt
index 06650f2..b2b28a2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarOrchestratorStoreTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarOrchestratorStoreTest.kt
@@ -50,11 +50,11 @@
@Before fun addDisplays() = runBlocking { fakeDisplayRepository.addDisplay(DEFAULT_DISPLAY) }
@Test
- fun displayRemoved_stopsInstance() =
+ fun systemDecorationRemovedEvent_stopsInstance() =
testScope.runTest {
val instance = underTest.forDisplay(DEFAULT_DISPLAY)!!
- fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)
+ fakeDisplayRepository.triggerRemoveSystemDecorationEvent(DEFAULT_DISPLAY)
verify(instance).stop()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt
index fee939d..18a2d07 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt
@@ -17,8 +17,7 @@
package com.android.systemui.statusbar.core
import android.platform.test.annotations.EnableFlags
-import android.view.Display
-import android.view.mockIWindowManager
+import android.view.Display.DEFAULT_DISPLAY
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -29,6 +28,8 @@
import com.android.systemui.testKosmos
import com.google.common.truth.Expect
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -37,11 +38,11 @@
import org.junit.runner.RunWith
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
-import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+@OptIn(ExperimentalCoroutinesApi::class)
class MultiDisplayStatusBarStarterTest : SysuiTestCase() {
@get:Rule val expect: Expect = Expect.create()
@@ -52,293 +53,116 @@
private val fakeInitializerStore = kosmos.fakeStatusBarInitializerStore
private val fakePrivacyDotStore = kosmos.fakePrivacyDotWindowControllerStore
private val fakeLightBarStore = kosmos.fakeLightBarControllerStore
- private val windowManager = kosmos.mockIWindowManager
// Lazy, so that @EnableFlags is set before initializer is instantiated.
private val underTest by lazy { kosmos.multiDisplayStatusBarStarter }
@Before
- fun setup() {
- whenever(windowManager.shouldShowSystemDecors(Display.DEFAULT_DISPLAY)).thenReturn(true)
- whenever(windowManager.shouldShowSystemDecors(DISPLAY_1)).thenReturn(true)
- whenever(windowManager.shouldShowSystemDecors(DISPLAY_2)).thenReturn(true)
- whenever(windowManager.shouldShowSystemDecors(DISPLAY_3)).thenReturn(true)
- whenever(windowManager.shouldShowSystemDecors(DISPLAY_4_NO_SYSTEM_DECOR)).thenReturn(false)
+ fun setUp() = runBlocking {
+ fakeDisplayRepository.addDisplay(DEFAULT_DISPLAY)
+ fakeDisplayRepository.addDisplay(DISPLAY_2)
}
@Test
- fun start_startsInitializersForCurrentDisplays() =
+ fun start_triggerAddDisplaySystemDecoration_startsInitializersForDisplay() =
testScope.runTest {
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_1)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_2)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
-
underTest.start()
runCurrent()
+ fakeDisplayRepository.triggerAddDisplaySystemDecorationEvent(
+ displayId = DEFAULT_DISPLAY
+ )
+ fakeDisplayRepository.triggerAddDisplaySystemDecorationEvent(displayId = DISPLAY_2)
+
expect
- .that(fakeInitializerStore.forDisplay(displayId = DISPLAY_1).startedByCoreStartable)
+ .that(
+ fakeInitializerStore
+ .forDisplay(displayId = DEFAULT_DISPLAY)
+ .startedByCoreStartable
+ )
.isTrue()
expect
.that(fakeInitializerStore.forDisplay(displayId = DISPLAY_2).startedByCoreStartable)
.isTrue()
- expect
- .that(
- fakeInitializerStore
- .forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
- .startedByCoreStartable
- )
- .isFalse()
}
@Test
- fun start_startsOrchestratorForCurrentDisplays() =
+ fun start_triggerAddDisplaySystemDecoration_startsOrchestratorForDisplay() =
testScope.runTest {
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_1)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_2)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
-
underTest.start()
runCurrent()
- verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = DISPLAY_1)!!)
+ fakeDisplayRepository.triggerAddDisplaySystemDecorationEvent(
+ displayId = DEFAULT_DISPLAY
+ )
+ fakeDisplayRepository.triggerAddDisplaySystemDecorationEvent(displayId = DISPLAY_2)
+ runCurrent()
+
+ verify(
+ fakeOrchestratorFactory.createdOrchestratorForDisplay(
+ displayId = DEFAULT_DISPLAY
+ )!!
+ )
.start()
verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = DISPLAY_2)!!)
.start()
- assertThat(
- fakeOrchestratorFactory.createdOrchestratorForDisplay(
- displayId = DISPLAY_4_NO_SYSTEM_DECOR
- )
- )
- .isNull()
}
@Test
- fun start_startsPrivacyDotForCurrentDisplays() =
+ fun start_triggerAddDisplaySystemDecoration_startsPrivacyDotForNonDefaultDisplay() =
testScope.runTest {
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_1)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_2)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
-
underTest.start()
runCurrent()
- verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_1)).start()
+ fakeDisplayRepository.triggerAddDisplaySystemDecorationEvent(displayId = DISPLAY_2)
+
verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_2)).start()
- verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR), never())
- .start()
}
@Test
- fun start_doesNotStartLightBarControllerForCurrentDisplays() =
+ fun start_triggerAddDisplaySystemDecoration_doesNotStartPrivacyDotForDefaultDisplay() =
testScope.runTest {
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_1)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_2)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
-
underTest.start()
runCurrent()
- verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_1), never()).start()
+ fakeDisplayRepository.triggerAddDisplaySystemDecorationEvent(
+ displayId = DEFAULT_DISPLAY
+ )
+
+ verify(fakePrivacyDotStore.forDisplay(displayId = DEFAULT_DISPLAY), never()).start()
+ }
+
+ @Test
+ fun start_triggerAddDisplaySystemDecoration_doesNotStartLightBarControllerForDisplays() =
+ testScope.runTest {
+ underTest.start()
+ runCurrent()
+
+ fakeDisplayRepository.triggerAddDisplaySystemDecorationEvent(
+ displayId = DEFAULT_DISPLAY
+ )
+ fakeDisplayRepository.triggerAddDisplaySystemDecorationEvent(displayId = DISPLAY_2)
+
+ verify(fakeLightBarStore.forDisplay(displayId = DEFAULT_DISPLAY), never()).start()
verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_2), never()).start()
- verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR), never())
- .start()
}
@Test
- fun start_createsLightBarControllerForCurrentDisplays() =
- testScope.runTest {
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_1)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_2)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
-
- underTest.start()
- runCurrent()
-
- assertThat(fakeLightBarStore.perDisplayMocks.keys).containsExactly(1, DISPLAY_2)
- }
-
- @Test
- fun start_doesNotStartPrivacyDotForDefaultDisplay() =
- testScope.runTest {
- fakeDisplayRepository.addDisplay(displayId = Display.DEFAULT_DISPLAY)
-
- underTest.start()
- runCurrent()
-
- verify(fakePrivacyDotStore.forDisplay(displayId = Display.DEFAULT_DISPLAY), never())
- .start()
- }
-
- @Test
- fun displayAdded_orchestratorForNewDisplay() =
+ fun start_triggerAddDisplaySystemDecoration_createsLightBarControllerForDisplay() =
testScope.runTest {
underTest.start()
runCurrent()
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
- runCurrent()
+ fakeDisplayRepository.triggerAddDisplaySystemDecorationEvent(
+ displayId = DEFAULT_DISPLAY
+ )
+ fakeDisplayRepository.triggerAddDisplaySystemDecorationEvent(displayId = DISPLAY_2)
- verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = DISPLAY_3)!!)
- .start()
- assertThat(
- fakeOrchestratorFactory.createdOrchestratorForDisplay(
- displayId = DISPLAY_4_NO_SYSTEM_DECOR
- )
- )
- .isNull()
- }
-
- @Test
- fun displayAdded_initializerForNewDisplay() =
- testScope.runTest {
- underTest.start()
- runCurrent()
-
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
- runCurrent()
-
- expect
- .that(fakeInitializerStore.forDisplay(displayId = DISPLAY_3).startedByCoreStartable)
- .isTrue()
- expect
- .that(
- fakeInitializerStore
- .forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
- .startedByCoreStartable
- )
- .isFalse()
- }
-
- @Test
- fun displayAdded_privacyDotForNewDisplay() =
- testScope.runTest {
- underTest.start()
- runCurrent()
-
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
- runCurrent()
-
- verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_3)).start()
- verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR), never())
- .start()
- }
-
- @Test
- fun displayAdded_lightBarForNewDisplayCreate() =
- testScope.runTest {
- underTest.start()
- runCurrent()
-
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
- runCurrent()
-
- assertThat(fakeLightBarStore.perDisplayMocks.keys).containsExactly(DISPLAY_3)
- }
-
- @Test
- fun displayAdded_lightBarForNewDisplayStart() =
- testScope.runTest {
- underTest.start()
- runCurrent()
-
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
- runCurrent()
-
- verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_3), never()).start()
- verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR), never())
- .start()
- }
-
- @Test
- fun displayAddedDuringStart_initializerForNewDisplay() =
- testScope.runTest {
- underTest.start()
-
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
- runCurrent()
-
- expect
- .that(fakeInitializerStore.forDisplay(displayId = DISPLAY_3).startedByCoreStartable)
- .isTrue()
- expect
- .that(
- fakeInitializerStore
- .forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
- .startedByCoreStartable
- )
- .isFalse()
- }
-
- @Test
- fun displayAddedDuringStart_orchestratorForNewDisplay() =
- testScope.runTest {
- underTest.start()
-
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
- runCurrent()
-
- verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = DISPLAY_3)!!)
- .start()
- assertThat(
- fakeOrchestratorFactory.createdOrchestratorForDisplay(
- displayId = DISPLAY_4_NO_SYSTEM_DECOR
- )
- )
- .isNull()
- }
-
- @Test
- fun displayAddedDuringStart_privacyDotForNewDisplay() =
- testScope.runTest {
- underTest.start()
-
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
- runCurrent()
-
- verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_3)).start()
- verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR), never())
- .start()
- }
-
- @Test
- fun displayAddedDuringStart_lightBarForNewDisplayCreate() =
- testScope.runTest {
- underTest.start()
-
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
- runCurrent()
-
- assertThat(fakeLightBarStore.perDisplayMocks.keys).containsExactly(DISPLAY_3)
- }
-
- @Test
- fun displayAddedDuringStart_lightBarForNewDisplayStart() =
- testScope.runTest {
- underTest.start()
-
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
- fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
- runCurrent()
-
- verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_3), never()).start()
- verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR), never())
- .start()
+ assertThat(fakeLightBarStore.perDisplayMocks.keys)
+ .containsExactly(DEFAULT_DISPLAY, DISPLAY_2)
}
companion object {
- const val DISPLAY_1 = 1
const val DISPLAY_2 = 2
- const val DISPLAY_3 = 3
- const val DISPLAY_4_NO_SYSTEM_DECOR = 4
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreImplTest.kt
index 884c35c..500332f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreImplTest.kt
@@ -65,11 +65,11 @@
}
@Test
- fun displayRemoved_stopsInstance() =
+ fun systemDecorationRemovedEvent_stopsInstance() =
testScope.runTest {
val instance = underTest.forDisplay(DEFAULT_DISPLAY)!!
- fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)
+ fakeDisplayRepository.triggerRemoveSystemDecorationEvent(DEFAULT_DISPLAY)
verify(instance).stop()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayDarkIconDispatcherStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayDarkIconDispatcherStoreTest.kt
index f37648a..62ead9b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayDarkIconDispatcherStoreTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayDarkIconDispatcherStoreTest.kt
@@ -62,11 +62,11 @@
}
@Test
- fun displayRemoved_stopsInstance() =
+ fun systemDecorationRemovedEvent_stopsInstance() =
testScope.runTest {
val instance = underTest.forDisplay(DEFAULT_DISPLAY)!!
- fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)
+ fakeDisplayRepository.triggerRemoveSystemDecorationEvent(DEFAULT_DISPLAY)
verify(instance).stop()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarContentInsetsProviderStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarContentInsetsProviderStoreTest.kt
index e0a1f27..486a845 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarContentInsetsProviderStoreTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarContentInsetsProviderStoreTest.kt
@@ -65,11 +65,11 @@
}
@Test
- fun displayRemoved_stopsInstance() =
+ fun systemDecorationRemovedEvent_stopsInstance() =
testScope.runTest {
val instance = underTest.forDisplay(DEFAULT_DISPLAY)!!
- fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)
+ fakeDisplayRepository.triggerRemoveSystemDecorationEvent(DEFAULT_DISPLAY)
verify(instance).stop()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarModeRepositoryStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarModeRepositoryStoreTest.kt
index 11fd902..2c474da 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarModeRepositoryStoreTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarModeRepositoryStoreTest.kt
@@ -59,11 +59,11 @@
}
@Test
- fun displayRemoved_stopsInstance() =
+ fun systemDecorationRemovedEvent_stopsInstance() =
testScope.runTest {
val instance = underTest.forDisplay(DEFAULT_DISPLAY)!!
- fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)
+ fakeDisplayRepository.triggerRemoveSystemDecorationEvent(DEFAULT_DISPLAY)
verify(instance).stop()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreImplTest.kt
index a5b7fc2..bc7d47c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreImplTest.kt
@@ -23,6 +23,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.testKosmos
import kotlinx.coroutines.runBlocking
@@ -36,7 +37,7 @@
@RunWith(AndroidJUnit4::class)
@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
class PrivacyDotWindowControllerStoreImplTest : SysuiTestCase() {
- private val kosmos = testKosmos()
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val testScope = kosmos.testScope
private val underTest by lazy { kosmos.privacyDotWindowControllerStoreImpl }
@@ -58,11 +59,11 @@
}
@Test
- fun displayRemoved_stopsInstance() =
+ fun systemDecorationRemovedEvent_stopsInstance() =
testScope.runTest {
val instance = underTest.forDisplay(DISPLAY_2)!!
- kosmos.displayRepository.removeDisplay(DISPLAY_2)
+ kosmos.displayRepository.triggerRemoveSystemDecorationEvent(DISPLAY_2)
verify(instance).stop()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayStoreImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayStoreImplTest.kt
new file mode 100644
index 0000000..41ae377
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayStoreImplTest.kt
@@ -0,0 +1,88 @@
+/*
+ * 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.data.repository
+
+import android.view.Display
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class StatusBarPerDisplayStoreImplTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+ private val testScope = kosmos.testScope
+ private val fakeDisplayRepository = kosmos.displayRepository
+
+ private val store = kosmos.fakeStatusBarPerDisplayStore
+
+ @Before
+ fun start() {
+ store.start()
+ }
+
+ @Before
+ fun addDisplays() = runBlocking {
+ fakeDisplayRepository.addDisplay(DEFAULT_DISPLAY_ID)
+ fakeDisplayRepository.addDisplay(NON_DEFAULT_DISPLAY_ID)
+ }
+
+ @Test
+ fun removeSystemDecoration_onDisplayRemovalActionInvoked() =
+ testScope.runTest {
+ val instance = store.forDisplay(NON_DEFAULT_DISPLAY_ID)
+
+ fakeDisplayRepository.triggerRemoveSystemDecorationEvent(NON_DEFAULT_DISPLAY_ID)
+
+ assertThat(store.removalActions).containsExactly(instance)
+ }
+
+ @Test
+ fun removeSystemDecoration_twice_onDisplayRemovalActionInvokedOnce() =
+ testScope.runTest {
+ val instance = store.forDisplay(NON_DEFAULT_DISPLAY_ID)
+
+ fakeDisplayRepository.triggerRemoveSystemDecorationEvent(NON_DEFAULT_DISPLAY_ID)
+ fakeDisplayRepository.triggerRemoveSystemDecorationEvent(NON_DEFAULT_DISPLAY_ID)
+
+ assertThat(store.removalActions).containsExactly(instance)
+ }
+
+ @Test
+ fun forDisplay_withoutDisplayRemoval_onDisplayRemovalActionIsNotInvoked() =
+ testScope.runTest {
+ store.forDisplay(NON_DEFAULT_DISPLAY_ID)
+
+ assertThat(store.removalActions).isEmpty()
+ }
+
+ companion object {
+ private const val DEFAULT_DISPLAY_ID = Display.DEFAULT_DISPLAY
+ private const val NON_DEFAULT_DISPLAY_ID = DEFAULT_DISPLAY_ID + 1
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreImplTest.kt
index 3cc592c..94394ed 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreImplTest.kt
@@ -62,11 +62,11 @@
}
@Test
- fun displayRemoved_stopsInstance() =
+ fun systemDecorationRemovedEvent_stopsInstance() =
testScope.runTest {
val instance = underTest.forDisplay(DEFAULT_DISPLAY)!!
- fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)
+ fakeDisplayRepository.triggerRemoveSystemDecorationEvent(DEFAULT_DISPLAY)
verify(instance).stop()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModelTest.kt
index d36dbbe..d4518e7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModelTest.kt
@@ -21,9 +21,10 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.parameterizeSceneContainerFlag
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.lifecycle.activateIn
import com.android.systemui.media.controls.data.repository.mediaFilterRepository
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
import com.android.systemui.media.controls.shared.model.MediaData
@@ -47,7 +48,8 @@
class MediaControlChipViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val mediaControlChipInteractor by lazy { kosmos.mediaControlChipInteractor }
- private val Kosmos.underTest by Kosmos.Fixture { kosmos.mediaControlChipViewModel }
+ private val Kosmos.underTest by
+ Kosmos.Fixture { kosmos.mediaControlChipViewModelFactory.create() }
@Captor lateinit var listener: ArgumentCaptor<MediaDataManager.Listener>
companion object {
@@ -62,6 +64,7 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
mediaControlChipInteractor.initialize()
+ kosmos.underTest.activateIn(kosmos.testScope)
}
init {
@@ -71,7 +74,7 @@
@Test
fun chip_noActiveMedia_IsHidden() =
kosmos.runTest {
- val chip by collectLastValue(underTest.chip)
+ val chip = underTest.chip
assertThat(chip).isInstanceOf(PopupChipModel.Hidden::class.java)
}
@@ -79,30 +82,26 @@
@Test
fun chip_activeMedia_IsShown() =
kosmos.runTest {
- val chip by collectLastValue(underTest.chip)
-
val userMedia = MediaData(active = true, song = "test")
updateMedia(userMedia)
- assertThat(chip).isInstanceOf(PopupChipModel.Shown::class.java)
+ assertThat(underTest.chip).isInstanceOf(PopupChipModel.Shown::class.java)
}
@Test
fun chip_songNameChanges_chipTextUpdated() =
kosmos.runTest {
- val chip by collectLastValue(underTest.chip)
-
val initialSongName = "Initial Song"
val newSongName = "New Song"
val userMedia = MediaData(active = true, song = initialSongName)
updateMedia(userMedia)
- assertThat(chip).isInstanceOf(PopupChipModel.Shown::class.java)
- assertThat((chip as PopupChipModel.Shown).chipText).isEqualTo(initialSongName)
+ assertThat(underTest.chip).isInstanceOf(PopupChipModel.Shown::class.java)
+ assertThat((underTest.chip as PopupChipModel.Shown).chipText).isEqualTo(initialSongName)
val updatedUserMedia = userMedia.copy(song = newSongName)
updateMedia(updatedUserMedia)
- assertThat((chip as PopupChipModel.Shown).chipText).isEqualTo(newSongName)
+ assertThat((underTest.chip as PopupChipModel.Shown).chipText).isEqualTo(newSongName)
}
private fun updateMedia(mediaData: MediaData) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/StatusBarPopupChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/StatusBarPopupChipsViewModelTest.kt
new file mode 100644
index 0000000..134ab93
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/StatusBarPopupChipsViewModelTest.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 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.featurepods.media.ui.viewmodel
+
+import android.platform.test.annotations.EnableFlags
+import androidx.compose.runtime.snapshots.Snapshot
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.media.controls.data.repository.mediaFilterRepository
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
+import com.android.systemui.statusbar.featurepods.popups.StatusBarPopupChips
+import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipId
+import com.android.systemui.statusbar.featurepods.popups.ui.viewmodel.statusBarPopupChipsViewModelFactory
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@EnableFlags(StatusBarPopupChips.FLAG_NAME)
+@RunWith(AndroidJUnit4::class)
+class StatusBarPopupChipsViewModelTest : SysuiTestCase() {
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+ private val underTest = kosmos.statusBarPopupChipsViewModelFactory.create()
+
+ @Before
+ fun setUp() {
+ underTest.activateIn(kosmos.testScope)
+ }
+
+ @Test
+ fun shownPopupChips_allHidden_empty() =
+ kosmos.runTest {
+ val shownPopupChips = underTest.shownPopupChips
+ assertThat(shownPopupChips).isEmpty()
+ }
+
+ @Test
+ fun shownPopupChips_activeMedia_restHidden_mediaControlChipShown() =
+ kosmos.runTest {
+ val shownPopupChips = underTest.shownPopupChips
+ val userMedia = MediaData(active = true, song = "test")
+ val instanceId = userMedia.instanceId
+
+ mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
+ mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId))
+
+ Snapshot.takeSnapshot {
+ assertThat(shownPopupChips).hasSize(1)
+ assertThat(shownPopupChips.first().chipId).isEqualTo(PopupChipId.MediaControl)
+ }
+ }
+
+ @Test
+ fun shownPopupChips_mediaChipToggled_popupShown() =
+ kosmos.runTest {
+ val shownPopupChips = underTest.shownPopupChips
+
+ val userMedia = MediaData(active = true, song = "test")
+ val instanceId = userMedia.instanceId
+
+ mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
+ mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId))
+
+ Snapshot.takeSnapshot {
+ assertThat(shownPopupChips).hasSize(1)
+ val mediaChip = shownPopupChips.first()
+ assertThat(mediaChip.isPopupShown).isFalse()
+
+ mediaChip.showPopup.invoke()
+ assertThat(shownPopupChips.first().isPopupShown).isTrue()
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index 609885d..3098355 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -549,7 +549,7 @@
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
- fun onPromotedNotificationChipTapped_chipTappedTwice_hunHiddenOnSecondTap() =
+ fun onPromotedNotificationChipTapped_chipTappedTwice_hunHiddenOnSecondTapImmediately() =
testScope.runTest {
whenever(notifCollection.getEntry(entry.key)).thenReturn(entry)
@@ -570,8 +570,9 @@
executor.runAllReady()
beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry))
- // THEN HUN is hidden
- verify(headsUpManager).removeNotification(eq(entry.key), eq(false), any())
+ // THEN HUN is hidden and it's hidden immediately
+ verify(headsUpManager)
+ .removeNotification(eq(entry.key), /* releaseImmediately= */ eq(true), any())
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/AvalancheControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/AvalancheControllerTest.kt
index dc27859..a2e4a32 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/AvalancheControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/AvalancheControllerTest.kt
@@ -17,9 +17,10 @@
import android.app.Notification
import android.os.Handler
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
import android.testing.TestableLooper.RunWithLooper
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.SysuiTestCase
@@ -28,6 +29,7 @@
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.statusbar.statusBarStateController
import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.collection.provider.visualStabilityProvider
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManagerImpl
@@ -53,12 +55,18 @@
import org.mockito.invocation.InvocationOnMock
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
@RunWithLooper
-@RunWith(AndroidJUnit4::class)
+@RunWith(ParameterizedAndroidJunit4::class)
@EnableFlags(NotificationThrottleHun.FLAG_NAME)
-class AvalancheControllerTest : SysuiTestCase() {
+class AvalancheControllerTest(val flags: FlagsParameterization) : SysuiTestCase() {
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
+
private val kosmos = testKosmos()
// For creating mocks
@@ -72,10 +80,10 @@
// For creating TestableHeadsUpManager
@Mock private val mAccessibilityMgr: AccessibilityManagerWrapper? = null
private val mUiEventLoggerFake = UiEventLoggerFake()
- @Mock private lateinit var mHeadsUpManagerLogger: HeadsUpManagerLogger
+ private val headsUpManagerLogger = HeadsUpManagerLogger(logcatLogBuffer())
@Mock private lateinit var mBgHandler: Handler
- private val mLogger = Mockito.spy(HeadsUpManagerLogger(logcatLogBuffer()))
+ private val mLogger = Mockito.spy(headsUpManagerLogger)
private val mGlobalSettings = FakeGlobalSettings()
private val mSystemClock = FakeSystemClock()
private val mExecutor = FakeExecutor(mSystemClock)
@@ -95,7 +103,7 @@
// Initialize AvalancheController and TestableHeadsUpManager during setUp instead of
// declaration, where mocks are null
mAvalancheController =
- AvalancheController(dumpManager, mUiEventLoggerFake, mHeadsUpManagerLogger, mBgHandler)
+ AvalancheController(dumpManager, mUiEventLoggerFake, headsUpManagerLogger, mBgHandler)
testableHeadsUpManager =
HeadsUpManagerImpl(
@@ -278,7 +286,7 @@
// Delete
mAvalancheController.delete(firstEntry, runnableMock, "testLabel")
- // Next entry is shown
+ // Showing entry becomes previous
assertThat(mAvalancheController.previousHunKey).isEqualTo(firstEntry.mEntry!!.key)
}
@@ -296,12 +304,12 @@
// Delete
mAvalancheController.delete(showingEntry, runnableMock!!, "testLabel")
- // Next entry is shown
+ // Previous key not filled in
assertThat(mAvalancheController.previousHunKey).isEqualTo("")
}
@Test
- fun testGetDurationMs_untrackedEntryEmptyAvalanche_useAutoDismissTime() {
+ fun testGetDuration_untrackedEntryEmptyAvalanche_useAutoDismissTime() {
val givenEntry = createHeadsUpEntry(id = 0)
// Nothing is showing
@@ -310,12 +318,12 @@
// Nothing is next
mAvalancheController.clearNext()
- val durationMs = mAvalancheController.getDurationMs(givenEntry, autoDismissMs = 5000)
- assertThat(durationMs).isEqualTo(5000)
+ val durationMs = mAvalancheController.getDuration(givenEntry, autoDismissMsValue = 5000)
+ assertThat((durationMs as RemainingDuration.UpdatedDuration).duration).isEqualTo(5000)
}
@Test
- fun testGetDurationMs_untrackedEntryNonEmptyAvalanche_useAutoDismissTime() {
+ fun testGetDuration_untrackedEntryNonEmptyAvalanche_useAutoDismissTime() {
val givenEntry = createHeadsUpEntry(id = 0)
// Given entry not tracked
@@ -325,12 +333,12 @@
val nextEntry = createHeadsUpEntry(id = 2)
mAvalancheController.addToNext(nextEntry, runnableMock!!)
- val durationMs = mAvalancheController.getDurationMs(givenEntry, autoDismissMs = 5000)
- assertThat(durationMs).isEqualTo(5000)
+ val durationMs = mAvalancheController.getDuration(givenEntry, autoDismissMsValue = 5000)
+ assertThat((durationMs as RemainingDuration.UpdatedDuration).duration).isEqualTo(5000)
}
@Test
- fun testGetDurationMs_lastEntry_useAutoDismissTime() {
+ fun testGetDuration_lastEntry_useAutoDismissTime() {
// Entry is showing
val showingEntry = createHeadsUpEntry(id = 0)
mAvalancheController.headsUpEntryShowing = showingEntry
@@ -338,12 +346,12 @@
// Nothing is next
mAvalancheController.clearNext()
- val durationMs = mAvalancheController.getDurationMs(showingEntry, autoDismissMs = 5000)
- assertThat(durationMs).isEqualTo(5000)
+ val durationMs = mAvalancheController.getDuration(showingEntry, autoDismissMsValue = 5000)
+ assertThat((durationMs as RemainingDuration.UpdatedDuration).duration).isEqualTo(5000)
}
@Test
- fun testGetDurationMs_nextEntryLowerPriority_5000() {
+ fun testGetDuration_nextEntryLowerPriority_5000() {
// Entry is showing
val showingEntry = createFsiHeadsUpEntry(id = 1)
mAvalancheController.headsUpEntryShowing = showingEntry
@@ -355,12 +363,12 @@
// Next entry has lower priority
assertThat(nextEntry.compareNonTimeFields(showingEntry)).isEqualTo(1)
- val durationMs = mAvalancheController.getDurationMs(showingEntry, autoDismissMs = 5000)
- assertThat(durationMs).isEqualTo(5000)
+ val durationMs = mAvalancheController.getDuration(showingEntry, autoDismissMsValue = 5000)
+ assertThat((durationMs as RemainingDuration.UpdatedDuration).duration).isEqualTo(5000)
}
@Test
- fun testGetDurationMs_nextEntrySamePriority_1000() {
+ fun testGetDuration_nextEntrySamePriority_1000() {
// Entry is showing
val showingEntry = createHeadsUpEntry(id = 0)
mAvalancheController.headsUpEntryShowing = showingEntry
@@ -372,12 +380,12 @@
// Same priority
assertThat(nextEntry.compareNonTimeFields(showingEntry)).isEqualTo(0)
- val durationMs = mAvalancheController.getDurationMs(showingEntry, autoDismissMs = 5000)
- assertThat(durationMs).isEqualTo(1000)
+ val durationMs = mAvalancheController.getDuration(showingEntry, autoDismissMsValue = 5000)
+ assertThat((durationMs as RemainingDuration.UpdatedDuration).duration).isEqualTo(1000)
}
@Test
- fun testGetDurationMs_nextEntryHigherPriority_500() {
+ fun testGetDuration_nextEntryHigherPriority_500() {
// Entry is showing
val showingEntry = createHeadsUpEntry(id = 0)
mAvalancheController.headsUpEntryShowing = showingEntry
@@ -389,7 +397,51 @@
// Next entry has higher priority
assertThat(nextEntry.compareNonTimeFields(showingEntry)).isEqualTo(-1)
- val durationMs = mAvalancheController.getDurationMs(showingEntry, autoDismissMs = 5000)
- assertThat(durationMs).isEqualTo(500)
+ val durationMs = mAvalancheController.getDuration(showingEntry, autoDismissMsValue = 5000)
+ assertThat((durationMs as RemainingDuration.UpdatedDuration).duration).isEqualTo(500)
+ }
+
+ @Test
+ @DisableFlags(StatusBarNotifChips.FLAG_NAME)
+ fun testGetDuration_nextEntryIsPinnedByUser_flagOff_1000() {
+ // Entry is showing
+ val showingEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.headsUpEntryShowing = showingEntry
+
+ // There's another entry waiting to show next and it's PinnedByUser
+ val nextEntry = createHeadsUpEntry(id = 1)
+ nextEntry.requestedPinnedStatus = PinnedStatus.PinnedByUser
+ mAvalancheController.addToNext(nextEntry, runnableMock!!)
+
+ val durationMs = mAvalancheController.getDuration(showingEntry, autoDismissMsValue = 5000)
+
+ // BUT PinnedByUser is ignored because flag is off, so the duration for a SAME priority next
+ // is used
+ assertThat((durationMs as RemainingDuration.UpdatedDuration).duration).isEqualTo(1000)
+ }
+
+ @Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ fun testGetDuration_nextEntryIsPinnedByUser_flagOn_hideImmediately() {
+ // Entry is showing
+ val showingEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.headsUpEntryShowing = showingEntry
+
+ // There's another entry waiting to show next and it's PinnedByUser
+ val nextEntry = createHeadsUpEntry(id = 1)
+ nextEntry.requestedPinnedStatus = PinnedStatus.PinnedByUser
+ mAvalancheController.addToNext(nextEntry, runnableMock!!)
+
+ val duration = mAvalancheController.getDuration(showingEntry, autoDismissMsValue = 5000)
+
+ assertThat(duration).isEqualTo(RemainingDuration.HideImmediately)
+ }
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf(StatusBarNotifChips.FLAG_NAME)
+ }
}
}
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
index 206eb89..706885b 100644
--- 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
@@ -21,6 +21,8 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.res.R
+import com.android.systemui.statusbar.ui.fakeSystemBarUtilsProxy
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import org.junit.Before
@@ -30,6 +32,8 @@
@RunWith(AndroidJUnit4::class)
@EnableFlags(NotificationsHunSharedAnimationValues.FLAG_NAME)
class HeadsUpAnimatorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
@Before
fun setUp() {
context.getOrCreateTestableResources().apply {
@@ -38,34 +42,64 @@
}
@Test
- fun getHeadsUpYTranslation_fromBottomTrue_usesBottomAndYAbove() {
- val underTest = HeadsUpAnimator(context)
+ fun getHeadsUpYTranslation_fromBottomTrue_hasStatusBarChipFalse_usesBottomAndYAbove() {
+ val underTest = HeadsUpAnimator(context, kosmos.fakeSystemBarUtilsProxy)
underTest.stackTopMargin = 30
underTest.headsUpAppearHeightBottom = 300
- val yTranslation = underTest.getHeadsUpYTranslation(isHeadsUpFromBottom = true)
+ val yTranslation =
+ underTest.getHeadsUpYTranslation(isHeadsUpFromBottom = true, hasStatusBarChip = false)
assertThat(yTranslation).isEqualTo(TEST_Y_ABOVE_SCREEN + 300)
}
@Test
- fun getHeadsUpYTranslation_fromBottomFalse_usesTopMarginAndYAbove() {
- val underTest = HeadsUpAnimator(context)
+ fun getHeadsUpYTranslation_fromBottomTrue_hasStatusBarChipTrue_usesBottomAndYAbove() {
+ val underTest = HeadsUpAnimator(context, kosmos.fakeSystemBarUtilsProxy)
underTest.stackTopMargin = 30
underTest.headsUpAppearHeightBottom = 300
- val yTranslation = underTest.getHeadsUpYTranslation(isHeadsUpFromBottom = false)
+ val yTranslation =
+ underTest.getHeadsUpYTranslation(isHeadsUpFromBottom = true, hasStatusBarChip = true)
+
+ // fromBottom takes priority
+ assertThat(yTranslation).isEqualTo(TEST_Y_ABOVE_SCREEN + 300)
+ }
+
+ @Test
+ fun getHeadsUpYTranslation_fromBottomFalse_hasStatusBarChipFalse_usesTopMarginAndYAbove() {
+ val underTest = HeadsUpAnimator(context, kosmos.fakeSystemBarUtilsProxy)
+ underTest.stackTopMargin = 30
+ underTest.headsUpAppearHeightBottom = 300
+
+ val yTranslation =
+ underTest.getHeadsUpYTranslation(isHeadsUpFromBottom = false, hasStatusBarChip = false)
assertThat(yTranslation).isEqualTo(-30 - TEST_Y_ABOVE_SCREEN)
}
@Test
+ fun getHeadsUpYTranslation_fromBottomFalse_hasStatusBarChipTrue_usesTopMarginAndStatusBarHeight() {
+ val underTest = HeadsUpAnimator(context, kosmos.fakeSystemBarUtilsProxy)
+ underTest.stackTopMargin = 30
+ underTest.headsUpAppearHeightBottom = 300
+ kosmos.fakeSystemBarUtilsProxy.fakeStatusBarHeight = 75
+ underTest.updateResources(context)
+
+ val yTranslation =
+ underTest.getHeadsUpYTranslation(isHeadsUpFromBottom = false, hasStatusBarChip = true)
+
+ assertThat(yTranslation).isEqualTo(75 - 30)
+ }
+
+ @Test
fun getHeadsUpYTranslation_resourcesUpdated() {
- val underTest = HeadsUpAnimator(context)
+ val underTest = HeadsUpAnimator(context, kosmos.fakeSystemBarUtilsProxy)
underTest.stackTopMargin = 30
underTest.headsUpAppearHeightBottom = 300
- val yTranslation = underTest.getHeadsUpYTranslation(isHeadsUpFromBottom = true)
+ val yTranslation =
+ underTest.getHeadsUpYTranslation(isHeadsUpFromBottom = true, hasStatusBarChip = false)
assertThat(yTranslation).isEqualTo(TEST_Y_ABOVE_SCREEN + 300)
@@ -77,7 +111,12 @@
underTest.updateResources(context)
// THEN HeadsUpAnimator knows about it
- assertThat(underTest.getHeadsUpYTranslation(isHeadsUpFromBottom = true))
+ assertThat(
+ underTest.getHeadsUpYTranslation(
+ isHeadsUpFromBottom = true,
+ hasStatusBarChip = false,
+ )
+ )
.isEqualTo(newYAbove + 300)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractorTest.kt
index aa6e76d..6926677 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractorTest.kt
@@ -111,7 +111,7 @@
)
val topPromotedNotificationContent by
- collectLastValue(underTest.topPromotedNotificationContent)
+ collectLastValue(underTest.aodPromotedNotification)
// THEN the ron is first because the call has no content
assertThat(topPromotedNotificationContent?.identity?.key)
@@ -131,7 +131,7 @@
)
val topPromotedNotificationContent by
- collectLastValue(underTest.topPromotedNotificationContent)
+ collectLastValue(underTest.aodPromotedNotification)
// THEN the call is the top notification
assertThat(topPromotedNotificationContent?.identity?.key)
@@ -148,7 +148,7 @@
renderNotificationListInteractor.setRenderedList(listOf(callEntry, otherEntry))
val topPromotedNotificationContent by
- collectLastValue(underTest.topPromotedNotificationContent)
+ collectLastValue(underTest.aodPromotedNotification)
// THEN there is no top promoted notification
assertThat(topPromotedNotificationContent).isNull()
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 9545150..08ecbac 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
@@ -2,6 +2,7 @@
import android.annotation.DimenRes
import android.content.pm.PackageManager
+import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
import android.widget.FrameLayout
import androidx.test.filters.SmallTest
@@ -19,6 +20,7 @@
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
import com.android.systemui.statusbar.NotificationShelf
import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.notification.RoundableState
import com.android.systemui.statusbar.notification.collection.EntryAdapter
import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -32,6 +34,8 @@
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import com.android.systemui.statusbar.ui.fakeSystemBarUtilsProxy
+import com.android.systemui.testKosmos
import com.google.common.truth.Expect
import com.google.common.truth.Truth.assertThat
import org.junit.Assume
@@ -53,6 +57,8 @@
@JvmField @Rule var expect: Expect = Expect.create()
+ private val kosmos = testKosmos()
+
private val largeScreenShadeInterpolator = mock<LargeScreenShadeInterpolator>()
private val avalancheController = mock<AvalancheController>()
@@ -131,13 +137,14 @@
hostView.addView(notificationRow)
if (NotificationsHunSharedAnimationValues.isEnabled) {
- headsUpAnimator = HeadsUpAnimator(context)
+ headsUpAnimator = HeadsUpAnimator(context, kosmos.fakeSystemBarUtilsProxy)
}
- stackScrollAlgorithm = StackScrollAlgorithm(
- context,
- hostView,
- if (::headsUpAnimator.isInitialized) headsUpAnimator else null,
- )
+ stackScrollAlgorithm =
+ StackScrollAlgorithm(
+ context,
+ hostView,
+ if (::headsUpAnimator.isInitialized) headsUpAnimator else null,
+ )
}
private fun isTv(): Boolean {
@@ -450,6 +457,46 @@
}
@Test
+ @EnableFlags(NotificationsHunSharedAnimationValues.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+ fun resetViewStates_hunAnimatingAway_noStatusBarChip_hunTranslatedToTopOfScreen() {
+ val topMargin = 100f
+ ambientState.maxHeadsUpTranslation = 2000f
+ ambientState.stackTopMargin = topMargin.toInt()
+ headsUpAnimator?.stackTopMargin = topMargin.toInt()
+ whenever(notificationRow.intrinsicHeight).thenReturn(100)
+
+ val statusBarHeight = 432
+ kosmos.fakeSystemBarUtilsProxy.fakeStatusBarHeight = statusBarHeight
+ headsUpAnimator!!.updateResources(context)
+
+ whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
+ whenever(notificationRow.hasStatusBarChipDuringHeadsUpAnimation()).thenReturn(false)
+
+ resetViewStates_hunYTranslationIs(
+ expected = -topMargin - stackScrollAlgorithm.mHeadsUpAppearStartAboveScreen
+ )
+ }
+
+ @Test
+ @EnableFlags(NotificationsHunSharedAnimationValues.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+ fun resetViewStates_hunAnimatingAway_withStatusBarChip_hunTranslatedToBottomOfStatusBar() {
+ val topMargin = 100f
+ ambientState.maxHeadsUpTranslation = 2000f
+ ambientState.stackTopMargin = topMargin.toInt()
+ headsUpAnimator?.stackTopMargin = topMargin.toInt()
+ whenever(notificationRow.intrinsicHeight).thenReturn(100)
+
+ val statusBarHeight = 432
+ kosmos.fakeSystemBarUtilsProxy.fakeStatusBarHeight = statusBarHeight
+ headsUpAnimator!!.updateResources(context)
+
+ whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
+ whenever(notificationRow.hasStatusBarChipDuringHeadsUpAnimation()).thenReturn(true)
+
+ resetViewStates_hunYTranslationIs(expected = statusBarHeight - topMargin)
+ }
+
+ @Test
fun resetViewStates_hunAnimatingAway_bottomNotClipped() {
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 cb4642c..f6c031f 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
@@ -26,12 +26,15 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.AnimatorTestRule
import com.android.systemui.res.R
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
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
import com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR
+import com.android.systemui.statusbar.ui.fakeSystemBarUtilsProxy
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
@@ -46,7 +49,6 @@
import org.mockito.Mockito.description
import org.mockito.Mockito.eq
import org.mockito.Mockito.verify
-import org.mockito.kotlin.doNothing
private const val VIEW_HEIGHT = 100
private const val FULL_SHADE_APPEAR_TRANSLATION = 300
@@ -60,6 +62,8 @@
@get:Rule val setFlagsRule = SetFlagsRule()
@get:Rule val animatorTestRule = AnimatorTestRule(this)
+ private val kosmos = testKosmos()
+
private lateinit var stackStateAnimator: StackStateAnimator
private lateinit var headsUpAnimator: HeadsUpAnimator
private val stackScroller: NotificationStackScrollLayout = mock()
@@ -80,13 +84,14 @@
whenever(view.viewState).thenReturn(viewState)
if (NotificationsHunSharedAnimationValues.isEnabled) {
- headsUpAnimator = HeadsUpAnimator(context)
+ headsUpAnimator = HeadsUpAnimator(context, kosmos.fakeSystemBarUtilsProxy)
}
- stackStateAnimator = StackStateAnimator(
- mContext,
- stackScroller,
- if (::headsUpAnimator.isInitialized) headsUpAnimator else null,
- )
+ stackStateAnimator =
+ StackStateAnimator(
+ mContext,
+ stackScroller,
+ if (::headsUpAnimator.isInitialized) headsUpAnimator else null,
+ )
}
@Test
@@ -134,6 +139,62 @@
}
@Test
+ @EnableFlags(NotificationsHunSharedAnimationValues.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+ fun startAnimationForEvents_headsUpFromTop_andHasStatusBarChipFalse() {
+ val statusBarHeight = 156
+ val topMargin = 50f
+ val expectedStartY = -topMargin - HEADS_UP_ABOVE_SCREEN
+
+ headsUpAnimator.stackTopMargin = topMargin.toInt()
+ kosmos.fakeSystemBarUtilsProxy.fakeStatusBarHeight = statusBarHeight
+ headsUpAnimator.updateResources(context)
+
+ val event = AnimationEvent(view, AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR)
+ event.headsUpHasStatusBarChip = false
+
+ 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
+ @EnableFlags(NotificationsHunSharedAnimationValues.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+ fun startAnimationForEvents_headsUpFromTop_andHasStatusBarChipTrue() {
+ val statusBarHeight = 156
+ val topMargin = 50f
+ val expectedStartY = statusBarHeight - topMargin
+
+ headsUpAnimator!!.stackTopMargin = topMargin.toInt()
+ kosmos.fakeSystemBarUtilsProxy.fakeStatusBarHeight = statusBarHeight
+ headsUpAnimator!!.updateResources(context)
+
+ val event = AnimationEvent(view, AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR)
+ event.headsUpHasStatusBarChip = true
+
+ stackStateAnimator.startAnimationForEvents(arrayListOf(event), 0)
+
+ verify(view).setFinalActualHeight(VIEW_HEIGHT)
+ verify(view, description("should animate below status bar")).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
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
similarity index 87%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
index 31f8590..46430af 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
@@ -70,6 +70,7 @@
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -83,6 +84,7 @@
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -155,7 +157,7 @@
testScope = kosmos.testScope
shadeViewStateProvider = TestShadeViewStateProvider()
- Mockito.`when`(
+ whenever(
kosmos.mockStatusBarContentInsetsProvider
.getStatusBarContentInsetsForCurrentRotation()
)
@@ -163,9 +165,9 @@
MockitoAnnotations.initMocks(this)
- Mockito.`when`(iconManagerFactory.create(ArgumentMatchers.any(), ArgumentMatchers.any()))
+ whenever(iconManagerFactory.create(ArgumentMatchers.any(), ArgumentMatchers.any()))
.thenReturn(iconManager)
- Mockito.`when`(statusBarContentInsetsProviderStore.forDisplay(context.displayId))
+ whenever(statusBarContentInsetsProviderStore.forDisplay(context.displayId))
.thenReturn(kosmos.mockStatusBarContentInsetsProvider)
allowTestableLooperAsMainThread()
looper.runWithLooper {
@@ -174,7 +176,7 @@
LayoutInflater.from(mContext).inflate(R.layout.keyguard_status_bar, null)
as KeyguardStatusBarView
)
- Mockito.`when`(keyguardStatusBarView.getDisplay()).thenReturn(mContext.display)
+ whenever(keyguardStatusBarView.display).thenReturn(mContext.display)
}
controller = createController()
@@ -404,14 +406,14 @@
fun updateViewState_alphaAndVisibilityGiven_viewUpdated() {
// Verify the initial values so we know the method triggers changes.
Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(1f)
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
val newAlpha = 0.5f
val newVisibility = View.INVISIBLE
controller.updateViewState(newAlpha, newVisibility)
Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(newAlpha)
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(newVisibility)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(newVisibility)
}
@Test
@@ -423,7 +425,7 @@
controller.updateViewState(1f, View.VISIBLE)
// Since we're disabled, we stay invisible
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
}
@Test
@@ -444,15 +446,15 @@
fun updateViewState_bypassEnabledAndShouldListenForFace_viewHidden() {
controller.onViewAttached()
updateStateToKeyguard()
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
- Mockito.`when`(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(true)
- Mockito.`when`(keyguardBypassController.bypassEnabled).thenReturn(true)
+ whenever(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(true)
+ whenever(keyguardBypassController.bypassEnabled).thenReturn(true)
onFinishedGoingToSleep()
controller.updateViewState()
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
}
@Test
@@ -461,13 +463,13 @@
controller.onViewAttached()
updateStateToKeyguard()
- Mockito.`when`(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(true)
- Mockito.`when`(keyguardBypassController.bypassEnabled).thenReturn(false)
+ whenever(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(true)
+ whenever(keyguardBypassController.bypassEnabled).thenReturn(false)
onFinishedGoingToSleep()
controller.updateViewState()
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
}
@Test
@@ -476,13 +478,13 @@
controller.onViewAttached()
updateStateToKeyguard()
- Mockito.`when`(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(false)
- Mockito.`when`(keyguardBypassController.bypassEnabled).thenReturn(true)
+ whenever(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(false)
+ whenever(keyguardBypassController.bypassEnabled).thenReturn(true)
onFinishedGoingToSleep()
controller.updateViewState()
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
}
@Test
@@ -495,7 +497,7 @@
controller.updateViewState()
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
}
@Test
@@ -508,7 +510,7 @@
controller.updateViewState()
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
}
@Test
@@ -520,7 +522,7 @@
controller.updateViewState()
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
}
@Test
@@ -532,7 +534,7 @@
controller.updateViewState()
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
}
@Test
@@ -544,7 +546,7 @@
controller.updateViewState()
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
}
@Test
@@ -556,7 +558,7 @@
controller.updateViewState()
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
}
@Test
@@ -568,7 +570,7 @@
controller.setDozing(true)
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
}
@Test
@@ -580,7 +582,7 @@
controller.setDozing(false)
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
}
@Test
@@ -595,7 +597,7 @@
controller.updateViewState()
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.GONE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.GONE)
Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(0.456f)
}
@@ -611,7 +613,7 @@
controller.updateViewState(0.789f, View.VISIBLE)
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.GONE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.GONE)
Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(0.456f)
}
@@ -635,13 +637,13 @@
controller.init()
controller.onViewAttached()
updateStateToKeyguard()
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
controller.setDozing(true)
// setDozing(true) should typically cause the view to hide. But since the flag is on, we
// should ignore these set dozing calls and stay the same visibility.
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
}
@Test
@@ -679,7 +681,7 @@
shadeViewStateProvider.setShouldHeadsUpBeVisible(true)
controller.updateForHeadsUp(/* animate= */ false)
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
}
@Test
@@ -695,7 +697,7 @@
shadeViewStateProvider.setShouldHeadsUpBeVisible(false)
controller.updateForHeadsUp(/* animate= */ false)
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
}
@Test
@@ -728,7 +730,7 @@
val str = mContext.getString(com.android.internal.R.string.status_bar_volume)
// GIVEN the setting is off
- Mockito.`when`(secureSettings.getInt(Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON, 0))
+ whenever(secureSettings.getInt(Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON, 0))
.thenReturn(0)
// WHEN CollapsedStatusBarFragment builds the blocklist
@@ -744,7 +746,7 @@
val str = mContext.getString(com.android.internal.R.string.status_bar_volume)
// GIVEN the setting is ON
- Mockito.`when`(
+ whenever(
secureSettings.getIntForUser(
Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON,
0,
@@ -779,42 +781,52 @@
controller.onViewAttached()
updateStateToKeyguard()
setDisableSystemInfo(true)
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
controller.animateKeyguardStatusBarIn()
// Since we're disabled, we don't actually animate in and stay invisible
- Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+ assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
}
@Test
fun animateToGlanceableHub_affectsAlpha() =
testScope.runTest {
- controller.init()
- val transitionAlphaAmount = .5f
- ViewUtils.attachView(keyguardStatusBarView)
- looper.processAllMessages()
- updateStateToKeyguard()
- kosmos.fakeCommunalSceneRepository.snapToScene(CommunalScenes.Communal)
- runCurrent()
- controller.updateCommunalAlphaTransition(transitionAlphaAmount)
- Truth.assertThat(keyguardStatusBarView.getAlpha()).isEqualTo(transitionAlphaAmount)
+ try {
+ controller.init()
+ val transitionAlphaAmount = .5f
+ ViewUtils.attachView(keyguardStatusBarView)
+
+ looper.processAllMessages()
+ updateStateToKeyguard()
+ kosmos.fakeCommunalSceneRepository.snapToScene(CommunalScenes.Communal)
+ runCurrent()
+ controller.updateCommunalAlphaTransition(transitionAlphaAmount)
+ assertThat(keyguardStatusBarView.getAlpha()).isEqualTo(transitionAlphaAmount)
+ } finally {
+ ViewUtils.detachView(keyguardStatusBarView)
+ }
}
@Test
fun animateToGlanceableHub_alphaResetOnCommunalNotShowing() =
testScope.runTest {
- controller.init()
- val transitionAlphaAmount = .5f
- ViewUtils.attachView(keyguardStatusBarView)
- looper.processAllMessages()
- updateStateToKeyguard()
- kosmos.fakeCommunalSceneRepository.snapToScene(CommunalScenes.Communal)
- runCurrent()
- controller.updateCommunalAlphaTransition(transitionAlphaAmount)
- kosmos.fakeCommunalSceneRepository.snapToScene(CommunalScenes.Blank)
- runCurrent()
- Truth.assertThat(keyguardStatusBarView.getAlpha()).isNotEqualTo(transitionAlphaAmount)
+ try {
+ controller.init()
+ val transitionAlphaAmount = .5f
+ ViewUtils.attachView(keyguardStatusBarView)
+
+ looper.processAllMessages()
+ updateStateToKeyguard()
+ kosmos.fakeCommunalSceneRepository.snapToScene(CommunalScenes.Communal)
+ runCurrent()
+ controller.updateCommunalAlphaTransition(transitionAlphaAmount)
+ kosmos.fakeCommunalSceneRepository.snapToScene(CommunalScenes.Blank)
+ runCurrent()
+ assertThat(keyguardStatusBarView.getAlpha()).isNotEqualTo(transitionAlphaAmount)
+ } finally {
+ ViewUtils.detachView(keyguardStatusBarView)
+ }
}
/**
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/MultiDisplayAutoHideControllerStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/MultiDisplayAutoHideControllerStoreTest.kt
index d163726..b9d9a53 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/MultiDisplayAutoHideControllerStoreTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/MultiDisplayAutoHideControllerStoreTest.kt
@@ -62,11 +62,11 @@
}
@Test
- fun displayRemoved_stopsInstance() =
+ fun systemDecorationRemovedEvent_stopsInstance() =
testScope.runTest {
val instance = underTest.forDisplay(DEFAULT_DISPLAY)!!
- fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)
+ fakeDisplayRepository.triggerRemoveSystemDecorationEvent(DEFAULT_DISPLAY)
verify(instance).stop()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
index 58856d9..ffde34e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
@@ -48,7 +48,6 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.model.SysUiState;
-import com.android.systemui.settings.FakeDisplayTracker;
import org.junit.Before;
import org.junit.Rule;
@@ -84,8 +83,7 @@
public void setup() {
MockitoAnnotations.initMocks(this);
KosmosJavaAdapter kosmos = new KosmosJavaAdapter(this);
- FakeDisplayTracker displayTracker = new FakeDisplayTracker(mContext);
- mSysUiState = new SysUiState(displayTracker, kosmos.getSceneContainerPlugin());
+ mSysUiState = kosmos.getSysuiState();
mDependency.injectTestDependency(BroadcastDispatcher.class, mBroadcastDispatcher);
when(mDelegate.getBackAnimationSpec(ArgumentMatchers.any()))
.thenReturn(mock(BackAnimationSpec.class));
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherKairosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherKairosTest.kt
new file mode 100644
index 0000000..80f4b2c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherKairosTest.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2024 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.pipeline.mobile.data.repository
+
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.demoModeController
+import com.android.systemui.demomode.DemoMode
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.KairosTestScope
+import com.android.systemui.kairos.runKairosTest
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.validMobileEvent
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.MutableStateFlow
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
+
+/**
+ * The switcher acts as a dispatcher to either the `prod` or `demo` versions of the repository
+ * interface it's switching on. These tests just need to verify that the entire interface properly
+ * switches over when the value of `demoMode` changes
+ */
+@OptIn(ExperimentalKairosApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MobileRepositorySwitcherKairosTest : SysuiTestCase() {
+ private val kosmos =
+ testKosmos().apply {
+ useUnconfinedTestDispatcher()
+ demoModeController.stub {
+ // Never start in demo mode
+ on { isInDemoMode } doReturn false
+ }
+ wifiDataSource.stub { on { wifiEvents } doReturn MutableStateFlow(null) }
+ }
+
+ private val Kosmos.underTest
+ get() = mobileRepositorySwitcherKairos
+
+ private val Kosmos.realRepo
+ get() = mobileConnectionsRepositoryKairosImpl
+
+ private fun runTest(block: suspend KairosTestScope.() -> Unit) =
+ kosmos.run { runKairosTest { block() } }
+
+ @Test
+ fun activeRepoMatchesDemoModeSetting() = runTest {
+ demoModeController.stub { on { isInDemoMode } doReturn false }
+
+ val latest by underTest.activeRepo.collectLastValue()
+
+ assertThat(latest).isEqualTo(realRepo)
+
+ startDemoMode()
+
+ assertThat(latest).isInstanceOf(DemoMobileConnectionsRepositoryKairos::class.java)
+
+ finishDemoMode()
+
+ assertThat(latest).isEqualTo(realRepo)
+ }
+
+ @Test
+ fun subscriptionListUpdatesWhenDemoModeChanges() = runTest {
+ demoModeController.stub { on { isInDemoMode } doReturn false }
+
+ subscriptionManager.stub {
+ on { completeActiveSubscriptionInfoList } doReturn listOf(SUB_1, SUB_2)
+ }
+
+ val latest by underTest.subscriptions.collectLastValue()
+
+ // The real subscriptions has 2 subs
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ assertThat(latest).isEqualTo(listOf(MODEL_1, MODEL_2))
+
+ // Demo mode turns on, and we should see only the demo subscriptions
+ startDemoMode()
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(validMobileEvent(subId = 3))
+
+ // Demo mobile connections repository makes arbitrarily-formed subscription info
+ // objects, so just validate the data we care about
+ assertThat(latest).hasSize(1)
+ assertThat(latest!!.first().subscriptionId).isEqualTo(3)
+
+ finishDemoMode()
+
+ assertThat(latest).isEqualTo(listOf(MODEL_1, MODEL_2))
+ }
+
+ private fun KairosTestScope.startDemoMode() {
+ demoModeController.stub { on { isInDemoMode } doReturn true }
+ getDemoModeCallback().onDemoModeStarted()
+ }
+
+ private fun KairosTestScope.finishDemoMode() {
+ demoModeController.stub { on { isInDemoMode } doReturn false }
+ getDemoModeCallback().onDemoModeFinished()
+ }
+
+ private fun KairosTestScope.getSubscriptionCallback():
+ SubscriptionManager.OnSubscriptionsChangedListener =
+ argumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener>()
+ .apply {
+ verify(subscriptionManager).addOnSubscriptionsChangedListener(any(), capture())
+ }
+ .lastValue
+
+ private fun KairosTestScope.getDemoModeCallback(): DemoMode =
+ argumentCaptor<DemoMode>()
+ .apply { verify(demoModeController).addCallback(capture()) }
+ .lastValue
+
+ companion object {
+ private const val SUB_1_ID = 1
+ private const val SUB_1_NAME = "Carrier $SUB_1_ID"
+ private val SUB_1: SubscriptionInfo = mock {
+ on { subscriptionId } doReturn SUB_1_ID
+ on { carrierName } doReturn SUB_1_NAME
+ on { profileClass } doReturn PROFILE_CLASS_UNSET
+ }
+ private val MODEL_1 =
+ SubscriptionModel(
+ subscriptionId = SUB_1_ID,
+ carrierName = SUB_1_NAME,
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+
+ private const val SUB_2_ID = 2
+ private const val SUB_2_NAME = "Carrier $SUB_2_ID"
+ private val SUB_2: SubscriptionInfo = mock {
+ on { subscriptionId } doReturn SUB_2_ID
+ on { carrierName } doReturn SUB_2_NAME
+ on { profileClass } doReturn PROFILE_CLASS_UNSET
+ }
+ private val MODEL_2 =
+ SubscriptionModel(
+ subscriptionId = SUB_2_ID,
+ carrierName = SUB_2_NAME,
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionKairosParameterizedTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionKairosParameterizedTest.kt
new file mode 100644
index 0000000..99cc93d
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionKairosParameterizedTest.kt
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2024 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.pipeline.mobile.data.repository.demo
+
+import android.telephony.Annotation
+import android.telephony.TelephonyManager
+import android.telephony.TelephonyManager.DATA_ACTIVITY_NONE
+import androidx.test.filters.SmallTest
+import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.KairosTestScope
+import com.android.systemui.kairos.kairos
+import com.android.systemui.kairos.map
+import com.android.systemui.kairos.runKairosTest
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demoMobileConnectionsRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demoModeMobileConnectionDataSourceKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fake
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.wifiDataSource
+import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.MutableStateFlow
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.stub
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+
+/**
+ * Parameterized test for all of the common values of [FakeNetworkEventModel]. This test simply
+ * verifies that passing the given model to [DemoMobileConnectionsRepositoryKairos] results in the
+ * correct flows emitting from the given connection.
+ */
+@OptIn(ExperimentalKairosApi::class)
+@SmallTest
+@RunWith(ParameterizedAndroidJunit4::class)
+internal class DemoMobileConnectionKairosParameterizedTest(private val testCase: TestCase) :
+ SysuiTestCase() {
+
+ private val Kosmos.fakeWifiEventFlow by Fixture { MutableStateFlow<FakeWifiEventModel?>(null) }
+
+ private val kosmos =
+ testKosmos().apply {
+ useUnconfinedTestDispatcher()
+ wifiDataSource.stub { on { wifiEvents } doReturn fakeWifiEventFlow }
+ }
+
+ private fun runTest(block: suspend KairosTestScope.() -> Unit) =
+ kosmos.run { runKairosTest { block() } }
+
+ @Test
+ fun demoNetworkData() = runTest {
+ val underTest by
+ demoMobileConnectionsRepositoryKairos.mobileConnectionsBySubId
+ .map { it[subId] }
+ .collectLastValue()
+ val networkModel =
+ FakeNetworkEventModel.Mobile(
+ level = testCase.level,
+ dataType = testCase.dataType,
+ subId = testCase.subId,
+ carrierId = testCase.carrierId,
+ inflateStrength = testCase.inflateStrength,
+ activity = testCase.activity,
+ carrierNetworkChange = testCase.carrierNetworkChange,
+ roaming = testCase.roaming,
+ name = "demo name",
+ slice = testCase.slice,
+ )
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(networkModel)
+ assertConnection(underTest!!, networkModel)
+ }
+
+ private suspend fun KairosTestScope.assertConnection(
+ conn: DemoMobileConnectionRepositoryKairos,
+ model: FakeNetworkEventModel,
+ ) {
+ when (model) {
+ is FakeNetworkEventModel.Mobile -> {
+ kairos.transact {
+ assertThat(conn.subId).isEqualTo(model.subId)
+ assertThat(conn.cdmaLevel.sample()).isEqualTo(model.level)
+ assertThat(conn.primaryLevel.sample()).isEqualTo(model.level)
+ assertThat(conn.dataActivityDirection.sample())
+ .isEqualTo(
+ (model.activity ?: DATA_ACTIVITY_NONE).toMobileDataActivityModel()
+ )
+ assertThat(conn.carrierNetworkChangeActive.sample())
+ .isEqualTo(model.carrierNetworkChange)
+ assertThat(conn.isRoaming.sample()).isEqualTo(model.roaming)
+ assertThat(conn.networkName.sample())
+ .isEqualTo(NetworkNameModel.IntentDerived(model.name))
+ assertThat(conn.carrierName.sample())
+ .isEqualTo(
+ NetworkNameModel.SubscriptionDerived("${model.name} ${model.subId}")
+ )
+ assertThat(conn.hasPrioritizedNetworkCapabilities.sample())
+ .isEqualTo(model.slice)
+ assertThat(conn.isNonTerrestrial.sample()).isEqualTo(model.ntn)
+
+ // TODO(b/261029387): check these once we start handling them
+ assertThat(conn.isEmergencyOnly.sample()).isFalse()
+ assertThat(conn.isGsm.sample()).isFalse()
+ assertThat(conn.dataConnectionState.sample())
+ .isEqualTo(DataConnectionState.Connected)
+ }
+ }
+ // MobileDisabled isn't combinatorial in nature, and is tested in
+ // DemoMobileConnectionsRepositoryTest.kt
+ else -> {}
+ }
+ }
+
+ /** Matches [FakeNetworkEventModel] */
+ internal data class TestCase(
+ val level: Int,
+ val dataType: SignalIcon.MobileIconGroup,
+ val subId: Int,
+ val carrierId: Int,
+ val inflateStrength: Boolean,
+ @Annotation.DataActivityType val activity: Int,
+ val carrierNetworkChange: Boolean,
+ val roaming: Boolean,
+ val name: String,
+ val slice: Boolean,
+ val ntn: Boolean,
+ ) {
+ override fun toString(): String {
+ return "INPUT(level=$level, " +
+ "dataType=${dataType.name}, " +
+ "subId=$subId, " +
+ "carrierId=$carrierId, " +
+ "inflateStrength=$inflateStrength, " +
+ "activity=$activity, " +
+ "carrierNetworkChange=$carrierNetworkChange, " +
+ "roaming=$roaming, " +
+ "name=$name," +
+ "slice=$slice" +
+ "ntn=$ntn)"
+ }
+
+ // Convenience for iterating test data and creating new cases
+ fun modifiedBy(
+ level: Int? = null,
+ dataType: SignalIcon.MobileIconGroup? = null,
+ subId: Int? = null,
+ carrierId: Int? = null,
+ inflateStrength: Boolean? = null,
+ @Annotation.DataActivityType activity: Int? = null,
+ carrierNetworkChange: Boolean? = null,
+ roaming: Boolean? = null,
+ name: String? = null,
+ slice: Boolean? = null,
+ ntn: Boolean? = null,
+ ): TestCase =
+ TestCase(
+ level = level ?: this.level,
+ dataType = dataType ?: this.dataType,
+ subId = subId ?: this.subId,
+ carrierId = carrierId ?: this.carrierId,
+ inflateStrength = inflateStrength ?: this.inflateStrength,
+ activity = activity ?: this.activity,
+ carrierNetworkChange = carrierNetworkChange ?: this.carrierNetworkChange,
+ roaming = roaming ?: this.roaming,
+ name = name ?: this.name,
+ slice = slice ?: this.slice,
+ ntn = ntn ?: this.ntn,
+ )
+ }
+
+ companion object {
+ private val subId = 1
+
+ private val booleanList = listOf(true, false)
+ private val levels = listOf(0, 1, 2, 3)
+ private val dataTypes =
+ listOf(
+ TelephonyIcons.THREE_G,
+ TelephonyIcons.LTE,
+ TelephonyIcons.FOUR_G,
+ TelephonyIcons.NR_5G,
+ TelephonyIcons.NR_5G_PLUS,
+ )
+ private val carrierIds = listOf(1, 10, 100)
+ private val inflateStrength = booleanList
+ private val activity =
+ listOf(
+ TelephonyManager.DATA_ACTIVITY_NONE,
+ TelephonyManager.DATA_ACTIVITY_IN,
+ TelephonyManager.DATA_ACTIVITY_OUT,
+ TelephonyManager.DATA_ACTIVITY_INOUT,
+ )
+ private val carrierNetworkChange = booleanList
+ // false first so the base case doesn't have roaming set (more common)
+ private val roaming = listOf(false, true)
+ private val names = listOf("name 1", "name 2")
+ private val slice = listOf(false, true)
+ private val ntn = listOf(false, true)
+
+ @Parameters(name = "{0}") @JvmStatic fun data() = testData()
+
+ /**
+ * Generate some test data. For the sake of convenience, we'll parameterize only non-null
+ * network event data. So given the lists of test data:
+ * ```
+ * list1 = [1, 2, 3]
+ * list2 = [false, true]
+ * list3 = [a, b, c]
+ * ```
+ *
+ * We'll generate test cases for:
+ *
+ * Test (1, false, a) Test (2, false, a) Test (3, false, a) Test (1, true, a) Test (1,
+ * false, b) Test (1, false, c)
+ *
+ * NOTE: this is not a combinatorial product of all of the possible sets of parameters.
+ * Since this test is built to exercise demo mode, the general approach is to define a
+ * fully-formed "base case", and from there to make sure to use every valid parameter once,
+ * by defining the rest of the test cases against the base case. Specific use-cases can be
+ * added to the non-parameterized test, or manually below the generated test cases.
+ */
+ private fun testData(): List<TestCase> {
+ val testSet = mutableSetOf<TestCase>()
+
+ val baseCase =
+ TestCase(
+ levels.first(),
+ dataTypes.first(),
+ subId,
+ carrierIds.first(),
+ inflateStrength.first(),
+ activity.first(),
+ carrierNetworkChange.first(),
+ roaming.first(),
+ names.first(),
+ slice.first(),
+ ntn.first(),
+ )
+
+ val tail =
+ sequenceOf(
+ levels.map { baseCase.modifiedBy(level = it) },
+ dataTypes.map { baseCase.modifiedBy(dataType = it) },
+ carrierIds.map { baseCase.modifiedBy(carrierId = it) },
+ inflateStrength.map { baseCase.modifiedBy(inflateStrength = it) },
+ activity.map { baseCase.modifiedBy(activity = it) },
+ carrierNetworkChange.map { baseCase.modifiedBy(carrierNetworkChange = it) },
+ roaming.map { baseCase.modifiedBy(roaming = it) },
+ names.map { baseCase.modifiedBy(name = it) },
+ slice.map { baseCase.modifiedBy(slice = it) },
+ ntn.map { baseCase.modifiedBy(ntn = it) },
+ )
+ .flatten()
+
+ testSet.add(baseCase)
+ tail.toCollection(testSet)
+
+ return testSet.toList()
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryKairosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryKairosTest.kt
new file mode 100644
index 0000000..503d561
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryKairosTest.kt
@@ -0,0 +1,465 @@
+/*
+ * Copyright (C) 2024 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.pipeline.mobile.data.repository.demo
+
+import android.telephony.TelephonyManager.DATA_ACTIVITY_INOUT
+import android.telephony.TelephonyManager.DATA_ACTIVITY_NONE
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.KairosTestScope
+import com.android.systemui.kairos.kairos
+import com.android.systemui.kairos.map
+import com.android.systemui.kairos.runKairosTest
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.MobileDisabled
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demoMobileConnectionsRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demoModeMobileConnectionDataSourceKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fake
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.wifiDataSource
+import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert
+import kotlinx.coroutines.flow.MutableStateFlow
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.stub
+
+@OptIn(ExperimentalKairosApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DemoMobileConnectionsRepositoryKairosTest : SysuiTestCase() {
+
+ private val Kosmos.fakeWifiEventFlow by
+ Kosmos.Fixture { MutableStateFlow<FakeWifiEventModel?>(null) }
+
+ private val Kosmos.underTest
+ get() = demoMobileConnectionsRepositoryKairos
+
+ private val kosmos =
+ testKosmos().apply {
+ useUnconfinedTestDispatcher()
+ wifiDataSource.stub { on { wifiEvents } doReturn fakeWifiEventFlow }
+ }
+
+ private fun runTest(block: suspend KairosTestScope.() -> Unit) =
+ kosmos.run { runKairosTest { block() } }
+
+ @Test
+ fun isDefault_defaultsToTrue() = runTest {
+ underTest
+ val isDefault = kairos.transact { underTest.mobileIsDefault.sample() }
+ assertThat(isDefault).isTrue()
+ }
+
+ @Test
+ fun validated_defaultsToTrue() = runTest {
+ underTest
+ val isValidated = kairos.transact { underTest.defaultConnectionIsValidated.sample() }
+ assertThat(isValidated).isTrue()
+ }
+
+ @Test
+ fun networkEvent_createNewSubscription() = runTest {
+ val latest by underTest.subscriptions.collectLastValue()
+
+ assertThat(latest).isEmpty()
+
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(validMobileEvent(subId = 1))
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!!.first().subscriptionId).isEqualTo(1)
+ }
+
+ @Test
+ fun wifiCarrierMergedEvent_createNewSubscription() = runTest {
+ val latest by underTest.subscriptions.collectLastValue()
+
+ assertThat(latest).isEmpty()
+
+ fakeWifiEventFlow.value = validCarrierMergedEvent(subId = 5)
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!!.first().subscriptionId).isEqualTo(5)
+ }
+
+ @Test
+ fun networkEvent_reusesSubscriptionWhenSameId() = runTest {
+ val latest by underTest.subscriptions.collectLastValue()
+
+ assertThat(latest).isEmpty()
+
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(
+ validMobileEvent(subId = 1, level = 1)
+ )
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!!.first().subscriptionId).isEqualTo(1)
+
+ // Second network event comes in with the same subId, does not create a new subscription
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(
+ validMobileEvent(subId = 1, level = 2)
+ )
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!!.first().subscriptionId).isEqualTo(1)
+ }
+
+ @Test
+ fun wifiCarrierMergedEvent_reusesSubscriptionWhenSameId() = runTest {
+ val latest by underTest.subscriptions.collectLastValue()
+
+ assertThat(latest).isEmpty()
+
+ fakeWifiEventFlow.value = validCarrierMergedEvent(subId = 5, level = 1)
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!!.first().subscriptionId).isEqualTo(5)
+
+ // Second network event comes in with the same subId, does not create a new subscription
+ fakeWifiEventFlow.value = validCarrierMergedEvent(subId = 5, level = 2)
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!!.first().subscriptionId).isEqualTo(5)
+ }
+
+ @Test
+ fun multipleSubscriptions() = runTest {
+ val latest by underTest.subscriptions.collectLastValue()
+
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(validMobileEvent(subId = 1))
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(validMobileEvent(subId = 2))
+
+ assertThat(latest).hasSize(2)
+ }
+
+ @Test
+ fun mobileSubscriptionAndCarrierMergedSubscription() = runTest {
+ val latest by underTest.subscriptions.collectLastValue()
+
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(validMobileEvent(subId = 1))
+ fakeWifiEventFlow.value = validCarrierMergedEvent(subId = 5)
+
+ assertThat(latest).hasSize(2)
+ }
+
+ @Test
+ fun multipleMobileSubscriptionsAndCarrierMergedSubscription() = runTest {
+ val latest by underTest.subscriptions.collectLastValue()
+
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(validMobileEvent(subId = 1))
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(validMobileEvent(subId = 2))
+ fakeWifiEventFlow.value = validCarrierMergedEvent(subId = 3)
+
+ assertThat(latest).hasSize(3)
+ }
+
+ @Test
+ fun mobileDisabledEvent_disablesConnection_subIdSpecified_singleConn() = runTest {
+ val latest by underTest.subscriptions.collectLastValue()
+
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(
+ validMobileEvent(subId = 1, level = 1)
+ )
+
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(MobileDisabled(subId = 1))
+
+ assertThat(latest).hasSize(0)
+ }
+
+ @Test
+ fun mobileDisabledEvent_disablesConnection_subIdNotSpecified_singleConn() = runTest {
+ val latest by underTest.subscriptions.collectLastValue()
+
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(
+ validMobileEvent(subId = 1, level = 1)
+ )
+
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(
+ MobileDisabled(subId = null)
+ )
+
+ assertThat(latest).hasSize(0)
+ }
+
+ @Test
+ fun mobileDisabledEvent_disablesConnection_subIdSpecified_multipleConn() = runTest {
+ val latest by underTest.subscriptions.collectLastValue()
+
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(
+ validMobileEvent(subId = 1, level = 1)
+ )
+
+ assertThat(latest).hasSize(1)
+
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(
+ validMobileEvent(subId = 2, level = 1)
+ )
+
+ assertThat(latest).hasSize(2)
+
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(MobileDisabled(subId = 2))
+
+ assertThat(latest).hasSize(1)
+ }
+
+ @Test
+ fun mobileDisabledEvent_subIdNotSpecified_multipleConn_ignoresCommand() = runTest {
+ val latest by underTest.subscriptions.collectLastValue()
+
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(
+ validMobileEvent(subId = 1, level = 1)
+ )
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(
+ validMobileEvent(subId = 2, level = 1)
+ )
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(
+ MobileDisabled(subId = null)
+ )
+
+ assertThat(latest).hasSize(2)
+ }
+
+ @Test
+ fun wifiNetworkUpdatesToDisabled_carrierMergedConnectionRemoved() = runTest {
+ val latest by underTest.subscriptions.collectLastValue()
+
+ fakeWifiEventFlow.value = validCarrierMergedEvent(subId = 1)
+
+ assertThat(latest).hasSize(1)
+
+ fakeWifiEventFlow.value = FakeWifiEventModel.WifiDisabled
+
+ assertThat(latest).isEmpty()
+ }
+
+ @Test
+ fun wifiNetworkUpdatesToActive_carrierMergedConnectionRemoved() = runTest {
+ val latest by underTest.subscriptions.collectLastValue()
+
+ fakeWifiEventFlow.value = validCarrierMergedEvent(subId = 1)
+
+ assertThat(latest).hasSize(1)
+
+ fakeWifiEventFlow.value =
+ FakeWifiEventModel.Wifi(level = 1, activity = 0, ssid = null, validated = true)
+
+ assertThat(latest).isEmpty()
+ }
+
+ @Test
+ fun mobileSubUpdatesToCarrierMerged_onlyOneConnection() = runTest {
+ val latestSubsList by underTest.subscriptions.collectLastValue()
+ val connections by underTest.mobileConnectionsBySubId.map { it.values }.collectLastValue()
+
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(
+ validMobileEvent(subId = 3, level = 2)
+ )
+ assertThat(latestSubsList).hasSize(1)
+
+ val carrierMergedEvent = validCarrierMergedEvent(subId = 3, level = 1)
+ fakeWifiEventFlow.value = carrierMergedEvent
+ assertThat(latestSubsList).hasSize(1)
+ val connection = connections!!.find { it.subId == 3 }!!
+ assertCarrierMergedConnection(connection, carrierMergedEvent)
+ }
+
+ @Test
+ fun mobileSubUpdatesToCarrierMergedThenBack_hasOldMobileData() = runTest {
+ val latestSubsList by underTest.subscriptions.collectLastValue()
+ val connections by underTest.mobileConnectionsBySubId.map { it.values }.collectLastValue()
+
+ val mobileEvent = validMobileEvent(subId = 3, level = 2)
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(mobileEvent)
+ assertThat(latestSubsList).hasSize(1)
+
+ val carrierMergedEvent = validCarrierMergedEvent(subId = 3, level = 1)
+ fakeWifiEventFlow.value = carrierMergedEvent
+ assertThat(latestSubsList).hasSize(1)
+ var connection = connections!!.find { it.subId == 3 }!!
+ assertCarrierMergedConnection(connection, carrierMergedEvent)
+
+ // WHEN the carrier merged is removed
+ fakeWifiEventFlow.value =
+ FakeWifiEventModel.Wifi(level = 4, activity = 0, ssid = null, validated = true)
+
+ assertThat(latestSubsList).hasSize(1)
+ assertThat(connections).hasSize(1)
+
+ // THEN the subId=3 connection goes back to the mobile information
+ connection = connections!!.find { it.subId == 3 }!!
+ assertConnection(connection, mobileEvent)
+ }
+
+ @Test
+ fun demoConnection_singleSubscription() = runTest {
+ var currentEvent: FakeNetworkEventModel = validMobileEvent(subId = 1)
+ val connections by underTest.mobileConnectionsBySubId.map { it.values }.collectLastValue()
+
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(currentEvent)
+
+ assertThat(connections).hasSize(1)
+ val connection1 = connections!!.first()
+
+ assertConnection(connection1, currentEvent)
+
+ // Exercise the whole api
+
+ currentEvent = validMobileEvent(subId = 1, level = 2)
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(currentEvent)
+ assertConnection(connection1, currentEvent)
+ }
+
+ @Test
+ fun demoConnection_twoConnections_updateSecond_noAffectOnFirst() = runTest {
+ var currentEvent1 = validMobileEvent(subId = 1)
+ var connection1: DemoMobileConnectionRepositoryKairos? = null
+ var currentEvent2 = validMobileEvent(subId = 2)
+ var connection2: DemoMobileConnectionRepositoryKairos? = null
+ val connections by underTest.mobileConnectionsBySubId.map { it.values }.collectLastValue()
+
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(currentEvent1)
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(currentEvent2)
+ assertThat(connections).hasSize(2)
+ connections!!.forEach {
+ when (it.subId) {
+ 1 -> connection1 = it
+ 2 -> connection2 = it
+ else -> Assert.fail("Unexpected subscription")
+ }
+ }
+
+ assertConnection(connection1!!, currentEvent1)
+ assertConnection(connection2!!, currentEvent2)
+
+ // WHEN the event changes for connection 2, it updates, and connection 1 stays the same
+ currentEvent2 = validMobileEvent(subId = 2, activity = DATA_ACTIVITY_INOUT)
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(currentEvent2)
+ assertConnection(connection1!!, currentEvent1)
+ assertConnection(connection2!!, currentEvent2)
+
+ // and vice versa
+ currentEvent1 = validMobileEvent(subId = 1, inflateStrength = true)
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(currentEvent1)
+ assertConnection(connection1!!, currentEvent1)
+ assertConnection(connection2!!, currentEvent2)
+ }
+
+ @Test
+ fun demoConnection_twoConnections_updateCarrierMerged_noAffectOnFirst() = runTest {
+ var currentEvent1 = validMobileEvent(subId = 1)
+ var connection1: DemoMobileConnectionRepositoryKairos? = null
+ var currentEvent2 = validCarrierMergedEvent(subId = 2)
+ var connection2: DemoMobileConnectionRepositoryKairos? = null
+ val connections by underTest.mobileConnectionsBySubId.map { it.values }.collectLastValue()
+
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(currentEvent1)
+ fakeWifiEventFlow.value = currentEvent2
+ assertThat(connections).hasSize(2)
+ connections!!.forEach {
+ when (it.subId) {
+ 1 -> connection1 = it
+ 2 -> connection2 = it
+ else -> Assert.fail("Unexpected subscription")
+ }
+ }
+
+ assertConnection(connection1!!, currentEvent1)
+ assertCarrierMergedConnection(connection2!!, currentEvent2)
+
+ // WHEN the event changes for connection 2, it updates, and connection 1 stays the same
+ currentEvent2 = validCarrierMergedEvent(subId = 2, level = 4)
+ fakeWifiEventFlow.value = currentEvent2
+ assertConnection(connection1!!, currentEvent1)
+ assertCarrierMergedConnection(connection2!!, currentEvent2)
+
+ // and vice versa
+ currentEvent1 = validMobileEvent(subId = 1, inflateStrength = true)
+ demoModeMobileConnectionDataSourceKairos.fake.mobileEvents.emit(currentEvent1)
+ assertConnection(connection1!!, currentEvent1)
+ assertCarrierMergedConnection(connection2!!, currentEvent2)
+ }
+
+ @Test
+ fun demoIsNotInEcmState() = runTest {
+ underTest
+ assertThat(kairos.transact { underTest.isInEcmMode.sample() }).isFalse()
+ }
+
+ private suspend fun KairosTestScope.assertConnection(
+ conn: DemoMobileConnectionRepositoryKairos,
+ model: FakeNetworkEventModel,
+ ) {
+ when (model) {
+ is FakeNetworkEventModel.Mobile -> {
+ kairos.transact {
+ assertThat(conn.subId).isEqualTo(model.subId)
+ assertThat(conn.cdmaLevel.sample()).isEqualTo(model.level)
+ assertThat(conn.primaryLevel.sample()).isEqualTo(model.level)
+ assertThat(conn.dataActivityDirection.sample())
+ .isEqualTo(
+ (model.activity ?: DATA_ACTIVITY_NONE).toMobileDataActivityModel()
+ )
+ assertThat(conn.carrierNetworkChangeActive.sample())
+ .isEqualTo(model.carrierNetworkChange)
+ assertThat(conn.isRoaming.sample()).isEqualTo(model.roaming)
+ assertThat(conn.networkName.sample())
+ .isEqualTo(NetworkNameModel.IntentDerived(model.name))
+ assertThat(conn.carrierName.sample())
+ .isEqualTo(
+ NetworkNameModel.SubscriptionDerived("${model.name} ${model.subId}")
+ )
+ assertThat(conn.hasPrioritizedNetworkCapabilities.sample())
+ .isEqualTo(model.slice)
+ assertThat(conn.isNonTerrestrial.sample()).isEqualTo(model.ntn)
+
+ // TODO(b/261029387) check these once we start handling them
+ assertThat(conn.isEmergencyOnly.sample()).isFalse()
+ assertThat(conn.isGsm.sample()).isFalse()
+ assertThat(conn.dataConnectionState.sample())
+ .isEqualTo(DataConnectionState.Connected)
+ }
+ }
+ else -> {}
+ }
+ }
+
+ private suspend fun KairosTestScope.assertCarrierMergedConnection(
+ conn: DemoMobileConnectionRepositoryKairos,
+ model: FakeWifiEventModel.CarrierMerged,
+ ) {
+ kairos.transact {
+ assertThat(conn.subId).isEqualTo(model.subscriptionId)
+ assertThat(conn.cdmaLevel.sample()).isEqualTo(model.level)
+ assertThat(conn.primaryLevel.sample()).isEqualTo(model.level)
+ assertThat(conn.carrierNetworkChangeActive.sample()).isEqualTo(false)
+ assertThat(conn.isRoaming.sample()).isEqualTo(false)
+ assertThat(conn.isEmergencyOnly.sample()).isFalse()
+ assertThat(conn.isGsm.sample()).isFalse()
+ assertThat(conn.dataConnectionState.sample()).isEqualTo(DataConnectionState.Connected)
+ assertThat(conn.hasPrioritizedNetworkCapabilities.sample()).isFalse()
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryKairosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryKairosTest.kt
new file mode 100644
index 0000000..1838d13
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryKairosTest.kt
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2024 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.pipeline.mobile.data.repository.prod
+
+import android.telephony.TelephonyManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kairos.ActivatedKairosFixture
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.KairosTestScope
+import com.android.systemui.kairos.runKairosTest
+import com.android.systemui.kairos.stateOf
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.log.table.logcatTableLogBuffer
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.fakeWifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.wifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
+
+@OptIn(ExperimentalKairosApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CarrierMergedConnectionRepositoryKairosTest : SysuiTestCase() {
+
+ private val Kosmos.underTest by ActivatedKairosFixture {
+ CarrierMergedConnectionRepositoryKairos(
+ subId = SUB_ID,
+ tableLogBuffer = logcatTableLogBuffer(this),
+ telephonyManager = telephonyManager,
+ wifiRepository = wifiRepository,
+ isInEcmMode = stateOf(false),
+ )
+ }
+
+ private val Kosmos.telephonyManager: TelephonyManager by Fixture {
+ mock {
+ on { subscriptionId } doReturn SUB_ID
+ on { simOperatorName } doReturn ""
+ }
+ }
+
+ private fun runTest(block: suspend KairosTestScope.() -> Unit) =
+ testKosmos().run {
+ useUnconfinedTestDispatcher()
+ runKairosTest { block() }
+ }
+
+ @Test
+ fun inactiveWifi_isDefault() = runTest {
+ val latestConnState by underTest.dataConnectionState.collectLastValue()
+ val latestNetType by underTest.resolvedNetworkType.collectLastValue()
+
+ fakeWifiRepository.setWifiNetwork(WifiNetworkModel.Inactive())
+
+ assertThat(latestConnState).isEqualTo(DataConnectionState.Disconnected)
+ assertThat(latestNetType).isNotEqualTo(ResolvedNetworkType.CarrierMergedNetworkType)
+ }
+
+ @Test
+ fun activeWifi_isDefault() = runTest {
+ val latestConnState by underTest.dataConnectionState.collectLastValue()
+ val latestNetType by underTest.resolvedNetworkType.collectLastValue()
+
+ fakeWifiRepository.setWifiNetwork(WifiNetworkModel.Active.of(level = 1))
+
+ assertThat(latestConnState).isEqualTo(DataConnectionState.Disconnected)
+ assertThat(latestNetType).isNotEqualTo(ResolvedNetworkType.CarrierMergedNetworkType)
+ }
+
+ @Test
+ fun carrierMergedWifi_isValidAndFieldsComeFromWifiNetwork() = runTest {
+ val latest by underTest.primaryLevel.collectLastValue()
+
+ fakeWifiRepository.setIsWifiEnabled(true)
+ fakeWifiRepository.setIsWifiDefault(true)
+
+ fakeWifiRepository.setWifiNetwork(
+ WifiNetworkModel.CarrierMerged.of(subscriptionId = SUB_ID, level = 3)
+ )
+
+ assertThat(latest).isEqualTo(3)
+ }
+
+ @Test
+ fun activity_comesFromWifiActivity() = runTest {
+ val latest by underTest.dataActivityDirection.collectLastValue()
+
+ fakeWifiRepository.setIsWifiEnabled(true)
+ fakeWifiRepository.setIsWifiDefault(true)
+ fakeWifiRepository.setWifiNetwork(
+ WifiNetworkModel.CarrierMerged.of(subscriptionId = SUB_ID, level = 3)
+ )
+ fakeWifiRepository.setWifiActivity(
+ DataActivityModel(hasActivityIn = true, hasActivityOut = false)
+ )
+
+ assertThat(latest!!.hasActivityIn).isTrue()
+ assertThat(latest!!.hasActivityOut).isFalse()
+
+ fakeWifiRepository.setWifiActivity(
+ DataActivityModel(hasActivityIn = false, hasActivityOut = true)
+ )
+
+ assertThat(latest!!.hasActivityIn).isFalse()
+ assertThat(latest!!.hasActivityOut).isTrue()
+ }
+
+ @Test
+ fun carrierMergedWifi_wrongSubId_isDefault() = runTest {
+ val latestLevel by underTest.primaryLevel.collectLastValue()
+ val latestType by underTest.resolvedNetworkType.collectLastValue()
+
+ fakeWifiRepository.setWifiNetwork(
+ WifiNetworkModel.CarrierMerged.of(subscriptionId = SUB_ID + 10, level = 3)
+ )
+
+ assertThat(latestLevel).isNotEqualTo(3)
+ assertThat(latestType).isNotEqualTo(ResolvedNetworkType.CarrierMergedNetworkType)
+ }
+
+ // This scenario likely isn't possible, but write a test for it anyway
+ @Test
+ fun carrierMergedButNotEnabled_isDefault() = runTest {
+ val latest by underTest.primaryLevel.collectLastValue()
+
+ fakeWifiRepository.setWifiNetwork(
+ WifiNetworkModel.CarrierMerged.of(subscriptionId = SUB_ID, level = 3)
+ )
+ fakeWifiRepository.setIsWifiEnabled(false)
+
+ assertThat(latest).isNotEqualTo(3)
+ }
+
+ // This scenario likely isn't possible, but write a test for it anyway
+ @Test
+ fun carrierMergedButWifiNotDefault_isDefault() = runTest {
+ val latest by underTest.primaryLevel.collectLastValue()
+
+ fakeWifiRepository.setWifiNetwork(
+ WifiNetworkModel.CarrierMerged.of(subscriptionId = SUB_ID, level = 3)
+ )
+ fakeWifiRepository.setIsWifiDefault(false)
+
+ assertThat(latest).isNotEqualTo(3)
+ }
+
+ @Test
+ fun numberOfLevels_comesFromCarrierMerged() = runTest {
+ val latest by underTest.numberOfLevels.collectLastValue()
+
+ fakeWifiRepository.setWifiNetwork(
+ WifiNetworkModel.CarrierMerged.of(
+ subscriptionId = SUB_ID,
+ level = 1,
+ numberOfLevels = 6,
+ )
+ )
+
+ assertThat(latest).isEqualTo(6)
+ }
+
+ @Test
+ fun dataEnabled_matchesWifiEnabled() = runTest {
+ val latest by underTest.dataEnabled.collectLastValue()
+
+ fakeWifiRepository.setIsWifiEnabled(true)
+ assertThat(latest).isTrue()
+
+ fakeWifiRepository.setIsWifiEnabled(false)
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun cdmaRoaming_alwaysFalse() = runTest {
+ val latest by underTest.cdmaRoaming.collectLastValue()
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun networkName_usesSimOperatorNameAsInitial() = runTest {
+ telephonyManager.stub { on { simOperatorName } doReturn "Test SIM name" }
+
+ val latest by underTest.networkName.collectLastValue()
+
+ assertThat(latest).isEqualTo(NetworkNameModel.SimDerived("Test SIM name"))
+ }
+
+ @Test
+ fun networkName_updatesOnNetworkUpdate() = runTest {
+ fakeWifiRepository.setIsWifiEnabled(true)
+ fakeWifiRepository.setIsWifiDefault(true)
+
+ telephonyManager.stub { on { simOperatorName } doReturn "Test SIM name" }
+
+ val latest by underTest.networkName.collectLastValue()
+
+ assertThat(latest).isEqualTo(NetworkNameModel.SimDerived("Test SIM name"))
+
+ telephonyManager.stub { on { simOperatorName } doReturn "New SIM name" }
+ fakeWifiRepository.setWifiNetwork(
+ WifiNetworkModel.CarrierMerged.of(subscriptionId = SUB_ID, level = 3)
+ )
+
+ assertThat(latest).isEqualTo(NetworkNameModel.SimDerived("New SIM name"))
+ }
+
+ @Test
+ fun isAllowedDuringAirplaneMode_alwaysTrue() = runTest {
+ val latest by underTest.isAllowedDuringAirplaneMode.collectLastValue()
+
+ assertThat(latest).isTrue()
+ }
+
+ private companion object {
+ const val SUB_ID = 123
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryKairosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryKairosTest.kt
new file mode 100644
index 0000000..858bb09
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryKairosTest.kt
@@ -0,0 +1,544 @@
+/*
+ * Copyright (C) 2024 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.pipeline.mobile.data.repository.prod
+
+import android.os.PersistableBundle
+import android.telephony.ServiceState
+import android.telephony.SignalStrength
+import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.activated
+import com.android.systemui.flags.Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.kairos.ActivatedKairosFixture
+import com.android.systemui.kairos.BuildSpec
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.KairosTestScope
+import com.android.systemui.kairos.MutableState
+import com.android.systemui.kairos.buildSpec
+import com.android.systemui.kairos.kairos
+import com.android.systemui.kairos.runKairosTest
+import com.android.systemui.kairos.stateOf
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.log.table.logcatTableLogBuffer
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_EMERGENCY
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_OPERATOR
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepositoryKairos.Companion.COL_PRIMARY_LEVEL
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.getTelephonyCallbackForType
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.fakeWifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.wifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+
+/**
+ * This repo acts as a dispatcher to either the `typical` or `carrier merged` versions of the
+ * repository interface it's switching on. These tests just need to verify that the entire interface
+ * properly switches over when the value of `isCarrierMerged` changes.
+ */
+@OptIn(ExperimentalKairosApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FullMobileConnectionRepositoryKairosTest : SysuiTestCase() {
+ private val Kosmos.fakeMobileRepo by Fixture {
+ FakeMobileConnectionRepositoryKairos(SUB_ID, kairos, mobileLogger)
+ }
+
+ private val Kosmos.fakeCarrierMergedRepo by Fixture {
+ FakeMobileConnectionRepositoryKairos(SUB_ID, kairos, mobileLogger).apply {
+ // Mimicks the real carrier merged repository
+ isAllowedDuringAirplaneMode.setValue(true)
+ }
+ }
+
+ private var Kosmos.mobileRepo: MobileConnectionRepositoryKairos by Fixture { fakeMobileRepo }
+ private var Kosmos.carrierMergedRepoSpec:
+ BuildSpec<MobileConnectionRepositoryKairos> by Fixture {
+ buildSpec { fakeCarrierMergedRepo }
+ }
+
+ private val Kosmos.mobileLogger by Fixture { logcatTableLogBuffer(this, "TestName") }
+
+ private val Kosmos.underTest by ActivatedKairosFixture {
+ FullMobileConnectionRepositoryKairos(
+ SUB_ID,
+ mobileLogger,
+ mobileRepo,
+ carrierMergedRepoSpec,
+ isCarrierMerged,
+ )
+ }
+
+ private val Kosmos.subscriptionModel by Fixture {
+ MutableState(
+ kairos,
+ SubscriptionModel(
+ subscriptionId = SUB_ID,
+ carrierName = DEFAULT_NAME,
+ profileClass = PROFILE_CLASS_UNSET,
+ ),
+ )
+ }
+
+ private val Kosmos.isCarrierMerged by Fixture { MutableState(kairos, false) }
+
+ // Use a real config, with no overrides
+ private val systemUiCarrierConfig = SystemUiCarrierConfig(SUB_ID, PersistableBundle())
+
+ private val kosmos =
+ testKosmos().apply {
+ useUnconfinedTestDispatcher()
+ fakeFeatureFlagsClassic.set(ROAMING_INDICATOR_VIA_DISPLAY_INFO, true)
+ }
+
+ private fun runTest(block: suspend KairosTestScope.() -> Unit) =
+ kosmos.run { runKairosTest { block() } }
+
+ @Test
+ fun startingIsCarrierMerged_usesCarrierMergedInitially() = runTest {
+ val carrierMergedOperatorName = "Carrier Merged Operator"
+ val nonCarrierMergedName = "Non-carrier-merged"
+
+ fakeCarrierMergedRepo.operatorAlphaShort.setValue(carrierMergedOperatorName)
+ fakeMobileRepo.operatorAlphaShort.setValue(nonCarrierMergedName)
+
+ isCarrierMerged.setValue(true)
+
+ val activeRepo by underTest.activeRepo.collectLastValue()
+ val operatorAlphaShort by underTest.operatorAlphaShort.collectLastValue()
+
+ assertThat(activeRepo).isEqualTo(fakeCarrierMergedRepo)
+ assertThat(operatorAlphaShort).isEqualTo(carrierMergedOperatorName)
+ }
+
+ @Test
+ fun startingNotCarrierMerged_usesTypicalInitially() = runTest {
+ val carrierMergedOperatorName = "Carrier Merged Operator"
+ val nonCarrierMergedName = "Typical Operator"
+
+ fakeCarrierMergedRepo.operatorAlphaShort.setValue(carrierMergedOperatorName)
+ fakeMobileRepo.operatorAlphaShort.setValue(nonCarrierMergedName)
+ isCarrierMerged.setValue(false)
+
+ assertThat(underTest.activeRepo.collectLastValue().value).isEqualTo(fakeMobileRepo)
+ assertThat(underTest.operatorAlphaShort.collectLastValue().value)
+ .isEqualTo(nonCarrierMergedName)
+ }
+
+ @Test
+ fun activeRepo_matchesIsCarrierMerged() = runTest {
+ isCarrierMerged.setValue(false)
+
+ val latest by underTest.activeRepo.collectLastValue()
+
+ isCarrierMerged.setValue(true)
+
+ assertThat(latest).isEqualTo(fakeCarrierMergedRepo)
+
+ isCarrierMerged.setValue(false)
+
+ assertThat(latest).isEqualTo(fakeMobileRepo)
+
+ isCarrierMerged.setValue(true)
+
+ assertThat(latest).isEqualTo(fakeCarrierMergedRepo)
+ }
+
+ @Test
+ fun connectionInfo_getsUpdatesFromRepo_carrierMerged() = runTest {
+ isCarrierMerged.setValue(false)
+
+ val latestName by underTest.operatorAlphaShort.collectLastValue()
+ val latestLevel by underTest.primaryLevel.collectLastValue()
+
+ isCarrierMerged.setValue(true)
+
+ val operator1 = "Carrier Merged Operator"
+ val level1 = 1
+ fakeCarrierMergedRepo.operatorAlphaShort.setValue(operator1)
+ fakeCarrierMergedRepo.primaryLevel.setValue(level1)
+
+ assertThat(latestName).isEqualTo(operator1)
+ assertThat(latestLevel).isEqualTo(level1)
+
+ val operator2 = "Carrier Merged Operator #2"
+ val level2 = 2
+ fakeCarrierMergedRepo.operatorAlphaShort.setValue(operator2)
+ fakeCarrierMergedRepo.primaryLevel.setValue(level2)
+
+ assertThat(latestName).isEqualTo(operator2)
+ assertThat(latestLevel).isEqualTo(level2)
+
+ val operator3 = "Carrier Merged Operator #3"
+ val level3 = 3
+ fakeCarrierMergedRepo.operatorAlphaShort.setValue(operator3)
+ fakeCarrierMergedRepo.primaryLevel.setValue(level3)
+
+ assertThat(latestName).isEqualTo(operator3)
+ assertThat(latestLevel).isEqualTo(level3)
+ }
+
+ @Test
+ fun connectionInfo_getsUpdatesFromRepo_mobile() = runTest {
+ isCarrierMerged.setValue(false)
+
+ val latestName by underTest.operatorAlphaShort.collectLastValue()
+ val latestLevel by underTest.primaryLevel.collectLastValue()
+
+ isCarrierMerged.setValue(false)
+
+ val operator1 = "Typical Merged Operator"
+ val level1 = 1
+ fakeMobileRepo.operatorAlphaShort.setValue(operator1)
+ fakeMobileRepo.primaryLevel.setValue(level1)
+
+ assertThat(latestName).isEqualTo(operator1)
+ assertThat(latestLevel).isEqualTo(level1)
+
+ val operator2 = "Typical Merged Operator #2"
+ val level2 = 2
+ fakeMobileRepo.operatorAlphaShort.setValue(operator2)
+ fakeMobileRepo.primaryLevel.setValue(level2)
+
+ assertThat(latestName).isEqualTo(operator2)
+ assertThat(latestLevel).isEqualTo(level2)
+
+ val operator3 = "Typical Merged Operator #3"
+ val level3 = 3
+ fakeMobileRepo.operatorAlphaShort.setValue(operator3)
+ fakeMobileRepo.primaryLevel.setValue(level3)
+
+ assertThat(latestName).isEqualTo(operator3)
+ assertThat(latestLevel).isEqualTo(level3)
+ }
+
+ @Test
+ fun connectionInfo_updatesWhenCarrierMergedUpdates() = runTest {
+ isCarrierMerged.setValue(false)
+
+ val latestName by underTest.operatorAlphaShort.collectLastValue()
+ val latestLevel by underTest.primaryLevel.collectLastValue()
+
+ val carrierMergedOperator = "Carrier Merged Operator"
+ val carrierMergedLevel = 4
+ fakeCarrierMergedRepo.operatorAlphaShort.setValue(carrierMergedOperator)
+ fakeCarrierMergedRepo.primaryLevel.setValue(carrierMergedLevel)
+
+ val mobileName = "Typical Operator"
+ val mobileLevel = 2
+ fakeMobileRepo.operatorAlphaShort.setValue(mobileName)
+ fakeMobileRepo.primaryLevel.setValue(mobileLevel)
+
+ // Start with the mobile info
+ assertThat(latestName).isEqualTo(mobileName)
+ assertThat(latestLevel).isEqualTo(mobileLevel)
+
+ // WHEN isCarrierMerged is set to true
+ isCarrierMerged.setValue(true)
+
+ // THEN the carrier merged info is used
+ assertThat(latestName).isEqualTo(carrierMergedOperator)
+ assertThat(latestLevel).isEqualTo(carrierMergedLevel)
+
+ val newCarrierMergedName = "New CM Operator"
+ val newCarrierMergedLevel = 0
+ fakeCarrierMergedRepo.operatorAlphaShort.setValue(newCarrierMergedName)
+ fakeCarrierMergedRepo.primaryLevel.setValue(newCarrierMergedLevel)
+
+ assertThat(latestName).isEqualTo(newCarrierMergedName)
+ assertThat(latestLevel).isEqualTo(newCarrierMergedLevel)
+
+ // WHEN isCarrierMerged is set to false
+ isCarrierMerged.setValue(false)
+
+ // THEN the typical info is used
+ assertThat(latestName).isEqualTo(mobileName)
+ assertThat(latestLevel).isEqualTo(mobileLevel)
+
+ val newMobileName = "New MobileOperator"
+ val newMobileLevel = 3
+ fakeMobileRepo.operatorAlphaShort.setValue(newMobileName)
+ fakeMobileRepo.primaryLevel.setValue(newMobileLevel)
+
+ assertThat(latestName).isEqualTo(newMobileName)
+ assertThat(latestLevel).isEqualTo(newMobileLevel)
+ }
+
+ @Test
+ fun isAllowedDuringAirplaneMode_updatesWhenCarrierMergedUpdates() = runTest {
+ isCarrierMerged.setValue(false)
+
+ val latest by underTest.isAllowedDuringAirplaneMode.collectLastValue()
+
+ assertThat(latest).isFalse()
+
+ isCarrierMerged.setValue(true)
+
+ assertThat(latest).isTrue()
+
+ isCarrierMerged.setValue(false)
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun connectionInfo_logging_notCarrierMerged_getsUpdates() = runTest {
+ // SETUP: Use real repositories to verify the diffing still works. (See b/267501739.)
+ val telephonyManager: TelephonyManager = mock {
+ on { simOperatorName } doReturn ""
+ on { subscriptionId } doReturn SUB_ID
+ }
+ fakeWifiRepository.setIsWifiEnabled(true)
+ fakeWifiRepository.setIsWifiDefault(true)
+ mobileRepo = createRealMobileRepo(telephonyManager)
+ carrierMergedRepoSpec = realCarrierMergedRepo(telephonyManager)
+
+ isCarrierMerged.setValue(false)
+
+ // Stand-up activated repository
+ underTest
+
+ // WHEN we set up some mobile connection info
+ val serviceState = ServiceState()
+ serviceState.setOperatorName("longName", "OpTypical", "1")
+ serviceState.isEmergencyOnly = true
+ getTelephonyCallbackForType<TelephonyCallback.ServiceStateListener>(telephonyManager)
+ .onServiceStateChanged(serviceState)
+
+ // THEN it's logged to the buffer
+ assertThat(dumpBuffer()).contains("$COL_OPERATOR${BUFFER_SEPARATOR}OpTypical")
+ assertThat(dumpBuffer()).contains("$COL_EMERGENCY${BUFFER_SEPARATOR}true")
+
+ // WHEN we update mobile connection info
+ val serviceState2 = ServiceState()
+ serviceState2.setOperatorName("longName", "OpDiff", "1")
+ serviceState2.isEmergencyOnly = false
+ getTelephonyCallbackForType<TelephonyCallback.ServiceStateListener>(telephonyManager)
+ .onServiceStateChanged(serviceState2)
+
+ // THEN the updates are logged
+ assertThat(dumpBuffer()).contains("$COL_OPERATOR${BUFFER_SEPARATOR}OpDiff")
+ assertThat(dumpBuffer()).contains("$COL_EMERGENCY${BUFFER_SEPARATOR}false")
+ }
+
+ @Test
+ fun connectionInfo_logging_carrierMerged_getsUpdates() = runTest {
+ // SETUP: Use real repositories to verify the diffing still works. (See b/267501739.)
+ val telephonyManager: TelephonyManager = mock {
+ on { simOperatorName } doReturn ""
+ on { subscriptionId } doReturn SUB_ID
+ }
+ fakeWifiRepository.setIsWifiEnabled(true)
+ fakeWifiRepository.setIsWifiDefault(true)
+ mobileRepo = createRealMobileRepo(telephonyManager)
+ carrierMergedRepoSpec = realCarrierMergedRepo(telephonyManager)
+
+ isCarrierMerged.setValue(true)
+
+ // Stand-up activated repository
+ underTest
+
+ // WHEN we set up carrier merged info
+ fakeWifiRepository.setWifiNetwork(WifiNetworkModel.CarrierMerged.of(SUB_ID, level = 3))
+
+ // THEN the carrier merged info is logged
+ assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}3")
+
+ // WHEN we update the info
+ fakeWifiRepository.setWifiNetwork(WifiNetworkModel.CarrierMerged.of(SUB_ID, level = 1))
+
+ // THEN the updates are logged
+ assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}1")
+ }
+
+ @Test
+ fun connectionInfo_logging_updatesWhenCarrierMergedUpdates() = runTest {
+ // SETUP: Use real repositories to verify the diffing still works. (See b/267501739.)
+ val telephonyManager: TelephonyManager = mock {
+ on { simOperatorName } doReturn ""
+ on { subscriptionId } doReturn SUB_ID
+ }
+ fakeWifiRepository.setIsWifiEnabled(true)
+ fakeWifiRepository.setIsWifiDefault(true)
+ mobileRepo = createRealMobileRepo(telephonyManager)
+ carrierMergedRepoSpec = realCarrierMergedRepo(telephonyManager)
+
+ isCarrierMerged.setValue(false)
+
+ // Stand-up activated repository
+ underTest
+
+ // WHEN we set up some mobile connection info
+ val cb =
+ getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>(telephonyManager)
+ cb.onSignalStrengthsChanged(mock(stubOnly = true) { on { level } doReturn 1 })
+
+ // THEN it's logged to the buffer
+ assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}1")
+
+ // WHEN isCarrierMerged is set to true
+ fakeWifiRepository.setWifiNetwork(WifiNetworkModel.CarrierMerged.of(SUB_ID, level = 3))
+ isCarrierMerged.setValue(true)
+
+ // THEN the carrier merged info is logged
+ assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}3")
+
+ // WHEN the carrier merge network is updated
+ fakeWifiRepository.setWifiNetwork(WifiNetworkModel.CarrierMerged.of(SUB_ID, level = 4))
+
+ // THEN the new level is logged
+ assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}4")
+
+ // WHEN isCarrierMerged is set to false
+ isCarrierMerged.setValue(false)
+
+ // THEN the typical info is logged
+ // Note: Since our first logs also had the typical info, we need to search the log
+ // contents for after our carrier merged level log.
+ val fullBuffer = dumpBuffer()
+ val carrierMergedContentIndex = fullBuffer.indexOf("${BUFFER_SEPARATOR}4")
+ val bufferAfterCarrierMerged = fullBuffer.substring(carrierMergedContentIndex)
+ assertThat(bufferAfterCarrierMerged).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}1")
+
+ // WHEN the normal network is updated
+ cb.onSignalStrengthsChanged(mock(stubOnly = true) { on { level } doReturn 0 })
+
+ // THEN the new level is logged
+ assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}0")
+ }
+
+ @Test
+ fun connectionInfo_logging_doesNotLogUpdatesForNotActiveRepo() = runTest {
+ // SETUP: Use real repositories to verify the diffing still works. (See b/267501739.)
+ val telephonyManager: TelephonyManager = mock {
+ on { simOperatorName } doReturn ""
+ on { subscriptionId } doReturn SUB_ID
+ }
+ fakeWifiRepository.setIsWifiEnabled(true)
+ fakeWifiRepository.setIsWifiDefault(true)
+ mobileRepo = createRealMobileRepo(telephonyManager)
+ carrierMergedRepoSpec = realCarrierMergedRepo(telephonyManager)
+
+ // WHEN isCarrierMerged = false
+ isCarrierMerged.setValue(false)
+
+ // Stand-up activated repository
+ underTest
+
+ fun setSignalLevel(newLevel: Int) {
+ val signalStrength =
+ mock<SignalStrength>(stubOnly = true) { on { level } doReturn newLevel }
+ argumentCaptor<TelephonyCallback>()
+ .apply { verify(telephonyManager).registerTelephonyCallback(any(), capture()) }
+ .allValues
+ .asSequence()
+ .filterIsInstance<TelephonyCallback.SignalStrengthsListener>()
+ .forEach { it.onSignalStrengthsChanged(signalStrength) }
+ }
+
+ // WHEN we set up some mobile connection info
+ setSignalLevel(1)
+
+ // THEN updates to the carrier merged level aren't logged
+ fakeWifiRepository.setWifiNetwork(WifiNetworkModel.CarrierMerged.of(SUB_ID, level = 4))
+ assertThat(dumpBuffer()).doesNotContain("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}4")
+
+ fakeWifiRepository.setWifiNetwork(WifiNetworkModel.CarrierMerged.of(SUB_ID, level = 3))
+ assertThat(dumpBuffer()).doesNotContain("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}3")
+
+ // WHEN isCarrierMerged is set to true
+ isCarrierMerged.setValue(true)
+
+ // THEN updates to the normal level aren't logged
+ setSignalLevel(5)
+ assertThat(dumpBuffer()).doesNotContain("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}5")
+
+ setSignalLevel(6)
+ assertThat(dumpBuffer()).doesNotContain("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}6")
+ }
+
+ private fun KairosTestScope.createRealMobileRepo(
+ telephonyManager: TelephonyManager
+ ): MobileConnectionRepositoryKairosImpl =
+ MobileConnectionRepositoryKairosImpl(
+ subId = SUB_ID,
+ context = context,
+ subscriptionModel = subscriptionModel,
+ defaultNetworkName = DEFAULT_NAME_MODEL,
+ networkNameSeparator = SEP,
+ connectivityManager = mock(stubOnly = true),
+ telephonyManager = telephonyManager,
+ systemUiCarrierConfig = systemUiCarrierConfig,
+ broadcastDispatcher = fakeBroadcastDispatcher,
+ mobileMappingsProxy = mock(stubOnly = true),
+ bgDispatcher = testDispatcher,
+ logger = mock(stubOnly = true),
+ tableLogBuffer = mobileLogger,
+ flags = featureFlagsClassic,
+ )
+ .activated()
+
+ private fun Kosmos.realCarrierMergedRepo(
+ telephonyManager: TelephonyManager
+ ): BuildSpec<CarrierMergedConnectionRepositoryKairos> = buildSpec {
+ activated {
+ CarrierMergedConnectionRepositoryKairos(
+ subId = SUB_ID,
+ tableLogBuffer = mobileLogger,
+ telephonyManager = telephonyManager,
+ wifiRepository = wifiRepository,
+ isInEcmMode = stateOf(false),
+ )
+ }
+ }
+
+ private fun Kosmos.dumpBuffer(): String {
+ val outputWriter = StringWriter()
+ mobileLogger.dump(PrintWriter(outputWriter), arrayOf())
+ return outputWriter.toString()
+ }
+
+ private companion object {
+ const val SUB_ID = 42
+ private const val DEFAULT_NAME = "default name"
+ private val DEFAULT_NAME_MODEL = NetworkNameModel.Default(DEFAULT_NAME)
+ private const val SEP = "-"
+ private const val BUFFER_SEPARATOR = "|"
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryKairosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryKairosTest.kt
new file mode 100644
index 0000000..32fc359
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryKairosTest.kt
@@ -0,0 +1,1228 @@
+/*
+ * Copyright (C) 2024 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.pipeline.mobile.data.repository.prod
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.net.ConnectivityManager.NetworkCallback
+import android.net.connectivityManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WLAN
+import android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN
+import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL
+import android.telephony.CarrierConfigManager.KEY_SHOW_5G_SLICE_ICON_BOOL
+import android.telephony.NetworkRegistrationInfo
+import android.telephony.NetworkRegistrationInfo.DOMAIN_PS
+import android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_DENIED
+import android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_HOME
+import android.telephony.ServiceState
+import android.telephony.ServiceState.STATE_IN_SERVICE
+import android.telephony.ServiceState.STATE_OUT_OF_SERVICE
+import android.telephony.SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX
+import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyCallback.CarrierRoamingNtnListener
+import android.telephony.TelephonyCallback.DataActivityListener
+import android.telephony.TelephonyCallback.DisplayInfoListener
+import android.telephony.TelephonyCallback.ServiceStateListener
+import android.telephony.TelephonyDisplayInfo
+import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA
+import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
+import android.telephony.TelephonyManager
+import android.telephony.TelephonyManager.DATA_ACTIVITY_DORMANT
+import android.telephony.TelephonyManager.DATA_ACTIVITY_IN
+import android.telephony.TelephonyManager.DATA_ACTIVITY_INOUT
+import android.telephony.TelephonyManager.DATA_ACTIVITY_NONE
+import android.telephony.TelephonyManager.DATA_ACTIVITY_OUT
+import android.telephony.TelephonyManager.DATA_CONNECTED
+import android.telephony.TelephonyManager.DATA_CONNECTING
+import android.telephony.TelephonyManager.DATA_DISCONNECTED
+import android.telephony.TelephonyManager.DATA_DISCONNECTING
+import android.telephony.TelephonyManager.DATA_HANDOVER_IN_PROGRESS
+import android.telephony.TelephonyManager.DATA_SUSPENDED
+import android.telephony.TelephonyManager.DATA_UNKNOWN
+import android.telephony.TelephonyManager.ERI_OFF
+import android.telephony.TelephonyManager.ERI_ON
+import android.telephony.TelephonyManager.EXTRA_CARRIER_ID
+import android.telephony.TelephonyManager.EXTRA_DATA_SPN
+import android.telephony.TelephonyManager.EXTRA_PLMN
+import android.telephony.TelephonyManager.EXTRA_SHOW_PLMN
+import android.telephony.TelephonyManager.EXTRA_SHOW_SPN
+import android.telephony.TelephonyManager.EXTRA_SPN
+import android.telephony.TelephonyManager.EXTRA_SUBSCRIPTION_ID
+import android.telephony.TelephonyManager.NETWORK_TYPE_LTE
+import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
+import android.telephony.telephonyManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.mobile.MobileMappings
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO
+import com.android.systemui.flags.fake
+import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.kairos.ActivatedKairosFixture
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.KairosTestScope
+import com.android.systemui.kairos.MutableState
+import com.android.systemui.kairos.kairos
+import com.android.systemui.kairos.runKairosTest
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogcatEchoTrackerAlways
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.tableLogBufferFactory
+import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.OverrideNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.UnknownNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig
+import com.android.systemui.statusbar.pipeline.mobile.data.model.testCarrierConfig
+import com.android.systemui.statusbar.pipeline.mobile.data.model.testCarrierConfigWithOverride
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.signalStrength
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.telephonyDisplayInfo
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
+import org.mockito.kotlin.whenever
+
+@OptIn(ExperimentalKairosApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MobileConnectionRepositoryKairosTest : SysuiTestCase() {
+
+ private val Kosmos.underTest by ActivatedKairosFixture {
+ MobileConnectionRepositoryKairosImpl(
+ SUB_1_ID,
+ context,
+ subscriptionModel,
+ DEFAULT_NAME_MODEL,
+ SEP,
+ connectivityManager,
+ telephonyManager,
+ systemUiCarrierConfig,
+ fakeBroadcastDispatcher,
+ mobileMappingsProxy,
+ testDispatcher,
+ logger,
+ tableLogger,
+ featureFlagsClassic,
+ )
+ }
+
+ private val Kosmos.logger: MobileInputLogger by Fixture {
+ MobileInputLogger(LogBuffer("test_buffer", 1, LogcatEchoTrackerAlways()))
+ }
+
+ private val Kosmos.tableLogger: TableLogBuffer by Fixture {
+ tableLogBufferFactory.getOrCreate("test_buffer", 1)
+ }
+
+ private val Kosmos.context: Context by Fixture { mock() }
+
+ private val systemUiCarrierConfig = SystemUiCarrierConfig(SUB_1_ID, testCarrierConfig())
+
+ private val Kosmos.subscriptionModel: MutableState<SubscriptionModel?> by Fixture {
+ MutableState(
+ kairos,
+ SubscriptionModel(
+ subscriptionId = SUB_1_ID,
+ carrierName = DEFAULT_NAME,
+ profileClass = PROFILE_CLASS_UNSET,
+ ),
+ )
+ }
+
+ private val kosmos =
+ testKosmos().apply {
+ useUnconfinedTestDispatcher()
+ featureFlagsClassic.fake.set(ROAMING_INDICATOR_VIA_DISPLAY_INFO, true)
+ telephonyManager.stub { on { subscriptionId } doReturn SUB_1_ID }
+ }
+
+ private fun runTest(block: suspend KairosTestScope.() -> Unit) =
+ kosmos.run { runKairosTest { block() } }
+
+ @Test
+ fun emergencyOnly() = runTest {
+ val latest by underTest.isEmergencyOnly.collectLastValue()
+
+ val serviceState = ServiceState().apply { isEmergencyOnly = true }
+
+ getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState)
+
+ assertThat(latest).isEqualTo(true)
+ }
+
+ @Test
+ fun emergencyOnly_toggles() = runTest {
+ val latest by underTest.isEmergencyOnly.collectLastValue()
+
+ val callback = getTelephonyCallbackForType<ServiceStateListener>()
+ callback.onServiceStateChanged(ServiceState().apply { isEmergencyOnly = true })
+
+ assertThat(latest).isTrue()
+
+ callback.onServiceStateChanged(ServiceState().apply { isEmergencyOnly = false })
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun cdmaLevelUpdates() = runTest {
+ val latest by underTest.cdmaLevel.collectLastValue()
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>()
+ var strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = true)
+ callback.onSignalStrengthsChanged(strength)
+
+ assertThat(latest).isEqualTo(2)
+
+ // gsmLevel updates, no change to cdmaLevel
+ strength = signalStrength(gsmLevel = 3, cdmaLevel = 2, isGsm = true)
+ callback.onSignalStrengthsChanged(strength)
+
+ assertThat(latest).isEqualTo(2)
+ }
+
+ @Test
+ fun gsmLevelUpdates() = runTest {
+ val latest by underTest.primaryLevel.collectLastValue()
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>()
+ var strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = true)
+ callback.onSignalStrengthsChanged(strength)
+
+ assertThat(latest).isEqualTo(1)
+
+ strength = signalStrength(gsmLevel = 3, cdmaLevel = 2, isGsm = true)
+ callback.onSignalStrengthsChanged(strength)
+
+ assertThat(latest).isEqualTo(3)
+ }
+
+ @Test
+ fun isGsm() = runTest {
+ val latest by underTest.isGsm.collectLastValue()
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>()
+ var strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = true)
+ callback.onSignalStrengthsChanged(strength)
+
+ assertThat(latest).isTrue()
+
+ strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = false)
+ callback.onSignalStrengthsChanged(strength)
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun dataConnectionState_connected() = runTest {
+ val latest by underTest.dataConnectionState.collectLastValue()
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
+ callback.onDataConnectionStateChanged(DATA_CONNECTED, 200 /* unused */)
+
+ assertThat(latest).isEqualTo(DataConnectionState.Connected)
+ }
+
+ @Test
+ fun dataConnectionState_connecting() = runTest {
+ val latest by underTest.dataConnectionState.collectLastValue()
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
+ callback.onDataConnectionStateChanged(DATA_CONNECTING, 200 /* unused */)
+
+ assertThat(latest).isEqualTo(DataConnectionState.Connecting)
+ }
+
+ @Test
+ fun dataConnectionState_disconnected() = runTest {
+ val latest by underTest.dataConnectionState.collectLastValue()
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
+ callback.onDataConnectionStateChanged(DATA_DISCONNECTED, 200 /* unused */)
+
+ assertThat(latest).isEqualTo(DataConnectionState.Disconnected)
+ }
+
+ @Test
+ fun dataConnectionState_disconnecting() = runTest {
+ val latest by underTest.dataConnectionState.collectLastValue()
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
+ callback.onDataConnectionStateChanged(DATA_DISCONNECTING, 200 /* unused */)
+
+ assertThat(latest).isEqualTo(DataConnectionState.Disconnecting)
+ }
+
+ @Test
+ fun dataConnectionState_suspended() = runTest {
+ val latest by underTest.dataConnectionState.collectLastValue()
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
+ callback.onDataConnectionStateChanged(DATA_SUSPENDED, 200 /* unused */)
+
+ assertThat(latest).isEqualTo(DataConnectionState.Suspended)
+ }
+
+ @Test
+ fun dataConnectionState_handoverInProgress() = runTest {
+ val latest by underTest.dataConnectionState.collectLastValue()
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
+ callback.onDataConnectionStateChanged(DATA_HANDOVER_IN_PROGRESS, 200 /* unused */)
+
+ assertThat(latest).isEqualTo(DataConnectionState.HandoverInProgress)
+ }
+
+ @Test
+ fun dataConnectionState_unknown() = runTest {
+ val latest by underTest.dataConnectionState.collectLastValue()
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
+ callback.onDataConnectionStateChanged(DATA_UNKNOWN, 200 /* unused */)
+
+ assertThat(latest).isEqualTo(DataConnectionState.Unknown)
+ }
+
+ @Test
+ fun dataConnectionState_invalid() = runTest {
+ val latest by underTest.dataConnectionState.collectLastValue()
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
+ callback.onDataConnectionStateChanged(45, 200 /* unused */)
+
+ assertThat(latest).isEqualTo(DataConnectionState.Invalid)
+ }
+
+ @Test
+ fun dataActivity() = runTest {
+ val latest by underTest.dataActivityDirection.collectLastValue()
+
+ val callback = getTelephonyCallbackForType<DataActivityListener>()
+ callback.onDataActivity(DATA_ACTIVITY_INOUT)
+
+ assertThat(latest).isEqualTo(DATA_ACTIVITY_INOUT.toMobileDataActivityModel())
+ }
+
+ @Test
+ fun carrierId_initialValueCaptured() = runTest {
+ whenever(telephonyManager.simCarrierId).thenReturn(1234)
+
+ val latest by underTest.carrierId.collectLastValue()
+
+ assertThat(latest).isEqualTo(1234)
+ }
+
+ @Test
+ fun carrierId_updatesOnBroadcast() = runTest {
+ whenever(telephonyManager.simCarrierId).thenReturn(1234)
+
+ val latest by underTest.carrierId.collectLastValue()
+
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ carrierIdIntent(carrierId = 4321),
+ )
+
+ assertThat(latest).isEqualTo(4321)
+ }
+
+ @Test
+ fun carrierNetworkChange() = runTest {
+ val latest by underTest.carrierNetworkChangeActive.collectLastValue()
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.CarrierNetworkListener>()
+ callback.onCarrierNetworkChange(true)
+
+ assertThat(latest).isEqualTo(true)
+ }
+
+ @Test
+ fun networkType_default() = runTest {
+ val latest by underTest.resolvedNetworkType.collectLastValue()
+
+ val expected = UnknownNetworkType
+
+ assertThat(latest).isEqualTo(expected)
+ }
+
+ @Test
+ fun networkType_unknown_hasCorrectKey() = runTest {
+ val latest by underTest.resolvedNetworkType.collectLastValue()
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
+ val ti =
+ telephonyDisplayInfo(
+ networkType = NETWORK_TYPE_UNKNOWN,
+ overrideNetworkType = NETWORK_TYPE_UNKNOWN,
+ )
+
+ callback.onDisplayInfoChanged(ti)
+
+ val expected = UnknownNetworkType
+ assertThat(latest).isEqualTo(expected)
+ assertThat(latest!!.lookupKey).isEqualTo(MobileMappings.toIconKey(NETWORK_TYPE_UNKNOWN))
+ }
+
+ @Test
+ fun networkType_updatesUsingDefault() = runTest {
+ val latest by underTest.resolvedNetworkType.collectLastValue()
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
+ val overrideType = OVERRIDE_NETWORK_TYPE_NONE
+ val type = NETWORK_TYPE_LTE
+ val ti = telephonyDisplayInfo(networkType = type, overrideNetworkType = overrideType)
+ callback.onDisplayInfoChanged(ti)
+
+ val expected = DefaultNetworkType(mobileMappingsProxy.toIconKey(type))
+ assertThat(latest).isEqualTo(expected)
+ }
+
+ @Test
+ fun networkType_updatesUsingOverride() = runTest {
+ val latest by underTest.resolvedNetworkType.collectLastValue()
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
+ val type = OVERRIDE_NETWORK_TYPE_LTE_CA
+ val ti = telephonyDisplayInfo(networkType = type, overrideNetworkType = type)
+ callback.onDisplayInfoChanged(ti)
+
+ val expected = OverrideNetworkType(mobileMappingsProxy.toIconKeyOverride(type))
+ assertThat(latest).isEqualTo(expected)
+ }
+
+ @Test
+ fun networkType_unknownNetworkWithOverride_usesOverrideKey() = runTest {
+ val latest by underTest.resolvedNetworkType.collectLastValue()
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
+ val unknown = NETWORK_TYPE_UNKNOWN
+ val type = OVERRIDE_NETWORK_TYPE_LTE_CA
+ val ti = telephonyDisplayInfo(unknown, type)
+ callback.onDisplayInfoChanged(ti)
+
+ val expected = OverrideNetworkType(mobileMappingsProxy.toIconKeyOverride(type))
+ assertThat(latest).isEqualTo(expected)
+ }
+
+ @Test
+ fun dataEnabled_initial_false() = runTest {
+ whenever(telephonyManager.isDataConnectionAllowed).thenReturn(false)
+
+ val latest by underTest.dataEnabled.collectLastValue()
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun isDataEnabled_tracksTelephonyCallback() = runTest {
+ val latest by underTest.dataEnabled.collectLastValue()
+
+ whenever(telephonyManager.isDataConnectionAllowed).thenReturn(false)
+ assertThat(latest).isFalse()
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.DataEnabledListener>()
+
+ callback.onDataEnabledChanged(true, 1)
+ assertThat(latest).isTrue()
+
+ callback.onDataEnabledChanged(false, 1)
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun numberOfLevels_isDefault() = runTest {
+ val latest by underTest.numberOfLevels.collectLastValue()
+
+ assertThat(latest).isEqualTo(DEFAULT_NUM_LEVELS)
+ }
+
+ @Test
+ fun roaming_cdma_queriesTelephonyManager() = runTest {
+ val latest by underTest.cdmaRoaming.collectLastValue()
+
+ val cb = getTelephonyCallbackForType<ServiceStateListener>()
+
+ // CDMA roaming is off, GSM roaming is on
+ whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(ERI_OFF)
+ cb.onServiceStateChanged(ServiceState().also { it.roaming = true })
+
+ assertThat(latest).isFalse()
+
+ // CDMA roaming is on, GSM roaming is off
+ whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(ERI_ON)
+ cb.onServiceStateChanged(ServiceState().also { it.roaming = false })
+
+ assertThat(latest).isTrue()
+ }
+
+ /**
+ * [TelephonyManager.getCdmaEnhancedRoamingIndicatorDisplayNumber] returns -1 if the service is
+ * not running or if there is an error while retrieving the cdma ERI
+ */
+ @Test
+ fun cdmaRoaming_ignoresNegativeOne() = runTest {
+ val latest by underTest.cdmaRoaming.collectLastValue()
+
+ val serviceState = ServiceState()
+ serviceState.roaming = false
+
+ val cb = getTelephonyCallbackForType<ServiceStateListener>()
+
+ // CDMA roaming is unavailable (-1), GSM roaming is off
+ whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(-1)
+ cb.onServiceStateChanged(serviceState)
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun roaming_gsm_queriesDisplayInfo_viaDisplayInfo() = runTest {
+ // GIVEN flag is true
+ featureFlagsClassic.fake.set(ROAMING_INDICATOR_VIA_DISPLAY_INFO, true)
+
+ val latest by underTest.isRoaming.collectLastValue()
+
+ val cb = getTelephonyCallbackForType<DisplayInfoListener>()
+
+ // CDMA roaming is off, GSM roaming is off
+ whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(ERI_OFF)
+ cb.onDisplayInfoChanged(TelephonyDisplayInfo(NETWORK_TYPE_LTE, NETWORK_TYPE_UNKNOWN, false))
+
+ assertThat(latest).isFalse()
+
+ // CDMA roaming is off, GSM roaming is on
+ cb.onDisplayInfoChanged(TelephonyDisplayInfo(NETWORK_TYPE_LTE, NETWORK_TYPE_UNKNOWN, true))
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun roaming_gsm_queriesDisplayInfo_viaServiceState() = runTest {
+ // GIVEN flag is false
+ featureFlagsClassic.fake.set(ROAMING_INDICATOR_VIA_DISPLAY_INFO, false)
+
+ val latest by underTest.isRoaming.collectLastValue()
+
+ val cb = getTelephonyCallbackForType<ServiceStateListener>()
+
+ // CDMA roaming is off, GSM roaming is off
+ whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(ERI_OFF)
+ cb.onServiceStateChanged(ServiceState().also { it.roaming = false })
+
+ assertThat(latest).isFalse()
+
+ // CDMA roaming is off, GSM roaming is on
+ cb.onServiceStateChanged(ServiceState().also { it.roaming = true })
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun activity_updatesFromCallback() = runTest {
+ val latest by underTest.dataActivityDirection.collectLastValue()
+
+ assertThat(latest)
+ .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
+
+ val cb = getTelephonyCallbackForType<DataActivityListener>()
+ cb.onDataActivity(DATA_ACTIVITY_IN)
+ assertThat(latest)
+ .isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = false))
+
+ cb.onDataActivity(DATA_ACTIVITY_OUT)
+ assertThat(latest)
+ .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = true))
+
+ cb.onDataActivity(DATA_ACTIVITY_INOUT)
+ assertThat(latest).isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = true))
+
+ cb.onDataActivity(DATA_ACTIVITY_NONE)
+ assertThat(latest)
+ .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
+
+ cb.onDataActivity(DATA_ACTIVITY_DORMANT)
+ assertThat(latest)
+ .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
+
+ cb.onDataActivity(1234)
+ assertThat(latest)
+ .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
+ }
+
+ @Test
+ fun networkNameForSubId_updates() = runTest {
+ val latest by underTest.carrierName.collectLastValue()
+
+ subscriptionModel.setValue(
+ SubscriptionModel(
+ subscriptionId = SUB_1_ID,
+ carrierName = DEFAULT_NAME,
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+ )
+
+ assertThat(latest?.name).isEqualTo(DEFAULT_NAME)
+
+ val updatedName = "Derived Carrier"
+ subscriptionModel.setValue(
+ SubscriptionModel(
+ subscriptionId = SUB_1_ID,
+ carrierName = updatedName,
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+ )
+
+ assertThat(latest?.name).isEqualTo(updatedName)
+ }
+
+ @Test
+ fun networkNameForSubId_defaultWhenSubscriptionModelNull() = runTest {
+ val latest by underTest.carrierName.collectLastValue()
+
+ subscriptionModel.setValue(null)
+
+ assertThat(latest?.name).isEqualTo(DEFAULT_NAME)
+ }
+
+ @Test
+ fun networkName_default() = runTest {
+ val latest by underTest.networkName.collectLastValue()
+
+ assertThat(latest).isEqualTo(DEFAULT_NAME_MODEL)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+ fun networkName_usesBroadcastInfo_returnsDerived() = runTest {
+ val latest by underTest.networkName.collectLastValue()
+
+ val intent = spnIntent()
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+ captor.lastValue.onReceive(context, intent)
+
+ // spnIntent() sets all values to true and test strings
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+ fun networkName_usesBroadcastInfo_returnsDerived_flagOff() = runTest {
+ val latest by underTest.networkName.collectLastValue()
+
+ val intent = spnIntent()
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+ captor.lastValue.onReceive(context, intent)
+
+ // spnIntent() sets all values to true and test strings
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+ fun networkName_broadcastNotForThisSubId_keepsOldValue() = runTest {
+ val latest by underTest.networkName.collectLastValue()
+
+ val intent = spnIntent()
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+ captor.lastValue.onReceive(context, intent)
+
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
+
+ // WHEN an intent with a different subId is sent
+ val wrongSubIntent = spnIntent(subId = 101)
+
+ captor.lastValue.onReceive(context, wrongSubIntent)
+
+ // THEN the previous intent's name is still used
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+ fun networkName_broadcastNotForThisSubId_keepsOldValue_flagOff() = runTest {
+ val latest by underTest.networkName.collectLastValue()
+
+ val intent = spnIntent()
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+ captor.lastValue.onReceive(context, intent)
+
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
+
+ // WHEN an intent with a different subId is sent
+ val wrongSubIntent = spnIntent(subId = 101)
+
+ captor.lastValue.onReceive(context, wrongSubIntent)
+
+ // THEN the previous intent's name is still used
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+ fun networkName_broadcastHasNoData_updatesToDefault() = runTest {
+ val latest by underTest.networkName.collectLastValue()
+
+ val intent = spnIntent()
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+ captor.lastValue.onReceive(context, intent)
+
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
+
+ val intentWithoutInfo = spnIntent(showSpn = false, showPlmn = false)
+
+ captor.lastValue.onReceive(context, intentWithoutInfo)
+
+ assertThat(latest).isEqualTo(DEFAULT_NAME_MODEL)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+ fun networkName_broadcastHasNoData_updatesToDefault_flagOff() = runTest {
+ val latest by underTest.networkName.collectLastValue()
+
+ val intent = spnIntent()
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+ captor.lastValue.onReceive(context, intent)
+
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
+
+ val intentWithoutInfo = spnIntent(showSpn = false, showPlmn = false)
+
+ captor.lastValue.onReceive(context, intentWithoutInfo)
+
+ assertThat(latest).isEqualTo(DEFAULT_NAME_MODEL)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+ fun networkName_usingEagerStrategy_retainsNameBetweenSubscribers() = runTest {
+ // Use the [StateFlow.value] getter so we can prove that the collection happens
+ // even when there is no [Job]
+
+ // Starts out default
+ val latest by underTest.networkName.collectLastValue()
+ assertThat(latest).isEqualTo(DEFAULT_NAME_MODEL)
+
+ val intent = spnIntent()
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+ captor.lastValue.onReceive(context, intent)
+
+ // The value is still there despite no active subscribers
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+ fun networkName_usingEagerStrategy_retainsNameBetweenSubscribers_flagOff() = runTest {
+ // Use the [StateFlow.value] getter so we can prove that the collection happens
+ // even when there is no [Job]
+
+ // Starts out default
+ val latest by underTest.networkName.collectLastValue()
+ assertThat(latest).isEqualTo(DEFAULT_NAME_MODEL)
+
+ val intent = spnIntent()
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+ captor.lastValue.onReceive(context, intent)
+
+ // The value is still there despite no active subscribers
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+ fun networkName_allFieldsSet_prioritizesDataSpnOverSpn() = runTest {
+ val latest by underTest.networkName.collectLastValue()
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+
+ val intent =
+ spnIntent(
+ subId = SUB_1_ID,
+ showSpn = true,
+ spn = SPN,
+ dataSpn = DATA_SPN,
+ showPlmn = true,
+ plmn = PLMN,
+ )
+ captor.lastValue.onReceive(context, intent)
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+ fun networkName_spnAndPlmn_fallbackToSpnWhenNullDataSpn() = runTest {
+ val latest by underTest.networkName.collectLastValue()
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+
+ val intent =
+ spnIntent(
+ subId = SUB_1_ID,
+ showSpn = true,
+ spn = SPN,
+ dataSpn = null,
+ showPlmn = true,
+ plmn = PLMN,
+ )
+ captor.lastValue.onReceive(context, intent)
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN"))
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+ fun networkName_allFieldsSet_flagOff() = runTest {
+ val latest by underTest.networkName.collectLastValue()
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+
+ val intent =
+ spnIntent(
+ subId = SUB_1_ID,
+ showSpn = true,
+ spn = SPN,
+ dataSpn = DATA_SPN,
+ showPlmn = true,
+ plmn = PLMN,
+ )
+ captor.lastValue.onReceive(context, intent)
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+ fun networkName_showPlmn_plmnNotNull_showSpn_spnNull_dataSpnNotNull() = runTest {
+ val latest by underTest.networkName.collectLastValue()
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+ val intent =
+ spnIntent(
+ subId = SUB_1_ID,
+ showSpn = true,
+ spn = null,
+ dataSpn = DATA_SPN,
+ showPlmn = true,
+ plmn = PLMN,
+ )
+ captor.lastValue.onReceive(context, intent)
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+ fun networkName_showPlmn_plmnNotNull_showSpn_spnNotNull_dataSpnNull() = runTest {
+ val latest by underTest.networkName.collectLastValue()
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+ val intent =
+ spnIntent(
+ subId = SUB_1_ID,
+ showSpn = true,
+ spn = SPN,
+ dataSpn = null,
+ showPlmn = true,
+ plmn = PLMN,
+ )
+ captor.lastValue.onReceive(context, intent)
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN"))
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+ fun networkName_showPlmn_plmnNotNull_showSpn_spnNull_dataSpnNotNull_flagOff() = runTest {
+ val latest by underTest.networkName.collectLastValue()
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+ val intent =
+ spnIntent(
+ subId = SUB_1_ID,
+ showSpn = true,
+ spn = null,
+ dataSpn = DATA_SPN,
+ showPlmn = true,
+ plmn = PLMN,
+ )
+ captor.lastValue.onReceive(context, intent)
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN"))
+ }
+
+ @Test
+ fun networkName_showPlmn_noShowSPN() = runTest {
+ val latest by underTest.networkName.collectLastValue()
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+ val intent =
+ spnIntent(
+ subId = SUB_1_ID,
+ showSpn = false,
+ spn = SPN,
+ dataSpn = DATA_SPN,
+ showPlmn = true,
+ plmn = PLMN,
+ )
+ captor.lastValue.onReceive(context, intent)
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN"))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+ fun networkName_showPlmn_plmnNull_showSpn() = runTest {
+ val latest by underTest.networkName.collectLastValue()
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+ val intent =
+ spnIntent(
+ subId = SUB_1_ID,
+ showSpn = true,
+ spn = SPN,
+ dataSpn = DATA_SPN,
+ showPlmn = true,
+ plmn = null,
+ )
+ captor.lastValue.onReceive(context, intent)
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$DATA_SPN"))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+ fun networkName_showPlmn_plmnNull_showSpn_dataSpnNull() = runTest {
+ val latest by underTest.networkName.collectLastValue()
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+ val intent =
+ spnIntent(
+ subId = SUB_1_ID,
+ showSpn = true,
+ spn = SPN,
+ dataSpn = null,
+ showPlmn = true,
+ plmn = null,
+ )
+ captor.lastValue.onReceive(context, intent)
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$SPN"))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+ fun networkName_showPlmn_plmnNull_showSpn_bothSpnNull() = runTest {
+ val latest by underTest.networkName.collectLastValue()
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+ val intent =
+ spnIntent(
+ subId = SUB_1_ID,
+ showSpn = true,
+ spn = null,
+ dataSpn = null,
+ showPlmn = true,
+ plmn = null,
+ )
+ captor.lastValue.onReceive(context, intent)
+ assertThat(latest).isEqualTo(DEFAULT_NAME_MODEL)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN)
+ fun networkName_showPlmn_plmnNull_showSpn_flagOff() = runTest {
+ val latest by underTest.networkName.collectLastValue()
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(context).registerReceiver(captor.capture(), any())
+ val intent =
+ spnIntent(
+ subId = SUB_1_ID,
+ showSpn = true,
+ spn = SPN,
+ dataSpn = DATA_SPN,
+ showPlmn = true,
+ plmn = null,
+ )
+ captor.lastValue.onReceive(context, intent)
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$DATA_SPN"))
+ }
+
+ @Test
+ fun operatorAlphaShort_tracked() = runTest {
+ val latest by underTest.operatorAlphaShort.collectLastValue()
+
+ val shortName = "short name"
+ val serviceState = ServiceState()
+ serviceState.setOperatorName(
+ /* longName */ "long name",
+ /* shortName */ shortName,
+ /* numeric */ "12345",
+ )
+
+ getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState)
+
+ assertThat(latest).isEqualTo(shortName)
+ }
+
+ @Test
+ fun isInService_notIwlan() = runTest {
+ val latest by underTest.isInService.collectLastValue()
+
+ val nriInService =
+ NetworkRegistrationInfo.Builder()
+ .setDomain(DOMAIN_PS)
+ .setTransportType(TRANSPORT_TYPE_WWAN)
+ .setRegistrationState(REGISTRATION_STATE_HOME)
+ .build()
+
+ getTelephonyCallbackForType<ServiceStateListener>()
+ .onServiceStateChanged(
+ ServiceState().also {
+ it.voiceRegState = STATE_IN_SERVICE
+ it.addNetworkRegistrationInfo(nriInService)
+ }
+ )
+
+ assertThat(latest).isTrue()
+
+ getTelephonyCallbackForType<ServiceStateListener>()
+ .onServiceStateChanged(
+ ServiceState().also {
+ it.voiceRegState = STATE_OUT_OF_SERVICE
+ it.addNetworkRegistrationInfo(nriInService)
+ }
+ )
+ assertThat(latest).isTrue()
+
+ val nriNotInService =
+ NetworkRegistrationInfo.Builder()
+ .setDomain(DOMAIN_PS)
+ .setTransportType(TRANSPORT_TYPE_WWAN)
+ .setRegistrationState(REGISTRATION_STATE_DENIED)
+ .build()
+ getTelephonyCallbackForType<ServiceStateListener>()
+ .onServiceStateChanged(
+ ServiceState().also {
+ it.voiceRegState = STATE_OUT_OF_SERVICE
+ it.addNetworkRegistrationInfo(nriNotInService)
+ }
+ )
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun isInService_isIwlan_voiceOutOfService_dataInService() = runTest {
+ val latest by underTest.isInService.collectLastValue()
+
+ val iwlanData =
+ NetworkRegistrationInfo.Builder()
+ .setDomain(DOMAIN_PS)
+ .setTransportType(TRANSPORT_TYPE_WLAN)
+ .setRegistrationState(REGISTRATION_STATE_HOME)
+ .build()
+ val serviceState =
+ ServiceState().also {
+ it.voiceRegState = STATE_OUT_OF_SERVICE
+ it.addNetworkRegistrationInfo(iwlanData)
+ }
+
+ getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState)
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun isNonTerrestrial_updatesFromCallback0() = runTest {
+ val latest by underTest.isNonTerrestrial.collectLastValue()
+
+ // Starts out false
+ assertThat(latest).isFalse()
+
+ val callback = getTelephonyCallbackForType<CarrierRoamingNtnListener>()
+
+ callback.onCarrierRoamingNtnModeChanged(true)
+ assertThat(latest).isTrue()
+
+ callback.onCarrierRoamingNtnModeChanged(false)
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun numberOfLevels_usesCarrierConfig() = runTest {
+ val latest by underTest.numberOfLevels.collectLastValue()
+
+ assertThat(latest).isEqualTo(DEFAULT_NUM_LEVELS)
+
+ systemUiCarrierConfig.processNewCarrierConfig(
+ testCarrierConfigWithOverride(KEY_INFLATE_SIGNAL_STRENGTH_BOOL, true)
+ )
+
+ assertThat(latest).isEqualTo(DEFAULT_NUM_LEVELS + 1)
+
+ systemUiCarrierConfig.processNewCarrierConfig(
+ testCarrierConfigWithOverride(KEY_INFLATE_SIGNAL_STRENGTH_BOOL, false)
+ )
+
+ assertThat(latest).isEqualTo(DEFAULT_NUM_LEVELS)
+ }
+
+ @Test
+ fun inflateSignalStrength_usesCarrierConfig() = runTest {
+ val latest by underTest.inflateSignalStrength.collectLastValue()
+
+ assertThat(latest).isEqualTo(false)
+
+ systemUiCarrierConfig.processNewCarrierConfig(
+ testCarrierConfigWithOverride(KEY_INFLATE_SIGNAL_STRENGTH_BOOL, true)
+ )
+
+ assertThat(latest).isEqualTo(true)
+
+ systemUiCarrierConfig.processNewCarrierConfig(
+ testCarrierConfigWithOverride(KEY_INFLATE_SIGNAL_STRENGTH_BOOL, false)
+ )
+
+ assertThat(latest).isEqualTo(false)
+ }
+
+ @Test
+ fun allowNetworkSliceIndicator_exposesCarrierConfigValue() = runTest {
+ val latest by underTest.allowNetworkSliceIndicator.collectLastValue()
+
+ systemUiCarrierConfig.processNewCarrierConfig(
+ testCarrierConfigWithOverride(KEY_SHOW_5G_SLICE_ICON_BOOL, true)
+ )
+
+ assertThat(latest).isTrue()
+
+ systemUiCarrierConfig.processNewCarrierConfig(
+ testCarrierConfigWithOverride(KEY_SHOW_5G_SLICE_ICON_BOOL, false)
+ )
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun isAllowedDuringAirplaneMode_alwaysFalse() = runTest {
+ val latest by underTest.isAllowedDuringAirplaneMode.collectLastValue()
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun hasPrioritizedCaps_defaultFalse() = runTest {
+ // stand up under-test to kick-off activation
+ underTest
+
+ assertThat(kairos.transact { underTest.hasPrioritizedNetworkCapabilities.sample() })
+ .isFalse()
+ }
+
+ @Test
+ fun hasPrioritizedCaps_trueWhenAvailable() = runTest {
+ val latest by underTest.hasPrioritizedNetworkCapabilities.collectLastValue()
+
+ val callback: NetworkCallback =
+ argumentCaptor<NetworkCallback>()
+ .apply { verify(connectivityManager).registerNetworkCallback(any(), capture()) }
+ .lastValue
+
+ callback.onAvailable(mock())
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun hasPrioritizedCaps_becomesFalseWhenNetworkLost() = runTest {
+ val latest by underTest.hasPrioritizedNetworkCapabilities.collectLastValue()
+
+ val callback: NetworkCallback =
+ argumentCaptor<NetworkCallback>()
+ .apply { verify(connectivityManager).registerNetworkCallback(any(), capture()) }
+ .lastValue
+
+ callback.onAvailable(mock())
+
+ assertThat(latest).isTrue()
+
+ callback.onLost(mock())
+
+ assertThat(latest).isFalse()
+ }
+
+ private inline fun <reified T> Kosmos.getTelephonyCallbackForType(): T {
+ return MobileTelephonyHelpers.getTelephonyCallbackForType(telephonyManager)
+ }
+
+ private fun carrierIdIntent(subId: Int = SUB_1_ID, carrierId: Int): Intent =
+ Intent(TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED).apply {
+ putExtra(EXTRA_SUBSCRIPTION_ID, subId)
+ putExtra(EXTRA_CARRIER_ID, carrierId)
+ }
+
+ private fun spnIntent(
+ subId: Int = SUB_1_ID,
+ showSpn: Boolean = true,
+ spn: String? = SPN,
+ dataSpn: String? = DATA_SPN,
+ showPlmn: Boolean = true,
+ plmn: String? = PLMN,
+ ): Intent =
+ Intent(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED).apply {
+ putExtra(EXTRA_SUBSCRIPTION_INDEX, subId)
+ putExtra(EXTRA_SHOW_SPN, showSpn)
+ putExtra(EXTRA_SPN, spn)
+ putExtra(EXTRA_DATA_SPN, dataSpn)
+ putExtra(EXTRA_SHOW_PLMN, showPlmn)
+ putExtra(EXTRA_PLMN, plmn)
+ }
+
+ companion object {
+ private const val SUB_1_ID = 1
+
+ private const val DEFAULT_NAME = "Fake Mobile Network"
+ private val DEFAULT_NAME_MODEL = NetworkNameModel.Default(DEFAULT_NAME)
+ private const val SEP = "-"
+
+ private const val SPN = "testSpn"
+ private const val DATA_SPN = "testDataSpn"
+ private const val PLMN = "testPlmn"
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryKairosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryKairosTest.kt
new file mode 100644
index 0000000..e04a96e
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryKairosTest.kt
@@ -0,0 +1,1350 @@
+/*
+ * Copyright (C) 2024 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.pipeline.mobile.data.repository.prod
+
+import android.annotation.SuppressLint
+import android.content.Intent
+import android.content.applicationContext
+import android.content.testableContext
+import android.net.ConnectivityManager
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.connectivityManager
+import android.net.vcn.VcnTransportInfo
+import android.net.wifi.WifiInfo
+import android.net.wifi.WifiManager
+import android.os.ParcelUuid
+import android.telephony.CarrierConfigManager
+import android.telephony.ServiceState
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
+import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener
+import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET
+import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
+import android.telephony.TelephonyCallback.EmergencyCallbackModeListener
+import android.telephony.TelephonyManager
+import android.telephony.telephonyManager
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.telephony.PhoneConstants
+import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.keyguard.keyguardUpdateMonitor
+import com.android.settingslib.R
+import com.android.settingslib.mobile.MobileMappings
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.broadcast.broadcastDispatcherContext
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.KairosTestScope
+import com.android.systemui.kairos.combine
+import com.android.systemui.kairos.kairos
+import com.android.systemui.kairos.map
+import com.android.systemui.kairos.runKairosTest
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.table.logcatTableLogBuffer
+import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.airplaneModeRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepositoryKairosImpl
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.getTelephonyCallbackForType
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.subscriptionManager
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.subscriptionManagerProxy
+import com.android.systemui.statusbar.pipeline.mobile.util.fake
+import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlots
+import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
+import com.android.systemui.statusbar.pipeline.shared.data.repository.connectivityRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.wifiRepository
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.userRepository
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.android.wifitrackerlib.MergedCarrierEntry
+import com.android.wifitrackerlib.WifiEntry
+import com.android.wifitrackerlib.WifiPickerTracker
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import java.util.UUID
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runCurrent
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mockito
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
+import org.mockito.kotlin.whenever
+
+@OptIn(ExperimentalKairosApi::class)
+@SmallTest
+// This is required because our [SubscriptionManager.OnSubscriptionsChangedListener] uses a looper
+// to run the callback and this makes the looper place nicely with TestScope etc.
+@TestableLooper.RunWithLooper
+@RunWith(AndroidJUnit4::class)
+class MobileConnectionsRepositoryKairosTest : SysuiTestCase() {
+
+ private val Kosmos.wifiManager: WifiManager by Fixture { mock {} }
+ private val Kosmos.wifiPickerTrackerFactory: WifiPickerTrackerFactory by Fixture {
+ mock {
+ on { create(any(), any(), wifiPickerTrackerCallback.capture(), any()) } doReturn
+ wifiPickerTracker
+ }
+ }
+ private val Kosmos.wifiPickerTracker: WifiPickerTracker by Fixture { mock {} }
+ private val Kosmos.wifiTableLogBuffer by Fixture { logcatTableLogBuffer(this, "wifiTableLog") }
+
+ private val mainExecutor = FakeExecutor(FakeSystemClock())
+ private val wifiLogBuffer = LogBuffer("wifi", maxSize = 100, logcatEchoTracker = mock())
+ private val wifiPickerTrackerCallback =
+ argumentCaptor<WifiPickerTracker.WifiPickerTrackerCallback>()
+ private val vcnTransportInfo = VcnTransportInfo.Builder().build()
+
+ private val Kosmos.underTest
+ get() = mobileConnectionsRepositoryKairosImpl
+
+ private val kosmos =
+ testKosmos().apply {
+ fakeFeatureFlagsClassic.set(Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO, true)
+ broadcastDispatcherContext = testableContext
+ connectivityRepository =
+ ConnectivityRepositoryImpl(
+ connectivityManager,
+ ConnectivitySlots(applicationContext),
+ applicationContext,
+ mock(),
+ mock(),
+ applicationCoroutineScope,
+ mock(),
+ )
+ wifiRepository =
+ WifiRepositoryImpl(
+ applicationContext,
+ userRepository,
+ applicationCoroutineScope,
+ mainExecutor,
+ testDispatcher,
+ wifiPickerTrackerFactory,
+ wifiManager,
+ wifiLogBuffer,
+ wifiTableLogBuffer,
+ )
+ subscriptionManager.stub {
+ // For convenience, set up the subscription info callbacks
+ on { getActiveSubscriptionInfo(anyInt()) } doAnswer
+ { invocation ->
+ when (invocation.getArgument(0) as Int) {
+ 1 -> SUB_1
+ 2 -> SUB_2
+ 3 -> SUB_3
+ 4 -> SUB_4
+ else -> null
+ }
+ }
+ }
+ telephonyManager.stub {
+ on { simOperatorName } doReturn ""
+ // Set up so the individual connection repositories
+ on { createForSubscriptionId(anyInt()) } doAnswer
+ { invocation ->
+ telephonyManager.stub {
+ on { subscriptionId } doReturn invocation.getArgument(0)
+ }
+ }
+ }
+ testScope.runCurrent()
+ }
+
+ private fun runTest(block: suspend KairosTestScope.() -> Unit) =
+ kosmos.run { runKairosTest { block() } }
+
+ @Test
+ fun testSubscriptions_initiallyEmpty() = runTest {
+ assertThat(underTest.subscriptions.collectLastValue().value)
+ .isEqualTo(listOf<SubscriptionModel>())
+ }
+
+ @Test
+ fun testSubscriptions_listUpdates() = runTest {
+ val latest by underTest.subscriptions.collectLastValue()
+
+ subscriptionManager.stub {
+ on { completeActiveSubscriptionInfoList } doReturn listOf(SUB_1, SUB_2)
+ }
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ assertThat(latest).isEqualTo(listOf(MODEL_1, MODEL_2))
+ }
+
+ @Test
+ fun testSubscriptions_removingSub_updatesList() = runTest {
+ val latest by underTest.subscriptions.collectLastValue()
+
+ // WHEN 2 networks show up
+ subscriptionManager.stub {
+ on { completeActiveSubscriptionInfoList } doReturn listOf(SUB_1, SUB_2)
+ }
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ // WHEN one network is removed
+ subscriptionManager.stub {
+ on { completeActiveSubscriptionInfoList } doReturn listOf(SUB_2)
+ }
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ // THEN the subscriptions list represents the newest change
+ assertThat(latest).isEqualTo(listOf(MODEL_2))
+ }
+
+ @Test
+ fun subscriptions_subIsOnlyNtn_modelHasExclusivelyNtnTrue() = runTest {
+ val latest by underTest.subscriptions.collectLastValue()
+
+ val onlyNtnSub =
+ mock<SubscriptionInfo> {
+ on { isOnlyNonTerrestrialNetwork } doReturn true
+ on { subscriptionId } doReturn 45
+ on { groupUuid } doReturn GROUP_1
+ on { carrierName } doReturn "NTN only"
+ on { profileClass } doReturn PROFILE_CLASS_UNSET
+ }
+
+ subscriptionManager.stub {
+ on { completeActiveSubscriptionInfoList } doReturn listOf(onlyNtnSub)
+ }
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0].isExclusivelyNonTerrestrial).isTrue()
+ }
+
+ @Test
+ fun subscriptions_subIsNotOnlyNtn_modelHasExclusivelyNtnFalse() = runTest {
+ val latest by underTest.subscriptions.collectLastValue()
+
+ val notOnlyNtnSub =
+ mock<SubscriptionInfo> {
+ on { isOnlyNonTerrestrialNetwork } doReturn false
+ on { subscriptionId } doReturn 45
+ on { groupUuid } doReturn GROUP_1
+ on { carrierName } doReturn "NTN only"
+ on { profileClass } doReturn PROFILE_CLASS_UNSET
+ }
+
+ subscriptionManager.stub {
+ on { completeActiveSubscriptionInfoList } doReturn listOf(notOnlyNtnSub)
+ }
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0].isExclusivelyNonTerrestrial).isFalse()
+ }
+
+ @Test
+ fun testSubscriptions_carrierMergedOnly_listHasCarrierMerged() = runTest {
+ val latest by underTest.subscriptions.collectLastValue()
+
+ setWifiState(isCarrierMerged = true)
+ subscriptionManager.stub {
+ on { completeActiveSubscriptionInfoList } doReturn listOf(SUB_CM)
+ }
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ assertThat(latest).isEqualTo(listOf(MODEL_CM))
+ }
+
+ @Test
+ fun testSubscriptions_carrierMergedAndOther_listHasBothWithCarrierMergedLast() = runTest {
+ val latest by underTest.subscriptions.collectLastValue()
+
+ setWifiState(isCarrierMerged = true)
+ subscriptionManager.stub {
+ on { completeActiveSubscriptionInfoList } doReturn listOf(SUB_1, SUB_2, SUB_CM)
+ }
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ assertThat(latest).isEqualTo(listOf(MODEL_1, MODEL_2, MODEL_CM))
+ }
+
+ @Test
+ fun testActiveDataSubscriptionId_initialValueIsNull() = runTest {
+ assertThat(underTest.activeMobileDataSubscriptionId.collectLastValue().value)
+ .isEqualTo(null)
+ }
+
+ @Test
+ fun testActiveDataSubscriptionId_updates() = runTest {
+ val active by underTest.activeMobileDataSubscriptionId.collectLastValue()
+ testScope.runCurrent()
+
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>(telephonyManager)
+ .onActiveDataSubscriptionIdChanged(SUB_2_ID)
+
+ assertThat(active).isEqualTo(SUB_2_ID)
+ }
+
+ @Test
+ fun activeSubId_nullIfInvalidSubIdIsReceived() = runTest {
+ val latest by underTest.activeMobileDataSubscriptionId.collectLastValue()
+ testScope.runCurrent()
+
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>(telephonyManager)
+ .onActiveDataSubscriptionIdChanged(SUB_2_ID)
+
+ assertThat(latest).isNotNull()
+
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>(telephonyManager)
+ .onActiveDataSubscriptionIdChanged(INVALID_SUBSCRIPTION_ID)
+
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ fun activeRepo_initiallyNull() = runTest {
+ assertThat(underTest.activeMobileDataRepository.collectLastValue().value).isNull()
+ }
+
+ @Test
+ fun activeRepo_updatesWithActiveDataId() = runTest {
+ val latest by underTest.activeMobileDataRepository.collectLastValue()
+ testScope.runCurrent()
+
+ // GIVEN the subscription list is then updated which includes the active data sub id
+ subscriptionManager.stub {
+ on { completeActiveSubscriptionInfoList } doReturn listOf(SUB_2)
+ }
+ getSubscriptionCallback().onSubscriptionsChanged()
+ testScope.runCurrent()
+
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>(telephonyManager)
+ .onActiveDataSubscriptionIdChanged(SUB_2_ID)
+
+ assertThat(latest?.subId).isEqualTo(SUB_2_ID)
+ }
+
+ @Test
+ fun activeRepo_nullIfActiveDataSubIdBecomesInvalid() = runTest {
+ val latest by underTest.activeMobileDataRepository.collectLastValue()
+ testScope.runCurrent()
+
+ // GIVEN the subscription list is then updated which includes the active data sub id
+ subscriptionManager.stub {
+ on { completeActiveSubscriptionInfoList } doReturn listOf(SUB_2)
+ }
+ getSubscriptionCallback().onSubscriptionsChanged()
+ testScope.runCurrent()
+
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>(telephonyManager)
+ .onActiveDataSubscriptionIdChanged(SUB_2_ID)
+ testScope.runCurrent()
+
+ assertThat(latest).isNotNull()
+
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>(telephonyManager)
+ .onActiveDataSubscriptionIdChanged(INVALID_SUBSCRIPTION_ID)
+ testScope.runCurrent()
+
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ /** Regression test for b/268146648. */
+ fun activeSubIdIsSetBeforeSubscriptionsAreUpdated_doesNotThrow() = runTest {
+ val activeRepo by underTest.activeMobileDataRepository.collectLastValue()
+ val subscriptions by underTest.subscriptions.collectLastValue()
+ testScope.runCurrent()
+
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>(telephonyManager)
+ .onActiveDataSubscriptionIdChanged(SUB_2_ID)
+ testScope.runCurrent()
+
+ assertThat(subscriptions).isEmpty()
+ assertThat(activeRepo).isNull()
+ }
+
+ @Test
+ fun getRepoForSubId_activeDataSubIdIsRequestedBeforeSubscriptionsUpdate() = runTest {
+ underTest
+
+ var latestActiveRepo: MobileConnectionRepositoryKairos? = null
+ testScope.backgroundScope.launch {
+ kairos.activateSpec {
+ underTest.activeMobileDataSubscriptionId
+ .combine(underTest.mobileConnectionsBySubId) { id, conns ->
+ id?.let { conns[id] }
+ }
+ .observe {
+ if (it != null) {
+ latestActiveRepo = it
+ }
+ }
+ }
+ }
+
+ val latestSubscriptions by underTest.subscriptions.collectLastValue()
+ testScope.runCurrent()
+
+ // Active data subscription id is sent, but no subscription change has been posted yet
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>(telephonyManager)
+ .onActiveDataSubscriptionIdChanged(SUB_2_ID)
+ testScope.runCurrent()
+
+ // Subscriptions list is empty
+ assertThat(latestSubscriptions).isEmpty()
+
+ // getRepoForSubId does not throw
+ assertThat(latestActiveRepo).isNull()
+ }
+
+ @Test
+ fun activeDataSentBeforeSubscriptionList_subscriptionReusesActiveDataRepo() = runTest {
+ val activeRepo by underTest.activeMobileDataRepository.collectLastValue()
+ testScope.runCurrent()
+
+ // GIVEN active repo is updated before the subscription list updates
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>(telephonyManager)
+ .onActiveDataSubscriptionIdChanged(SUB_2_ID)
+ testScope.runCurrent()
+
+ assertThat(activeRepo).isNull()
+
+ // GIVEN the subscription list is then updated which includes the active data sub id
+ subscriptionManager.stub {
+ on { completeActiveSubscriptionInfoList } doReturn listOf(SUB_2)
+ }
+ getSubscriptionCallback().onSubscriptionsChanged()
+ testScope.runCurrent()
+
+ // WHEN requesting a connection repository for the subscription
+ val newRepo =
+ kairos.transact { underTest.mobileConnectionsBySubId.map { it[SUB_2_ID] }.sample() }
+
+ // THEN the newly request repo has been cached and reused
+ assertThat(activeRepo).isSameInstanceAs(newRepo)
+ }
+
+ @Test
+ fun testConnectionRepository_validSubId_isCached() = runTest {
+ underTest
+
+ subscriptionManager.stub {
+ on { completeActiveSubscriptionInfoList } doReturn listOf(SUB_1)
+ }
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ val repo1 by underTest.mobileConnectionsBySubId.map { it[SUB_1_ID] }.collectLastValue()
+ val repo2 by underTest.mobileConnectionsBySubId.map { it[SUB_1_ID] }.collectLastValue()
+
+ assertThat(repo1).isNotNull()
+ assertThat(repo1).isSameInstanceAs(repo2)
+ }
+
+ @Test
+ fun testConnectionRepository_carrierMergedSubId_isCached() = runTest {
+ underTest
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+ setWifiState(isCarrierMerged = true)
+ subscriptionManager.stub {
+ on { completeActiveSubscriptionInfoList } doReturn listOf(SUB_CM)
+ }
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ val repo1 by underTest.mobileConnectionsBySubId.map { it[SUB_CM_ID] }.collectLastValue()
+ val repo2 by underTest.mobileConnectionsBySubId.map { it[SUB_CM_ID] }.collectLastValue()
+
+ assertThat(repo1).isNotNull()
+ assertThat(repo1).isSameInstanceAs(repo2)
+ }
+
+ @SuppressLint("UnspecifiedRegisterReceiverFlag")
+ @Test
+ fun testDeviceEmergencyCallState_eagerlyChecksState() = runTest {
+ val latest by underTest.isDeviceEmergencyCallCapable.collectLastValue()
+
+ // Value starts out false
+ assertThat(latest).isFalse()
+ telephonyManager.stub { on { activeModemCount } doReturn 1 }
+ whenever(telephonyManager.getServiceStateForSlot(any())).thenAnswer { _ ->
+ ServiceState().apply { isEmergencyOnly = true }
+ }
+
+ // WHEN an appropriate intent gets sent out
+ val intent = serviceStateIntent(subId = -1)
+ broadcastDispatcher.sendIntentToMatchingReceiversOnly(applicationContext, intent)
+ testScope.runCurrent()
+
+ // THEN the repo's state is updated despite no listeners
+ assertThat(latest).isEqualTo(true)
+ }
+
+ @Test
+ fun testDeviceEmergencyCallState_aggregatesAcrossSlots_oneTrue() = runTest {
+ val latest by underTest.isDeviceEmergencyCallCapable.collectLastValue()
+
+ // GIVEN there are multiple slots
+ telephonyManager.stub { on { activeModemCount } doReturn 4 }
+ // GIVEN only one of them reports ECM
+ whenever(telephonyManager.getServiceStateForSlot(any())).thenAnswer { invocation ->
+ when (invocation.getArgument(0) as Int) {
+ 0 -> ServiceState().apply { isEmergencyOnly = false }
+ 1 -> ServiceState().apply { isEmergencyOnly = false }
+ 2 -> ServiceState().apply { isEmergencyOnly = true }
+ 3 -> ServiceState().apply { isEmergencyOnly = false }
+ else -> null
+ }
+ }
+
+ // GIVEN a broadcast goes out for the appropriate subID
+ val intent = serviceStateIntent(subId = -1)
+ broadcastDispatcher.sendIntentToMatchingReceiversOnly(applicationContext, intent)
+ testScope.runCurrent()
+
+ // THEN the device is in ECM, because one of the service states is
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun testDeviceEmergencyCallState_aggregatesAcrossSlots_allFalse() = runTest {
+ val latest by underTest.isDeviceEmergencyCallCapable.collectLastValue()
+
+ // GIVEN there are multiple slots
+ telephonyManager.stub { on { activeModemCount } doReturn 4 }
+ // GIVEN only one of them reports ECM
+ whenever(telephonyManager.getServiceStateForSlot(any())).thenAnswer { invocation ->
+ when (invocation.getArgument(0) as Int) {
+ 0 -> ServiceState().apply { isEmergencyOnly = false }
+ 1 -> ServiceState().apply { isEmergencyOnly = false }
+ 2 -> ServiceState().apply { isEmergencyOnly = false }
+ 3 -> ServiceState().apply { isEmergencyOnly = false }
+ else -> null
+ }
+ }
+
+ // GIVEN a broadcast goes out for the appropriate subID
+ val intent = serviceStateIntent(subId = -1)
+ broadcastDispatcher.sendIntentToMatchingReceiversOnly(applicationContext, intent)
+ testScope.runCurrent()
+
+ // THEN the device is in ECM, because one of the service states is
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun testConnectionCache_clearsInvalidSubscriptions() = runTest {
+ underTest
+
+ subscriptionManager.stub {
+ on { completeActiveSubscriptionInfoList } doReturn listOf(SUB_1, SUB_2)
+ }
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ val repoCache by underTest.mobileConnectionsBySubId.collectLastValue()
+
+ assertThat(repoCache?.keys).containsExactly(SUB_1_ID, SUB_2_ID)
+
+ // SUB_2 disappears
+ subscriptionManager.stub {
+ on { completeActiveSubscriptionInfoList } doReturn listOf(SUB_1)
+ }
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ assertThat(repoCache?.keys).containsExactly(SUB_1_ID)
+ }
+
+ @Test
+ fun testConnectionCache_clearsInvalidSubscriptions_includingCarrierMerged() = runTest {
+ underTest
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+ setWifiState(isCarrierMerged = true)
+ subscriptionManager.stub {
+ on { completeActiveSubscriptionInfoList } doReturn listOf(SUB_1, SUB_2, SUB_CM)
+ }
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ val repoCache by underTest.mobileConnectionsBySubId.collectLastValue()
+
+ assertThat(repoCache?.keys).containsExactly(SUB_1_ID, SUB_2_ID, SUB_CM_ID)
+
+ // SUB_2 and SUB_CM disappear
+ subscriptionManager.stub {
+ on { completeActiveSubscriptionInfoList } doReturn listOf(SUB_1)
+ }
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ assertThat(repoCache?.keys).containsExactly(SUB_1_ID)
+ }
+
+ /** Regression test for b/261706421 */
+ @Test
+ fun testConnectionsCache_clearMultipleSubscriptionsAtOnce_doesNotThrow() = runTest {
+ underTest
+
+ subscriptionManager.stub {
+ on { completeActiveSubscriptionInfoList } doReturn listOf(SUB_1, SUB_2)
+ }
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ val repoCache by underTest.mobileConnectionsBySubId.collectLastValue()
+
+ assertThat(repoCache?.keys).containsExactly(SUB_1_ID, SUB_2_ID)
+
+ // All subscriptions disappear
+ subscriptionManager.stub { on { completeActiveSubscriptionInfoList } doReturn listOf() }
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ assertThat(repoCache).isEmpty()
+ }
+
+ @Test
+ fun testDefaultDataSubId_updatesOnBroadcast() = runTest {
+ val latest by underTest.defaultDataSubId.collectLastValue()
+
+ assertThat(latest).isEqualTo(null)
+
+ val intent2 =
+ Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
+ .putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_2_ID)
+ broadcastDispatcher.sendIntentToMatchingReceiversOnly(applicationContext, intent2)
+
+ assertThat(latest).isEqualTo(SUB_2_ID)
+
+ val intent1 =
+ Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
+ .putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_1_ID)
+ broadcastDispatcher.sendIntentToMatchingReceiversOnly(applicationContext, intent1)
+
+ assertThat(latest).isEqualTo(SUB_1_ID)
+ }
+
+ @Test
+ fun defaultDataSubId_fetchesInitialValueOnStart() = runTest {
+ subscriptionManagerProxy.fake.defaultDataSubId = 2
+ val latest by underTest.defaultDataSubId.collectLastValue()
+
+ assertThat(latest).isEqualTo(2)
+ }
+
+ @Test
+ fun mobileIsDefault_startsAsFalse() = runTest {
+ assertThat(underTest.mobileIsDefault.collectLastValue().value).isFalse()
+ }
+
+ @Test
+ fun mobileIsDefault_capsHaveCellular_isDefault() = runTest {
+ val caps =
+ Mockito.mock(NetworkCapabilities::class.java).stub {
+ on { hasTransport(TRANSPORT_CELLULAR) } doReturn true
+ }
+
+ val latest by underTest.mobileIsDefault.collectLastValue()
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun mobileIsDefault_capsDoNotHaveCellular_isNotDefault() = runTest {
+ val caps =
+ Mockito.mock(NetworkCapabilities::class.java).stub {
+ on { hasTransport(TRANSPORT_CELLULAR) } doReturn false
+ }
+
+ val latest by underTest.mobileIsDefault.collectLastValue()
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun mobileIsDefault_carrierMergedViaMobile_isDefault() = runTest {
+ val carrierMergedInfo = mock<WifiInfo> { on { isCarrierMerged } doReturn true }
+ val caps =
+ Mockito.mock(NetworkCapabilities::class.java).stub {
+ on { hasTransport(TRANSPORT_CELLULAR) } doReturn true
+ on { transportInfo } doReturn carrierMergedInfo
+ }
+
+ val latest by underTest.mobileIsDefault.collectLastValue()
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun mobileIsDefault_wifiDefault_mobileNotDefault() = runTest {
+ val caps =
+ Mockito.mock(NetworkCapabilities::class.java).stub {
+ on { hasTransport(TRANSPORT_WIFI) } doReturn true
+ }
+
+ val latest by underTest.mobileIsDefault.collectLastValue()
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun mobileIsDefault_ethernetDefault_mobileNotDefault() = runTest {
+ val caps =
+ Mockito.mock(NetworkCapabilities::class.java).stub {
+ on { hasTransport(TRANSPORT_ETHERNET) } doReturn true
+ }
+
+ val latest by underTest.mobileIsDefault.collectLastValue()
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+ assertThat(latest).isFalse()
+ }
+
+ /** Regression test for b/272586234. */
+ @Test
+ fun hasCarrierMergedConnection_carrierMergedViaWifi_isTrue() = runTest {
+ val carrierMergedInfo =
+ mock<WifiInfo> {
+ on { isCarrierMerged } doReturn true
+ on { isPrimary } doReturn true
+ }
+ val caps =
+ Mockito.mock(NetworkCapabilities::class.java).stub {
+ on { hasTransport(TRANSPORT_WIFI) } doReturn true
+ on { transportInfo } doReturn carrierMergedInfo
+ }
+
+ val latest by underTest.hasCarrierMergedConnection.collectLastValue()
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+ setWifiState(isCarrierMerged = true)
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun hasCarrierMergedConnection_carrierMergedViaMobile_isTrue() = runTest {
+ val carrierMergedInfo =
+ mock<WifiInfo> {
+ on { isCarrierMerged } doReturn true
+ on { isPrimary } doReturn true
+ }
+ val caps =
+ Mockito.mock(NetworkCapabilities::class.java).stub {
+ on { hasTransport(TRANSPORT_CELLULAR) } doReturn true
+ on { transportInfo } doReturn carrierMergedInfo
+ }
+
+ val latest by underTest.hasCarrierMergedConnection.collectLastValue()
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+ setWifiState(isCarrierMerged = true)
+
+ assertThat(latest).isTrue()
+ }
+
+ private fun KairosTestScope.newWifiNetwork(wifiInfo: WifiInfo): Network {
+ val network = mock<Network>()
+ val capabilities =
+ Mockito.mock(NetworkCapabilities::class.java).stub {
+ on { hasTransport(TRANSPORT_WIFI) } doReturn true
+ on { transportInfo } doReturn wifiInfo
+ }
+ connectivityManager.stub { on { getNetworkCapabilities(network) } doReturn capabilities }
+ return network
+ }
+
+ /** Regression test for b/272586234. */
+ @Test
+ fun hasCarrierMergedConnection_carrierMergedViaWifiWithVcnTransport_isTrue() = runTest {
+ val carrierMergedInfo =
+ mock<WifiInfo> {
+ on { isCarrierMerged } doReturn true
+ on { isPrimary } doReturn true
+ }
+ val underlyingWifi = newWifiNetwork(carrierMergedInfo)
+ val caps =
+ Mockito.mock(NetworkCapabilities::class.java).stub {
+ on { hasTransport(TRANSPORT_WIFI) } doReturn true
+ on { transportInfo } doReturn vcnTransportInfo
+ on { underlyingNetworks } doReturn listOf(underlyingWifi)
+ }
+
+ val latest by underTest.hasCarrierMergedConnection.collectLastValue()
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+ setWifiState(isCarrierMerged = true)
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun hasCarrierMergedConnection_carrierMergedViaMobileWithVcnTransport_isTrue() = runTest {
+ val carrierMergedInfo =
+ mock<WifiInfo> {
+ on { isCarrierMerged } doReturn true
+ on { isPrimary } doReturn true
+ }
+ val underlyingWifi = newWifiNetwork(carrierMergedInfo)
+ val caps =
+ Mockito.mock(NetworkCapabilities::class.java).stub {
+ on { hasTransport(TRANSPORT_CELLULAR) } doReturn true
+ on { transportInfo } doReturn vcnTransportInfo
+ on { underlyingNetworks } doReturn listOf(underlyingWifi)
+ }
+
+ val latest by underTest.hasCarrierMergedConnection.collectLastValue()
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+ setWifiState(isCarrierMerged = true)
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun hasCarrierMergedConnection_isCarrierMergedViaUnderlyingWifi_isTrue() = runTest {
+ val latest by underTest.hasCarrierMergedConnection.collectLastValue()
+
+ val underlyingNetwork = mock<Network>()
+ val carrierMergedInfo =
+ mock<WifiInfo> {
+ on { isCarrierMerged } doReturn true
+ on { isPrimary } doReturn true
+ }
+ val underlyingWifiCapabilities =
+ Mockito.mock(NetworkCapabilities::class.java).stub {
+ on { hasTransport(TRANSPORT_WIFI) } doReturn true
+ on { transportInfo } doReturn carrierMergedInfo
+ }
+ connectivityManager.stub {
+ on { getNetworkCapabilities(underlyingNetwork) } doReturn underlyingWifiCapabilities
+ }
+
+ // WHEN the main capabilities have an underlying carrier merged network via WIFI
+ // transport and WifiInfo
+ val mainCapabilities =
+ Mockito.mock(NetworkCapabilities::class.java).stub {
+ on { hasTransport(TRANSPORT_CELLULAR) } doReturn true
+ on { transportInfo } doReturn null
+ on { underlyingNetworks } doReturn listOf(underlyingNetwork)
+ }
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+ setWifiState(isCarrierMerged = true)
+
+ // THEN there's a carrier merged connection
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun hasCarrierMergedConnection_isCarrierMergedViaUnderlyingCellular_isTrue() = runTest {
+ val latest by underTest.hasCarrierMergedConnection.collectLastValue()
+
+ val underlyingCarrierMergedNetwork = mock<Network>()
+ val carrierMergedInfo =
+ mock<WifiInfo> {
+ on { isCarrierMerged } doReturn true
+ on { isPrimary } doReturn true
+ }
+
+ // The Wifi network that is under the VCN network
+ val physicalWifiNetwork = newWifiNetwork(carrierMergedInfo)
+
+ val underlyingCapabilities =
+ Mockito.mock(NetworkCapabilities::class.java).stub {
+ on { hasTransport(TRANSPORT_CELLULAR) } doReturn true
+ on { transportInfo } doReturn vcnTransportInfo
+ on { underlyingNetworks } doReturn listOf(physicalWifiNetwork)
+ }
+ connectivityManager.stub {
+ on { getNetworkCapabilities(underlyingCarrierMergedNetwork) } doReturn
+ underlyingCapabilities
+ }
+
+ // WHEN the main capabilities have an underlying carrier merged network via CELLULAR
+ // transport and VcnTransportInfo
+ val mainCapabilities =
+ Mockito.mock(NetworkCapabilities::class.java).stub {
+ on { hasTransport(TRANSPORT_CELLULAR) } doReturn true
+ on { transportInfo } doReturn null
+ on { underlyingNetworks } doReturn listOf(underlyingCarrierMergedNetwork)
+ }
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+ setWifiState(isCarrierMerged = true)
+
+ // THEN there's a carrier merged connection
+ assertThat(latest).isTrue()
+ }
+
+ /** Regression test for b/272586234. */
+ @Test
+ fun hasCarrierMergedConnection_defaultIsWifiNotCarrierMerged_wifiRepoIsCarrierMerged_isTrue() =
+ runTest {
+ val latest by underTest.hasCarrierMergedConnection.collectLastValue()
+
+ // WHEN the default callback is TRANSPORT_WIFI but not carrier merged
+ val carrierMergedInfo = mock<WifiInfo> { on { isCarrierMerged } doReturn false }
+ val caps =
+ Mockito.mock(NetworkCapabilities::class.java).stub {
+ on { hasTransport(TRANSPORT_WIFI) } doReturn true
+ on { transportInfo } doReturn carrierMergedInfo
+ }
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+ // BUT the wifi repo has gotten updates that it *is* carrier merged
+ setWifiState(isCarrierMerged = true)
+
+ // THEN hasCarrierMergedConnection is true
+ assertThat(latest).isTrue()
+ }
+
+ /** Regression test for b/278618530. */
+ @Test
+ fun hasCarrierMergedConnection_defaultIsCellular_wifiRepoIsCarrierMerged_isFalse() = runTest {
+ val latest by underTest.hasCarrierMergedConnection.collectLastValue()
+
+ // WHEN the default callback is TRANSPORT_CELLULAR and not carrier merged
+ val caps =
+ Mockito.mock(NetworkCapabilities::class.java).stub {
+ on { hasTransport(TRANSPORT_CELLULAR) } doReturn true
+ on { transportInfo } doReturn null
+ }
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+ // BUT the wifi repo has gotten updates that it *is* carrier merged
+ setWifiState(isCarrierMerged = true)
+
+ // THEN hasCarrierMergedConnection is **false** (The default network being CELLULAR
+ // takes precedence over the wifi network being carrier merged.)
+ assertThat(latest).isFalse()
+ }
+
+ /** Regression test for b/278618530. */
+ @Test
+ fun hasCarrierMergedConnection_defaultCellular_wifiIsCarrierMerged_airplaneMode_isTrue() =
+ runTest {
+ val latest by underTest.hasCarrierMergedConnection.collectLastValue()
+
+ // WHEN the default callback is TRANSPORT_CELLULAR and not carrier merged
+ val caps =
+ Mockito.mock(NetworkCapabilities::class.java).stub {
+ on { hasTransport(TRANSPORT_CELLULAR) } doReturn true
+ on { transportInfo } doReturn null
+ }
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+ // BUT the wifi repo has gotten updates that it *is* carrier merged
+ setWifiState(isCarrierMerged = true)
+ // AND we're in airplane mode
+ airplaneModeRepository.setIsAirplaneMode(true)
+
+ // THEN hasCarrierMergedConnection is true.
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun defaultConnectionIsValidated_startsAsFalse() = runTest {
+ assertThat(underTest.defaultConnectionIsValidated.collectLastValue().value).isFalse()
+ }
+
+ @Test
+ fun defaultConnectionIsValidated_capsHaveValidated_isValidated() = runTest {
+ val caps =
+ Mockito.mock(NetworkCapabilities::class.java).stub {
+ on { hasCapability(NET_CAPABILITY_VALIDATED) } doReturn true
+ }
+
+ val latest by underTest.defaultConnectionIsValidated.collectLastValue()
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun defaultConnectionIsValidated_capsHaveNotValidated_isNotValidated() = runTest {
+ val caps =
+ Mockito.mock(NetworkCapabilities::class.java).stub {
+ on { hasCapability(NET_CAPABILITY_VALIDATED) } doReturn false
+ }
+
+ val latest by underTest.defaultConnectionIsValidated.collectLastValue()
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun config_initiallyFromContext() = runTest {
+ overrideResource(R.bool.config_showMin3G, true)
+ val configFromContext = MobileMappings.Config.readConfig(applicationContext)
+ assertThat(configFromContext.showAtLeast3G).isTrue()
+
+ val latest by underTest.defaultDataSubRatConfig.collectLastValue()
+
+ assertTrue(latest!!.areEqual(configFromContext))
+ assertTrue(latest!!.showAtLeast3G)
+ }
+
+ @Test
+ fun config_subIdChangeEvent_updated() = runTest {
+ val latest by underTest.defaultDataSubRatConfig.collectLastValue()
+
+ assertThat(latest!!.showAtLeast3G).isFalse()
+
+ overrideResource(R.bool.config_showMin3G, true)
+ val configFromContext = MobileMappings.Config.readConfig(applicationContext)
+ assertThat(configFromContext.showAtLeast3G).isTrue()
+
+ // WHEN the change event is fired
+ val intent =
+ Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
+ .putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_1_ID)
+ broadcastDispatcher.sendIntentToMatchingReceiversOnly(applicationContext, intent)
+
+ // THEN the config is updated
+ assertThat(latest?.areEqual(configFromContext)).isEqualTo(true)
+ assertThat(latest?.showAtLeast3G).isEqualTo(true)
+ }
+
+ @Test
+ fun config_carrierConfigChangeEvent_updated() = runTest {
+ val latest by underTest.defaultDataSubRatConfig.collectLastValue()
+
+ assertThat(latest!!.showAtLeast3G).isFalse()
+
+ overrideResource(R.bool.config_showMin3G, true)
+ val configFromContext = MobileMappings.Config.readConfig(applicationContext)
+ assertThat(configFromContext.showAtLeast3G).isTrue()
+
+ // WHEN the change event is fired
+ broadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ applicationContext,
+ Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED),
+ )
+
+ // THEN the config is updated
+ assertThat(latest?.areEqual(configFromContext)).isEqualTo(true)
+ assertThat(latest?.showAtLeast3G).isEqualTo(true)
+ }
+
+ @Test
+ fun carrierConfig_initialValueIsFetched() = runTest {
+ underTest
+ testScope.runCurrent()
+
+ // Value starts out false
+ assertThat(underTest.defaultDataSubRatConfig.sample().showAtLeast3G).isFalse()
+
+ overrideResource(R.bool.config_showMin3G, true)
+ val configFromContext = MobileMappings.Config.readConfig(applicationContext)
+ assertThat(configFromContext.showAtLeast3G).isTrue()
+
+ assertThat(broadcastDispatcher.numReceiversRegistered).isAtLeast(1)
+
+ // WHEN the change event is fired
+ broadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ applicationContext,
+ Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED),
+ )
+ testScope.runCurrent()
+
+ // WHEN collection starts AFTER the broadcast is sent out
+ val latest by underTest.defaultDataSubRatConfig.collectLastValue()
+
+ // THEN the config has the updated value
+ assertWithMessage("showAtLeast3G is false").that(latest!!.showAtLeast3G).isTrue()
+ assertWithMessage("not equal").that(latest!!.areEqual(configFromContext)).isTrue()
+ }
+
+ @Test
+ fun activeDataChange_inSameGroup_emitsUnit() = runTest {
+ var eventCount = 0
+ underTest
+ testScope.backgroundScope.launch {
+ kairos.activateSpec { underTest.activeSubChangedInGroupEvent.observe { eventCount++ } }
+ }
+ testScope.runCurrent()
+
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>(telephonyManager)
+ .onActiveDataSubscriptionIdChanged(SUB_3_ID_GROUPED)
+ testScope.runCurrent()
+
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>(telephonyManager)
+ .onActiveDataSubscriptionIdChanged(SUB_4_ID_GROUPED)
+ testScope.runCurrent()
+
+ assertThat(eventCount).isEqualTo(1)
+ }
+
+ @Test
+ fun activeDataChange_notInSameGroup_doesNotEmit() = runTest {
+ var eventCount = 0
+ underTest
+ testScope.backgroundScope.launch {
+ kairos.activateSpec { underTest.activeSubChangedInGroupEvent.observe { eventCount++ } }
+ }
+ testScope.runCurrent()
+
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>(telephonyManager)
+ .onActiveDataSubscriptionIdChanged(SUB_3_ID_GROUPED)
+ testScope.runCurrent()
+
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>(telephonyManager)
+ .onActiveDataSubscriptionIdChanged(SUB_1_ID)
+ testScope.runCurrent()
+
+ assertThat(eventCount).isEqualTo(0)
+ }
+
+ @Test
+ fun anySimSecure_propagatesStateFromKeyguardUpdateMonitor() = runTest {
+ val latest by underTest.isAnySimSecure.collectLastValue()
+ assertThat(latest).isFalse()
+
+ val updateMonitorCallback = argumentCaptor<KeyguardUpdateMonitorCallback>()
+ verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
+
+ keyguardUpdateMonitor.stub { on { isSimPinSecure } doReturn true }
+ updateMonitorCallback.lastValue.onSimStateChanged(0, 0, 0)
+
+ assertThat(latest).isTrue()
+
+ keyguardUpdateMonitor.stub { on { isSimPinSecure } doReturn false }
+ updateMonitorCallback.lastValue.onSimStateChanged(0, 0, 0)
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun getIsAnySimSecure_delegatesCallToKeyguardUpdateMonitor() = runTest {
+ val anySimSecure by underTest.isAnySimSecure.collectLastValue()
+
+ assertThat(anySimSecure).isFalse()
+
+ keyguardUpdateMonitor.stub { on { isSimPinSecure } doReturn true }
+ argumentCaptor<KeyguardUpdateMonitorCallback>()
+ .apply { verify(keyguardUpdateMonitor).registerCallback(capture()) }
+ .lastValue
+ .onSimStateChanged(0, 0, 0)
+
+ assertThat(anySimSecure).isTrue()
+ }
+
+ @Test
+ fun noSubscriptionsInEcmMode_notInEcmMode() = runTest {
+ val latest by underTest.isInEcmMode.collectLastValue()
+ testScope.runCurrent()
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun someSubscriptionsInEcmMode_inEcmMode() = runTest {
+ val latest by underTest.isInEcmMode.collectLastValue()
+ testScope.runCurrent()
+
+ getTelephonyCallbackForType<EmergencyCallbackModeListener>(telephonyManager)
+ .onCallbackModeStarted(0, mock(), 0)
+
+ assertThat(latest).isTrue()
+ }
+
+ private fun KairosTestScope.getDefaultNetworkCallback(): ConnectivityManager.NetworkCallback {
+ testScope.runCurrent()
+ val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>()
+ verify(connectivityManager).registerDefaultNetworkCallback(callbackCaptor.capture())
+ return callbackCaptor.lastValue
+ }
+
+ private fun KairosTestScope.setWifiState(isCarrierMerged: Boolean) {
+ if (isCarrierMerged) {
+ val mergedEntry =
+ mock<MergedCarrierEntry> {
+ on { isPrimaryNetwork } doReturn true
+ on { isDefaultNetwork } doReturn true
+ on { subscriptionId } doReturn SUB_CM_ID
+ }
+ wifiPickerTracker.stub {
+ on { mergedCarrierEntry } doReturn mergedEntry
+ on { connectedWifiEntry } doReturn null
+ }
+ } else {
+ val wifiEntry =
+ mock<WifiEntry> {
+ on { isPrimaryNetwork } doReturn true
+ on { isDefaultNetwork } doReturn true
+ }
+ wifiPickerTracker.stub {
+ on { connectedWifiEntry } doReturn wifiEntry
+ on { mergedCarrierEntry } doReturn null
+ }
+ }
+ wifiPickerTrackerCallback.allValues.forEach { it.onWifiEntriesChanged() }
+ }
+
+ private fun KairosTestScope.getSubscriptionCallback(): OnSubscriptionsChangedListener {
+ testScope.runCurrent()
+ return argumentCaptor<OnSubscriptionsChangedListener>()
+ .apply {
+ verify(subscriptionManager).addOnSubscriptionsChangedListener(any(), capture())
+ }
+ .lastValue
+ }
+
+ companion object {
+ // Subscription 1
+ private const val SUB_1_ID = 1
+ private const val SUB_1_NAME = "Carrier $SUB_1_ID"
+ private val GROUP_1 = ParcelUuid(UUID.randomUUID())
+ private val SUB_1 =
+ mock<SubscriptionInfo> {
+ on { subscriptionId } doReturn SUB_1_ID
+ on { groupUuid } doReturn GROUP_1
+ on { carrierName } doReturn SUB_1_NAME
+ on { profileClass } doReturn PROFILE_CLASS_UNSET
+ }
+ private val MODEL_1 =
+ SubscriptionModel(
+ subscriptionId = SUB_1_ID,
+ groupUuid = GROUP_1,
+ carrierName = SUB_1_NAME,
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+
+ // Subscription 2
+ private const val SUB_2_ID = 2
+ private const val SUB_2_NAME = "Carrier $SUB_2_ID"
+ private val GROUP_2 = ParcelUuid(UUID.randomUUID())
+ private val SUB_2 =
+ mock<SubscriptionInfo> {
+ on { subscriptionId } doReturn SUB_2_ID
+ on { groupUuid } doReturn GROUP_2
+ on { carrierName } doReturn SUB_2_NAME
+ on { profileClass } doReturn PROFILE_CLASS_UNSET
+ }
+ private val MODEL_2 =
+ SubscriptionModel(
+ subscriptionId = SUB_2_ID,
+ groupUuid = GROUP_2,
+ carrierName = SUB_2_NAME,
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+
+ // Subs 3 and 4 are considered to be in the same group ------------------------------------
+ private val GROUP_ID_3_4 = ParcelUuid(UUID.randomUUID())
+
+ // Subscription 3
+ private const val SUB_3_ID_GROUPED = 3
+ private val SUB_3 =
+ mock<SubscriptionInfo> {
+ on { subscriptionId } doReturn SUB_3_ID_GROUPED
+ on { groupUuid } doReturn GROUP_ID_3_4
+ on { profileClass } doReturn PROFILE_CLASS_UNSET
+ }
+
+ // Subscription 4
+ private const val SUB_4_ID_GROUPED = 4
+ private val SUB_4 =
+ mock<SubscriptionInfo> {
+ on { subscriptionId } doReturn SUB_4_ID_GROUPED
+ on { groupUuid } doReturn GROUP_ID_3_4
+ on { profileClass } doReturn PROFILE_CLASS_UNSET
+ }
+
+ // Subs 3 and 4 are considered to be in the same group ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+ private const val NET_ID = 123
+ private val NETWORK = mock<Network> { on { getNetId() } doReturn NET_ID }
+
+ // Carrier merged subscription
+ private const val SUB_CM_ID = 5
+ private const val SUB_CM_NAME = "Carrier $SUB_CM_ID"
+ private val SUB_CM =
+ mock<SubscriptionInfo> {
+ on { subscriptionId } doReturn SUB_CM_ID
+ on { carrierName } doReturn SUB_CM_NAME
+ on { profileClass } doReturn PROFILE_CLASS_UNSET
+ }
+ private val MODEL_CM =
+ SubscriptionModel(
+ subscriptionId = SUB_CM_ID,
+ carrierName = SUB_CM_NAME,
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+
+ private val WIFI_INFO_CM =
+ mock<WifiInfo> {
+ on { isPrimary } doReturn true
+ on { isCarrierMerged } doReturn true
+ on { subscriptionId } doReturn SUB_CM_ID
+ }
+ private val WIFI_NETWORK_CAPS_CM =
+ Mockito.mock(NetworkCapabilities::class.java).stub {
+ on { hasTransport(TRANSPORT_WIFI) } doReturn true
+ on { transportInfo } doReturn WIFI_INFO_CM
+ on { hasCapability(NET_CAPABILITY_VALIDATED) } doReturn true
+ }
+
+ private val WIFI_INFO_ACTIVE =
+ mock<WifiInfo> {
+ on { isPrimary } doReturn true
+ on { isCarrierMerged } doReturn false
+ }
+
+ private val WIFI_NETWORK_CAPS_ACTIVE =
+ Mockito.mock(NetworkCapabilities::class.java).stub {
+ on { hasTransport(TRANSPORT_WIFI) } doReturn true
+ on { transportInfo } doReturn WIFI_INFO_ACTIVE
+ on { hasCapability(NET_CAPABILITY_VALIDATED) } doReturn true
+ }
+
+ /**
+ * To properly mimic telephony manager, create a service state, and then turn it into an
+ * intent
+ */
+ private fun serviceStateIntent(subId: Int): Intent {
+ return Intent(Intent.ACTION_SERVICE_STATE).apply {
+ putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairosTest.kt
new file mode 100644
index 0000000..c89dc57
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairosTest.kt
@@ -0,0 +1,868 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
+
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.telephony.CellSignalStrength
+import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.mobile.MobileIconCarrierIdOverrides
+import com.android.settingslib.mobile.MobileIconCarrierIdOverridesImpl
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.log.table.logcatTableLogBuffer
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.CarrierMergedNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.OverrideNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.FIVE_G_OVERRIDE
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.FOUR_G
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.THREE_G
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MobileIconInteractorKairosTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
+ private lateinit var underTest: MobileIconInteractorKairos
+ private val mobileMappingsProxy = FakeMobileMappingsProxy()
+ private val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy, mock())
+
+ private val connectionRepository =
+ FakeMobileConnectionRepository(
+ SUB_1_ID,
+ logcatTableLogBuffer(kosmos, "MobileIconInteractorTest"),
+ )
+
+ private val testDispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ @Before
+ fun setUp() {
+ underTest = createInteractor()
+
+ mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = true
+ connectionRepository.isInService.value = true
+ }
+
+ @Test
+ fun gsm_usesGsmLevel() =
+ testScope.runTest {
+ connectionRepository.isGsm.value = true
+ connectionRepository.primaryLevel.value = GSM_LEVEL
+ connectionRepository.cdmaLevel.value = CDMA_LEVEL
+
+ var latest: Int? = null
+ val job = underTest.signalLevelIcon.onEach { latest = it.level }.launchIn(this)
+
+ assertThat(latest).isEqualTo(GSM_LEVEL)
+
+ job.cancel()
+ }
+
+ @Test
+ fun gsm_alwaysShowCdmaTrue_stillUsesGsmLevel() =
+ testScope.runTest {
+ connectionRepository.isGsm.value = true
+ connectionRepository.primaryLevel.value = GSM_LEVEL
+ connectionRepository.cdmaLevel.value = CDMA_LEVEL
+ mobileIconsInteractor.alwaysUseCdmaLevel.value = true
+
+ var latest: Int? = null
+ val job = underTest.signalLevelIcon.onEach { latest = it.level }.launchIn(this)
+
+ assertThat(latest).isEqualTo(GSM_LEVEL)
+
+ job.cancel()
+ }
+
+ @Test
+ fun notGsm_level_default_unknown() =
+ testScope.runTest {
+ connectionRepository.isGsm.value = false
+
+ var latest: Int? = null
+ val job = underTest.signalLevelIcon.onEach { latest = it.level }.launchIn(this)
+
+ assertThat(latest).isEqualTo(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+ job.cancel()
+ }
+
+ @Test
+ fun notGsm_alwaysShowCdmaTrue_usesCdmaLevel() =
+ testScope.runTest {
+ connectionRepository.isGsm.value = false
+ connectionRepository.primaryLevel.value = GSM_LEVEL
+ connectionRepository.cdmaLevel.value = CDMA_LEVEL
+ mobileIconsInteractor.alwaysUseCdmaLevel.value = true
+
+ var latest: Int? = null
+ val job = underTest.signalLevelIcon.onEach { latest = it.level }.launchIn(this)
+
+ assertThat(latest).isEqualTo(CDMA_LEVEL)
+
+ job.cancel()
+ }
+
+ @Test
+ fun notGsm_alwaysShowCdmaFalse_usesPrimaryLevel() =
+ testScope.runTest {
+ connectionRepository.isGsm.value = false
+ connectionRepository.primaryLevel.value = GSM_LEVEL
+ connectionRepository.cdmaLevel.value = CDMA_LEVEL
+ mobileIconsInteractor.alwaysUseCdmaLevel.value = false
+
+ var latest: Int? = null
+ val job = underTest.signalLevelIcon.onEach { latest = it.level }.launchIn(this)
+
+ assertThat(latest).isEqualTo(GSM_LEVEL)
+
+ job.cancel()
+ }
+
+ @Test
+ fun numberOfLevels_comesFromRepo_whenApplicable() =
+ testScope.runTest {
+ var latest: Int? = null
+ val job =
+ underTest.signalLevelIcon
+ .onEach { latest = (it as? SignalIconModel.Cellular)?.numberOfLevels }
+ .launchIn(this)
+
+ connectionRepository.numberOfLevels.value = 5
+ assertThat(latest).isEqualTo(5)
+
+ connectionRepository.numberOfLevels.value = 4
+ assertThat(latest).isEqualTo(4)
+
+ job.cancel()
+ }
+
+ @Test
+ fun inflateSignalStrength_arbitrarilyAddsOneToTheReportedLevel() =
+ testScope.runTest {
+ connectionRepository.inflateSignalStrength.value = false
+ val latest by collectLastValue(underTest.signalLevelIcon)
+
+ connectionRepository.primaryLevel.value = 4
+ assertThat(latest!!.level).isEqualTo(4)
+
+ connectionRepository.inflateSignalStrength.value = true
+ connectionRepository.primaryLevel.value = 4
+
+ // when INFLATE_SIGNAL_STRENGTH is true, we add 1 to the reported signal level
+ assertThat(latest!!.level).isEqualTo(5)
+ }
+
+ @Test
+ fun networkSlice_configOn_hasPrioritizedCaps_showsSlice() =
+ testScope.runTest {
+ connectionRepository.allowNetworkSliceIndicator.value = true
+ val latest by collectLastValue(underTest.showSliceAttribution)
+
+ connectionRepository.hasPrioritizedNetworkCapabilities.value = true
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun networkSlice_configOn_noPrioritizedCaps_noSlice() =
+ testScope.runTest {
+ connectionRepository.allowNetworkSliceIndicator.value = true
+ val latest by collectLastValue(underTest.showSliceAttribution)
+
+ connectionRepository.hasPrioritizedNetworkCapabilities.value = false
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun networkSlice_configOff_hasPrioritizedCaps_noSlice() =
+ testScope.runTest {
+ connectionRepository.allowNetworkSliceIndicator.value = false
+ val latest by collectLastValue(underTest.showSliceAttribution)
+
+ connectionRepository.hasPrioritizedNetworkCapabilities.value = true
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun networkSlice_configOff_noPrioritizedCaps_noSlice() =
+ testScope.runTest {
+ connectionRepository.allowNetworkSliceIndicator.value = false
+ val latest by collectLastValue(underTest.showSliceAttribution)
+
+ connectionRepository.hasPrioritizedNetworkCapabilities.value = false
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun iconGroup_three_g() =
+ testScope.runTest {
+ connectionRepository.resolvedNetworkType.value =
+ DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
+
+ var latest: NetworkTypeIconModel? = null
+ val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G))
+
+ job.cancel()
+ }
+
+ @Test
+ fun iconGroup_updates_on_change() =
+ testScope.runTest {
+ connectionRepository.resolvedNetworkType.value =
+ DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
+
+ var latest: NetworkTypeIconModel? = null
+ val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
+
+ connectionRepository.resolvedNetworkType.value =
+ DefaultNetworkType(mobileMappingsProxy.toIconKey(FOUR_G))
+
+ assertThat(latest).isEqualTo(NetworkTypeIconModel.DefaultIcon(TelephonyIcons.FOUR_G))
+
+ job.cancel()
+ }
+
+ @Test
+ fun iconGroup_5g_override_type() =
+ testScope.runTest {
+ connectionRepository.resolvedNetworkType.value =
+ OverrideNetworkType(mobileMappingsProxy.toIconKeyOverride(FIVE_G_OVERRIDE))
+
+ var latest: NetworkTypeIconModel? = null
+ val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(NetworkTypeIconModel.DefaultIcon(TelephonyIcons.NR_5G))
+
+ job.cancel()
+ }
+
+ @Test
+ fun iconGroup_default_if_no_lookup() =
+ testScope.runTest {
+ connectionRepository.resolvedNetworkType.value =
+ DefaultNetworkType(mobileMappingsProxy.toIconKey(NETWORK_TYPE_UNKNOWN))
+
+ var latest: NetworkTypeIconModel? = null
+ val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest)
+ .isEqualTo(NetworkTypeIconModel.DefaultIcon(FakeMobileIconsInteractor.DEFAULT_ICON))
+
+ job.cancel()
+ }
+
+ @Test
+ fun iconGroup_carrierMerged_usesOverride() =
+ testScope.runTest {
+ connectionRepository.resolvedNetworkType.value = CarrierMergedNetworkType
+
+ var latest: NetworkTypeIconModel? = null
+ val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest)
+ .isEqualTo(
+ NetworkTypeIconModel.DefaultIcon(CarrierMergedNetworkType.iconGroupOverride)
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun overrideIcon_usesCarrierIdOverride() =
+ testScope.runTest {
+ val overrides =
+ mock<MobileIconCarrierIdOverrides>().also {
+ whenever(it.carrierIdEntryExists(anyInt())).thenReturn(true)
+ whenever(it.getOverrideFor(anyInt(), anyString(), any())).thenReturn(1234)
+ }
+
+ underTest = createInteractor(overrides)
+
+ connectionRepository.resolvedNetworkType.value =
+ DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
+
+ var latest: NetworkTypeIconModel? = null
+ val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest)
+ .isEqualTo(NetworkTypeIconModel.OverriddenIcon(TelephonyIcons.THREE_G, 1234))
+
+ job.cancel()
+ }
+
+ @Test
+ fun alwaysShowDataRatIcon_matchesParent() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job = underTest.alwaysShowDataRatIcon.onEach { latest = it }.launchIn(this)
+
+ mobileIconsInteractor.alwaysShowDataRatIcon.value = true
+ assertThat(latest).isTrue()
+
+ mobileIconsInteractor.alwaysShowDataRatIcon.value = false
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun dataState_connected() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this)
+
+ connectionRepository.dataConnectionState.value = DataConnectionState.Connected
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun dataState_notConnected() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this)
+
+ connectionRepository.dataConnectionState.value = DataConnectionState.Disconnected
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isInService_usesRepositoryValue() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job = underTest.isInService.onEach { latest = it }.launchIn(this)
+
+ connectionRepository.isInService.value = true
+
+ assertThat(latest).isTrue()
+
+ connectionRepository.isInService.value = false
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun roaming_isGsm_usesConnectionModel() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
+
+ connectionRepository.cdmaRoaming.value = true
+ connectionRepository.isGsm.value = true
+ connectionRepository.isRoaming.value = false
+
+ assertThat(latest).isFalse()
+
+ connectionRepository.isRoaming.value = true
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun roaming_isCdma_usesCdmaRoamingBit() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
+
+ connectionRepository.cdmaRoaming.value = false
+ connectionRepository.isGsm.value = false
+ connectionRepository.isRoaming.value = true
+
+ assertThat(latest).isFalse()
+
+ connectionRepository.cdmaRoaming.value = true
+ connectionRepository.isGsm.value = false
+ connectionRepository.isRoaming.value = false
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun roaming_falseWhileCarrierNetworkChangeActive() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
+
+ connectionRepository.cdmaRoaming.value = true
+ connectionRepository.isGsm.value = false
+ connectionRepository.isRoaming.value = true
+ connectionRepository.carrierNetworkChangeActive.value = true
+
+ assertThat(latest).isFalse()
+
+ connectionRepository.cdmaRoaming.value = true
+ connectionRepository.isGsm.value = true
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun networkName_usesOperatorAlphaShortWhenNonNullAndRepoIsDefault() =
+ testScope.runTest {
+ var latest: NetworkNameModel? = null
+ val job = underTest.networkName.onEach { latest = it }.launchIn(this)
+
+ val testOperatorName = "operatorAlphaShort"
+
+ // Default network name, operator name is non-null, uses the operator name
+ connectionRepository.networkName.value = DEFAULT_NAME_MODEL
+ connectionRepository.operatorAlphaShort.value = testOperatorName
+
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived(testOperatorName))
+
+ // Default network name, operator name is null, uses the default
+ connectionRepository.operatorAlphaShort.value = null
+
+ assertThat(latest).isEqualTo(DEFAULT_NAME_MODEL)
+
+ // Derived network name, operator name non-null, uses the derived name
+ connectionRepository.networkName.value = DERIVED_NAME_MODEL
+ connectionRepository.operatorAlphaShort.value = testOperatorName
+
+ assertThat(latest).isEqualTo(DERIVED_NAME_MODEL)
+
+ job.cancel()
+ }
+
+ @Test
+ fun networkNameForSubId_usesOperatorAlphaShortWhenNonNullAndRepoIsDefault() =
+ testScope.runTest {
+ var latest: String? = null
+ val job = underTest.carrierName.onEach { latest = it }.launchIn(this)
+
+ val testOperatorName = "operatorAlphaShort"
+
+ // Default network name, operator name is non-null, uses the operator name
+ connectionRepository.carrierName.value = DEFAULT_NAME_MODEL
+ connectionRepository.operatorAlphaShort.value = testOperatorName
+
+ assertThat(latest).isEqualTo(testOperatorName)
+
+ // Default network name, operator name is null, uses the default
+ connectionRepository.operatorAlphaShort.value = null
+
+ assertThat(latest).isEqualTo(DEFAULT_NAME)
+
+ // Derived network name, operator name non-null, uses the derived name
+ connectionRepository.carrierName.value =
+ NetworkNameModel.SubscriptionDerived(DERIVED_NAME)
+ connectionRepository.operatorAlphaShort.value = testOperatorName
+
+ assertThat(latest).isEqualTo(DERIVED_NAME)
+
+ job.cancel()
+ }
+
+ @Test
+ fun isSingleCarrier_matchesParent() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job = underTest.isSingleCarrier.onEach { latest = it }.launchIn(this)
+
+ mobileIconsInteractor.isSingleCarrier.value = true
+ assertThat(latest).isTrue()
+
+ mobileIconsInteractor.isSingleCarrier.value = false
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isForceHidden_matchesParent() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this)
+
+ mobileIconsInteractor.isForceHidden.value = true
+ assertThat(latest).isTrue()
+
+ mobileIconsInteractor.isForceHidden.value = false
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isAllowedDuringAirplaneMode_matchesRepo() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.isAllowedDuringAirplaneMode)
+
+ connectionRepository.isAllowedDuringAirplaneMode.value = true
+ assertThat(latest).isTrue()
+
+ connectionRepository.isAllowedDuringAirplaneMode.value = false
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun cellBasedIconId_correctLevel_notCutout() =
+ testScope.runTest {
+ connectionRepository.isNonTerrestrial.value = false
+ connectionRepository.isInService.value = true
+ connectionRepository.primaryLevel.value = 1
+ connectionRepository.setDataEnabled(false)
+ connectionRepository.isNonTerrestrial.value = false
+
+ var latest: SignalIconModel.Cellular? = null
+ val job =
+ underTest.signalLevelIcon
+ .onEach { latest = it as? SignalIconModel.Cellular }
+ .launchIn(this)
+
+ assertThat(latest?.level).isEqualTo(1)
+ assertThat(latest?.showExclamationMark).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun icon_usesLevelFromInteractor() =
+ testScope.runTest {
+ connectionRepository.isNonTerrestrial.value = false
+ connectionRepository.isInService.value = true
+
+ var latest: SignalIconModel? = null
+ val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this)
+
+ connectionRepository.primaryLevel.value = 3
+ assertThat(latest!!.level).isEqualTo(3)
+
+ connectionRepository.primaryLevel.value = 1
+ assertThat(latest!!.level).isEqualTo(1)
+
+ job.cancel()
+ }
+
+ @Test
+ fun cellBasedIcon_usesNumberOfLevelsFromInteractor() =
+ testScope.runTest {
+ connectionRepository.isNonTerrestrial.value = false
+
+ var latest: SignalIconModel.Cellular? = null
+ val job =
+ underTest.signalLevelIcon
+ .onEach { latest = it as? SignalIconModel.Cellular }
+ .launchIn(this)
+
+ connectionRepository.numberOfLevels.value = 5
+ assertThat(latest!!.numberOfLevels).isEqualTo(5)
+
+ connectionRepository.numberOfLevels.value = 2
+ assertThat(latest!!.numberOfLevels).isEqualTo(2)
+
+ job.cancel()
+ }
+
+ @Test
+ fun cellBasedIcon_defaultDataDisabled_showExclamationTrue() =
+ testScope.runTest {
+ connectionRepository.isNonTerrestrial.value = false
+ mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = false
+
+ var latest: SignalIconModel.Cellular? = null
+ val job =
+ underTest.signalLevelIcon
+ .onEach { latest = it as? SignalIconModel.Cellular }
+ .launchIn(this)
+
+ assertThat(latest!!.showExclamationMark).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun cellBasedIcon_defaultConnectionFailed_showExclamationTrue() =
+ testScope.runTest {
+ connectionRepository.isNonTerrestrial.value = false
+ mobileIconsInteractor.isDefaultConnectionFailed.value = true
+
+ var latest: SignalIconModel.Cellular? = null
+ val job =
+ underTest.signalLevelIcon
+ .onEach { latest = it as? SignalIconModel.Cellular }
+ .launchIn(this)
+
+ assertThat(latest!!.showExclamationMark).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun cellBasedIcon_enabledAndNotFailed_showExclamationFalse() =
+ testScope.runTest {
+ connectionRepository.isNonTerrestrial.value = false
+ connectionRepository.isInService.value = true
+ mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = true
+ mobileIconsInteractor.isDefaultConnectionFailed.value = false
+
+ var latest: SignalIconModel.Cellular? = null
+ val job =
+ underTest.signalLevelIcon
+ .onEach { latest = it as? SignalIconModel.Cellular }
+ .launchIn(this)
+
+ assertThat(latest!!.showExclamationMark).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun cellBasedIcon_usesEmptyState_whenNotInService() =
+ testScope.runTest {
+ var latest: SignalIconModel.Cellular? = null
+ val job =
+ underTest.signalLevelIcon
+ .onEach { latest = it as? SignalIconModel.Cellular }
+ .launchIn(this)
+
+ connectionRepository.isNonTerrestrial.value = false
+ connectionRepository.isInService.value = false
+
+ assertThat(latest?.level).isEqualTo(0)
+ assertThat(latest?.showExclamationMark).isTrue()
+
+ // Changing the level doesn't overwrite the disabled state
+ connectionRepository.primaryLevel.value = 2
+ assertThat(latest?.level).isEqualTo(0)
+ assertThat(latest?.showExclamationMark).isTrue()
+
+ // Once back in service, the regular icon appears
+ connectionRepository.isInService.value = true
+ assertThat(latest?.level).isEqualTo(2)
+ assertThat(latest?.showExclamationMark).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun cellBasedIcon_usesCarrierNetworkState_whenInCarrierNetworkChangeMode() =
+ testScope.runTest {
+ var latest: SignalIconModel.Cellular? = null
+ val job =
+ underTest.signalLevelIcon
+ .onEach { latest = it as? SignalIconModel.Cellular? }
+ .launchIn(this)
+
+ connectionRepository.isNonTerrestrial.value = false
+ connectionRepository.isInService.value = true
+ connectionRepository.carrierNetworkChangeActive.value = true
+ connectionRepository.primaryLevel.value = 1
+ connectionRepository.cdmaLevel.value = 1
+
+ assertThat(latest!!.level).isEqualTo(1)
+ assertThat(latest!!.carrierNetworkChange).isTrue()
+
+ // SignalIconModel respects the current level
+ connectionRepository.primaryLevel.value = 2
+
+ assertThat(latest!!.level).isEqualTo(2)
+ assertThat(latest!!.carrierNetworkChange).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun satBasedIcon_isUsedWhenNonTerrestrial() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.signalLevelIcon)
+
+ // Start off using cellular
+ assertThat(latest).isInstanceOf(SignalIconModel.Cellular::class.java)
+
+ connectionRepository.isNonTerrestrial.value = true
+
+ assertThat(latest).isInstanceOf(SignalIconModel.Satellite::class.java)
+ }
+
+ @DisableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ @Test
+ // See b/346904529 for more context
+ fun satBasedIcon_doesNotInflateSignalStrength_flagOff() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.signalLevelIcon)
+
+ // GIVEN a satellite connection
+ connectionRepository.isNonTerrestrial.value = true
+ // GIVEN this carrier has set INFLATE_SIGNAL_STRENGTH
+ connectionRepository.inflateSignalStrength.value = true
+
+ connectionRepository.primaryLevel.value = 4
+ assertThat(latest!!.level).isEqualTo(4)
+
+ connectionRepository.inflateSignalStrength.value = true
+ connectionRepository.primaryLevel.value = 4
+
+ // Icon level is unaffected
+ assertThat(latest!!.level).isEqualTo(4)
+ }
+
+ @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ @Test
+ // See b/346904529 for more context
+ fun satBasedIcon_doesNotInflateSignalStrength_flagOn() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.signalLevelIcon)
+
+ // GIVEN a satellite connection
+ connectionRepository.isNonTerrestrial.value = true
+ // GIVEN this carrier has set INFLATE_SIGNAL_STRENGTH
+ connectionRepository.inflateSignalStrength.value = true
+
+ connectionRepository.satelliteLevel.value = 4
+ assertThat(latest!!.level).isEqualTo(4)
+
+ connectionRepository.inflateSignalStrength.value = true
+ connectionRepository.primaryLevel.value = 4
+
+ // Icon level is unaffected
+ assertThat(latest!!.level).isEqualTo(4)
+ }
+
+ @DisableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ @Test
+ fun satBasedIcon_usesPrimaryLevel_flagOff() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.signalLevelIcon)
+
+ // GIVEN a satellite connection
+ connectionRepository.isNonTerrestrial.value = true
+
+ // GIVEN primary level is set
+ connectionRepository.primaryLevel.value = 4
+ connectionRepository.satelliteLevel.value = 0
+
+ // THEN icon uses the primary level because the flag is off
+ assertThat(latest!!.level).isEqualTo(4)
+ }
+
+ @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ @Test
+ fun satBasedIcon_usesSatelliteLevel_flagOn() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.signalLevelIcon)
+
+ // GIVEN a satellite connection
+ connectionRepository.isNonTerrestrial.value = true
+
+ // GIVEN satellite level is set
+ connectionRepository.satelliteLevel.value = 4
+ connectionRepository.primaryLevel.value = 0
+
+ // THEN icon uses the satellite level because the flag is on
+ assertThat(latest!!.level).isEqualTo(4)
+ }
+
+ /**
+ * Context (b/377518113), this test will not be needed after FLAG_CARRIER_ROAMING_NB_IOT_NTN is
+ * rolled out. The new API should report 0 automatically if not in service.
+ */
+ @DisableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ @Test
+ fun satBasedIcon_reportsLevelZeroWhenOutOfService() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.signalLevelIcon)
+
+ // GIVEN a satellite connection
+ connectionRepository.isNonTerrestrial.value = true
+ // GIVEN this carrier has set INFLATE_SIGNAL_STRENGTH
+ connectionRepository.inflateSignalStrength.value = true
+
+ connectionRepository.primaryLevel.value = 4
+ assertThat(latest!!.level).isEqualTo(4)
+
+ connectionRepository.isInService.value = false
+ connectionRepository.primaryLevel.value = 4
+
+ // THEN level reports 0, by policy
+ assertThat(latest!!.level).isEqualTo(0)
+ }
+
+ private fun createInteractor(
+ overrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl()
+ ) =
+ MobileIconInteractorKairosImpl(
+ testScope.backgroundScope,
+ mobileIconsInteractor.activeDataConnectionHasDataEnabled,
+ mobileIconsInteractor.alwaysShowDataRatIcon,
+ mobileIconsInteractor.alwaysUseCdmaLevel,
+ mobileIconsInteractor.isSingleCarrier,
+ mobileIconsInteractor.mobileIsDefault,
+ mobileIconsInteractor.defaultMobileIconMapping,
+ mobileIconsInteractor.defaultMobileIconGroup,
+ mobileIconsInteractor.isDefaultConnectionFailed,
+ mobileIconsInteractor.isForceHidden,
+ connectionRepository,
+ context,
+ overrides,
+ )
+
+ companion object {
+ private const val GSM_LEVEL = 1
+ private const val CDMA_LEVEL = 2
+
+ private const val SUB_1_ID = 1
+
+ private const val DEFAULT_NAME = "test default name"
+ private val DEFAULT_NAME_MODEL = NetworkNameModel.Default(DEFAULT_NAME)
+ private const val DERIVED_NAME = "test derived name"
+ private val DERIVED_NAME_MODEL = NetworkNameModel.IntentDerived(DERIVED_NAME)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosTest.kt
new file mode 100644
index 0000000..a9360d1
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosTest.kt
@@ -0,0 +1,1046 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
+
+import android.os.ParcelUuid
+import android.platform.test.annotations.EnableFlags
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
+import android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING
+import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.mobile.MobileMappings
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fake
+import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runCurrent
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.core.NewStatusBarIcons
+import com.android.systemui.statusbar.core.StatusBarRootModernization
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fake
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepositoryLogbufferName
+import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
+import com.android.systemui.statusbar.pipeline.shared.data.repository.connectivityRepository
+import com.android.systemui.statusbar.pipeline.shared.data.repository.fake
+import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository
+import com.android.systemui.testKosmos
+import com.android.systemui.util.CarrierConfigTracker
+import com.google.common.truth.Truth.assertThat
+import java.util.UUID
+import kotlinx.coroutines.test.advanceTimeBy
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MobileIconsInteractorKairosTest : SysuiTestCase() {
+ private val kosmos by lazy {
+ testKosmos().apply {
+ mobileConnectionsRepositoryLogbufferName = "MobileIconsInteractorTest"
+ mobileConnectionsRepository.fake.run {
+ setMobileConnectionRepositoryMap(
+ mapOf(
+ SUB_1_ID to FakeMobileConnectionRepository(SUB_1_ID, mock()),
+ SUB_2_ID to FakeMobileConnectionRepository(SUB_2_ID, mock()),
+ SUB_3_ID to FakeMobileConnectionRepository(SUB_3_ID, mock()),
+ SUB_4_ID to FakeMobileConnectionRepository(SUB_4_ID, mock()),
+ )
+ )
+ setActiveMobileDataSubscriptionId(SUB_1_ID)
+ }
+ featureFlagsClassic.fake.set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, true)
+ }
+ }
+
+ // shortcut rename
+ private val Kosmos.connectionsRepository by Fixture { mobileConnectionsRepository.fake }
+
+ private val Kosmos.carrierConfigTracker by Fixture { mock<CarrierConfigTracker>() }
+
+ private val Kosmos.underTest by Fixture {
+ MobileIconsInteractorKairosImpl(
+ mobileConnectionsRepository,
+ carrierConfigTracker,
+ tableLogger = mock(),
+ connectivityRepository,
+ FakeUserSetupRepository(),
+ testScope.backgroundScope,
+ context,
+ featureFlagsClassic,
+ )
+ }
+
+ @Test
+ fun filteredSubscriptions_default() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.filteredSubscriptions)
+
+ assertThat(latest).isEqualTo(listOf<SubscriptionModel>())
+ }
+
+ // Based on the logic from the old pipeline, we'll never filter subs when there are more than 2
+ @Test
+ fun filteredSubscriptions_moreThanTwo_doesNotFilter() =
+ kosmos.runTest {
+ connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP, SUB_4_OPP))
+ connectionsRepository.setActiveMobileDataSubscriptionId(SUB_4_ID)
+
+ val latest by collectLastValue(underTest.filteredSubscriptions)
+
+ assertThat(latest).isEqualTo(listOf(SUB_1, SUB_3_OPP, SUB_4_OPP))
+ }
+
+ @Test
+ fun filteredSubscriptions_nonOpportunistic_updatesWithMultipleSubs() =
+ kosmos.runTest {
+ connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
+
+ val latest by collectLastValue(underTest.filteredSubscriptions)
+
+ assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2))
+ }
+
+ @Test
+ fun filteredSubscriptions_opportunistic_differentGroups_doesNotFilter() =
+ kosmos.runTest {
+ connectionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP))
+ connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
+
+ val latest by collectLastValue(underTest.filteredSubscriptions)
+
+ assertThat(latest).isEqualTo(listOf(SUB_3_OPP, SUB_4_OPP))
+ }
+
+ @Test
+ fun filteredSubscriptions_opportunistic_nonGrouped_doesNotFilter() =
+ kosmos.runTest {
+ val (sub1, sub2) =
+ createSubscriptionPair(
+ subscriptionIds = Pair(SUB_1_ID, SUB_2_ID),
+ opportunistic = Pair(true, true),
+ grouped = false,
+ )
+ connectionsRepository.setSubscriptions(listOf(sub1, sub2))
+ connectionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID)
+
+ val latest by collectLastValue(underTest.filteredSubscriptions)
+
+ assertThat(latest).isEqualTo(listOf(sub1, sub2))
+ }
+
+ @Test
+ fun filteredSubscriptions_opportunistic_grouped_configFalse_showsActive_3() =
+ kosmos.runTest {
+ val (sub3, sub4) =
+ createSubscriptionPair(
+ subscriptionIds = Pair(SUB_3_ID, SUB_4_ID),
+ opportunistic = Pair(true, true),
+ grouped = true,
+ )
+ connectionsRepository.setSubscriptions(listOf(sub3, sub4))
+ connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
+ whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
+ .thenReturn(false)
+
+ val latest by collectLastValue(underTest.filteredSubscriptions)
+
+ // Filtered subscriptions should show the active one when the config is false
+ assertThat(latest).isEqualTo(listOf(sub3))
+ }
+
+ @Test
+ fun filteredSubscriptions_opportunistic_grouped_configFalse_showsActive_4() =
+ kosmos.runTest {
+ val (sub3, sub4) =
+ createSubscriptionPair(
+ subscriptionIds = Pair(SUB_3_ID, SUB_4_ID),
+ opportunistic = Pair(true, true),
+ grouped = true,
+ )
+ connectionsRepository.setSubscriptions(listOf(sub3, sub4))
+ connectionsRepository.setActiveMobileDataSubscriptionId(SUB_4_ID)
+ whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
+ .thenReturn(false)
+
+ val latest by collectLastValue(underTest.filteredSubscriptions)
+
+ // Filtered subscriptions should show the active one when the config is false
+ assertThat(latest).isEqualTo(listOf(sub4))
+ }
+
+ @Test
+ fun filteredSubscriptions_oneOpportunistic_grouped_configTrue_showsPrimary_active_1() =
+ kosmos.runTest {
+ val (sub1, sub3) =
+ createSubscriptionPair(
+ subscriptionIds = Pair(SUB_1_ID, SUB_3_ID),
+ opportunistic = Pair(false, true),
+ grouped = true,
+ )
+ connectionsRepository.setSubscriptions(listOf(sub1, sub3))
+ connectionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID)
+ whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
+ .thenReturn(true)
+
+ val latest by collectLastValue(underTest.filteredSubscriptions)
+
+ // Filtered subscriptions should show the primary (non-opportunistic) if the config is
+ // true
+ assertThat(latest).isEqualTo(listOf(sub1))
+ }
+
+ @Test
+ fun filteredSubscriptions_oneOpportunistic_grouped_configTrue_showsPrimary_nonActive_1() =
+ kosmos.runTest {
+ val (sub1, sub3) =
+ createSubscriptionPair(
+ subscriptionIds = Pair(SUB_1_ID, SUB_3_ID),
+ opportunistic = Pair(false, true),
+ grouped = true,
+ )
+ connectionsRepository.setSubscriptions(listOf(sub1, sub3))
+ connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
+ whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
+ .thenReturn(true)
+
+ val latest by collectLastValue(underTest.filteredSubscriptions)
+
+ // Filtered subscriptions should show the primary (non-opportunistic) if the config is
+ // true
+ assertThat(latest).isEqualTo(listOf(sub1))
+ }
+
+ @Test
+ fun filteredSubscriptions_vcnSubId_agreesWithActiveSubId_usesActiveAkaVcnSub() =
+ kosmos.runTest {
+ val (sub1, sub3) =
+ createSubscriptionPair(
+ subscriptionIds = Pair(SUB_1_ID, SUB_3_ID),
+ opportunistic = Pair(true, true),
+ grouped = true,
+ )
+ connectionsRepository.setSubscriptions(listOf(sub1, sub3))
+ connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
+ kosmos.connectivityRepository.fake.vcnSubId.value = SUB_3_ID
+ whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
+ .thenReturn(false)
+
+ val latest by collectLastValue(underTest.filteredSubscriptions)
+
+ assertThat(latest).isEqualTo(listOf(sub3))
+ }
+
+ @Test
+ fun filteredSubscriptions_vcnSubId_disagreesWithActiveSubId_usesVcnSub() =
+ kosmos.runTest {
+ val (sub1, sub3) =
+ createSubscriptionPair(
+ subscriptionIds = Pair(SUB_1_ID, SUB_3_ID),
+ opportunistic = Pair(true, true),
+ grouped = true,
+ )
+ connectionsRepository.setSubscriptions(listOf(sub1, sub3))
+ connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
+ kosmos.connectivityRepository.fake.vcnSubId.value = SUB_1_ID
+ whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
+ .thenReturn(false)
+
+ val latest by collectLastValue(underTest.filteredSubscriptions)
+
+ assertThat(latest).isEqualTo(listOf(sub1))
+ }
+
+ @Test
+ fun filteredSubscriptions_doesNotFilterProvisioningWhenFlagIsFalse() =
+ kosmos.runTest {
+ // GIVEN the flag is false
+ featureFlagsClassic.fake.set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, false)
+
+ // GIVEN 1 sub that is in PROFILE_CLASS_PROVISIONING
+ val sub1 =
+ SubscriptionModel(
+ subscriptionId = SUB_1_ID,
+ isOpportunistic = false,
+ carrierName = "Carrier 1",
+ profileClass = PROFILE_CLASS_PROVISIONING,
+ )
+
+ connectionsRepository.setSubscriptions(listOf(sub1))
+
+ // WHEN filtering is applied
+ val latest by collectLastValue(underTest.filteredSubscriptions)
+
+ // THEN the provisioning sub is still present (unfiltered)
+ assertThat(latest).isEqualTo(listOf(sub1))
+ }
+
+ @Test
+ fun filteredSubscriptions_filtersOutProvisioningSubs() =
+ kosmos.runTest {
+ val sub1 =
+ SubscriptionModel(
+ subscriptionId = SUB_1_ID,
+ isOpportunistic = false,
+ carrierName = "Carrier 1",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+ val sub2 =
+ SubscriptionModel(
+ subscriptionId = SUB_2_ID,
+ isOpportunistic = false,
+ carrierName = "Carrier 2",
+ profileClass = PROFILE_CLASS_PROVISIONING,
+ )
+
+ connectionsRepository.setSubscriptions(listOf(sub1, sub2))
+
+ val latest by collectLastValue(underTest.filteredSubscriptions)
+
+ assertThat(latest).isEqualTo(listOf(sub1))
+ }
+
+ /** Note: I'm not sure if this will ever be the case, but we can test it at least */
+ @Test
+ fun filteredSubscriptions_filtersOutProvisioningSubsBeforeOpportunistic() =
+ kosmos.runTest {
+ // This is a contrived test case, where the active subId is the one that would
+ // also be filtered by opportunistic filtering.
+
+ // GIVEN grouped, opportunistic subscriptions
+ val groupUuid = ParcelUuid(UUID.randomUUID())
+ val sub1 =
+ SubscriptionModel(
+ subscriptionId = 1,
+ isOpportunistic = true,
+ groupUuid = groupUuid,
+ carrierName = "Carrier 1",
+ profileClass = PROFILE_CLASS_PROVISIONING,
+ )
+
+ val sub2 =
+ SubscriptionModel(
+ subscriptionId = 2,
+ isOpportunistic = true,
+ groupUuid = groupUuid,
+ carrierName = "Carrier 2",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+
+ // GIVEN active subId is 1
+ connectionsRepository.setSubscriptions(listOf(sub1, sub2))
+ connectionsRepository.setActiveMobileDataSubscriptionId(1)
+
+ // THEN filtering of provisioning subs takes place first, and we result in sub2
+
+ val latest by collectLastValue(underTest.filteredSubscriptions)
+
+ assertThat(latest).isEqualTo(listOf(sub2))
+ }
+
+ @Test
+ fun filteredSubscriptions_groupedPairAndNonProvisioned_groupedFilteringStillHappens() =
+ kosmos.runTest {
+ // Grouped filtering only happens when the list of subs is length 2. In this case
+ // we'll show that filtering of provisioning subs happens before, and thus grouped
+ // filtering happens even though the unfiltered list is length 3
+ val (sub1, sub3) =
+ createSubscriptionPair(
+ subscriptionIds = Pair(SUB_1_ID, SUB_3_ID),
+ opportunistic = Pair(true, true),
+ grouped = true,
+ )
+
+ val sub2 =
+ SubscriptionModel(
+ subscriptionId = 2,
+ isOpportunistic = true,
+ groupUuid = null,
+ carrierName = "Carrier 2",
+ profileClass = PROFILE_CLASS_PROVISIONING,
+ )
+
+ connectionsRepository.setSubscriptions(listOf(sub1, sub2, sub3))
+ connectionsRepository.setActiveMobileDataSubscriptionId(1)
+
+ val latest by collectLastValue(underTest.filteredSubscriptions)
+
+ assertThat(latest).isEqualTo(listOf(sub1))
+ }
+
+ @Test
+ fun filteredSubscriptions_subNotExclusivelyNonTerrestrial_hasSub() =
+ kosmos.runTest {
+ val notExclusivelyNonTerrestrialSub =
+ SubscriptionModel(
+ isExclusivelyNonTerrestrial = false,
+ subscriptionId = 5,
+ carrierName = "Carrier 5",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+
+ connectionsRepository.setSubscriptions(listOf(notExclusivelyNonTerrestrialSub))
+
+ val latest by collectLastValue(underTest.filteredSubscriptions)
+
+ assertThat(latest).isEqualTo(listOf(notExclusivelyNonTerrestrialSub))
+ }
+
+ @Test
+ fun filteredSubscriptions_subExclusivelyNonTerrestrial_doesNotHaveSub() =
+ kosmos.runTest {
+ val exclusivelyNonTerrestrialSub =
+ SubscriptionModel(
+ isExclusivelyNonTerrestrial = true,
+ subscriptionId = 5,
+ carrierName = "Carrier 5",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+
+ connectionsRepository.setSubscriptions(listOf(exclusivelyNonTerrestrialSub))
+
+ val latest by collectLastValue(underTest.filteredSubscriptions)
+
+ assertThat(latest).isEmpty()
+ }
+
+ @Test
+ fun filteredSubscription_mixOfExclusivelyNonTerrestrialAndOther_hasOtherSubsOnly() =
+ kosmos.runTest {
+ val exclusivelyNonTerrestrialSub =
+ SubscriptionModel(
+ isExclusivelyNonTerrestrial = true,
+ subscriptionId = 5,
+ carrierName = "Carrier 5",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+ val otherSub1 =
+ SubscriptionModel(
+ isExclusivelyNonTerrestrial = false,
+ subscriptionId = 1,
+ carrierName = "Carrier 1",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+ val otherSub2 =
+ SubscriptionModel(
+ isExclusivelyNonTerrestrial = false,
+ subscriptionId = 2,
+ carrierName = "Carrier 2",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+
+ connectionsRepository.setSubscriptions(
+ listOf(otherSub1, exclusivelyNonTerrestrialSub, otherSub2)
+ )
+
+ val latest by collectLastValue(underTest.filteredSubscriptions)
+
+ assertThat(latest).isEqualTo(listOf(otherSub1, otherSub2))
+ }
+
+ @Test
+ fun filteredSubscriptions_exclusivelyNonTerrestrialSub_andOpportunistic_bothFiltersHappen() =
+ kosmos.runTest {
+ // Exclusively non-terrestrial sub
+ val exclusivelyNonTerrestrialSub =
+ SubscriptionModel(
+ isExclusivelyNonTerrestrial = true,
+ subscriptionId = 5,
+ carrierName = "Carrier 5",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+
+ // Opportunistic subs
+ val (sub3, sub4) =
+ createSubscriptionPair(
+ subscriptionIds = Pair(SUB_3_ID, SUB_4_ID),
+ opportunistic = Pair(true, true),
+ grouped = true,
+ )
+
+ // WHEN both an exclusively non-terrestrial sub and opportunistic sub pair is included
+ connectionsRepository.setSubscriptions(listOf(sub3, sub4, exclusivelyNonTerrestrialSub))
+ connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
+
+ val latest by collectLastValue(underTest.filteredSubscriptions)
+
+ // THEN both the only-non-terrestrial sub and the non-active sub are filtered out,
+ // leaving only sub3.
+ assertThat(latest).isEqualTo(listOf(sub3))
+ }
+
+ @Test
+ fun activeDataConnection_turnedOn() =
+ kosmos.runTest {
+ (fakeMobileConnectionsRepository.getRepoForSubId(SUB_1_ID)
+ as FakeMobileConnectionRepository)
+ .dataEnabled
+ .value = true
+
+ val latest by collectLastValue(underTest.activeDataConnectionHasDataEnabled)
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun activeDataConnection_turnedOff() =
+ kosmos.runTest {
+ (fakeMobileConnectionsRepository.getRepoForSubId(SUB_1_ID)
+ as FakeMobileConnectionRepository)
+ .dataEnabled
+ .value = true
+
+ val latest by collectLastValue(underTest.activeDataConnectionHasDataEnabled)
+
+ (fakeMobileConnectionsRepository.getRepoForSubId(SUB_1_ID)
+ as FakeMobileConnectionRepository)
+ .dataEnabled
+ .value = false
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun activeDataConnection_invalidSubId() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.activeDataConnectionHasDataEnabled)
+
+ connectionsRepository.setActiveMobileDataSubscriptionId(INVALID_SUBSCRIPTION_ID)
+
+ // An invalid active subId should tell us that data is off
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun failedConnection_default_validated_notFailed() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.isDefaultConnectionFailed)
+
+ connectionsRepository.mobileIsDefault.value = true
+ connectionsRepository.defaultConnectionIsValidated.value = true
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun failedConnection_notDefault_notValidated_notFailed() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.isDefaultConnectionFailed)
+
+ connectionsRepository.mobileIsDefault.value = false
+ connectionsRepository.defaultConnectionIsValidated.value = false
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun failedConnection_default_notValidated_failed() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.isDefaultConnectionFailed)
+
+ connectionsRepository.mobileIsDefault.value = true
+ connectionsRepository.defaultConnectionIsValidated.value = false
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun failedConnection_carrierMergedDefault_notValidated_failed() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.isDefaultConnectionFailed)
+
+ connectionsRepository.hasCarrierMergedConnection.value = true
+ connectionsRepository.defaultConnectionIsValidated.value = false
+
+ assertThat(latest).isTrue()
+ }
+
+ /** Regression test for b/275076959. */
+ @Test
+ fun failedConnection_dataSwitchInSameGroup_notFailed() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.isDefaultConnectionFailed)
+
+ connectionsRepository.mobileIsDefault.value = true
+ connectionsRepository.defaultConnectionIsValidated.value = true
+ runCurrent()
+
+ // WHEN there's a data change in the same subscription group
+ connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
+ connectionsRepository.defaultConnectionIsValidated.value = false
+ runCurrent()
+
+ // THEN the default connection is *not* marked as failed because of forced validation
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun failedConnection_dataSwitchNotInSameGroup_isFailed() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.isDefaultConnectionFailed)
+
+ connectionsRepository.mobileIsDefault.value = true
+ connectionsRepository.defaultConnectionIsValidated.value = true
+ runCurrent()
+
+ // WHEN the connection is invalidated without a activeSubChangedInGroupEvent
+ connectionsRepository.defaultConnectionIsValidated.value = false
+
+ // THEN the connection is immediately marked as failed
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun alwaysShowDataRatIcon_configHasTrue() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.alwaysShowDataRatIcon)
+
+ val config = MobileMappings.Config()
+ config.alwaysShowDataRatIcon = true
+ connectionsRepository.defaultDataSubRatConfig.value = config
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun alwaysShowDataRatIcon_configHasFalse() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.alwaysShowDataRatIcon)
+
+ val config = MobileMappings.Config()
+ config.alwaysShowDataRatIcon = false
+ connectionsRepository.defaultDataSubRatConfig.value = config
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun alwaysUseCdmaLevel_configHasTrue() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.alwaysUseCdmaLevel)
+
+ val config = MobileMappings.Config()
+ config.alwaysShowCdmaRssi = true
+ connectionsRepository.defaultDataSubRatConfig.value = config
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun alwaysUseCdmaLevel_configHasFalse() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.alwaysUseCdmaLevel)
+
+ val config = MobileMappings.Config()
+ config.alwaysShowCdmaRssi = false
+ connectionsRepository.defaultDataSubRatConfig.value = config
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun isSingleCarrier_zeroSubscriptions_false() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.isSingleCarrier)
+
+ connectionsRepository.setSubscriptions(emptyList())
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun isSingleCarrier_oneSubscription_true() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.isSingleCarrier)
+
+ connectionsRepository.setSubscriptions(listOf(SUB_1))
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun isSingleCarrier_twoSubscriptions_false() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.isSingleCarrier)
+
+ connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun isSingleCarrier_updates() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.isSingleCarrier)
+
+ connectionsRepository.setSubscriptions(listOf(SUB_1))
+ assertThat(latest).isTrue()
+
+ connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun mobileIsDefault_mobileFalseAndCarrierMergedFalse_false() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.mobileIsDefault)
+
+ connectionsRepository.mobileIsDefault.value = false
+ connectionsRepository.hasCarrierMergedConnection.value = false
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun mobileIsDefault_mobileTrueAndCarrierMergedFalse_true() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.mobileIsDefault)
+
+ connectionsRepository.mobileIsDefault.value = true
+ connectionsRepository.hasCarrierMergedConnection.value = false
+
+ assertThat(latest).isTrue()
+ }
+
+ /** Regression test for b/272586234. */
+ @Test
+ fun mobileIsDefault_mobileFalseAndCarrierMergedTrue_true() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.mobileIsDefault)
+
+ connectionsRepository.mobileIsDefault.value = false
+ connectionsRepository.hasCarrierMergedConnection.value = true
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun mobileIsDefault_updatesWhenRepoUpdates() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.mobileIsDefault)
+
+ connectionsRepository.mobileIsDefault.value = true
+ assertThat(latest).isTrue()
+
+ connectionsRepository.mobileIsDefault.value = false
+ assertThat(latest).isFalse()
+
+ connectionsRepository.hasCarrierMergedConnection.value = true
+ assertThat(latest).isTrue()
+ }
+
+ // The data switch tests are mostly testing the [forcingCellularValidation] flow, but that flow
+ // is private and can only be tested by looking at [isDefaultConnectionFailed].
+
+ @Test
+ fun dataSwitch_inSameGroup_validatedMatchesPreviousValue_expiresAfter2s() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.isDefaultConnectionFailed)
+
+ connectionsRepository.mobileIsDefault.value = true
+ connectionsRepository.defaultConnectionIsValidated.value = true
+ runCurrent()
+
+ // Trigger a data change in the same subscription group that's not yet validated
+ connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
+ connectionsRepository.defaultConnectionIsValidated.value = false
+ runCurrent()
+
+ // After 1s, the force validation bit is still present, so the connection is not marked
+ // as failed
+ testScope.advanceTimeBy(1000)
+ assertThat(latest).isFalse()
+
+ // After 2s, the force validation expires so the connection updates to failed
+ testScope.advanceTimeBy(1001)
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun dataSwitch_inSameGroup_notValidated_immediatelyMarkedAsFailed() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.isDefaultConnectionFailed)
+
+ connectionsRepository.mobileIsDefault.value = true
+ connectionsRepository.defaultConnectionIsValidated.value = false
+ runCurrent()
+
+ connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun dataSwitch_loseValidation_thenSwitchHappens_clearsForcedBit() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.isDefaultConnectionFailed)
+
+ // GIVEN the network starts validated
+ connectionsRepository.mobileIsDefault.value = true
+ connectionsRepository.defaultConnectionIsValidated.value = true
+ runCurrent()
+
+ // WHEN a data change happens in the same group
+ connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
+
+ // WHEN the validation bit is lost
+ connectionsRepository.defaultConnectionIsValidated.value = false
+ runCurrent()
+
+ // WHEN another data change happens in the same group
+ connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
+
+ // THEN the forced validation bit is still used...
+ assertThat(latest).isFalse()
+
+ testScope.advanceTimeBy(1000)
+ assertThat(latest).isFalse()
+
+ // ... but expires after 2s
+ testScope.advanceTimeBy(1001)
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun dataSwitch_whileAlreadyForcingValidation_resetsClock() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.isDefaultConnectionFailed)
+ connectionsRepository.mobileIsDefault.value = true
+ connectionsRepository.defaultConnectionIsValidated.value = true
+ runCurrent()
+
+ connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
+
+ testScope.advanceTimeBy(1000)
+
+ // WHEN another change in same group event happens
+ connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
+ connectionsRepository.defaultConnectionIsValidated.value = false
+ runCurrent()
+
+ // THEN the forced validation remains for exactly 2 more seconds from now
+
+ // 1.500s from second event
+ testScope.advanceTimeBy(1500)
+ assertThat(latest).isFalse()
+
+ // 2.001s from the second event
+ testScope.advanceTimeBy(501)
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun isForceHidden_repoHasMobileHidden_true() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.isForceHidden)
+
+ kosmos.connectivityRepository.fake.setForceHiddenIcons(setOf(ConnectivitySlot.MOBILE))
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun isForceHidden_repoDoesNotHaveMobileHidden_false() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.isForceHidden)
+
+ kosmos.connectivityRepository.fake.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI))
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun iconInteractor_cachedPerSubId() =
+ kosmos.runTest {
+ val interactor1 = underTest.getMobileConnectionInteractorForSubId(SUB_1_ID)
+ val interactor2 = underTest.getMobileConnectionInteractorForSubId(SUB_1_ID)
+
+ assertThat(interactor1).isNotNull()
+ assertThat(interactor1).isSameInstanceAs(interactor2)
+ }
+
+ @Test
+ fun deviceBasedEmergencyMode_emergencyCallsOnly_followsDeviceServiceStateFromRepo() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.isDeviceInEmergencyCallsOnlyMode)
+
+ connectionsRepository.isDeviceEmergencyCallCapable.value = true
+
+ assertThat(latest).isTrue()
+
+ connectionsRepository.isDeviceEmergencyCallCapable.value = false
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun defaultDataSubId_tracksRepo() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.defaultDataSubId)
+
+ connectionsRepository.defaultDataSubId.value = 1
+
+ assertThat(latest).isEqualTo(1)
+
+ connectionsRepository.defaultDataSubId.value = 2
+
+ assertThat(latest).isEqualTo(2)
+ }
+
+ @Test
+ @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+ fun isStackable_tracksNumberOfSubscriptions() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.isStackable)
+
+ connectionsRepository.setSubscriptions(listOf(SUB_1))
+ assertThat(latest).isFalse()
+
+ connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
+ assertThat(latest).isTrue()
+
+ connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2, SUB_3_OPP))
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+ fun isStackable_checksForTerrestrialConnections() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.isStackable)
+
+ connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
+ setNumberOfLevelsForSubId(SUB_1_ID, 5)
+ setNumberOfLevelsForSubId(SUB_2_ID, 5)
+ assertThat(latest).isTrue()
+
+ (fakeMobileConnectionsRepository.getRepoForSubId(SUB_1_ID)
+ as FakeMobileConnectionRepository)
+ .isNonTerrestrial
+ .value = true
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME)
+ fun isStackable_checksForNumberOfBars() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.isStackable)
+
+ // Number of levels is the same for both
+ connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
+ setNumberOfLevelsForSubId(SUB_1_ID, 5)
+ setNumberOfLevelsForSubId(SUB_2_ID, 5)
+
+ assertThat(latest).isTrue()
+
+ // Change the number of levels to be different than SUB_2
+ setNumberOfLevelsForSubId(SUB_1_ID, 6)
+
+ assertThat(latest).isFalse()
+ }
+
+ private fun setNumberOfLevelsForSubId(subId: Int, numberOfLevels: Int) {
+ with(kosmos) {
+ (fakeMobileConnectionsRepository.getRepoForSubId(subId)
+ as FakeMobileConnectionRepository)
+ .numberOfLevels
+ .value = numberOfLevels
+ }
+ }
+
+ /**
+ * Convenience method for creating a pair of subscriptions to test the filteredSubscriptions
+ * flow.
+ */
+ private fun createSubscriptionPair(
+ subscriptionIds: Pair<Int, Int>,
+ opportunistic: Pair<Boolean, Boolean> = Pair(false, false),
+ grouped: Boolean = false,
+ ): Pair<SubscriptionModel, SubscriptionModel> {
+ val groupUuid = if (grouped) ParcelUuid(UUID.randomUUID()) else null
+ val sub1 =
+ SubscriptionModel(
+ subscriptionId = subscriptionIds.first,
+ isOpportunistic = opportunistic.first,
+ groupUuid = groupUuid,
+ carrierName = "Carrier ${subscriptionIds.first}",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+
+ val sub2 =
+ SubscriptionModel(
+ subscriptionId = subscriptionIds.second,
+ isOpportunistic = opportunistic.second,
+ groupUuid = groupUuid,
+ carrierName = "Carrier ${opportunistic.second}",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+
+ return Pair(sub1, sub2)
+ }
+
+ companion object {
+
+ private const val SUB_1_ID = 1
+ private val SUB_1 =
+ SubscriptionModel(
+ subscriptionId = SUB_1_ID,
+ carrierName = "Carrier $SUB_1_ID",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+
+ private const val SUB_2_ID = 2
+ private val SUB_2 =
+ SubscriptionModel(
+ subscriptionId = SUB_2_ID,
+ carrierName = "Carrier $SUB_2_ID",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+
+ private const val SUB_3_ID = 3
+ private val SUB_3_OPP =
+ SubscriptionModel(
+ subscriptionId = SUB_3_ID,
+ isOpportunistic = true,
+ groupUuid = ParcelUuid(UUID.randomUUID()),
+ carrierName = "Carrier $SUB_3_ID",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+
+ private const val SUB_4_ID = 4
+ private val SUB_4_OPP =
+ SubscriptionModel(
+ subscriptionId = SUB_4_ID,
+ isOpportunistic = true,
+ groupUuid = ParcelUuid(UUID.randomUUID()),
+ carrierName = "Carrier $SUB_4_ID",
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
index f91e3a6..a083e59 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
@@ -31,6 +31,7 @@
import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel
import com.android.systemui.statusbar.phone.domain.interactor.IsAreaDark
import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.BatteryViewModel
+import com.android.systemui.statusbar.pipeline.shared.ui.model.ChipsVisibilityModel
import com.android.systemui.statusbar.pipeline.shared.ui.model.SystemInfoCombinedVisibilityModel
import com.android.systemui.statusbar.pipeline.shared.ui.model.VisibilityModel
import kotlinx.coroutines.flow.Flow
@@ -52,7 +53,10 @@
override val primaryOngoingActivityChip: MutableStateFlow<OngoingActivityChipModel> =
MutableStateFlow(OngoingActivityChipModel.Inactive())
- override val ongoingActivityChips = MutableStateFlow(MultipleOngoingActivityChipsModel())
+ override val ongoingActivityChips =
+ MutableStateFlow(
+ ChipsVisibilityModel(MultipleOngoingActivityChipsModel(), areChipsAllowed = false)
+ )
override val ongoingActivityChipsLegacy =
MutableStateFlow(MultipleOngoingActivityChipsModelLegacy())
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
index 7e8ee1b..27aa4ba 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
@@ -64,6 +64,7 @@
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.shareToAppChipViewModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.assertIsCallChip
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.assertIsScreenRecordChip
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.assertIsShareToAppChip
import com.android.systemui.statusbar.core.StatusBarRootModernization
@@ -88,6 +89,7 @@
import com.android.systemui.statusbar.phone.data.repository.fakeDarkIconRepository
import com.android.systemui.statusbar.phone.ongoingcall.EnableChipsModernization
import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
+import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallTestHelper.addOngoingCallState
import com.android.systemui.statusbar.pipeline.shared.domain.interactor.setHomeStatusBarIconBlockList
import com.android.systemui.statusbar.pipeline.shared.domain.interactor.setHomeStatusBarInteractorShowOperatorName
import com.android.systemui.statusbar.pipeline.shared.ui.model.VisibilityModel
@@ -716,7 +718,26 @@
}
@Test
- fun canShowOngoingActivityChips_statusBarNotHidden_noSecureCamera_hun_false() =
+ @DisableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ fun canShowOngoingActivityChips_statusBarNotHidden_noSecureCamera_hunBySystem_noHunFlagOff_false() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.canShowOngoingActivityChips)
+
+ transitionKeyguardToGone()
+
+ headsUpNotificationRepository.setNotifications(
+ UnconfinedFakeHeadsUpRowRepository(
+ key = "key",
+ pinnedStatus = MutableStateFlow(PinnedStatus.PinnedBySystem),
+ )
+ )
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ @DisableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ fun canShowOngoingActivityChips_statusBarNotHidden_noSecureCamera_hunByUser_noHunFlagOff_true() =
kosmos.runTest {
val latest by collectLastValue(underTest.canShowOngoingActivityChips)
@@ -729,7 +750,177 @@
)
)
- assertThat(latest).isFalse()
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ @EnableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ fun canShowOngoingActivityChips_statusBarNotHidden_noSecureCamera_hunBySystem_noHunFlagOn_true() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.canShowOngoingActivityChips)
+
+ transitionKeyguardToGone()
+
+ headsUpNotificationRepository.setNotifications(
+ UnconfinedFakeHeadsUpRowRepository(
+ key = "key",
+ pinnedStatus = MutableStateFlow(PinnedStatus.PinnedBySystem),
+ )
+ )
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ @EnableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ fun canShowOngoingActivityChips_statusBarNotHidden_noSecureCamera_hunByUser_noHunFlagOn_true() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.canShowOngoingActivityChips)
+
+ transitionKeyguardToGone()
+
+ headsUpNotificationRepository.setNotifications(
+ UnconfinedFakeHeadsUpRowRepository(
+ key = "key",
+ pinnedStatus = MutableStateFlow(PinnedStatus.PinnedByUser),
+ )
+ )
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ @EnableChipsModernization
+ fun ongoingActivityChips_statusBarHidden_noSecureCamera_noHun_notAllowed() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.ongoingActivityChips)
+
+ // home status bar not allowed
+ kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(false, taskInfo = null)
+
+ assertThat(latest!!.areChipsAllowed).isFalse()
+ }
+
+ @Test
+ @EnableChipsModernization
+ fun ongoingActivityChips_statusBarNotHidden_noSecureCamera_noHun_isAllowed() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.ongoingActivityChips)
+
+ transitionKeyguardToGone()
+
+ assertThat(latest!!.areChipsAllowed).isTrue()
+ }
+
+ @Test
+ @EnableChipsModernization
+ fun ongoingActivityChips_statusBarNotHidden_secureCamera_noHun_notAllowed() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.ongoingActivityChips)
+
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
+ testScope = testScope,
+ )
+ kosmos.keyguardInteractor.onCameraLaunchDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
+
+ assertThat(latest!!.areChipsAllowed).isFalse()
+ }
+
+ @Test
+ @DisableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ @EnableChipsModernization
+ fun ongoingActivityChips_statusBarNotHidden_noSecureCamera_hunBySystem_noHunFlagOff_notAllowed() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.ongoingActivityChips)
+
+ transitionKeyguardToGone()
+
+ headsUpNotificationRepository.setNotifications(
+ UnconfinedFakeHeadsUpRowRepository(
+ key = "key",
+ pinnedStatus = MutableStateFlow(PinnedStatus.PinnedBySystem),
+ )
+ )
+
+ assertThat(latest!!.areChipsAllowed).isFalse()
+ }
+
+ @Test
+ @DisableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ @EnableChipsModernization
+ fun ongoingActivityChips_statusBarNotHidden_noSecureCamera_hunByUser_noHunFlagOff_isAllowed() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.ongoingActivityChips)
+
+ transitionKeyguardToGone()
+
+ headsUpNotificationRepository.setNotifications(
+ UnconfinedFakeHeadsUpRowRepository(
+ key = "key",
+ pinnedStatus = MutableStateFlow(PinnedStatus.PinnedByUser),
+ )
+ )
+
+ assertThat(latest!!.areChipsAllowed).isTrue()
+ }
+
+ @Test
+ @EnableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ @EnableChipsModernization
+ fun ongoingActivityChips_tatusBarNotHidden_noSecureCamera_hunBySystem_noHunFlagOn_isAllowed() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.ongoingActivityChips)
+
+ transitionKeyguardToGone()
+
+ headsUpNotificationRepository.setNotifications(
+ UnconfinedFakeHeadsUpRowRepository(
+ key = "key",
+ pinnedStatus = MutableStateFlow(PinnedStatus.PinnedBySystem),
+ )
+ )
+
+ assertThat(latest!!.areChipsAllowed).isTrue()
+ }
+
+ @Test
+ @EnableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ @EnableChipsModernization
+ fun ongoingActivityChips_statusBarNotHidden_noSecureCamera_hunByUser_noHunFlagOn_isAllowed() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.ongoingActivityChips)
+
+ transitionKeyguardToGone()
+
+ headsUpNotificationRepository.setNotifications(
+ UnconfinedFakeHeadsUpRowRepository(
+ key = "key",
+ pinnedStatus = MutableStateFlow(PinnedStatus.PinnedByUser),
+ )
+ )
+
+ assertThat(latest!!.areChipsAllowed).isTrue()
+ }
+
+ @Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ @EnableChipsModernization
+ fun ongoingActivityChips_followsChipsViewModel() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.ongoingActivityChips)
+ transitionKeyguardToGone()
+
+ screenRecordRepository.screenRecordState.value = ScreenRecordModel.Recording
+
+ assertIsScreenRecordChip(latest!!.chips.active[0])
+
+ addOngoingCallState(key = "call")
+
+ assertIsScreenRecordChip(latest!!.chips.active[0])
+ assertIsCallChip(latest!!.chips.active[1], "call")
}
@Test
@@ -892,7 +1083,7 @@
@Test
@EnableChipsModernization
- fun isNotificationIconContainerVisible_anyChipShowing_ChipsModernizationOn() =
+ fun isNotificationIconContainerVisible_anyChipShowing_chipsModernizationOn() =
kosmos.runTest {
val latest by collectLastValue(underTest.isNotificationIconContainerVisible)
transitionKeyguardToGone()
@@ -909,7 +1100,7 @@
@Test
@DisableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
- fun isNotificationIconContainerVisible_anyChipShowing_PromotedNotifsOn() =
+ fun isNotificationIconContainerVisible_anyChipShowing_promotedNotifsOn() =
kosmos.runTest {
val latest by collectLastValue(underTest.isNotificationIconContainerVisible)
transitionKeyguardToGone()
@@ -929,7 +1120,7 @@
StatusBarRootModernization.FLAG_NAME,
StatusBarChipsModernization.FLAG_NAME,
)
- fun isNotificationIconContainerVisible_anyChipShowing_ChipsModernizationAndPromotedNotifsOff() =
+ fun isNotificationIconContainerVisible_anyChipShowing_chipsModernizationAndPromotedNotifsOff() =
kosmos.runTest {
val latest by collectLastValue(underTest.isNotificationIconContainerVisible)
transitionKeyguardToGone()
@@ -943,6 +1134,86 @@
assertThat(latest!!.visibility).isEqualTo(View.VISIBLE)
}
+ @DisableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ fun isNotificationIconContainerVisible_hasChipButAlsoHun_hunBySystem_noHunFlagOff_visible() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.isNotificationIconContainerVisible)
+ transitionKeyguardToGone()
+
+ // Chip
+ kosmos.screenRecordRepository.screenRecordState.value = ScreenRecordModel.Recording
+
+ // HUN, PinnedBySystem
+ headsUpNotificationRepository.setNotifications(
+ UnconfinedFakeHeadsUpRowRepository(
+ key = "key",
+ pinnedStatus = MutableStateFlow(PinnedStatus.PinnedBySystem),
+ )
+ )
+
+ assertThat(latest!!.visibility).isEqualTo(View.VISIBLE)
+ }
+
+ @DisableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ fun isNotificationIconContainerVisible_hasChipButAlsoHun_hunByUser_noHunFlagOff_gone() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.isNotificationIconContainerVisible)
+ transitionKeyguardToGone()
+
+ // Chip
+ kosmos.screenRecordRepository.screenRecordState.value = ScreenRecordModel.Recording
+
+ // HUN, PinnedByUser
+ headsUpNotificationRepository.setNotifications(
+ UnconfinedFakeHeadsUpRowRepository(
+ key = "key",
+ pinnedStatus = MutableStateFlow(PinnedStatus.PinnedByUser),
+ )
+ )
+
+ assertThat(latest!!.visibility).isEqualTo(View.GONE)
+ }
+
+ @EnableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ fun isNotificationIconContainerVisible_hasChipButAlsoHun_hunBySystem_noHunFlagOn_gone() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.isNotificationIconContainerVisible)
+ transitionKeyguardToGone()
+
+ // Chip
+ kosmos.screenRecordRepository.screenRecordState.value = ScreenRecordModel.Recording
+
+ // HUN, PinnedBySystem
+ headsUpNotificationRepository.setNotifications(
+ UnconfinedFakeHeadsUpRowRepository(
+ key = "key",
+ pinnedStatus = MutableStateFlow(PinnedStatus.PinnedBySystem),
+ )
+ )
+
+ assertThat(latest!!.visibility).isEqualTo(View.GONE)
+ }
+
+ @EnableFlags(StatusBarNoHunBehavior.FLAG_NAME)
+ fun isNotificationIconContainerVisible_hasChipButAlsoHun_hunByUser_noHunFlagOn_gone() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.isNotificationIconContainerVisible)
+ transitionKeyguardToGone()
+
+ // Chip
+ kosmos.screenRecordRepository.screenRecordState.value = ScreenRecordModel.Recording
+
+ // HUN, PinnedByUser
+ headsUpNotificationRepository.setNotifications(
+ UnconfinedFakeHeadsUpRowRepository(
+ key = "key",
+ pinnedStatus = MutableStateFlow(PinnedStatus.PinnedByUser),
+ )
+ )
+
+ assertThat(latest!!.visibility).isEqualTo(View.GONE)
+ }
+
@Test
fun isSystemInfoVisible_allowedByDisableFlags_visible() =
kosmos.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt
index 769f012..8722a48 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt
@@ -23,6 +23,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.testKosmos
import kotlinx.coroutines.runBlocking
@@ -37,7 +38,7 @@
@RunWith(AndroidJUnit4::class)
@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
class MultiDisplayStatusBarWindowControllerStoreTest : SysuiTestCase() {
- private val kosmos = testKosmos()
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val fakeDisplayRepository = kosmos.displayRepository
private val testScope = kosmos.testScope
@@ -59,11 +60,11 @@
}
@Test
- fun displayRemoved_stopsInstance() =
+ fun systemDecorationRemovedEvent_stopsInstance() =
testScope.runTest {
val instance = underTest.forDisplay(DEFAULT_DISPLAY)!!
- fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)
+ fakeDisplayRepository.triggerRemoveSystemDecorationEvent(DEFAULT_DISPLAY)
verify(instance).stop()
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockController.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockController.kt
index 32fec32..4c1f645 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockController.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockController.kt
@@ -13,6 +13,7 @@
*/
package com.android.systemui.plugins.clocks
+import android.graphics.RectF
import com.android.systemui.plugins.annotations.ProtectedInterface
import com.android.systemui.plugins.annotations.SimpleProperty
import java.io.PrintWriter
@@ -37,7 +38,12 @@
val events: ClockEvents
/** Initializes various rendering parameters. If never called, provides reasonable defaults. */
- fun initialize(isDarkTheme: Boolean, dozeFraction: Float, foldFraction: Float)
+ fun initialize(
+ isDarkTheme: Boolean,
+ dozeFraction: Float,
+ foldFraction: Float,
+ onBoundsChanged: (RectF) -> Unit,
+ )
/** Optional method for dumping debug information */
fun dump(pw: PrintWriter)
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
index 7426f06..0ef62a3 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
@@ -53,5 +53,21 @@
/** Identifies a clock design */
typealias ClockId = String
-/** Some data about a clock design */
-data class ClockMetadata(val clockId: ClockId)
+/** Some metadata about a clock design */
+data class ClockMetadata(
+ /** Id for the clock design. */
+ val clockId: ClockId,
+
+ /**
+ * true if this clock is deprecated and should not be used. The ID may still show up in certain
+ * locations to help migrations, but it will not be selectable by new users.
+ */
+ val isDeprecated: Boolean = false,
+
+ /**
+ * Optional mapping of a legacy clock to a new id. This will map users that already are using
+ * `clockId` to the `replacementTarget` instead. The provider should still support the old id
+ * w/o crashing, but can consider it deprecated and the id reserved.
+ */
+ val replacementTarget: ClockId? = null,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/BroadcastRunning.java b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/BroadcastRunning.java
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/BroadcastRunning.java
rename to packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/BroadcastRunning.java
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/Default.java b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/Default.java
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/Default.java
rename to packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/Default.java
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/DisplayId.java b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/DisplayId.java
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/DisplayId.java
rename to packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/DisplayId.java
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/InstrumentationTest.java b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/InstrumentationTest.java
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/InstrumentationTest.java
rename to packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/InstrumentationTest.java
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/LongRunning.java b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/LongRunning.java
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/LongRunning.java
rename to packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/LongRunning.java
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/NotifInflation.kt
similarity index 94%
rename from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt
rename to packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/NotifInflation.kt
index 231fb2d..52a808d 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt
+++ b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/NotifInflation.kt
@@ -21,4 +21,4 @@
@Qualifier
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
-annotation class NotifInflation
+public annotation class NotifInflation
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/PerUser.java b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/PerUser.java
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/PerUser.java
rename to packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/PerUser.java
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/RootView.java b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/RootView.java
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/RootView.java
rename to packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/RootView.java
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/SystemUser.kt
similarity index 88%
copy from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt
copy to packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/SystemUser.kt
index 231fb2d..0ea815f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt
+++ b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/SystemUser.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2024 The Android Open Source Project
+ * Copyright (C) 2023 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.
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.android.systemui.dagger.qualifiers
import javax.inject.Qualifier
@@ -21,4 +20,4 @@
@Qualifier
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
-annotation class NotifInflation
+public annotation class SystemUser
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/TestHarness.java b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/TestHarness.java
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/TestHarness.java
rename to packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/TestHarness.java
diff --git a/packages/SystemUI/res-keyguard/drawable/qs_media_recommendation_bg_gradient.xml b/packages/SystemUI/res-keyguard/drawable/qs_media_recommendation_bg_gradient.xml
deleted file mode 100644
index 495fbb8..0000000
--- a/packages/SystemUI/res-keyguard/drawable/qs_media_recommendation_bg_gradient.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2021 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<shape
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <corners android:radius="24dp"/>
- <gradient
- android:angle="0"
- android:startColor="#00000000"
- android:endColor="#ff000000"
- android:type="linear" />
-</shape>
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index 6d44645..24fd860 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -115,6 +115,7 @@
<dimen name="below_clock_padding_end">16dp</dimen>
<dimen name="below_clock_padding_start_icons">28dp</dimen>
<dimen name="smartspace_padding_horizontal">16dp</dimen>
+ <dimen name="smartspace_padding_vertical">12dp</dimen>
<!-- Proportion of the screen height to use to set the maximum height of the bouncer to when
the device is in the DEVICE_POSTURE_HALF_OPENED posture, for the PIN/pattern entry. 0 will
diff --git a/packages/SystemUI/res/drawable/media_output_dialog_seekbar_background.xml b/packages/SystemUI/res/drawable/media_output_dialog_seekbar_background.xml
index adeb81f..592217b 100644
--- a/packages/SystemUI/res/drawable/media_output_dialog_seekbar_background.xml
+++ b/packages/SystemUI/res/drawable/media_output_dialog_seekbar_background.xml
@@ -23,16 +23,24 @@
/>
<solid android:color="@android:color/transparent" />
<size
- android:height="64dp"/>
+ android:height="@dimen/media_output_dialog_item_height"/>
+ </shape>
+ </item>
+ <item android:id="@+id/contrast_dot" android:right="8dp" android:gravity="center_vertical|end">
+ <shape android:shape="oval">
+ <solid android:color="@color/media_dialog_seekbar_progress" />
+ <size
+ android:width="4dp"
+ android:height="4dp" />
</shape>
</item>
<item android:id="@android:id/progress">
<clip>
<shape>
<corners
- android:radius="16dp"/>
+ android:radius="@dimen/media_output_dialog_background_radius"/>
<size
- android:height="64dp"/>
+ android:height="@dimen/media_output_dialog_item_height"/>
<solid android:color="@color/material_dynamic_primary80" />
</shape>
</clip>
diff --git a/packages/SystemUI/res/drawable/qs_media_rec_scrim.xml b/packages/SystemUI/res/drawable/qs_media_rec_scrim.xml
deleted file mode 100644
index de0a620..0000000
--- a/packages/SystemUI/res/drawable/qs_media_rec_scrim.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <!-- gradient from 25% in the center to 100% at edges -->
- <gradient
- android:type="radial"
- android:gradientRadius="40%p"
- android:startColor="#AE000000"
- android:endColor="#00000000" />
-</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/settingslib_track_off_background.xml b/packages/SystemUI/res/drawable/settingslib_track_off_background.xml
index 3a09284..284953a 100644
--- a/packages/SystemUI/res/drawable/settingslib_track_off_background.xml
+++ b/packages/SystemUI/res/drawable/settingslib_track_off_background.xml
@@ -23,4 +23,5 @@
android:right="@dimen/settingslib_switch_thumb_margin"/>
<solid android:color="@color/settingslib_track_off_color"/>
<corners android:radius="@dimen/settingslib_switch_track_radius"/>
+ <stroke android:width="2dp" android:color="@color/settingslib_track_online_color"/>
</shape>
diff --git a/packages/SystemUI/res/layout/media_output_list_item_advanced.xml b/packages/SystemUI/res/layout/media_output_list_item_advanced.xml
index 6b868b3..ec80cf0 100644
--- a/packages/SystemUI/res/layout/media_output_list_item_advanced.xml
+++ b/packages/SystemUI/res/layout/media_output_list_item_advanced.xml
@@ -151,6 +151,8 @@
<ImageButton
android:id="@+id/end_area_image_button"
android:background="@android:color/transparent"
+ android:padding="20dp"
+ android:scaleType="fitCenter"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@null"
diff --git a/packages/SystemUI/res/layout/media_recommendation_view.xml b/packages/SystemUI/res/layout/media_recommendation_view.xml
deleted file mode 100644
index e63aa21..0000000
--- a/packages/SystemUI/res/layout/media_recommendation_view.xml
+++ /dev/null
@@ -1,90 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 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
- -->
-<!-- Layout for media recommendation item inside QSPanel carousel -->
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
- <!-- Album cover -->
- <ImageView
- android:id="@+id/media_cover"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:translationZ="0dp"
- android:scaleType="matrix"
- android:adjustViewBounds="true"
- android:clipToOutline="true"
- android:layerType="hardware"
- android:background="@drawable/bg_smartspace_media_item"/>
-
- <!-- App icon -->
- <com.android.internal.widget.CachingIconView
- android:id="@+id/media_rec_app_icon"
- android:layout_width="@dimen/qs_media_rec_album_icon_size"
- android:layout_height="@dimen/qs_media_rec_album_icon_size"
- android:minWidth="@dimen/qs_media_rec_album_icon_size"
- android:minHeight="@dimen/qs_media_rec_album_icon_size"
- android:layout_marginStart="@dimen/qs_media_info_spacing"
- android:layout_marginTop="@dimen/qs_media_info_spacing"/>
-
- <!-- Artist name -->
- <TextView
- android:id="@+id/media_title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginStart="@dimen/qs_media_info_spacing"
- android:layout_marginEnd="@dimen/qs_media_info_spacing"
- android:layout_marginBottom="@dimen/qs_media_rec_album_title_bottom_margin"
- android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
- android:singleLine="true"
- android:textSize="12sp"
- android:gravity="top"
- android:layout_gravity="bottom"
- android:importantForAccessibility="no"/>
-
- <!-- Album name -->
- <TextView
- android:id="@+id/media_subtitle"
- android:layout_width="match_parent"
- android:layout_height="@dimen/qs_media_rec_album_subtitle_height"
- android:layout_marginEnd="@dimen/qs_media_info_spacing"
- android:layout_marginStart="@dimen/qs_media_info_spacing"
- android:layout_marginBottom="@dimen/qs_media_info_spacing"
- android:fontFamily="@*android:string/config_headlineFontFamily"
- android:singleLine="true"
- android:textSize="11sp"
- android:gravity="center_vertical"
- android:layout_gravity="bottom"
- android:importantForAccessibility="no"/>
-
- <!-- Seek Bar -->
- <SeekBar
- android:id="@+id/media_progress_bar"
- android:layout_width="match_parent"
- android:layout_height="12dp"
- android:layout_gravity="bottom"
- android:maxHeight="@dimen/qs_media_enabled_seekbar_height"
- android:thumb="@android:color/transparent"
- android:splitTrack="false"
- android:clickable="false"
- android:progressTint="?android:attr/textColorPrimary"
- android:progressBackgroundTint="?android:attr/textColorTertiary"
- android:paddingTop="5dp"
- android:paddingBottom="5dp"
- android:paddingStart="0dp"
- android:paddingEnd="0dp"
- android:layout_marginEnd="@dimen/qs_media_info_spacing"
- android:layout_marginStart="@dimen/qs_media_info_spacing"
- android:layout_marginBottom="@dimen/qs_media_info_spacing"/>
-</merge>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/media_recommendations.xml b/packages/SystemUI/res/layout/media_recommendations.xml
deleted file mode 100644
index 65fc19c..0000000
--- a/packages/SystemUI/res/layout/media_recommendations.xml
+++ /dev/null
@@ -1,75 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 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
- -->
-
-<!-- Layout for media recommendations inside QSPanel carousel -->
-<com.android.systemui.util.animation.TransitionLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/media_recommendations_updated"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:forceHasOverlappingRendering="false"
- android:background="@drawable/qs_media_background"
- android:theme="@style/MediaPlayer">
-
- <!-- This view just ensures the full media player is a certain height. -->
- <View
- android:id="@+id/sizing_view"
- android:layout_width="match_parent"
- android:layout_height="@dimen/qs_media_session_height_expanded" />
-
- <TextView
- android:id="@+id/media_rec_title"
- style="@style/MediaPlayer.Recommendation.Header"
- android:text="@string/controls_media_smartspace_rec_header"/>
-
- <FrameLayout
- android:id="@+id/media_cover1_container"
- style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
- >
-
- <include
- layout="@layout/media_recommendation_view"/>
-
- </FrameLayout>
-
-
- <FrameLayout
- android:id="@+id/media_cover2_container"
- style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
- >
-
- <include
- layout="@layout/media_recommendation_view"/>
-
- </FrameLayout>
-
- <FrameLayout
- android:id="@+id/media_cover3_container"
- style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
- >
-
- <include
- layout="@layout/media_recommendation_view"/>
-
- </FrameLayout>
-
- <include
- layout="@layout/media_long_press_menu" />
-
-</com.android.systemui.util.animation.TransitionLayout>
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index d8e08fc..4179d8a 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Omgewing"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Links"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Regs"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Vou uit na links- en regsgeskeide kontroles"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Vou in na verenigde kontrole"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Demp omgewing"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Wys laeprioriteit-kennisgewingikone"</string>
<string name="other" msgid="429768510980739978">"Ander"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"wissel die teël se grootte"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"verwyder teël"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"voeg teël by die laaste posisie"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Skuif teël"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Swiep op en hou met drie vingers op die raakpaneel om onlangse apps te bekyk"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Druk die handelingsleutel op jou sleutelbord om al jou apps te bekyk"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Gewysig"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Ontsluit om te kyk"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Ontsluit om kode te kyk"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Kontekstuele opvoeding"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Gebruik jou raakpaneel om terug te gaan"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Swiep links of regs met drie vingers. Tik om meer gebare te leer."</string>
@@ -1571,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Onbekend"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Stel alle teëls terug?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Alle Kitsinstellingsteëls sal na die toestel se oorspronklike instellings teruggestel word"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index ea51f00..a92a141 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"በዙሪያ ያሉ"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"ግራ"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"ቀኝ"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"ወደ ግራ እና ቀኝ የተለያዩ ቁጥጥሮች ዘርጋ"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"ወደ የተዋሃደ ቁጥጥር ሰብስብ"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"በዙሪያ ያሉትን ድምፀ-ከል አድርግ"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"አነስተኛ ቅድሚያ ያላቸው የማሳወቂያ አዶዎችን አሳይ"</string>
<string name="other" msgid="429768510980739978">"ሌላ"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"የርዕሱን መጠን ይቀያይሩ"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"ሰቅ አስወግድ"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"በመጨረሻው ቦታ ላይ ሰቅ ያክሉ"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"ሰቁን ውሰድ"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"የቅርብ ጊዜ መተግበሪያዎችን ለማየት የመዳሰሻ ሰሌዳው ላይ በሦስት ጣቶች ወደላይ ያንሸራትቱ እና ይያዙ"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"ሁሉንም መተግበሪያዎችዎን ለማየት በቁልፍ ሰሌዳዎ ላይ ያለውን የተግባር ቁልፍ ይጫኑ"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"ጽሁፍ ተቀይሯል"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"ለመመልከት ይክፈቱ"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"ኮድን ለመመልከት ይክፈቱ"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"የዓውድ ትምህርት"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"ለመመለስ የመዳሰሻ ሰሌዳዎን ይጠቀሙ"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"ሦስት ጣቶችን በመጠቀም ወደ ግራ ወይም ወደ ቀኝ ያንሸራትቱ። ምልክቶችን የበለጠ ለማወቅ መታ ያድርጉ።"</string>
@@ -1571,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"ያልታወቀ"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"ሁሉም ሰቆች ዳግም ይጀምሩ?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"ሁሉም የፈጣን ቅንብሮች ሰቆች ወደ የመሣሪያው የመጀመሪያ ቅንብሮች ዳግም ይጀምራሉ"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 99c156b..13f3fd8 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -245,12 +245,9 @@
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"تم توصيل البلوتوث."</string>
<string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"رمز الجهاز الذي يتضمّن بلوتوث"</string>
<string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"انقر هنا لضبط إعدادات الجهاز."</string>
- <!-- no translation found for accessibility_bluetooth_device_settings_gear_with_name (114373701123165491) -->
- <skip />
- <!-- no translation found for accessibility_bluetooth_device_settings_see_all (5260390270128256620) -->
- <skip />
- <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (7988547106800504256) -->
- <skip />
+ <string name="accessibility_bluetooth_device_settings_gear_with_name" msgid="114373701123165491">"<xliff:g id="DEVICE_NAME">%s</xliff:g>. ضبط تفاصيل الجهاز"</string>
+ <string name="accessibility_bluetooth_device_settings_see_all" msgid="5260390270128256620">"عرض جميع الأجهزة"</string>
+ <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="7988547106800504256">"إقران جهاز جديد"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"نسبة شحن البطارية غير معروفة."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"متصل بـ <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"تم الاتصال بـ <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -427,8 +424,14 @@
<string name="hearing_devices_preset_label" msgid="7878267405046232358">"الإعدادات المسبقة"</string>
<string name="hearing_devices_spinner_item_selected" msgid="3137083889662762383">"تمّ اختياره"</string>
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"الأصوات المحيطة"</string>
- <string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"الجهاز الأيسر"</string>
- <string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"الجهاز الأيمن"</string>
+ <string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"اليسرى"</string>
+ <string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"اليمنى"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"توسيع لوحة التحكّم الموحّدة إلى عناصر تحكُّم منفصلة على اليسار واليمين"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"تصغير عناصر التحكّم في الصوت إلى لوحة تحكُّم موحّدة"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"كتم الأصوات المحيطة"</string>
@@ -906,10 +909,8 @@
<string name="keyboard_shortcut_group_system_multitasking" msgid="6967816258924795558">"تعدُّد المهام"</string>
<string name="system_multitasking_rhs" msgid="8779289852395243004">"استخدام \"وضع تقسيم الشاشة\" مع تثبيت التطبيق على اليمين"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"استخدام \"وضع تقسيم الشاشة\" مع تثبيت التطبيق على اليسار"</string>
- <!-- no translation found for system_multitasking_full_screen (4221409316059910349) -->
- <skip />
- <!-- no translation found for system_multitasking_desktop_view (8829838918507805921) -->
- <skip />
+ <string name="system_multitasking_full_screen" msgid="4221409316059910349">"استخدام وضع ملء الشاشة"</string>
+ <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"استخدام وضع العرض المخصّص للكمبيوتر المكتبي"</string>
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"التبديل إلى التطبيق على اليسار أو الأسفل أثناء استخدام \"تقسيم الشاشة\""</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"التبديل إلى التطبيق على اليمين أو الأعلى أثناء استخدام \"تقسيم الشاشة\""</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"استبدال تطبيق بآخر في وضع \"تقسيم الشاشة\""</string>
@@ -1511,8 +1512,7 @@
<string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"عرض التطبيقات المستخدَمة مؤخرًا"</string>
<string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"التبديل بين التطبيقات"</string>
<string name="touchpad_tutorial_done_button" msgid="176168488821755503">"تم"</string>
- <!-- no translation found for touchpad_tutorial_next_button (9169718126626806688) -->
- <skip />
+ <string name="touchpad_tutorial_next_button" msgid="9169718126626806688">"التالي"</string>
<string name="gesture_error_title" msgid="469064941635578511">"يُرجى إعادة المحاولة"</string>
<string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"رجوع"</string>
<string name="touchpad_back_gesture_guidance" msgid="5352221087725906542">"مرِّر سريعًا لليمين أو لليسار باستخدام 3 أصابع على لوحة اللمس"</string>
@@ -1574,4 +1574,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"غير معروفة"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"هل تريد إعادة ضبط كل المربّعات؟"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"ستتم إعادة ضبط جميع مربّعات \"الإعدادات السريعة\" إلى الإعدادات الأصلية للجهاز"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"\"<xliff:g id="STREAM_NAME">%1$s</xliff:g>\"، <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index 17d24da..067649c 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"আশ-পাশ"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"বাওঁফাল"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"সোঁফাল"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"বাওঁ আৰু সোঁফালৰ পৃথক কৰা নিয়ন্ত্ৰণলৈ সংকোচন কৰক"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"একত্ৰিত নিয়ন্ত্ৰণলৈ সংকোচন কৰক"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"আশ-পাশৰ ধ্বনি মিউট কৰক"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"কম গুৰুত্বপূৰ্ণ জাননীৰ আইকনসমূহ দেখুৱাওক"</string>
<string name="other" msgid="429768510980739978">"অন্যান্য"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"টাইলৰ আকাৰ ট’গল কৰক"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"টাইল আঁতৰাবলৈ"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"অন্তিম স্থানত টাইল যোগ দিয়ক"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"টাইল স্থানান্তৰ কৰক"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"শেহতীয়া এপ্সমূহ চাবলৈ টাচ্চপেডখনত তিনিটা আঙুলিৰে ওপৰলৈ ছোৱাইপ কৰি ধৰি ৰাখক"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"আপোনাৰ আটাইবোৰ এপ্ চাবলৈ আপোনাৰ কীব’ৰ্ডৰ কাৰ্য কীটোত টিপক"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"সম্পাদনা কৰা হৈছে"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"চাবলৈ আনলক কৰক"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"ক’ড চাবলৈ আনলক কৰক"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"প্ৰাসংগিক শিক্ষা"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"উভতি যাবলৈ আপোনাৰ টাচ্চপেড ব্যৱহাৰ কৰক"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"তিনিটা আঙুলি ব্যৱহাৰ কৰি বাওঁফাললৈ বা সোঁফাললৈ ছোৱাইপ কৰক। অধিক নিৰ্দেশ শিকিবলৈ টিপক।"</string>
@@ -1571,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"অজ্ঞাত"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"আটাইবোৰ টাইল ৰিছেট কৰিবনে?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"আটাইবোৰ ক্ষিপ্ৰ ছেটিঙৰ টাইল ডিভাইচৰ মূল ছেটিংছলৈ ৰিছেট হ’ব"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index a0eecd1..9df9923 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Ətraf mühit"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Sol"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Sağ"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Sola və sağa ayrılmış idarəetmələr üçün genişləndirin"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Vahid nəzarət üçün yığcamlaşdırın"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Ətraf mühiti səssiz edin"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Aşağı prioritet bildiriş işarələrini göstərin"</string>
<string name="other" msgid="429768510980739978">"Digər"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"mozaik ölçüsünü aktiv/deaktiv edin"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"lövhəni silin"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"son mövqeyə mozaik əlavə edin"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Lövhəni köçürün"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Son tətbiqlərə baxmaq üçün taçpeddə üç barmağınızla yuxarı çəkib saxlayın"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Bütün tətbiqlərə baxmaq üçün klaviaturada fəaliyyət açarını basın"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Çıxarılıb"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Baxmaq üçün kiliddən çıxarın"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Koda baxmaq üçün kiliddən çıxarın"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Kontekstual təhsil"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Geri qayıtmaq üçün taçped istifadə edin"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Üç barmaqla sola və ya sağa çəkin. Daha çox jest öyrənmək üçün toxunun."</string>
@@ -1571,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Naməlum"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Bütün mozaiklər sıfırlansın?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Bütün Sürətli Ayarlar mozaiki cihazın orijinal ayarlarına sıfırlanacaq"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index 8921eff..6ab7bf1 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Povezani ste sa <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Povezani smo sa uređajem <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Proširite grupu."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Dodajte uređaj u grupu."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Uklonite uređaj iz grupe."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Otvorite aplikaciju."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Nije povezano."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Roming"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Unos"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Slušni aparati"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Uključuje se..."</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Ne možete da prilagodite osvetljenost jer je kontroliše aplikacija u prvom planu"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automatska rotacija"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Automatsko rotiranje ekrana"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Lokacija"</string>
@@ -426,6 +423,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Okruženje"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Levo"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Desno"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Proširi na kontrole razdvojene na levu i desnu stranu"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Skupi u jedinstvenu kontrolu"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Isključi zvuk okruženja"</string>
@@ -1568,4 +1571,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Nepoznato"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Želite da resetujete sve pločice?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Sve pločice Brzih podešavanja će se resetovati na prvobitna podešavanja uređaja"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 3568586..94f9507 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Навакольныя гукі"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Левы бок"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Правы бок"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Зрабіць левую і правую панэлі кіравання"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Зрабіць адну панэль кіравання"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Выключыць навакольныя гукі"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Паказваць значкі апавяшчэнняў з нізкім прыярытэтам"</string>
<string name="other" msgid="429768510980739978">"Іншае"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"змяніць памер пліткі"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"выдаліць плітку"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"дадаць плітку ў апошнюю пазіцыю"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Перамясціць плітку"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Для прагляду нядаўніх праграм правядзіце па сэнсарнай панэлі трыма пальцамі ўверх і затрымайцеся"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Каб праглядзець усе праграмы, націсніце на клавішу дзеяння на клавіятуры"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Схавана"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Разблакіруйце экран, каб праглядзець"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Разблакіруйце экран, каб праглядзець код"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Кантэкстнае навучанне"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Выкарыстайце сэнсарную панэль для вяртання"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Правядзіце ўлева ці ўправа трыма пальцамі. Націсніце, каб азнаёміцца з іншымі жэстамі."</string>
@@ -1571,4 +1574,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Невядома"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Скінуць усе пліткі?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Усе пліткі хуткіх налад будуць скінуты да першапачатковых налад прылады"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 9d6be13..4615e60 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Околни звуци"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Ляво"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Дясно"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Разгъване до отделни контроли за ляво и дясно"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Свиване до обединена контрола"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Спиране на околните звуци"</string>
@@ -1568,4 +1574,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Неизвестно"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Да се нулират ли всички панели?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Всички панели с бързи настройки ще бъдат нулирани до първоначалните настройки на устройството"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g> – <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 636d4f2..6247bf1 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"সারাউন্ডিং"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"বাঁদিক"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"ডানদিক"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"বাঁদিক ও ডানদিকের আলাদা করা কন্ট্রোল বড় করুন"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"ইউনিফায়েড কন্ট্রোল আড়াল করুন"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"সারাউন্ডিং মিউট করুন"</string>
@@ -582,7 +588,7 @@
<string name="media_projection_entry_app_permission_dialog_continue_entire_screen" msgid="1850848182344377579">"স্ক্রিন শেয়ার করুন"</string>
<string name="media_projection_entry_app_permission_dialog_single_app_disabled" msgid="8999903044874669995">"<xliff:g id="APP_NAME">%1$s</xliff:g> এই বিকল্পটি বন্ধ করে দিয়েছে"</string>
<string name="media_projection_entry_share_app_selector_title" msgid="1419515119767501822">"শেয়ার করার জন্য অ্যাপ বেছে নিন"</string>
- <string name="media_projection_entry_cast_permission_dialog_title" msgid="752756942658159416">"আপনার স্ক্রিন কাস্ট করুন?"</string>
+ <string name="media_projection_entry_cast_permission_dialog_title" msgid="752756942658159416">"আপনার স্ক্রিন কাস্ট করতে চান?"</string>
<string name="media_projection_entry_cast_permission_dialog_option_text_single_app" msgid="6073353940838561981">"একটি অ্যাপ কাস্ট করুন"</string>
<string name="media_projection_entry_cast_permission_dialog_option_text_entire_screen" msgid="8389508187954155307">"সম্পূর্ণ স্ক্রিন কাস্ট করুন"</string>
<string name="media_projection_entry_cast_permission_dialog_warning_entire_screen" msgid="4040447861037324017">"আপনি সম্পূর্ণ স্ক্রিন কাস্ট করলে, আপনার স্ক্রিনে থাকা সব কিছুই দেখা যাবে। তাই পাসওয়ার্ড, পেমেন্টের বিবরণ, মেসেজ, ফটো এবং অডিও ও ভিডিওর মতো বিষয়ে সতর্ক থাকুন।"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"কম-গুরুত্বপূর্ণ বিজ্ঞপ্তির আইকন দেখুন"</string>
<string name="other" msgid="429768510980739978">"অন্যান্য"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"টাইলের সাইজ টগল করুন"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"টাইল সরান"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"শেষ জায়গাতে টাইল যোগ করুন"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"টাইল সরান"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"সম্প্রতি ব্যবহার করা অ্যাপ দেখতে, টাচপ্যাডে তিনটি আঙুল ব্যবহার করে উপরের দিকে সোয়াইপ করে ধরে রাখুন"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"আপনার সব অ্যাপ দেখতে, কীবোর্ডে অ্যাকশন কী প্রেস করুন"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"রিড্যাক্ট করা হয়েছে"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"দেখার জন্য আনলক করুন"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"কোড দেখার জন্য আনলক করুন"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"প্রাসঙ্গিক শিক্ষা"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"ফিরে যেতে টাচপ্যাড ব্যবহার করুন"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"তিনটি আঙুলের ব্যবহার করে ডান বা বাঁদিকে সোয়াইপ করুন। আরও জেসচার সম্পর্কে জানতে ট্যাপ করুন।"</string>
@@ -1571,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"অজানা"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"সব টাইল রিসেট করবেন?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"সব কুইক সেটিংস টাইল, ডিভাইসের আসল সেটিংসে রিসেট হয়ে যাবে"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index 331bac2..75f3f08 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Okruženje"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Lijevo"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Desno"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Proširivanje u zasebne kontrole ulijevo i udesno"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Sužavanje u objedinjenu kontrolu"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Isključivanje zvuka okruženja"</string>
@@ -991,7 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Prikaži ikone obavještenja niskog prioriteta"</string>
<string name="other" msgid="429768510980739978">"Ostalo"</string>
- <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"promjenu veličine pločice"</string>
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"uključivanje/isključivanje veličine kartice"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"uklanjanje kartice"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"dodavanje kartice na posljednji položaj"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Pomjeranje kartice"</string>
@@ -1544,8 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Da pregledate nedavne aplikacije, prevucite nagore i zadržite s tri prsta na dodirnoj podlozi"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Da pregledate sve aplikacije, pritisnite tipku radnji na tastaturi"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Redigovano"</string>
- <string name="public_notification_single_line_text" msgid="3576190291791654933">"Otključajte za prikaz"</string>
- <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Otključajte za prikaz koda"</string>
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Otključajte da pogledate"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Otključajte da pogledate kôd"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Kontekstualno obrazovanje"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Koristite dodirnu podlogu da se vratite"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Prevucite ulijevo ili udesno s tri prsta. Dodirnite da naučite više pokreta."</string>
@@ -1568,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Nepoznato"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Vratiti sve kartice na zadano?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Sve kartice Brze postavke će se vratiti na originalne postavke uređaja"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index 01d7692..19b299f 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Entorn"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Esquerra"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Dreta"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Desplega els controls separats d\'esquerra i dreta"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Replega per unificar el control"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Silencia l\'entorn"</string>
@@ -1568,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Desconegut"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Vols restablir totes les icones?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Totes les icones de configuració ràpida es restabliran a les opcions originals del dispositiu"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 15e4703c..51dfa2e 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Okolí"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Vlevo"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Vpravo"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Rozbalit na samostatné ovládání levé a pravé strany"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Sbalit na sjednocené ovládání"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Ztlumit okolí"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Zobrazit ikony oznámení s nízkou prioritou"</string>
<string name="other" msgid="429768510980739978">"Jiné"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"přepnout velikost dlaždice"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"odstranit dlaždici"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"přidat dlaždici na poslední pozici"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Přesunout dlaždici"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Pokud chcete zobrazit poslední aplikace, přejeďte na touchpadu třemi prsty nahoru a podržte je"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Pokud chcete zobrazit všechny aplikace, stiskněte na klávesnici akční klávesu"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Odstraněno"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"K zobrazení je potřeba zařízení odemknout"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Pokud chcete zobrazit kód, odemkněte zařízení"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Kontextová výuka"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Návrat zpět pomocí touchpadu"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Přejeďte třemi prsty doleva nebo doprava. Další gesta zjistíte klepnutím."</string>
@@ -1571,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Neznámé"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Resetovat všechny dlaždice?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Všechny dlaždice Rychlého nastavení se resetují do původní konfigurace zařízení"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index ab87c46..ad6633e 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Omgivelser"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Venstre"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Højre"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Udvid til adskilte styringselementer til venstre og højre"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Minimer til samlet styringselement"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Ignorer omgivelser"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Vis ikoner for notifikationer med lav prioritet"</string>
<string name="other" msgid="429768510980739978">"Andet"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"ændre feltets størrelse"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"fjern felt"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"føj handlingsfeltet til den sidste position"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Flyt felt"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Du kan se nyligt brugte apps ved at stryge opad og holde tre fingre nede på touchpladen"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Du kan se alle dine apps ved at trykke på handlingstasten på dit tastatur"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Skjult"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Lås op for at se"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Lås op for at se koden"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Kontekstbaseret uddannelse"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Brug din touchplade til at gå tilbage"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Stryg til venstre eller højre med tre fingre. Tryk for at lære flere bevægelser."</string>
@@ -1571,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Ukendt"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Vil du nulstille alle handlingsfelter?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Alle handlingsfelter i kvikmenuen nulstilles til enhedens oprindelige indstillinger"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index 99e32ec..8bc3815 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Umgebungsgeräusche"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Links"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Rechts"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"In ein linkes und ein rechtes Steuerfeld maximieren"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Zu einem einzigen Steuerfeld minimieren"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Umgebungsgeräusche stummschalten"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Symbole für Benachrichtigungen mit einer niedrigen Priorität anzeigen"</string>
<string name="other" msgid="429768510980739978">"Sonstiges"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"Größe der Kachel umschalten"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"Entfernen der Kachel"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"Kachel an letzter Position hinzufügen"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Kachel verschieben"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Um zuletzt verwendete Apps aufzurufen, wische mit 3 Fingern nach oben und halte das Touchpad gedrückt"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Wenn du alle deine Apps aufrufen möchtest, drücke auf der Tastatur die Aktionstaste"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Entfernt"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Zum Ansehen entsperren"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Zum Ansehen des Codes entsperren"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Kontextbezogene Informationen"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Zum Zurückgehen Touchpad verwenden"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Wische mit drei Fingern nach links oder rechts. Tippe für mehr Infos zu Touch-Gesten."</string>
@@ -1571,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Unbekannt"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Alle Kacheln zurücksetzen?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Alle Schnelleinstellungen-Kacheln werden auf die Standardeinstellungen des Geräts zurückgesetzt"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index d3bfc99..dade080 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Ήχοι περιβάλλοντος"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Αριστερά"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Δεξιά"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Ανάπτυξη σε ξεχωριστά στοιχεία ελέγχου αριστερά και δεξιά"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Σύμπτυξη σε ενοποιημένο στοιχείο ελέγχου"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Σίγαση ήχων περιβάλλοντος"</string>
@@ -1568,4 +1574,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Άγνωστο"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Επαναφορά σε όλα τα πλακάκια;"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Σε όλα τα πλακάκια Γρήγορων ρυθμίσεων θα γίνει επαναφορά στις αρχικές ρυθμίσεις της συσκευής"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index c253711..fb251dd 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Surroundings"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Left"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Right"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Expand to left and right separated controls"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Collapse to unified control"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Mute surroundings"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Show low-priority notification icons"</string>
<string name="other" msgid="429768510980739978">"Other"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"toggle the tile\'s size"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"remove tile"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"add tile to the last position"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Move tile"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"To view recent apps, swipe up and hold with three fingers on the touchpad"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"To view all your apps, press the action key on your keyboard"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Redacted"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Unlock to view"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Unlock to view code"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Contextual education"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Use your touchpad to go back"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Swipe left or right using three fingers. Tap to learn more gestures."</string>
@@ -1571,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Unknown"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Reset all tiles?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"All Quick Settings tiles will reset to the device\'s original settings"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index 92e9d8d..54ef0cc 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -423,6 +423,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Surroundings"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Left"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Right"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Expand to left and right separated controls"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Collapse to unified control"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Mute surroundings"</string>
@@ -1565,4 +1571,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Unknown"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Reset all tiles?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"All Quick Settings tiles will reset to the device’s original settings"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index c253711..fb251dd 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Surroundings"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Left"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Right"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Expand to left and right separated controls"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Collapse to unified control"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Mute surroundings"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Show low-priority notification icons"</string>
<string name="other" msgid="429768510980739978">"Other"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"toggle the tile\'s size"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"remove tile"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"add tile to the last position"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Move tile"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"To view recent apps, swipe up and hold with three fingers on the touchpad"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"To view all your apps, press the action key on your keyboard"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Redacted"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Unlock to view"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Unlock to view code"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Contextual education"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Use your touchpad to go back"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Swipe left or right using three fingers. Tap to learn more gestures."</string>
@@ -1571,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Unknown"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Reset all tiles?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"All Quick Settings tiles will reset to the device\'s original settings"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index c253711..fb251dd 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Surroundings"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Left"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Right"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Expand to left and right separated controls"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Collapse to unified control"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Mute surroundings"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Show low-priority notification icons"</string>
<string name="other" msgid="429768510980739978">"Other"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"toggle the tile\'s size"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"remove tile"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"add tile to the last position"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Move tile"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"To view recent apps, swipe up and hold with three fingers on the touchpad"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"To view all your apps, press the action key on your keyboard"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Redacted"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Unlock to view"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Unlock to view code"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Contextual education"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Use your touchpad to go back"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Swipe left or right using three fingers. Tap to learn more gestures."</string>
@@ -1571,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Unknown"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Reset all tiles?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"All Quick Settings tiles will reset to the device\'s original settings"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 7f09671..371638b3 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Sonido envolvente"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Izquierda"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Derecha"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Expandir los controles separados a la izquierda y a la derecha"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Contraer al control unificado"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Silenciar el sonido envolvente"</string>
@@ -1568,4 +1574,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Desconocido"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"¿Quieres restablecer todas las tarjetas?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Se restablecerán todas las tarjeta de Configuración rápida a la configuración original del dispositivo"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 531d1df..b02ab30 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Alrededores"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Izquierda"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Derecha"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Expandir a los controles separados de izquierda y derecha"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Contraer al control unificado"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Silenciar alrededores"</string>
@@ -588,7 +594,7 @@
<string name="media_projection_entry_cast_permission_dialog_warning_entire_screen" msgid="4040447861037324017">"Cuando envías toda tu pantalla, se ve todo lo que hay en ella. Debes tener cuidado con elementos como contraseñas, detalles de pagos, mensajes, fotos, audio y vídeo."</string>
<string name="media_projection_entry_cast_permission_dialog_warning_single_app" msgid="7487834861348460736">"Cuando envías una aplicación, se ve todo lo que se muestre o reproduzca en ella. Debes tener cuidado con elementos como contraseñas, detalles de pagos, mensajes, fotos, audio y vídeo."</string>
<string name="media_projection_entry_cast_permission_dialog_continue_entire_screen" msgid="3261124185304676483">"Enviar pantalla"</string>
- <string name="media_projection_entry_cast_app_selector_title" msgid="6323062146661922387">"Elegir una aplicación para enviar"</string>
+ <string name="media_projection_entry_cast_app_selector_title" msgid="6323062146661922387">"Elige una aplicación para enviar"</string>
<string name="media_projection_entry_generic_permission_dialog_title" msgid="4519802931547483628">"¿Empezar a compartir?"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Cuando compartes, grabas o envías contenido, Android puede acceder a todo lo que se muestre en la pantalla o se reproduzca en tu dispositivo. Debes tener cuidado con elementos como contraseñas, detalles de pagos, mensajes, fotos, audio y vídeo."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Cuando compartes, grabas o envías una aplicación, Android puede acceder a todo lo que se muestre o se reproduzca en ella. Debes tener cuidado con elementos como contraseñas, detalles de pagos, mensajes, fotos, audio y vídeo."</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Mostrar iconos de notificaciones con prioridad baja"</string>
<string name="other" msgid="429768510980739978">"Otros"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"mostrar el tamaño del recuadro"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"quitar recuadro"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"añadir el recuadro a la última posición"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Mover recuadro"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Para ver las aplicaciones recientes, desliza hacia arriba y mantén pulsado el panel táctil con tres dedos"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Para ver todas tus aplicaciones, pulsa la tecla de acción de tu teclado"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Oculta"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Desbloquea para ver"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Desbloquea para ver el código"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Educación contextual"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Usa el panel táctil para volver atrás"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Desliza hacia la izquierda o hacia la derecha con tres dedos. Toca para aprender a usar más gestos."</string>
@@ -1571,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Desconocido"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"¿Borrar todos los recuadros?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Todos los recuadros de ajustes rápidos se restablecerán a los ajustes originales del dispositivo"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 30b93b0..578ca4d 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Ümbritsevad helid"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Vasakule"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Paremale"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Vasaku ja parema poole eraldi juhtimine"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Mõlema poole ühtne juhtimine"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Ümbritsevate helide vaigistamine"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Kuva madala prioriteediga märguande ikoonid"</string>
<string name="other" msgid="429768510980739978">"Muu"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"muutke paani suurust"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"paani eemaldamiseks"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"lisage paan viimasesse asukohta"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Teisalda paan"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Hiljutiste rakenduste kuvamiseks pühkige puuteplaadil kolme sõrmega üles ja hoidke sõrmi puuteplaadil."</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Kõigi oma rakenduste kuvamiseks vajutage klaviatuuril toiminguklahvi"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Peidetud"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Vaatamiseks avage"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Koodi vaatamiseks avage"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Kontekstipõhised õpetused"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Puuteplaadi kasutamine tagasiliikumiseks"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Pühkige kolme sõrmega vasakule või paremale. Puudutage liigutuste kohta lisateabe saamiseks."</string>
@@ -1571,4 +1574,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Teadmata"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Kas lähtestada kõik paanid?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Kõik kiirseadete paanid lähtestatakse seadme algseadetele"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 50f8750..c6b8f56 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Ingurunea"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Ezkerrekoa"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Eskuinekoa"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Zabaldu ezkerreko eta eskuineko kontrolatzeko aukerak bereiz erabiltzeko"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Tolestu kontrolatzeko aukerak bateratuta erabiltzeko"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Desaktibatu ingurunearen audioa"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Erakutsi lehentasun txikiko jakinarazpenen ikonoak"</string>
<string name="other" msgid="429768510980739978">"Beste bat"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"aldatu lauzaren tamaina"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"kendu lauza"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"gehitu lauza azken posizioan"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Mugitu lauza"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Azkenaldiko aplikazioak ikusteko, pasatu 3 hatz ukipen-panelean gora eta eduki sakatuta"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Aplikazio guztiak ikusteko, sakatu teklatuko ekintza-tekla"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Desitxuratuta"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Desblokeatu ikusteko"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Desblokeatu kodea ikusteko"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Testuinguruaren araberako hezkuntza"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Erabili ukipen-panela atzera egiteko"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Pasatu 3 hatz ezkerrera edo eskuinera. Sakatu keinu gehiago ikasteko."</string>
@@ -1571,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Ezezagunak"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Lauza guztiak berrezarri nahi dituzu?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Gailuaren jatorrizko ezarpenak berrezarriko dira ezarpen bizkorren lauza guztietan"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index d53805d..6f4d1bc 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"پیرامون"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"چپ"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"راست"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"ازهم بازکردن برای کنترلهای جداگانه چپ و راست"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"جمع کردن برای کنترل یکپارچه"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"بیصدا کردن پیرامون"</string>
@@ -1568,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"نامشخص"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"همه کاشیها بازنشانی شود؟"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"همه کاشیهای «تنظیمات فوری» به تنظیمات اصلی دستگاه بازنشانی خواهد شد"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index fa9468d..527a292 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -428,6 +428,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Ympäristö"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Vasen"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Oikea"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Laajenna vasemmalle ja oikealle erilliset ohjaimet"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Tiivistä yhtenäiseksi säätimeksi"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Mykistä ympäristö"</string>
@@ -993,8 +999,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Näytä vähemmän tärkeät ilmoituskuvakkeet"</string>
<string name="other" msgid="429768510980739978">"Muu"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"ruudun koko päälle/pois"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"poista kiekko"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"lisää laatta viimeiseen kohtaan"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Siirrä kiekkoa"</string>
@@ -1547,10 +1552,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Näet äskeiset sovellukset, kun pyyhkäiset ylös ja pidät kosketuslevyä painettuna kolmella sormella."</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Jos haluat nähdä kaikki sovellukset, paina näppäimistön toimintonäppäintä"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Sensuroitu"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Avaa lukitus ja katso tiedot"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Avaa lukitus nähdäksesi koodin"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Kontekstuaalinen koulutus"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Takaisin siirtyminen kosketuslevyn avulla"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Pyyhkäise vasemmalle tai oikealle kolmella sormella. Lue lisää eleistä napauttamalla."</string>
@@ -1573,4 +1576,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Tuntematon"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Nollataanko kaikki laatat?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Kaikki pika-asetuslaatat palautetaan laitteen alkuperäisiin asetuksiin"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 97702f0..6a2d7c8 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Environnement"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Gauche"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Droit"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Développer les commandes distinctes à gauche et à droite"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Passer au contrôle unifié"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Ignorer les sons de l\'environnement"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Afficher les icônes de notification de faible priorité"</string>
<string name="other" msgid="429768510980739978">"Autre"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"activer ou désactiver la taille de la tuile"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"retirer la tuile"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"Ajouter une tuile à la dernière position"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Déplacer la tuile"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Pour afficher les applis récentes, balayez l\'écran vers le haut avec trois doigts sur le pavé tactile et maintenez-les en place"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Pour afficher toutes vos applis, appuyez sur la touche d\'action de votre clavier"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Supprimé"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Déverrouillez pour afficher"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Déverrouillez pour afficher le code"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Enseignement contextuel"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Utiliser votre pavé tactile pour revenir en arrière"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Balayez vers la gauche ou vers la droite avec trois doigts. Touchez pour apprendre d\'autres gestes."</string>
@@ -1571,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Inconnu"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Réinitialiser toutes les tuiles?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Toutes les tuiles des paramètres rapides seront réinitialisées aux paramètres par défaut de l\'appareil."</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index 1fefa2d..62916e8 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Sons environnants"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Gauche"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Droite"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Développer les commandes gauche et droite"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Réduire en une commande unifiée"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Couper le mode Sons environnants"</string>
@@ -1568,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Inconnu"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Réinitialiser tous les blocs ?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Tous les blocs \"Réglages rapides\" seront réinitialisés aux paramètres d\'origine de l\'appareil"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index d9a991e..87980c8 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Ambiente"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Esquerdo"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Dereito"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Despregar para controis separados do lado esquerdo e do dereito"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Contraer para control unificado"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Silenciar o ambiente"</string>
@@ -1568,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Categoría descoñecida"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Queres restablecer todos os atallos?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Restablecerase a configuración orixinal do dispositivo para todos os atallos de Configuración rápida"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 55f6e94..e61d4e1 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"આસપાસના અવાજો"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"ડાબે"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"જમણે"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"ડાબે અને જમણે અલગ કરેલા નિયંત્રણો સુધી વિસ્તૃત કરો"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"એકીકૃત નિયંત્રણ સુધી નાનું કરો"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"આસપાસના અવાજો મ્યૂટ કરો"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"ઓછી પ્રાધાન્યતાનું નોટિફિકેશન આઇકન બતાવો"</string>
<string name="other" msgid="429768510980739978">"અન્ય"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"ટાઇલના કદને ટૉગલ કરો"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"ટાઇલ કાઢી નાખો"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"છેલ્લા સ્થાનમાં ટાઇલ ઉમેરો"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"ટાઇલ ખસેડો"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"તાજેતરની ઍપ જોવા માટે, ટચપૅડ પર ત્રણ આંગળીઓ વડે ઉપર સ્વાઇપ કરો અને દબાવી રાખો"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"તમારી બધી ઍપ જોવા માટે, તમારા કીબોર્ડ પર ઍક્શન કી દબાવો"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"બદલાવેલું"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"જોવા માટે અનલૉક કરો"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"કોડ જોવા માટે અનલૉક કરો"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"સંદર્ભાત્મક શિક્ષણ"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"પાછા જવા માટે તમારા ટચપૅડનો ઉપયોગ કરો"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"ત્રણ આંગળીનો ઉપયોગ કરીને ડાબે અથવા જમણે સ્વાઇપ કરો. સંકેતો વિશે વધુ જાણવા માટે ટૅપ કરો."</string>
@@ -1571,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"અજાણ"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"તમામ ટાઇલ રીસેટ કરીએ?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"તમામ ઝડપી સેટિંગ ટાઇલને ડિવાઇસના ઑરિજિનલ સેટિંગ પર રીસેટ કરવામાં આવશે"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 8878c14..0dfbb4c 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"आस-पास का वॉल्यूम"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"बाईं ओर के वॉल्यूम के लिए"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"दाईं ओर के वॉल्यूम के लिए"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"दाईं और बाईं ओर के वॉल्यूम को अलग-अलग मैनेज करने के लिए, वॉल्यूम पैनल को बड़ा करें"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"यूनिफ़ाइड कंट्रोल पर जाने के लिए पैनल को छोटा करें"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"आस-पास के वॉल्यूम को म्यूट करें"</string>
@@ -606,7 +612,7 @@
<string name="notification_settings_button_description" msgid="2441994740884163889">"सूचना सेटिंग"</string>
<string name="notification_history_button_description" msgid="1578657591405033383">"सूचनाओं का इतिहास"</string>
<string name="notification_section_header_incoming" msgid="850925217908095197">"नई सूचनाएं"</string>
- <string name="notification_section_header_gentle" msgid="6804099527336337197">"साइलेंट मोड में मिली सूचनाएं"</string>
+ <string name="notification_section_header_gentle" msgid="6804099527336337197">"बिना आवाज़ के मिलने वाली सूचनाएं"</string>
<string name="notification_section_header_alerting" msgid="5581175033680477651">"सूचनाएं"</string>
<string name="notification_section_header_conversations" msgid="821834744538345661">"बातचीत"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"बिना आवाज़ की सभी सूचनाएं हटाएं"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"कम प्राथमिकता वाली सूचना के आइकॉन दिखाएं"</string>
<string name="other" msgid="429768510980739978">"अन्य"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"टाइल के साइज़ को टॉगल करने के लिए दो बार टैप करें"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"टाइल हटाएं"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"आखिरी जगह पर टाइल जोड़ें"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"टाइल को किसी और पोज़िशन पर ले जाएं"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"हाल ही में इस्तेमाल हुए ऐप देखने के लिए, टचपैड पर तीन उंगलियों से ऊपर की ओर स्वाइप करके दबाकर रखें"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"सभी ऐप्लिकेशन देखने के लिए, कीबोर्ड पर ऐक्शन बटन दबाएं"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"जानकारी छिपाने के लिए सूचना में बदलाव किया गया"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"सार्वजनिक सूचना देखने के लिए डिवाइस अनलॉक करें"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"कोड देखने के लिए अनलॉक करें"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"कॉन्टेक्स्ट के हिसाब से जानकारी"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"वापस जाने के लिए, अपने डिवाइस के टचपैड का इस्तेमाल करें"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"तीन उंगलियों से बाईं या दाईं ओर स्वाइप करें. ज़्यादा जेस्चर के बारे में जानने के लिए टैप करें."</string>
@@ -1571,4 +1574,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"कोई जानकारी नहीं है"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"क्या सभी टाइल रीसेट करनी हैं?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"क्विक सेटिंग टाइल, डिवाइस की ओरिजनल सेटिंग पर रीसेट हो जाएंगी"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index 054f394..4dba930 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Okruženje"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Lijevo"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Desno"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Proširi u zasebne kontrole slijeva i zdesna"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Sažmi u objedinjenu kontrolu"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Isključi zvuk okruženja"</string>
@@ -1568,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Nepoznato"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Želite li poništiti sve pločice?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Sve pločice Brze postavke vratit će se na izvorne postavke uređaja"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 03f1bc6..891cf28 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Környezet"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Bal"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Jobb"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Kibontás a balra és jobbra elválasztott vezérlőkhöz"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Összecsukás az egységes vezérléshez"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Környezet némítása"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Alacsony prioritású értesítési ikonok mutatása"</string>
<string name="other" msgid="429768510980739978">"Egyéb"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"a mozaik méretének módosítása"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"mozaik eltávolításához"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"mozaik hozzáadása az utolsó pozícióhoz"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Mozaik áthelyezése"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"A legutóbbi appokért csúsztasson lefelé három ujjal az érintőpadon, és tartsa lenyomva ujjait."</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Az összes alkalmazás megtekintéséhez nyomja meg a billentyűzet műveletbillentyűjét."</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Törölve"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Oldja fel a megtekintéshez"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Kód a megtekintéshez való feloldáshoz"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Kontextusfüggő tájékoztató párbeszédpanel"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"A visszalépéshez használja az érintőpadot"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Csúsztatasson gyorsan három ujjal balra vagy jobbra. Koppintson a további kézmozdulatokért."</string>
@@ -1571,4 +1574,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Ismeretlen"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Visszaállítja az összes mozaikot?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Az összes Gyorsbeállítások mozaik visszaáll az eszköz eredeti beállításaira"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index e1b6639..f231822 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Շրջակայք"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Ձախ"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Աջ"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Ծավալել՝ դարձնելով կառավարման աջ և ձախ առանձնացված տարրեր"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Ծալել՝ դարձնելով կառավարման մեկ միասնական տարր"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Անջատել շրջակայքի ձայները"</string>
@@ -1568,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Անհայտ"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Զրոյացնե՞լ բոլոր սալիկները"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Արագ կարգավորումների բոլոր սալիկները կզրոյացվեն սարքի սկզբնական կարգավորումների համաձայն։"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 6ef3cdc..e0e69a9 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Suara sekitar"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Kiri"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Kanan"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Luaskan ke kontrol terpisah kiri dan kanan"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Ciutkan ke kontrol terpadu"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Bisukan suara sekitar"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Tampilkan ikon notifikasi prioritas rendah"</string>
<string name="other" msgid="429768510980739978">"Lainnya"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"mengubah ukuran kartu"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"menghapus kartu"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"menambahkan kartu ke posisi terakhir"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Pindahkan kartu"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Untuk melihat aplikasi terkini, geser ke atas dan tahan menggunakan tiga jari di touchpad"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Untuk melihat semua aplikasi, tekan tombol tindakan di keyboard"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Disamarkan"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Buka kunci untuk melihat"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Buka kunci untuk melihat kode"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Pendidikan kontekstual"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Gunakan touchpad untuk kembali"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Geser ke kiri atau kanan menggunakan tiga jari. Ketuk untuk mempelajari gestur lainnya."</string>
@@ -1571,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Tidak diketahui"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Reset semua kartu?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Semua kartu Setelan Cepat akan direset ke setelan asli perangkat"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index 2b8a5cc..faff4c5 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Umhverfi"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Vinstri"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Hægri"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Aðskilja stýringar til vinstri og hægri"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Taka saman í eina stýringu"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Þagga umhverfi"</string>
@@ -1568,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Óþekkt"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Endurstilla alla reiti?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Allir flýtistillingareitir munu endurstillast á upprunalegar stillingar tækisins"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index 87507d6..e6f5dee 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Connesso a <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Connesso a: <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Espandi il gruppo."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Aggiungi il dispositivo al gruppo."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Rimuovi il dispositivo dal gruppo."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Apri l\'applicazione."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Non connesso."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Ingresso"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Apparecchi acustici"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Attivazione…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Impossibile regolare la luminosità perché è controllata dall\'app in primo piano."</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Rotazione automatica"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Rotazione automatica dello schermo"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Posizione"</string>
@@ -426,6 +423,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Audio ambientale"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Sinistra"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Destra"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Espandi controlli separati a sinistra e a destra"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Comprimi in controllo unificato"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Disattiva audio ambientale"</string>
@@ -1568,4 +1571,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Sconosciuti"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Reimpostare tutti i riquadri?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Tutti i riquadri Impostazioni rapide verranno reimpostati sulle impostazioni originali del dispositivo"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 8a687af..8e2df32 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -245,12 +245,9 @@
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth מחובר."</string>
<string name="accessibility_bluetooth_device_icon" msgid="9163840051642587982">"סמל של מכשיר Bluetooth"</string>
<string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"יש ללחוץ כדי להגדיר את פרטי המכשיר"</string>
- <!-- no translation found for accessibility_bluetooth_device_settings_gear_with_name (114373701123165491) -->
- <skip />
- <!-- no translation found for accessibility_bluetooth_device_settings_see_all (5260390270128256620) -->
- <skip />
- <!-- no translation found for accessibility_bluetooth_device_settings_pair_new_device (7988547106800504256) -->
- <skip />
+ <string name="accessibility_bluetooth_device_settings_gear_with_name" msgid="114373701123165491">"<xliff:g id="DEVICE_NAME">%s</xliff:g>. הגדרה של פרטי המכשיר"</string>
+ <string name="accessibility_bluetooth_device_settings_see_all" msgid="5260390270128256620">"הצגת כל המכשירים"</string>
+ <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="7988547106800504256">"התאמה של מכשיר חדש"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"אחוז טעינת הסוללה לא ידוע."</string>
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"התבצע חיבור אל <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"מחובר אל <xliff:g id="CAST">%s</xliff:g>."</string>
@@ -354,7 +351,7 @@
<string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"אין רשתות זמינות"</string>
<string name="quick_settings_wifi_detail_empty_text" msgid="483130889414601732">"אין רשתות Wi-Fi זמינות"</string>
<string name="quick_settings_wifi_secondary_label_transient" msgid="7501659015509357887">"בתהליך הפעלה…"</string>
- <string name="quick_settings_cast_title" msgid="3033553249449938182">"העברה (cast)"</string>
+ <string name="quick_settings_cast_title" msgid="3033553249449938182">"הפעלת Cast"</string>
<string name="quick_settings_casting" msgid="1435880708719268055">"מופעל Cast"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"מכשיר ללא שם"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"אין מכשירים זמינים"</string>
@@ -429,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"הרעשים בסביבה"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"שמאל"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"ימין"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"הרחבה לאמצעי בקרה נפרדים לצד שמאל ולצד ימין"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"כיווץ לאמצעי בקרה מאוחד"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"השתקת הרעשים בסביבה"</string>
@@ -589,7 +592,7 @@
<string name="media_projection_entry_cast_permission_dialog_option_text_single_app" msgid="6073353940838561981">"הפעלת Cast של אפליקציה אחת"</string>
<string name="media_projection_entry_cast_permission_dialog_option_text_entire_screen" msgid="8389508187954155307">"הפעלת Cast של כל המסך"</string>
<string name="media_projection_entry_cast_permission_dialog_warning_entire_screen" msgid="4040447861037324017">"כשמפעילים Cast של כל המסך, כל מה שמופיע בו יהיה גלוי לצופים. מומלץ להיזהר עם סיסמאות, פרטי תשלום, הודעות, תמונות, אודיו וסרטונים."</string>
- <string name="media_projection_entry_cast_permission_dialog_warning_single_app" msgid="7487834861348460736">"כשמפעילים Cast של כל אפליקציה, כל מה שמופיע או מופעל בה יהיה גלוי לצופים. מומלץ להיזהר עם סיסמאות, פרטי תשלום, הודעות, תמונות, אודיו וסרטונים."</string>
+ <string name="media_projection_entry_cast_permission_dialog_warning_single_app" msgid="7487834861348460736">"כשמפעילים Cast של אפליקציה, כל מה שרואים או מפעילים בה מופיע גם לצופים. מומלץ להיזהר ולא לחשוף פרטים אישיים כמו סיסמאות, פרטי תשלום, הודעות, תמונות, אודיו וסרטונים."</string>
<string name="media_projection_entry_cast_permission_dialog_continue_entire_screen" msgid="3261124185304676483">"הפעלת Cast של המסך"</string>
<string name="media_projection_entry_cast_app_selector_title" msgid="6323062146661922387">"בחירת אפליקציה להפעלת Cast"</string>
<string name="media_projection_entry_generic_permission_dialog_title" msgid="4519802931547483628">"להתחיל את השיתוף?"</string>
@@ -906,10 +909,8 @@
<string name="keyboard_shortcut_group_system_multitasking" msgid="6967816258924795558">"ריבוי משימות"</string>
<string name="system_multitasking_rhs" msgid="8779289852395243004">"שימוש במסך מפוצל כשהאפליקציה בצד ימין"</string>
<string name="system_multitasking_lhs" msgid="7348595296208696452">"שימוש במסך מפוצל כשהאפליקציה בצד שמאל"</string>
- <!-- no translation found for system_multitasking_full_screen (4221409316059910349) -->
- <skip />
- <!-- no translation found for system_multitasking_desktop_view (8829838918507805921) -->
- <skip />
+ <string name="system_multitasking_full_screen" msgid="4221409316059910349">"שימוש במסך מלא"</string>
+ <string name="system_multitasking_desktop_view" msgid="8829838918507805921">"שימוש בתצוגה למחשב"</string>
<string name="system_multitasking_splitscreen_focus_rhs" msgid="3838578650313318508">"מעבר לאפליקציה משמאל או למטה בזמן שימוש במסך מפוצל"</string>
<string name="system_multitasking_splitscreen_focus_lhs" msgid="3164261844398662518">"מעבר לאפליקציה מימין או למעלה בזמן שימוש במסך מפוצל"</string>
<string name="system_multitasking_replace" msgid="7410071959803642125">"כשהמסך מפוצל: החלפה בין אפליקציה אחת לאחרת"</string>
@@ -996,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"הצגת סמלי התראות בעדיפות נמוכה"</string>
<string name="other" msgid="429768510980739978">"אחר"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"שינוי גודל הלחצן"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"הסרת הלחצן"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"הוספת הלחצן במיקום האחרון"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"העברת הלחצן"</string>
@@ -1512,8 +1512,7 @@
<string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"הצגת האפליקציות האחרונות"</string>
<string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"מעבר בין אפליקציות"</string>
<string name="touchpad_tutorial_done_button" msgid="176168488821755503">"סיום"</string>
- <!-- no translation found for touchpad_tutorial_next_button (9169718126626806688) -->
- <skip />
+ <string name="touchpad_tutorial_next_button" msgid="9169718126626806688">"הבא"</string>
<string name="gesture_error_title" msgid="469064941635578511">"צריך לנסות שוב."</string>
<string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"חזרה"</string>
<string name="touchpad_back_gesture_guidance" msgid="5352221087725906542">"מחליקים שמאלה או ימינה עם שלוש אצבעות על לוח המגע"</string>
@@ -1551,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"כדי לראות את האפליקציות האחרונות, מחליקים למעלה לוחצים לחיצה ארוכה עם שלוש אצבעות על לוח המגע"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"כדי לראות את כל האפליקציות, מקישים על מקש הפעולה במקלדת"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"מצונזר"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"צריך לפתוח את הנעילה כדי לראות"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"כדי לראות את הקוד, צריך לפתוח את הנעילה"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"חינוך בהתאם להקשר"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"אפשר להשתמש בלוח המגע כדי לחזור אחורה"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"מחליקים ימינה או שמאלה עם שלוש אצבעות. ניתן ללחוץ כדי לקבל מידע נוסף על התנועות."</string>
@@ -1577,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"לא ידוע"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"לאפס את כל הלחצנים?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"כל הלחצנים ב\'הגדרות מהירות\' יאופסו להגדרות המקוריות של המכשיר"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index 29f93cc..5daa645e 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>に接続しました。"</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g>に接続されています。"</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"グループを開きます。"</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"グループにデバイスを追加します。"</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"グループからデバイスを削除します。"</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"アプリを開きます。"</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"接続されていません。"</string>
<string name="data_connection_roaming" msgid="375650836665414797">"ローミング"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"入力"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"補聴器"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ON にしています…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"明るさはトップ アプリによって制御されているため、調整できません"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"自動回転"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"画面を自動回転します"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"位置情報"</string>
@@ -426,6 +423,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"周囲の音"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"左"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"右"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"開く - 左右それぞれで制御する"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"閉じる - まとめて制御する"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"周囲の音をミュート"</string>
@@ -1568,4 +1571,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"不明"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"すべてのタイルをリセットしますか?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"すべてのクイック設定タイルがデバイスの元の設定にリセットされます"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>、<xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index d9a5824..68f48b0 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"გარემოცვა"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"მარცხენა"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"მარჯვენა"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"განცალკევებული მართვის საშუალებების გაფართოება მარცხნივ და მარჯვნივ"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"ერთიანი მართვის ჩაკეცვა"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"გარემოცვის დადუმება"</string>
@@ -1568,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"უცნობი"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"გსურთ ყველა ფილის გადაყენება?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"სწრაფი პარამეტრების ყველა ფილა გადაყენდება მოწყობილობის ორიგინალ პარამეტრებზე"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index 141e99c..1ecc743 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -147,7 +147,7 @@
<string name="share_to_app_stop_dialog_message_generic" msgid="7622174291691249392">"Қазір қолданбамен бөлісіп жатырсыз."</string>
<string name="share_to_app_stop_dialog_button" msgid="6334056916284230217">"Бөлісуді тоқтату"</string>
<string name="cast_screen_to_other_device_chip_accessibility_label" msgid="4687917476203009885">"Экранды трансляциялап жатырсыз."</string>
- <string name="cast_to_other_device_stop_dialog_title" msgid="7836517190930357326">"Трансляциялау тоқтасын ба?"</string>
+ <string name="cast_to_other_device_stop_dialog_title" msgid="7836517190930357326">"Трансляцияны тоқтату керек пе?"</string>
<string name="cast_to_other_device_stop_dialog_message_entire_screen_with_device" msgid="1474703115926205251">"Қазір бүкіл экранды құрылғыға (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>) трансляциялап жатырсыз."</string>
<string name="cast_to_other_device_stop_dialog_message_entire_screen" msgid="8419219169553867625">"Қазір бүкіл экранды маңайдағы құрылғыға трансляциялап жатырсыз."</string>
<string name="cast_to_other_device_stop_dialog_message_specific_app_with_device" msgid="2715934698604085519">"Қазір қолданбаны (<xliff:g id="APP_BEING_SHARED_NAME">%1$s</xliff:g>) құрылғыға (<xliff:g id="DEVICE_NAME">%2$s</xliff:g>) трансляциялап жатырсыз."</string>
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Айнала"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Сол жақ"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Оң жақ"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Сол жақ және оң жақ бөлек бақылау құралдарына жаю"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Біріктірілген бақылау құралына жию"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Айналаның дыбысын өшіру"</string>
@@ -586,7 +592,7 @@
<string name="media_projection_entry_cast_permission_dialog_option_text_single_app" msgid="6073353940838561981">"Бір қолданба экранын трансляциялау"</string>
<string name="media_projection_entry_cast_permission_dialog_option_text_entire_screen" msgid="8389508187954155307">"Бүкіл экранды трансляциялау"</string>
<string name="media_projection_entry_cast_permission_dialog_warning_entire_screen" msgid="4040447861037324017">"Бүкіл экранды трансляциялаған кезде экранда барлық нәрсе көрсетіледі. Сондықтан құпия сөздерге, төлем туралы мәліметке, хабарларға, фотосуреттерге, аудиоконтент пен бейнелерге сақ болыңыз."</string>
- <string name="media_projection_entry_cast_permission_dialog_warning_single_app" msgid="7487834861348460736">"Қолданба экранын трансляциялаған кезде қолданбадағы барлық контент көрсетіледі. Сондықтан құпия сөздерге, төлем туралы мәліметке, хабарларға, фотосуреттерге, аудиоконтент пен бейнелерге сақ болыңыз."</string>
+ <string name="media_projection_entry_cast_permission_dialog_warning_single_app" msgid="7487834861348460736">"Қолданба экранын трансляциялаған кезде, қолданбадағы барлық контент көрсетіледі. Сондықтан құпия сөздерге, төлем туралы мәліметке, хабарларға, фотосуреттерге, аудиоконтент пен бейнелерге сақ болыңыз."</string>
<string name="media_projection_entry_cast_permission_dialog_continue_entire_screen" msgid="3261124185304676483">"Экранды трансляциялау"</string>
<string name="media_projection_entry_cast_app_selector_title" msgid="6323062146661922387">"Трансляциялайтын қолданба экранын таңдау"</string>
<string name="media_projection_entry_generic_permission_dialog_title" msgid="4519802931547483628">"Бөлісу басталсын ба?"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Маңызды емес хабарландыру белгішелерін көрсету"</string>
<string name="other" msgid="429768510980739978">"Басқа"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"бөлшек өлшемін ауыстыру"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"бөлшекті өшіру"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"соңғы позицияға бөлшек қосу"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Бөлшекті жылжыту"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Сенсорлық тақтада үш саусақпен жоғары сырғытып, басып тұрсаңыз, соңғы ашылған қолданбаларды көресіз."</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Пернетақтада әрекет пернесін басып, барлық қолданбаны көре аласыз."</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Жасырылған"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Көру үшін құлыпты ашыңыз."</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Кодты көру үшін құлыпты ашыңыз."</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Контекстік білім"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Артқа қайту үшін сенсорлық тақтаны қолданыңыз"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Үш саусақпен солға не оңға сырғытыңыз. Басқа қимылдарды үйрену үшін түртіңіз."</string>
@@ -1571,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Белгісіз"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Барлық бөлшекті бастапқы күйге қайтару керек пе?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Барлық \"Жылдам параметрлер\" бөлшегі құрылғының бастапқы параметрлеріне қайтарылады."</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index 9323308..8c5ea90 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"មជ្ឈដ្ឋានជុំវិញ"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"ឆ្វេង"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"ស្ដាំ"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"ពង្រីកទៅជាការគ្រប់គ្រងខាងឆ្វេង និងខាងស្ដាំដាច់ដោយឡែកពីគ្នា"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"បង្រួមទៅជាការគ្រប់គ្រងដែលបានរួមបញ្ចូលគ្នា"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"បិទសំឡេងមជ្ឈដ្ឋានជុំវិញ"</string>
@@ -1568,4 +1574,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"មិនស្គាល់"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"កំណត់ប្រអប់ទាំងអស់ឡើងវិញឬ?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"ប្រអប់ការកំណត់រហ័សទាំងអស់នឹងកំណត់ឡើងវិញទៅការកំណត់ដើមរបស់ឧបករណ៍"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index d7f8b89..7c785d9 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"ಆ್ಯಂಬಿಯೆಂಟ್ ವಾಲ್ಯೂಮ್"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"ಎಡ"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"ಬಲ"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"ಪ್ರತ್ಯೇಕ ಎಡ ಮತ್ತು ಬಲ ಕಂಟ್ರೋಲ್ಗಳಿಗಾಗಿ ವಿಸ್ತರಿಸಿ"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"ಯೂನಿಫೈಡ್ ಕಂಟ್ರೋಲ್ಗಾಗಿ ಕುಗ್ಗಿಸಿ"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"ಆ್ಯಂಬಿಯೆಂಟ್ ವಾಲ್ಯೂಮ್ ಅನ್ನು ಮ್ಯೂಟ್ ಮಾಡಿ"</string>
@@ -1568,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"ಅಪರಿಚಿತ"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"ಎಲ್ಲಾ ಟೈಲ್ಗಳನ್ನು ರೀಸೆಟ್ ಮಾಡಬೇಕೆ?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"ಎಲ್ಲಾ ತ್ವರಿತ ಸೆಟ್ಟಿಂಗ್ಗಳ ಟೈಲ್ಗಳನ್ನು ಸಾಧನದ ಮೂಲ ಸೆಟ್ಟಿಂಗ್ಗಳಿಗೆ ರೀಸೆಟ್ ಮಾಡಲಾಗುತ್ತದೆ"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 0c43ea1..b3c21b1 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"주변 소리"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"왼쪽"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"오른쪽"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"왼쪽 및 오른쪽 개별 제어로 확장"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"통합 제어로 축소"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"주변 소리 음소거"</string>
@@ -586,7 +592,7 @@
<string name="media_projection_entry_cast_permission_dialog_option_text_single_app" msgid="6073353940838561981">"앱 1개 전송"</string>
<string name="media_projection_entry_cast_permission_dialog_option_text_entire_screen" msgid="8389508187954155307">"전체 화면 전송"</string>
<string name="media_projection_entry_cast_permission_dialog_warning_entire_screen" msgid="4040447861037324017">"전체 화면을 전송하면 화면에 있는 모든 항목을 볼 수 있게 됩니다. 따라서 비밀번호, 결제 세부정보, 메시지, 사진, 오디오 및 동영상 등이 노출되지 않도록 주의하세요."</string>
- <string name="media_projection_entry_cast_permission_dialog_warning_single_app" msgid="7487834861348460736">"앱을 전송하면 해당 앱에 표시되거나 재생되는 모든 항목을 볼 수 있게 됩니다. 따라서 비밀번호, 결제 세부정보, 메시지, 사진, 오디오 및 동영상 등이 노출되지 않도록 주의하세요."</string>
+ <string name="media_projection_entry_cast_permission_dialog_warning_single_app" msgid="7487834861348460736">"앱을 전송하면 해당 앱에 표시되거나 재생되는 모든 항목을 볼 수 있게 됩니다. 비밀번호, 결제 세부정보, 메시지, 사진, 오디오 및 동영상 등이 노출되지 않도록 주의하세요."</string>
<string name="media_projection_entry_cast_permission_dialog_continue_entire_screen" msgid="3261124185304676483">"화면 전송"</string>
<string name="media_projection_entry_cast_app_selector_title" msgid="6323062146661922387">"전송할 앱 선택"</string>
<string name="media_projection_entry_generic_permission_dialog_title" msgid="4519802931547483628">"공유를 시작하시겠습니까?"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"우선순위가 낮은 알림 아이콘 표시"</string>
<string name="other" msgid="429768510980739978">"기타"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"타일 크기 전환"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"타일 삭제"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"마지막 위치에 타일 추가"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"타일 이동"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"최근 앱을 보려면 터치패드에서 세 손가락으로 위로 스와이프한 후 잠시 기다리세요"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"모든 앱을 보려면 키보드의 작업 키를 누르세요"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"수정됨"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"잠금 해제하여 보기"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"잠금 해제하여 코드 보기"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"컨텍스트 교육"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"터치패드를 사용하여 돌아가기"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"세 손가락을 사용해 왼쪽 또는 오른쪽으로 스와이프하세요. 더 많은 동작을 알아보려면 탭하세요."</string>
@@ -1571,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"알 수 없음"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"모든 타일을 재설정하시겠습니까?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"모든 빠른 설정 타일이 기기의 원래 설정으로 재설정됩니다."</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index b411bdc..9aedbb5 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Айланадагы үндөр"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Сол"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Оң"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Cол жана оң жактагы өзүнчө башкаруу элементтерине жайып көрсөтүү"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Бирдиктүү башкаруу элементине жыйыштыруу"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Айланадагы үндөрдү басуу"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Анча маанилүү эмес билдирменин сүрөтчөлөрүн көрсөтүү"</string>
<string name="other" msgid="429768510980739978">"Башка"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"плитканын өлчөмүн которуштуруу"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"ыкчам баскычты өчүрүү"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"аягына карта кошуу"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Ыкчам баскычты жылдыруу"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Акыркы колдонмолорду көрүү үчүн сенсордук тактаны үч манжаңыз менен өйдө сүрүп, кармап туруңуз"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Бардык колдонмолоруңузду көрүү үчүн баскычтобуңуздагы аракет баскычын басыңыз"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Жашырылды"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Көрүү үчүн кулпусун ачыңыз"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Кодду көрүү үчүн кулпусун ачыңыз"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Контексттик билим берүү"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Артка кайтуу үчүн сенсордук тактаны колдонуңуз"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Үч манжаңыз менен солго же оңго сүрүңүз. Башка жаңсоолорду үйрөнүү үчүн таптаңыз."</string>
@@ -1571,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Белгисиз"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Бардык параметрлерди кайра коесузбу?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Бардык ыкчам параметрлер түзмөктүн баштапкы маанилерине кайтарылат"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index e9e585e..47b3235 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"ສຽງແວດລ້ອມ"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"ຊ້າຍ"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"ຂວາ"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"ຂະຫຍາຍເປັນການຄວບຄຸມທີ່ແຍກເບື້ອງຊ້າຍ ແລະ ຂວາ"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"ຫຍໍ້ລົງເປັນການຄວບຄຸມແບບຮວມ"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"ປິດສຽງແວດລ້ອມ"</string>
@@ -1568,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"ບໍ່ຮູ້ຈັກ"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"ຣີເຊັດແຜ່ນທັງໝົດບໍ?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"ແຜ່ນການຕັ້ງຄ່າດ່ວນທັງໝົດຈະຣີເຊັດເປັນການຕັ້ງຄ່າແບບເກົ່າຂອງອຸປະກອນ"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 0d4398e..5971e73 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Aplinka"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Kairė"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Dešinė"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Išskleisti į atskirus kairįjį ir dešinįjį valdiklius"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Sutraukti į bendrą valdiklį"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Nutildyti aplinką"</string>
@@ -1568,4 +1574,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Nežinoma"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Iš naujo nustatyti visus išklotines elementus?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Visi sparčiųjų nustatymų išklotinės elementai bus iš naujo nustatyti į pradinius įrenginio nustatymus"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index 60e1e6f..bb8d702 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Apkārtnes skaņas"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Pa kreisi"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Pa labi"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Izvērst, lai rādītu atsevišķu kreiso un labo vadīklu"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Sakļaut, lai rādītu vienotu vadīklu"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Izslēgt apkārtnes skaņas"</string>
@@ -1568,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Nezināma"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Vai atiestatīt visus elementus?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Visiem ātro iestatījumu elementiem tiks atiestatīti sākotnējie iestatījumi"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index 761eac1..189390f 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Опкружување"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Лево"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Десно"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Прошири на одвоените контроли одлево и оддесно"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Собери на унифицирана контрола"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Исклучи го звукот на опкружувањето"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Прикажувај икони за известувања со низок приоритет"</string>
<string name="other" msgid="429768510980739978">"Друго"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"вклучување/исклучување на големината на плочката"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"отстранување на плочката"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"додајте плочка на последната позиција"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Преместување на плочката"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"За да ги видите скорешните апликации, повлечете нагоре и задржете со три прста на допирната подлога"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Притиснете го копчето за дејство на тастатурата за да ги видите сите апликации"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Редактирано"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Отклучете за да прегледате"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Отклучете за да го прегледате кодот"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Контекстуално образование"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Користете ја допирната подлога за да се вратите назад"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Повлечете налево или надесно со три прста. Допрете за да научите повеќе движења."</string>
@@ -1571,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Непознато"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Да се ресетираат сите плочки?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Сите плочки на „Брзи поставки“ ќе се ресетираат на првичните поставки на уредот"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index aae5ac48..7e4282f 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> എന്നതിലേക്ക് കണക്റ്റുചെയ്തു."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> എന്നതിലേക്ക് കണക്റ്റുചെയ്തു."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"ഗ്രൂപ്പ് വികസിപ്പിക്കുക."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"ഉപകരണം ഗ്രൂപ്പിലേക്ക് ചേർക്കുക."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"ഉപകരണം ഗ്രൂപ്പിൽ നിന്ന് നീക്കം ചെയ്യുക."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"ആപ്പ് തുറക്കുക."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"കണക്റ്റുചെയ്തിട്ടില്ല."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"റോമിംഗ്"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ഇൻപുട്ട്"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"ശ്രവണ സഹായികൾ"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ഓണാക്കുന്നു…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"തെളിച്ചം അഡ്ജസ്റ്റ് ചെയ്യാനാകില്ല, അത് നിയന്ത്രിക്കുന്നത് ടോപ്പ് ആപ്പാണ്"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"സ്ക്രീൻ സ്വയമേവ തിരിയൽ"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"സ്ക്രീൻ സ്വയമേവ തിരിക്കുക"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"ലൊക്കേഷൻ"</string>
@@ -426,6 +423,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"സറൗണ്ടിംഗ്സ്"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"ഇടത്"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"വലത്"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"വേർതിരിച്ച ഇടത്, വലത് നിയന്ത്രണങ്ങളിലേക്ക് വികസിപ്പിക്കുക"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"ഏകീകൃത നിയന്ത്രണത്തിലേക്ക് ചുരുക്കുക"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"സറൗണ്ടിംഗ്സ് മ്യൂട്ട് ചെയ്യുക"</string>
@@ -1568,4 +1571,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"അജ്ഞാതം"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"എല്ലാ ടൈലുകളും റീസെറ്റ് ചെയ്യണോ?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"എല്ലാ ദ്രുത ക്രമീകരണ ടൈലുകളും ഉപകരണത്തിന്റെ ഒറിജിനൽ ക്രമീകരണത്തിലേക്ക് റീസെറ്റ് ചെയ്യും"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index 5938893..c6ab2c5 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Орчин тойрон"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Зүүн"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Баруун"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Зүүн, баруун талын тусдаа тохиргоо руу дэлгэх"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Нэгдсэн тохиргоо руу хураах"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Орчин тойрны дууг хаах"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Бага ач холбогдолтой мэдэгдлийн дүрс тэмдгийг харуулах"</string>
<string name="other" msgid="429768510980739978">"Бусад"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"хавтангийн хэмжээг асаах/унтраах"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"хавтанг хасна уу"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"хавтанг сүүлийн байрлалд нэмэх"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Хавтанг зөөх"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Саяхны аппуудыг харахын тулд мэдрэгч самбар дээр гурван хуруугаараа дээш шудраад, удаан дарна уу"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Бүх аппаа харахын тулд гар дээр тань байх тусгай товчлуурыг дарна уу"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Хассан"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Харахын тулд түгжээг тайлна уу"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Кодыг харахын тулд түгжээг тайлна уу"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Хам сэдэвт боловсрол"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Буцахын тулд мэдрэгч самбараа ашиглах"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Гурван хуруугаараа зүүн эсвэл баруун тийш шударна уу. Илүү олон зангаа сурахын тулд товшино уу."</string>
@@ -1571,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Тодорхойгүй"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Бүх хавтанг шинэчлэх үү?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Шуурхай тохиргооны бүх хавтан төхөөрөмжийн эх тохиргоо руу шинэчлэгдэнэ"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index c3c7160..9fa4051 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"जवळपासचे"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"डावे"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"उजवे"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"डाव्या आणि उजव्या स्वतंत्र नियंत्रणांचा विस्तार करा"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"युनिफाइड नियंत्रणासाठी कोलॅप्स करा"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"जवळपासचे आवाज म्यूट करा"</string>
@@ -696,7 +702,7 @@
<string name="screen_pinning_start" msgid="7483998671383371313">"ॲप पिन केले"</string>
<string name="screen_pinning_exit" msgid="4553787518387346893">"ॲप अनपिन केले"</string>
<string name="stream_voice_call" msgid="7468348170702375660">"कॉल"</string>
- <string name="stream_system" msgid="7663148785370565134">"सिस्टम"</string>
+ <string name="stream_system" msgid="7663148785370565134">"सिस्टीम"</string>
<string name="stream_ring" msgid="7550670036738697526">"रिंग"</string>
<string name="stream_music" msgid="2188224742361847580">"मीडिया"</string>
<string name="stream_alarm" msgid="16058075093011694">"अलार्म"</string>
@@ -741,9 +747,9 @@
<string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> वर प्ले करत आहे"</string>
<string name="media_output_title_without_playing" msgid="3825663683169305013">"यावर ऑडिओ प्ले होईल"</string>
<string name="media_output_title_ongoing_call" msgid="208426888064112006">"यावर कॉल करत आहे"</string>
- <string name="system_ui_tuner" msgid="1471348823289954729">"सिस्टम UI ट्युनर"</string>
+ <string name="system_ui_tuner" msgid="1471348823289954729">"सिस्टीम UI ट्युनर"</string>
<string name="status_bar" msgid="4357390266055077437">"स्टेटस बार"</string>
- <string name="demo_mode" msgid="263484519766901593">"सिस्टम UI डेमो मोड"</string>
+ <string name="demo_mode" msgid="263484519766901593">"सिस्टीम UI डेमो मोड"</string>
<string name="enable_demo_mode" msgid="3180345364745966431">"डेमो मोड सुरू करा"</string>
<string name="show_demo_mode" msgid="3677956462273059726">"डेमो मोड दर्शवा"</string>
<string name="status_bar_ethernet" msgid="5690979758988647484">"इथरनेट"</string>
@@ -781,12 +787,12 @@
<string name="accessibility_signal_full" msgid="1519655809806462972">"पूर्ण सिग्नल"</string>
<string name="accessibility_managed_profile" msgid="4703836746209377356">"कार्य प्रोफाईल"</string>
<string name="tuner_warning_title" msgid="7721976098452135267">"सर्वांसाठी नाही तर काहींसाठी मजेदार असू शकते"</string>
- <string name="tuner_warning" msgid="1861736288458481650">"सिस्टम UI ट्युनर आपल्याला Android यूझर इंटरफेस ट्विक आणि कस्टमाइझ करण्याचे अनेक प्रकार देते. ही प्रयोगात्मक वैशिष्ट्ये बदलू शकतात, खंडित होऊ शकतात किंवा भविष्यातील रिलीज मध्ये कदाचित दिसणार नाहीत. सावधगिरी बाळगून पुढे सुरू ठेवा."</string>
+ <string name="tuner_warning" msgid="1861736288458481650">"सिस्टीम UI ट्युनर आपल्याला Android यूझर इंटरफेस ट्विक आणि कस्टमाइझ करण्याचे अनेक प्रकार देते. ही प्रयोगात्मक वैशिष्ट्ये बदलू शकतात, खंडित होऊ शकतात किंवा भविष्यातील रिलीझ मध्ये कदाचित दिसणार नाहीत. सावधगिरी बाळगून पुढे सुरू ठेवा."</string>
<string name="tuner_persistent_warning" msgid="230466285569307806">"ही प्रयोगात्मक वैशिष्ट्ये बदलू शकतात, खंडित होऊ शकतात किंवा भविष्यातील रिलीज मध्ये कदाचित दिसणार नाहीत."</string>
<string name="got_it" msgid="477119182261892069">"समजले"</string>
- <string name="tuner_toast" msgid="3812684836514766951">"अभिनंदन! सिस्टम UI ट्युनर सेटिंग्जमध्ये जोडले गेले आहे"</string>
+ <string name="tuner_toast" msgid="3812684836514766951">"अभिनंदन! सिस्टीम UI ट्युनर सेटिंग्जमध्ये जोडले गेले आहे"</string>
<string name="remove_from_settings" msgid="633775561782209994">"सेटिंग्ज मधून काढा"</string>
- <string name="remove_from_settings_prompt" msgid="551565437265615426">"सेटिंग्ज मधून सिस्टम UI ट्युनर काढून त्याची सर्व वैशिष्ट्ये वापरणे थांबवायचे?"</string>
+ <string name="remove_from_settings_prompt" msgid="551565437265615426">"सेटिंग्ज मधून सिस्टीम UI ट्युनर काढून त्याची सर्व वैशिष्ट्ये वापरणे थांबवायचे?"</string>
<string name="enable_bluetooth_title" msgid="866883307336662596">"ब्लूटूथ सुरू करायचे?"</string>
<string name="enable_bluetooth_message" msgid="6740938333772779717">"तुमचा कीबोर्ड तुमच्या टॅबलेटसह कनेक्ट करण्यासाठी, तुम्ही प्रथम ब्लूटूथ सुरू करणे आवश्यक आहे."</string>
<string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"सुरू करा"</string>
@@ -1568,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"अज्ञात"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"सर्व टाइल रीसेट करायच्या?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"सर्व क्विक सेटिंग्ज टाइल डिव्हाइसच्या मूळ सेटिंग्जवर रीसेट केल्या जातील"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index 3c96d0a..91e7e63 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Disambungkan kepada <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Disambungkan ke <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Kembangkan kumpulan."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Tambahkan peranti pada kumpulan."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Alih keluar peranti daripada kumpulan."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Buka aplikasi."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Tidak disambungkan."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Perayauan"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Input"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Alat bantu pendengaran"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Menghidupkan…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Tidak dapat melaraskan kecerahan kerana peranti dikawal oleh apl bahagian atas"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Autoputar"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Autoputar skrin"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Lokasi"</string>
@@ -426,6 +423,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Persekitaran"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Kiri"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Kanan"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Kembangkan kepada kawalan berasingan sebelah kiri dan kanan"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Kuncupkan kepada kawalan yang disatukan"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Redamkan persekitaran"</string>
@@ -1568,4 +1571,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Tidak diketahui"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Tetapkan semula semua jubin?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Semua jubin Tetapan Pantas akan ditetapkan semula kepada tetapan asal peranti"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index b259e39..703562e 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"ဝန်းကျင်အသံ"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"ဘယ်"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"ညာ"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"ဘယ်ညာခွဲထားသော ထိန်းချုပ်မှုများအဖြစ် ပိုပြပါ"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"ပေါင်းစည်းထားသော ထိန်းချုပ်မှုအဖြစ် လျှော့ပြပါ"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"ဝန်းကျင်အသံ ပိတ်ရန်"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"အရေးမကြီးသော အကြောင်းကြားချက် သင်္ကေတများ ပြရန်"</string>
<string name="other" msgid="429768510980739978">"အခြား"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"အကွက်ငယ်၏ အရွယ်အစားကို ပြောင်းရန်"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"အကွက်ငယ်ကို ဖယ်ရှားရန်"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"နောက်ဆုံးနေရာတွင် အကွက်ငယ် ထည့်ရန်"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"အကွက်ငယ်ကို ရွှေ့ရန်"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"လတ်တလောအက်ပ်များကို ကြည့်ရန် တာ့ချ်ပက်ပေါ်တွင် လက်သုံးချောင်းဖြင့် အပေါ်သို့ပွတ်ဆွဲပြီး ဖိထားပါ"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"သင့်အက်ပ်အားလုံးကြည့်ရန် ကီးဘုတ်ပေါ်ရှိ လုပ်ဆောင်ချက်ကီးကို နှိပ်ပါ"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"အစားထိုးထားသည်"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"ကြည့်ရန် ဖွင့်ပါ"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"ကုဒ်ကြည့်ရန် ဖွင့်ပါ"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"အကြောင်းအရာအလိုက် ပညာရေး"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"နောက်ပြန်သွားရန် သင့်တာ့ချ်ပက်ကို သုံးပါ"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"လက်သုံးချောင်းဖြင့် ဘယ် (သို့) ညာသို့ ပွတ်ဆွဲပါ။ လက်ဟန်များ ပိုမိုလေ့လာရန် တို့ပါ။"</string>
@@ -1571,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"အမျိုးအမည်မသိ"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"အကွက်ငယ်အားလုံးကို ပြင်ဆင်သတ်မှတ်မလား။"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"အမြန်ဆက်တင်များ အကွက်ငယ်အားလုံးကို စက်ပစ္စည်း၏ မူရင်းဆက်တင်များသို့ ပြင်ဆင်သတ်မှတ်ပါမည်"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 1c655f1..b75f644 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Omgivelser"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Venstre"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Høyre"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Utvid til separate kontroller for venstre og høyre"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Skjul til samlet kontroll"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Kutt lyden for omgivelsene"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Vis ikoner for varsler med lav prioritet"</string>
<string name="other" msgid="429768510980739978">"Annet"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"bytt størrelse på brikken"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"fjerne infobrikken"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"legge til en brikke på den siste posisjonen"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Flytt infobrikken"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"For å se nylige apper, sveip opp og hold med tre fingre på styreflaten"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"For å se alle appene dine, trykk på handlingstasten på tastaturet"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Fjernet"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Lås opp for å se"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Lås opp for å se koden"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Kontekstuell opplæring"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Bruk styreflaten for å gå tilbake"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Sveip til venstre eller høyre med tre fingre. Trykk for å lære flere bevegelser."</string>
@@ -1571,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Ukjent"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Vil du tilbakestille alle brikkene?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Alle brikker for hurtiginnstillinger tilbakestilles til enhetens opprinnelige innstillinger"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index 133003e..cd20def 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"वरपरका आवाज"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"बायाँ"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"दायाँ"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"दायाँ र बायाँतर्फको भोल्युम छुट्टाछुट्टै व्यवस्थापन गर्न भोल्युम प्यानल छुट्ट्याउनुहोस्"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"कोल्याप्स गरी एउटै कन्ट्रोल बनाउनुहोस्"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"वरपरका आवाज म्युट गर्नुहोस्"</string>
@@ -1571,4 +1577,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"अज्ञात"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"सबै टाइलहरू रिसेट गर्ने हो?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"द्रुत सेटिङका सबै टाइलहरू रिसेट गरी डिभाइसका मूल सेटिङ लागू गरिने छन्"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 3881907..1df27df 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Omgevingsgeluid"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Links"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Rechts"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Uitvouwen naar gescheiden bediening voor links en rechts"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Samenvouwen tot geïntegreerde bediening"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Omgevingsgeluid uitzetten"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Iconen voor meldingen met lage prioriteit tonen"</string>
<string name="other" msgid="429768510980739978">"Overig"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"het formaat van de tegel schakelen"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"tegel verwijderen"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"tegel toevoegen op de laatste positie"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Tegel verplaatsen"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Als je recente apps wilt bekijken, swipe je met 3 vingers omhoog op de touchpad en houd je vast"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Als je alle apps wilt bekijken, druk je op de actietoets op je toetsenbord"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Verborgen"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Ontgrendelen om te bekijken"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Ontgrendelen om de code te bekijken"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Contextuele educatie"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Je touchpad gebruiken om terug te gaan"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Swipe met 3 vingers naar links of rechts. Tik voor meer gebaren."</string>
@@ -1571,4 +1574,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Onbekend"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Alle tegels resetten?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Alle tegels voor Snelle instellingen worden teruggezet naar de oorspronkelijke instellingen van het apparaat"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index 9c561c9..ddb9157 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> ସହ ସଂଯୁକ୍ତ"</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> ସହିତ ସଂଯୁକ୍ତ।"</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"ଗ୍ରୁପକୁ ବିସ୍ତାର କରନ୍ତୁ।"</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"ଗ୍ରୁପରେ ଡିଭାଇସ ଯୋଗ କରନ୍ତୁ।"</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"ଗ୍ରୁପରୁ ଡିଭାଇସ କାଢ଼ି ଦିଅନ୍ତୁ।"</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"ଆପ୍ଲିକେସନ ଖୋଲନ୍ତୁ।"</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"କନେକ୍ଟ ହୋଇନାହିଁ।"</string>
<string name="data_connection_roaming" msgid="375650836665414797">"ରୋମିଙ୍ଗ"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ଇନପୁଟ୍"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"ଶ୍ରବଣ ଯନ୍ତ୍ର"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ଅନ୍ ହେଉଛି…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"ଟପ ଆପ ଦ୍ୱାରା ଉଜ୍ଜ୍ୱଳତା ନିୟନ୍ତ୍ରିତ ହେଉଥିବା ଯୋଗୁଁ ଏହାକୁ ଆଡଜଷ୍ଟ କରିପାରିବେ ନାହିଁ"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ଅଟୋ-ରୋଟେଟ"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"ଅଟୋ-ରୋଟେଟ ସ୍କ୍ରିନ"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"ଲୋକେସନ"</string>
@@ -426,6 +423,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"ପରିପାର୍ଶ୍ୱ"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"ବାମ"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"ଡାହାଣ"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"ବାମ ଏବଂ ଡାହାଣ ଅଲଗା ନିୟନ୍ତ୍ରଣକୁ ବିସ୍ତାର କରନ୍ତୁ"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"ଏକତ୍ରିତ ନିୟନ୍ତ୍ରଣକୁ ସଙ୍କୁଚିତ କରନ୍ତୁ"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"ପରିପାର୍ଶ୍ୱକୁ ମ୍ୟୁଟ କରନ୍ତୁ"</string>
@@ -991,8 +994,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"କମ୍-ଅଗ୍ରାଧିକାର ବିଜ୍ଞପ୍ତି ଆଇକନ୍ ଦେଖାନ୍ତୁ"</string>
<string name="other" msgid="429768510980739978">"ଅନ୍ୟ"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"ଟାଇଲର ସାଇଜକୁ ଟୋଗଲ କରନ୍ତୁ"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"ଟାଇଲ୍ କାଢ଼ି ଦିଅନ୍ତୁ"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"ଶେଷ ପୋଜିସନରେ ଟାଇଲ ଯୋଗ କରିବା"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"ଟାଇଲ୍ ମୁଭ୍ କରନ୍ତୁ"</string>
@@ -1545,10 +1547,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"ବର୍ତ୍ତମାନର ଆପ୍ସ ଭ୍ୟୁ କରିବାକୁ, ଟଚପେଡରେ ତିନୋଟି ଆଙ୍ଗୁଠିରେ ଉପରକୁ ସ୍ୱାଇପ କରି ଧରି ରଖନ୍ତୁ"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"ଆପଣଙ୍କ ସମସ୍ତ ଆପ୍ସ ଭ୍ୟୁ କରିବା ପାଇଁ ଆପଣଙ୍କ କୀବୋର୍ଡରେ ଆକ୍ସନ କୀ\'କୁ ଦବାନ୍ତୁ"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"ଲୁଚା ଯାଇଥିବା"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"ଭ୍ୟୁ କରିବାକୁ ଅନଲକ କରନ୍ତୁ"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"ଭ୍ୟୁ କରିବାକୁ ଅନଲକ କରିବା କୋଡ"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"ପ୍ରାସଙ୍ଗିକ ଶିକ୍ଷା"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"ପଛକୁ ଫେରିବା ପାଇଁ ଆପଣଙ୍କ ଟଚପେଡକୁ ବ୍ୟବହାର କରନ୍ତୁ"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"ତିନୋଟି ଆଙ୍ଗୁଠିରେ ବାମ ବା ଡାହାଣକୁ ସ୍ୱାଇପ କରନ୍ତୁ। ଜେଶ୍ଚରଗୁଡ଼ିକ ବିଷୟରେ ଅଧିକ ଜାଣିବାକୁ ଟାପ କରନ୍ତୁ।"</string>
@@ -1571,4 +1571,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"ଅଜଣା"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"ସମସ୍ତ ଟାଇଲକୁ ରିସେଟ କରିବେ?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"ସମସ୍ତ କୁଇକ ସେଟିଂସ ଟାଇଲ ଡିଭାଇସର ମୂଳ ସେଟିଂସରେ ରିସେଟ ହୋଇଯିବ"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 6027538..39c02d2 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ।"</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ ਗਿਆ।"</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"ਗਰੁੱਪ ਦਾ ਵਿਸਤਾਰ ਕਰੋ।"</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"ਗਰੁੱਪ ਵਿੱਚ ਡੀਵਾਈਸ ਸ਼ਾਮਲ ਕਰੋ।"</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"ਗਰੁੱਪ ਤੋਂ ਡੀਵਾਈਸ ਹਟਾਓ।"</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"ਐਪਲੀਕੇਸ਼ਨ ਖੋਲ੍ਹੋ।"</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"ਕਨੈਕਟ ਨਹੀਂ ਕੀਤਾ।"</string>
<string name="data_connection_roaming" msgid="375650836665414797">"ਰੋਮਿੰਗ"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ਇਨਪੁੱਟ"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"ਸੁਣਨ ਦੇ ਸਾਧਨ"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ਚਾਲੂ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ…"</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"ਚਮਕ ਨੂੰ ਵਿਵਸਥਿਤ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ ਕਿਉਂਕਿ ਪਹਿਲਾਂ ਤੋਂ ਚੱਲ ਰਹੀ ਐਪ ਇਸਨੂੰ ਕੰਟਰੋਲ ਕਰ ਰਹੀ ਹੈ"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ਸਵੈ-ਘੁਮਾਓ"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"ਸਕ੍ਰੀਨ ਨੂੰ ਆਪਣੇ ਆਪ ਘੁੰਮਾਓ"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"ਟਿਕਾਣਾ"</string>
@@ -426,6 +423,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"ਆਲੇ-ਦੁਆਲੇ"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"ਖੱਬੇ"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"ਸੱਜੇ"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"ਖੱਬੇ ਅਤੇ ਸੱਜੇ ਪਾਸੇ ਦੇ ਸ਼ੋਰ ਨੂੰ ਵੱਖ-ਵੱਖ ਕੰਟਰੋਲ ਕਰਨ ਲਈ ਵਿਸਤਾਰ ਕਰੋ"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"ਏਕੀਕ੍ਰਿਤ ਕੰਟਰੋਲ \'ਤੇ ਜਾਣ ਲਈ ਸਮੇਟੋ"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"ਆਲੇ-ਦੁਆਲੇ ਦੇ ਸ਼ੋਰ ਨੂੰ ਮਿਊਟ ਕਰੋ"</string>
@@ -1568,4 +1571,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"ਅਗਿਆਤ"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"ਕੀ ਸਾਰੀਆਂ ਟਾਇਲਾਂ ਨੂੰ ਰੀਸੈੱਟ ਕਰਨਾ ਹੈ?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"ਸਾਰੀਆਂ ਤਤਕਾਲ ਸੈਟਿੰਗਾਂ ਟਾਇਲਾਂ ਡੀਵਾਈਸ ਦੀਆਂ ਮੂਲ ਸੈਟਿੰਗਾਂ \'ਤੇ ਰੀਸੈੱਟ ਹੋ ਜਾਣਗੀਆਂ"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 35ff6c9..b93bf30 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Otoczenie"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Po lewej"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Po prawej"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Rozwiń, aby oddzielić elementy sterujące po lewej i po prawej stronie"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Zwiń do ujednoliconego sterowania"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Wycisz otoczenie"</string>
@@ -582,13 +588,13 @@
<string name="media_projection_entry_app_permission_dialog_continue_entire_screen" msgid="1850848182344377579">"Udostępnij ekran"</string>
<string name="media_projection_entry_app_permission_dialog_single_app_disabled" msgid="8999903044874669995">"<xliff:g id="APP_NAME">%1$s</xliff:g> ma wyłączoną tę opcję"</string>
<string name="media_projection_entry_share_app_selector_title" msgid="1419515119767501822">"Wybierz aplikację do udostępniania"</string>
- <string name="media_projection_entry_cast_permission_dialog_title" msgid="752756942658159416">"Włączyć przesyłanie treści wyświetlanych na ekranie?"</string>
+ <string name="media_projection_entry_cast_permission_dialog_title" msgid="752756942658159416">"Włączyć przesyłanie treści z ekranu?"</string>
<string name="media_projection_entry_cast_permission_dialog_option_text_single_app" msgid="6073353940838561981">"Przesyłanie obrazu z 1 aplikacji"</string>
<string name="media_projection_entry_cast_permission_dialog_option_text_entire_screen" msgid="8389508187954155307">"Przesyłanie całego ekranu"</string>
<string name="media_projection_entry_cast_permission_dialog_warning_entire_screen" msgid="4040447861037324017">"Kiedy przesyłasz treści z całego ekranu, widoczny jest cały obraz z wyświetlacza. Dlatego zachowaj ostrożność w zakresie haseł, danych do płatności, wiadomości, zdjęć, audio i filmów."</string>
- <string name="media_projection_entry_cast_permission_dialog_warning_single_app" msgid="7487834861348460736">"Kiedy przesyłasz obraz z aplikacji, widoczne jest wszystko to, co jest w niej wyświetlane lub odtwarzane. Dlatego zachowaj ostrożność w zakresie haseł, danych do płatności, wiadomości, zdjęć, audio i filmów."</string>
+ <string name="media_projection_entry_cast_permission_dialog_warning_single_app" msgid="7487834861348460736">"Kiedy przesyłasz obraz z aplikacji, widoczne jest wszystko, co jest w niej wyświetlane lub odtwarzane. Uważaj więc na takie treści jak hasła, dane do płatności, wiadomości, zdjęcia, audio czy filmy."</string>
<string name="media_projection_entry_cast_permission_dialog_continue_entire_screen" msgid="3261124185304676483">"Prześlij ekran"</string>
- <string name="media_projection_entry_cast_app_selector_title" msgid="6323062146661922387">"Wybieranie aplikacji do przesyłania"</string>
+ <string name="media_projection_entry_cast_app_selector_title" msgid="6323062146661922387">"Wybierz aplikację do przesyłania"</string>
<string name="media_projection_entry_generic_permission_dialog_title" msgid="4519802931547483628">"Rozpocząć udostępnianie?"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Podczas udostępniania, nagrywania lub przesyłania treści Android ma dostęp do wszystkiego, co jest widoczne na ekranie lub odtwarzane na urządzeniu. Dlatego zachowaj ostrożność w zakresie haseł, danych do płatności, wiadomości, zdjęć, dźwięku i filmów."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Podczas udostępniania, nagrywania lub przesyłania treści Android ma dostęp do wszystkiego, co jest w niej wyświetlane lub odtwarzane. Dlatego zachowaj ostrożność w zakresie haseł, danych do płatności, wiadomości, zdjęć, dźwięku i filmów."</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Pokazuj ikony powiadomień o niskim priorytecie"</string>
<string name="other" msgid="429768510980739978">"Inne"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"przełącz rozmiar kafelka"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"usunąć kartę"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"dodaj kafelek do ostatniej pozycji"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Przenieś kartę"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Aby wyświetlić ostatnie aplikacje, przesuń w górę za pomocą 3 palców na touchpadzie i przytrzymaj."</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Aby wyświetlić wszystkie swoje aplikacje, naciśnij klawisz działania na klawiaturze"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Usunięto"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Odblokuj, aby zobaczyć"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Odblokuj, aby zobaczyć kod"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Edukacja kontekstowa"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Przechodzenie wstecz za pomocą touchpada"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Przesuń trzema palcami w prawo lub lewo. Kliknij, aby poznać więcej gestów."</string>
@@ -1571,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Nieznane"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Zresetować wszystkie kafelki?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Wszystkie kafelki Szybkich ustawień zostaną zresetowane do oryginalnych ustawień urządzenia"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index fc7e2af..7c9028b 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -351,7 +351,7 @@
<string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Redes indisponíveis"</string>
<string name="quick_settings_wifi_detail_empty_text" msgid="483130889414601732">"Nenhuma rede Wi-Fi disponível"</string>
<string name="quick_settings_wifi_secondary_label_transient" msgid="7501659015509357887">"Ativando…"</string>
- <string name="quick_settings_cast_title" msgid="3033553249449938182">"Transmitir"</string>
+ <string name="quick_settings_cast_title" msgid="3033553249449938182">"Transmissão"</string>
<string name="quick_settings_casting" msgid="1435880708719268055">"Transmitindo"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Dispositivo sem nome"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Não há dispositivos disponíveis"</string>
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Som ambiente"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Lado esquerdo"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Lado direito"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Abrir para controles separados da esquerda e da direita"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Fechar para controle unificado"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Silenciar som ambiente"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Mostrar ícones de notificações de baixa prioridade"</string>
<string name="other" msgid="429768510980739978">"Outros"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"alternar o tamanho do bloco"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"remover o bloco"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"adicionar o bloco à última posição"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Mover bloco"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Se quiser ver os apps recentes, deslize para cima e pressione o touchpad com três dedos"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Para ver todos os apps, pressione a tecla de ação no teclado"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Encoberto"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Desbloqueie para ver"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Desbloqueie para ver o código"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Educação contextual"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Use o touchpad para voltar"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Deslize para a esquerda ou direita usando três dedos. Toque para aprender outros gestos."</string>
@@ -1571,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Desconhecidos"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Redefinir todos os blocos?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Todos os blocos \"Configurações rápidas\" serão redefinidos para as configurações originais do dispositivo"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 14b85b7..a8e0f05 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Ambiente"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Esquerda"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Direita"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Expandir para controlos separados do lado direito e esquerdo"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Reduzir para controlo unificado"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Desativar som do ambiente"</string>
@@ -1568,4 +1574,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Desconhecido"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Repor todos os mosaicos?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Todos os mosaicos de Definições rápidas vão ser repostos para as definições originais do dispositivo"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index fc7e2af..7c9028b 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -351,7 +351,7 @@
<string name="quick_settings_networks_unavailable" msgid="1167847013337940082">"Redes indisponíveis"</string>
<string name="quick_settings_wifi_detail_empty_text" msgid="483130889414601732">"Nenhuma rede Wi-Fi disponível"</string>
<string name="quick_settings_wifi_secondary_label_transient" msgid="7501659015509357887">"Ativando…"</string>
- <string name="quick_settings_cast_title" msgid="3033553249449938182">"Transmitir"</string>
+ <string name="quick_settings_cast_title" msgid="3033553249449938182">"Transmissão"</string>
<string name="quick_settings_casting" msgid="1435880708719268055">"Transmitindo"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Dispositivo sem nome"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Não há dispositivos disponíveis"</string>
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Som ambiente"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Lado esquerdo"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Lado direito"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Abrir para controles separados da esquerda e da direita"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Fechar para controle unificado"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Silenciar som ambiente"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Mostrar ícones de notificações de baixa prioridade"</string>
<string name="other" msgid="429768510980739978">"Outros"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"alternar o tamanho do bloco"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"remover o bloco"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"adicionar o bloco à última posição"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Mover bloco"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Se quiser ver os apps recentes, deslize para cima e pressione o touchpad com três dedos"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Para ver todos os apps, pressione a tecla de ação no teclado"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Encoberto"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Desbloqueie para ver"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Desbloqueie para ver o código"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Educação contextual"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Use o touchpad para voltar"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Deslize para a esquerda ou direita usando três dedos. Toque para aprender outros gestos."</string>
@@ -1571,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Desconhecidos"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Redefinir todos os blocos?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Todos os blocos \"Configurações rápidas\" serão redefinidos para as configurações originais do dispositivo"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index b577be5..23270ea 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Împrejurimi"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Stânga"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Dreapta"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Extinde comenzile separate la stânga și la dreapta"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Restrânge la comanda unificată"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Dezactivează sunetul ambiental"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Afișează pictogramele de notificare cu prioritate redusă"</string>
<string name="other" msgid="429768510980739978">"Altele"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"comută dimensiunea cardului"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"elimină cardul"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"adaugă cardul în ultima poziție"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Mută cardul"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Ca să vezi aplicațiile recente, glisează în sus și ține apăsat cu trei degete pe touchpad"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Ca să vezi toate aplicațiile, apasă tasta de acțiuni de pe tastatură"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Ascunsă"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Deblochează pentru a afișa"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Deblochează pentru a vedea codul"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Educație contextuală"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Folosește-ți touchpadul ca să revii"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Glisează la stânga sau la dreapta cu trei degete. Atinge ca să înveți mai multe gesturi."</string>
@@ -1571,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Necunoscută"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Resetezi toate cardurile?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Toate cardurile Setări rapide se vor reseta la setările inițiale ale dispozitivului"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 03b5423..e4c1d45 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Окружающие звуки"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Левый"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Правый"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Разделить на левый и правый элемент управления"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Объединить в один элемент управления"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Заглушить окружающие звуки"</string>
@@ -1568,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Неизвестно"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Сбросить все параметры?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Для всех параметров быстрых настроек будут восстановлены значения по умолчанию."</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index 15441b5..437a35b 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"වටපිටාව"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"වම"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"දකුණ"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"වමට සහ දකුණට වෙන් වූ පාලන වෙත පුළුල් කරන්න"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"ඒකාබද්ධ පාලනයට හකුළන්න"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"අවට පරිසරය නිහඬ කරන්න"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"අඩු ප්රමුඛතා දැනුම්දීම් අයිකන පෙන්වන්න"</string>
<string name="other" msgid="429768510980739978">"වෙනත්"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"ටයිල් එකේ ප්රමාණය මාරු කරන්න"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"ටයිල් ඉවත් කරන්න"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"ටයිල් එක අවසාන ස්ථානයට එක් කරන්න"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"ටයිල් ගෙන යන්න"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"මෑත යෙදුම් බැලීමට, ඉහළට ස්වයිප් කර ස්පර්ශ පුවරුව මත ඇඟිලි තුනකින් අල්ලාගෙන සිටින්න"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"ඔබේ සියලුම යෙදුම් බැලීමට, ඔබේ යතුරුපුවරුවේ ක්රියාකාරී යතුර ඔබන්න"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"නැවත සකස් කරන ලද"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"බැලීමට අගුළු හරින්න"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"කේතය බැලීමට අගුළු හරින්න"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"සන්දර්භීය අධ්යාපනය"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"ආපසු යාමට ඔබේ ස්පර්ශ පුවරුව භාවිත කරන්න"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"ඇඟිලි තුනක් භාවිතයෙන් වමට හෝ දකුණට ස්වයිප් කරන්න. තව ඉංගිත දැන ගැනීමට තට්ටු කරන්න."</string>
@@ -1571,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"නොදනී"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"සියලු ටයිල් නැවත සකසන්න ද?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"සියලු ඉක්මන් සැකසීම් ටයිල් උපාංගයේ මුල් සැකසීම් වෙත නැවත සකසනු ඇත"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 868e939..859c976 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Okolie"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Vľavo"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Vpravo"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Rozbaliť na samostatné ovládanie ľavej a pravej strany"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Zbaliť na jednotné ovládanie"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Vypnúť zvuk okolia"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Zobraziť ikony upozornení s nízkou prioritou"</string>
<string name="other" msgid="429768510980739978">"Ďalšie"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"prepnúť veľkosť karty"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"odstrániť kartu"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"pridáte kartu na poslednú pozíciu"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Presunúť kartu"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Ak si chcete zobraziť nedávne aplikácie, potiahnite po touchpade troma prstami nahor a pridržte ich."</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Ak si chcete zobraziť všetky aplikácie, stlačte na klávesnici akčný kláves"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Zamaskované"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Zobrazíte odomknutím"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Kód sa zobrazí po odomknutí"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Kontextová náuka"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Prechádzajte späť pomocou touchpadu"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Potiahnite troma prstami doľava alebo doprava. Viac o gestách sa dozviete klepnutím."</string>
@@ -1571,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Neznáme"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Chcete resetovať všetky karty?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Všetky karty rýchlych nastavení sa resetujú na pôvodné nastavenia zariadenia"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index e6d133e..2a1ed469 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Okolica"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Levo"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Desno"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Razširitev na ločene kontrolnike za levo in desno stran"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Strnitev v enotni kontrolnik"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Izklop okoliškega zvoka"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Pokaži ikone obvestil z nizko stopnjo prednosti"</string>
<string name="other" msgid="429768510980739978">"Drugo"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"preklop velikosti ploščice"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"odstranitev ploščice"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"dodajanje ploščice na zadnji položaj"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Premik ploščice"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Za ogled nedavnih aplikacij povlecite s tremi prsti navzgor po sledilni ploščici in pridržite"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Za ogled vseh aplikacij pritisnite tipko za dejanja na tipkovnici"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Zakrito"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Odklenite za ogled"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Odklenite za ogled kode"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Kontekstualno izobraževanje"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Uporaba sledilne ploščice za pomik nazaj"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"S tremi prsti povlecite levo ali desno. Dotaknite se, če želite spoznati več potez."</string>
@@ -1571,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Neznano"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Želite ponastaviti vse ploščice?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Vse ploščice v hitrih nastavitvah bodo ponastavljene na prvotne nastavitve naprave."</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index 724c6c0..916595f 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Ambienti rrethues"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Majtas"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Djathtas"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Zgjero te kontrollet e veçuara majtas dhe djathtas"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Palos te kontrolli i unifikuar"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Vendos në heshtje ambientin rrethues"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Shfaq ikonat e njoftimeve me përparësi të ulët"</string>
<string name="other" msgid="429768510980739978">"Të tjera"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"ndrysho madhësinë e pllakëzës"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"hiq pllakëzën"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"shtuar pllakëzën në pozicionin e fundit"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Zhvendos pllakëzën"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Për aplikacionet e fundit, rrëshqit shpejt lart dhe mbaj shtypur me tre gishta në bllokun me prekje"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Për të shikuar të gjitha aplikacionet, shtyp tastin e veprimit në tastierë"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Redaktuar"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Shkyçe për ta parë"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Shkyçe për të parë kodin"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Edukimi kontekstual"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Përdor bllokun me prekje për t\'u kthyer prapa"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Rrëshqit shpejt majtas ose djathtas duke përdorur tre gishta. Trokit për të mësuar më shumë gjeste."</string>
@@ -1571,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Nuk njihet"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Të rivendosen të gjitha pllakëzat?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Të gjitha pllakëzat e \"Cilësimeve të shpejta\" do të rivendosen te cilësimet origjinale të pajisjes"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index e6471eb..6bf3a69 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Повезани сте са <xliff:g id="BLUETOOTH">%s</xliff:g>."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"Повезани смо са уређајем <xliff:g id="CAST">%s</xliff:g>."</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"Проширите групу."</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Додајте уређај у групу."</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Уклоните уређај из групе."</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"Отворите апликацију."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Није повезано."</string>
<string name="data_connection_roaming" msgid="375650836665414797">"Роминг"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Унос"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Слушни апарати"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Укључује се..."</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Не можете да прилагодите осветљеност јер је контролише апликација у првом плану"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Аутоматска ротација"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Аутоматско ротирање екрана"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Локација"</string>
@@ -426,6 +423,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Окружење"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Лево"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Десно"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Прошири на контроле раздвојене на леву и десну страну"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Скупи у јединствену контролу"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Искључи звук окружења"</string>
@@ -1568,4 +1571,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Непознато"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Желите да ресетујете све плочице?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Све плочице Брзих подешавања ће се ресетовати на првобитна подешавања уређаја"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index 9f2a208..aa9f4a4 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Omgivningsläge"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Vänster"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Höger"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Utöka till kontroller till vänster och höger"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Komprimera till enhetlig kontroll"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Stäng av omgivningsljudet"</string>
@@ -1568,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Okänt"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Vill du återställa alla rutor?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Alla Snabbinställningsrutor återställs till enhetens ursprungliga inställningar"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 6222a76..72bfb83 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Mazingira"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Kushoto"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Kulia"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Panua iwe vidhibiti vilivyotenganishwa kushoto na kulia"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Kunja iwe kidhibiti cha pamoja"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Zima sauti ya mazingira"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Onyesha aikoni za arifa zisizo muhimu"</string>
<string name="other" msgid="429768510980739978">"Nyingine"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"ubadilishe ukubwa wa kigae"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"ondoa kigae"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"weka kigae kwenye nafasi ya mwisho"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Hamisha kigae"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Telezesha vidole vitatu juu na ushikilie kwenye padi ya kugusa ili uangalie programu za hivi majuzi"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Bonyeza kitufe cha vitendo kwenye kibodi yako ili uangalie programu zako zote"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Maandishi yameondolewa"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Fungua ili uone"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Fungua ili uone msimbo"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Elimu inayolingana na muktadha"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Kutumia padi yako ya kugusa ili kurudi nyuma"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Telezesha vidole vitatu kulia au kushoto. Gusa ili upate maelezo kuhusu miguso zaidi."</string>
@@ -1571,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Visivyojulikana"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Ungependa kubadilisha vigae vyote?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Vigae vyote vya Mipangilio ya Haraka vitabadilishwa kuwa katika mipangilio halisi ya kifaa"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-sw/tiles_states_strings.xml b/packages/SystemUI/res/values-sw/tiles_states_strings.xml
index fb6e38f..a16a7b7 100644
--- a/packages/SystemUI/res/values-sw/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-sw/tiles_states_strings.xml
@@ -68,7 +68,7 @@
</string-array>
<string-array name="tile_states_bt">
<item msgid="5330252067413512277">"Hakipatikani"</item>
- <item msgid="5315121904534729843">"Kimezimwa"</item>
+ <item msgid="5315121904534729843">"Imezimwa"</item>
<item msgid="503679232285959074">"Imewashwa"</item>
</string-array>
<string-array name="tile_states_airplane">
diff --git a/packages/SystemUI/res/values-sw720dp-land/dimens.xml b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
index 41bb37e..f4f0424 100644
--- a/packages/SystemUI/res/values-sw720dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
@@ -30,11 +30,6 @@
not appear immediately after user swipes to the side -->
<dimen name="qs_tiles_page_horizontal_margin">20dp</dimen>
- <!-- Size of Smartspace media recommendations cards in the QSPanel carousel -->
- <dimen name="qs_media_rec_icon_top_margin">16dp</dimen>
- <dimen name="qs_media_rec_album_size">112dp</dimen>
- <dimen name="qs_media_rec_album_side_margin">16dp</dimen>
-
<dimen name="controls_panel_corner_radius">40dp</dimen>
<dimen name="lockscreen_shade_max_over_scroll_amount">42dp</dimen>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 0bf5f22..31ab515 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"சுற்றுப்புறங்கள்"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"இடது"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"வலது"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"இடது மற்றும் வலதுபுறம் உள்ள கட்டுப்பாடுகளை விரிவாக்கும்"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"ஒருங்கிணைந்த கட்டுப்பாட்டுக்குச் சுருக்கும்"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"சுற்றுப்புறங்களின் ஒலியை அடக்கும்"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"குறைந்த முன்னுரிமை உள்ள அறிவிப்பு ஐகான்களைக் காட்டு"</string>
<string name="other" msgid="429768510980739978">"மற்றவை"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"கட்டத்தின் அளவை நிலைமாற்றும்"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"கட்டத்தை அகற்றும்"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"கடைசி இடத்தில் கட்டத்தைச் சேர்க்கலாம்"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"கட்டத்தை நகர்த்து"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"சமீபத்திய ஆப்ஸைப் பார்க்க, டச்பேடில் மூன்று விரல்களால் மேல்நோக்கி ஸ்வைப் செய்து பிடிக்கவும்"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"அனைத்து ஆப்ஸையும் பார்க்க, உங்கள் கீபோர்டில் உள்ள ஆக்ஷன் பட்டனை அழுத்தவும்"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"அர்த்தம் புரியாதபடி திருத்தப்பட்டது"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"பார்ப்பதற்கு அன்லாக் செய்யவும்"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"குறியீட்டைப் பார்க்க அன்லாக் செய்யவும்"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"சூழல் சார்ந்த கல்வி"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"பின்செல்ல, உங்கள் டச்பேடைப் பயன்படுத்துங்கள்"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"மூன்று விரல்களால் இடது அல்லது வலதுபுறம் ஸ்வைப் செய்யவும். சைகைகள் குறித்து மேலும் அறிய தட்டவும்."</string>
@@ -1571,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"தெரியவில்லை"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"அனைத்துக் கட்டங்களையும் மீட்டமைக்கவா?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"விரைவு அமைப்புகளின் கட்டங்கள் அனைத்தும் சாதனத்தின் அசல் அமைப்புகளுக்கு மீட்டமைக்கப்படும்"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 2042ead..d26bfac 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"పరిసరాలు"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"ఎడమ వైపునకు"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"కుడి వైపునకు"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"ఎడమ, కుడి అని వేరు చేయబడిన కంట్రోల్స్కు విస్తరించండి"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"యూనిఫైడ్ కంట్రోల్కు కుదించండి"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"పరిసరాలను మ్యూట్ చేయండి"</string>
@@ -1571,4 +1577,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"తెలియదు"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"టైల్స్ అన్ని రీసెట్ చేయాలా?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"అన్ని క్విక్ సెట్టింగ్ల టైల్స్, పరికరం తాలూకు ఒరిజినల్ సెట్టింగ్లకు రీసెట్ చేయబడతాయి"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index 9ac9d01b..9196f95 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -252,10 +252,8 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"เชื่อมต่อกับ <xliff:g id="BLUETOOTH">%s</xliff:g> แล้ว"</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"เชื่อมต่อกับ <xliff:g id="CAST">%s</xliff:g>"</string>
<string name="accessibility_expand_group" msgid="521237935987978624">"ขยายกลุ่ม"</string>
- <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) -->
- <skip />
- <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) -->
- <skip />
+ <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"เพิ่มอุปกรณ์ลงในกลุ่ม"</string>
+ <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"นำอุปกรณ์ออกจากกลุ่ม"</string>
<string name="accessibility_open_application" msgid="1749126077501259712">"เปิดแอปพลิเคชัน"</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"ไม่ได้เชื่อมต่อ"</string>
<string name="data_connection_roaming" msgid="375650836665414797">"โรมมิ่ง"</string>
@@ -333,8 +331,7 @@
<string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"อินพุต"</string>
<string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"เครื่องช่วยฟัง"</string>
<string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"กำลังเปิด..."</string>
- <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) -->
- <skip />
+ <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"ปรับความสว่างไม่ได้เนื่องจากควบคุมโดยแอปที่อยู่ด้านบน"</string>
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"หมุนอัตโนมัติ"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"หมุนหน้าจออัตโนมัติ"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"ตำแหน่ง"</string>
@@ -426,6 +423,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"เสียงแวดล้อม"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"ซ้าย"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"ขวา"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"ขยายเป็นการควบคุมที่แยกด้านซ้ายและขวา"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"ยุบเป็นการควบคุมแบบรวม"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"ปิดเสียงแวดล้อม"</string>
@@ -991,8 +994,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"แสดงไอคอนการแจ้งเตือนลำดับความสำคัญต่ำ"</string>
<string name="other" msgid="429768510980739978">"อื่นๆ"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"สลับขนาดของการ์ด"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"นำชิ้นส่วนออก"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"เพิ่มการ์ดไปยังตำแหน่งสุดท้าย"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"ย้ายชิ้นส่วน"</string>
@@ -1545,10 +1547,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"หากต้องการดูแอปล่าสุด ให้ใช้ 3 นิ้วปัดขึ้นแล้วค้างไว้บนทัชแพด"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"หากต้องการดูแอปทั้งหมด ให้กดปุ่มดำเนินการบนแป้นพิมพ์"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"ปกปิดไว้"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"ปลดล็อกเพื่อดู"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"ปลดล็อกเพื่อดูรหัส"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"การศึกษาตามบริบท"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"ใช้ทัชแพดเพื่อย้อนกลับ"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"ใช้ 3 นิ้วปัดไปทางซ้ายหรือขวา แตะเพื่อดูข้อมูลเพิ่มเติมเกี่ยวกับท่าทางสัมผัสต่างๆ"</string>
@@ -1571,4 +1571,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"ไม่ทราบ"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"รีเซ็ตการ์ดทั้งหมดใช่ไหม"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"การ์ดการตั้งค่าด่วนทั้งหมดจะรีเซ็ตเป็นการตั้งค่าเดิมของอุปกรณ์"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 516386b..a6b0d6a 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Paligid"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Kaliwa"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Kanan"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"I-expand sa kaliwa at kanang magkahiwalay na mga kontrol"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"I-collapse sa pinag-isang kontrol"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"I-mute ang paligid"</string>
@@ -1568,4 +1574,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Hindi Alam"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"I-reset ang lahat ng tile?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Magre-reset sa mga orihinal na setting ng device ang lahat ng tile ng Mga Mabilisang Setting"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index b643203..13d6cd7 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Çevredeki sesler"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Sol"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Sağ"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Sol ve sağ kontrolleri ayırarak genişlet"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Kontrolleri birleştirerek daralt"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Çevredeki sesleri kapat"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Düşük öncelikli bildirim simgelerini göster"</string>
<string name="other" msgid="429768510980739978">"Diğer"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"kutu boyutunu değiştir"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"Kutuyu kaldırmak için"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"kutuyu son konuma ekleyin"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Kutuyu taşı"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Son uygulamaları görüntülemek için dokunmatik alanda üç parmağınızla yukarı kaydırıp basılı tutun"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Tüm uygulamalarınızı görüntülemek için klavyenizdeki eylem tuşuna basın"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Çıkartıldı"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Görüntülemek için kilidi açın"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Kodu görüntülemek için kilidi açın"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Bağlama dayalı eğitim"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Geri dönmek için dokunmatik alanınızı kullanın"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Üç parmağınızla sola veya sağa kaydırın. Daha fazla hareket öğrenmek için dokunun."</string>
@@ -1571,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Bilinmiyor"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Tüm ayar kutuları sıfırlansın mı?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Tüm Hızlı Ayarlar kutuları cihazın özgün ayarlarına sıfırlanır"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index db3c02d..f0f4e38 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Звуки оточення"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Ліворуч"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Праворуч"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Розгорнути в окремі елементи керування ліворуч і праворуч"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Згорнути в єдиний елемент керування"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Вимкнути звуки оточення"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Показувати значки сповіщень із низьким пріоритетом"</string>
<string name="other" msgid="429768510980739978">"Інше"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"змінити розмір плитки"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"вилучити опцію"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"додати панель на останню позицію"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Перемістити опцію"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Щоб переглянути останні додатки, проведіть трьома пальцями вгору по сенсорній панелі й утримуйте їх"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Щоб переглянути всі додатки, натисніть клавішу дії на клавіатурі"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Замасковано"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Розблокуйте, щоб переглянути"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Розблокуйте, щоб переглянути код"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Контекстне навчання"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Щоб повернутися, використовуйте сенсорну панель"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Проведіть трьома пальцями вліво чи вправо. Натисніть, щоб дізнатися про інші жести."</string>
@@ -1571,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Невідомо"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Скинути всі панелі?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Усі панелі швидких налаштувань буде скинуто до стандартних налаштувань пристрою"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index ffc3f5e..ef10b87 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"اطراف"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"دائیں"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"بائیں"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"بائیں اور دائیں علیحدہ کردہ کنٹرولز کو پھیلائیں"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"یونیفائیڈ کنٹرول کیلئے سکیڑیں"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"اطراف کو خاموش کریں"</string>
@@ -1568,4 +1574,5 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"نامعلوم"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"سبھی ٹائلز ری سیٹ کریں؟"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"سبھی فوری ترتیبات کی ٹائلز آلہ کی اصل ترتیبات پر ری سیٹ ہو جائیں گی"</string>
+ <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>، <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string>
</resources>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index e325cf3..facc51f 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Atrof-muhit"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Chap"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Oʻng"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Chap va oʻngga ajratilgan boshqaruv elementlariga yoyish"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Yagona boshqaruvga yigʻish"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Atrof-muhitni ovozsiz qilish"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Muhim boʻlmagan bildirishnoma ikonkalarini koʻrsatish"</string>
<string name="other" msgid="429768510980739978">"Boshqa"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"katak oʻlchamini almashtirish"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"katakchani olib tashlash"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"kartochkani oxirgi oʻringa qoʻshish"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Katakchani boshqa joyga olish"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Oxirgi ilovalarni koʻrish uchun sensorli panelda uchta barmoq bilan tepaga surib, bosib turing"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Barcha ishoralarni koʻrish uchun klaviaturadagi amal tugmasini bosing"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Chiqarildi"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Koʻrish uchun qulfdan chiqaring"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Kodni koʻrish uchun qulfdan chiqaring"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Kontekstual taʼlim"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Sensorli panel orqali orqaga qaytish"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Uchta barmoq bilan chapga yoki oʻngga suring. Boshqa ishoralar bilan tanishish uchun bosing."</string>
@@ -1571,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Noaniq"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Barcha katakchalar asliga qaytarilsinmi?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Barcha Tezkor sozlamalar katakchalari qurilmaning asl sozlamalariga qaytariladi"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index f64f084..80db132 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Âm lượng xung quanh"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Trái"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Phải"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Mở rộng thành các nút điều khiển tách biệt bên trái và bên phải"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Thu gọn thành nút điều khiển hợp nhất"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Tắt tiếng xung quanh"</string>
@@ -586,7 +592,7 @@
<string name="media_projection_entry_cast_permission_dialog_option_text_single_app" msgid="6073353940838561981">"Truyền một ứng dụng"</string>
<string name="media_projection_entry_cast_permission_dialog_option_text_entire_screen" msgid="8389508187954155307">"Truyền toàn bộ màn hình"</string>
<string name="media_projection_entry_cast_permission_dialog_warning_entire_screen" msgid="4040447861037324017">"Khi bạn truyền toàn bộ màn hình thì người khác sẽ thấy được mọi nội dung trên màn hình của bạn. Vì vậy, hãy thận trọng đối với những thông tin như mật khẩu, thông tin thanh toán, tin nhắn, ảnh, âm thanh và video."</string>
- <string name="media_projection_entry_cast_permission_dialog_warning_single_app" msgid="7487834861348460736">"Khi bạn truyền một ứng dụng, thì người khác sẽ thấy được mọi nội dung xuất hiện hoặc phát trên ứng dụng đó. Vì vậy, hãy thận trọng đối với những thông tin như mật khẩu, thông tin thanh toán, tin nhắn, ảnh, âm thanh và video."</string>
+ <string name="media_projection_entry_cast_permission_dialog_warning_single_app" msgid="7487834861348460736">"Khi bạn truyền một ứng dụng, mọi nội dung xuất hiện hoặc phát trên ứng dụng đó đều hiển thị trên thiết bị được truyền tới. Vì vậy, hãy thận trọng đối với những thông tin như mật khẩu, thông tin thanh toán, tin nhắn, ảnh, âm thanh và video."</string>
<string name="media_projection_entry_cast_permission_dialog_continue_entire_screen" msgid="3261124185304676483">"Màn hình truyền"</string>
<string name="media_projection_entry_cast_app_selector_title" msgid="6323062146661922387">"Chọn ứng dụng để truyền"</string>
<string name="media_projection_entry_generic_permission_dialog_title" msgid="4519802931547483628">"Bắt đầu chia sẻ?"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Hiển thị biểu tượng thông báo có mức ưu tiên thấp"</string>
<string name="other" msgid="429768510980739978">"Khác"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"bật hoặc tắt kích thước của ô"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"xóa ô"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"thêm ô vào vị trí cuối cùng"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Di chuyển ô"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Để xem các ứng dụng gần đây, hãy dùng 3 ngón tay vuốt lên và giữ trên bàn di chuột"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Để xem tất cả ứng dụng của bạn, hãy nhấn phím hành động trên bàn phím"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Bị loại bỏ"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Mở khoá để xem"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Mở khoá để xem mã"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Hướng dẫn theo bối cảnh"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Dùng bàn di chuột để quay lại"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Dùng 3 ngón tay vuốt sang trái hoặc sang phải. Hãy nhấn để tìm hiểu các cử chỉ khác."</string>
@@ -1571,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Không xác định"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Đặt lại mọi ô?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Mọi ô Cài đặt nhanh sẽ được đặt lại về chế độ cài đặt ban đầu của thiết bị"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index a355a67..feb3989 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"周围声音"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"左侧"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"右侧"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"展开为左侧和右侧的单独控件"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"收起为统一控件"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"将周围声音静音"</string>
@@ -991,8 +997,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"显示低优先级的通知图标"</string>
<string name="other" msgid="429768510980739978">"其他"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"切换功能块大小"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"移除功能块"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"将功能块添加到最后一个位置"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"移动功能块"</string>
@@ -1545,10 +1550,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"如要查看最近用过的应用,请用三根手指在触控板上向上滑动并按住"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"如要查看所有应用,请按下键盘上的快捷操作按键"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"已隐去"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"解锁即可查看"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"解锁即可查看验证码"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"内容相关指导"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"使用触控板返回"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"用三根手指向左或向右滑动。点按即可了解更多手势。"</string>
@@ -1571,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"未知"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"要重置所有功能块吗?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"所有“快捷设置”功能块都将重置为设备的原始设置"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index c5a81fb2..031df00 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"環境聲音"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"左"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"右"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"打開就可以分開左右控制"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"收埋就可以統一控制"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"環境聲音靜音"</string>
@@ -1568,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"不明"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"要重設所有圖塊嗎?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"所有「快速設定」圖塊將重設為裝置的原始設定"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 24a15a1..df05577 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -426,6 +426,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"環境"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"左"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"右"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"展開為左右獨立控制選項"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"收合為統合控制選項"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"將環境靜音"</string>
@@ -1568,4 +1574,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"不明"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"要重設所有設定方塊嗎?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"所有快速設定方塊都會恢復裝置的原始設定"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 9188648..d571f0e 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -428,6 +428,12 @@
<string name="hearing_devices_ambient_label" msgid="629440938614895797">"Izindawo ezizungezile"</string>
<string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Kwesokunxele"</string>
<string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Kwesokudla"</string>
+ <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) -->
+ <skip />
+ <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) -->
+ <skip />
<string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Nwebela ezilawulini ezihlukanisiwe zakwesokunxele nakwesokudla"</string>
<string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Goqa ezilawulini ezihlanganisiwe"</string>
<string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Thulisa izindawo ezizungezile"</string>
@@ -993,8 +999,7 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Bonisa izithonjana zesaziso zokubaluleka okuncane"</string>
<string name="other" msgid="429768510980739978">"Okunye"</string>
- <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) -->
- <skip />
+ <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"guqula usayizi wethayela"</string>
<string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"susa ithayela"</string>
<string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"faka ithayela endaweni yokugcina"</string>
<string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Hambisa ithayela"</string>
@@ -1547,10 +1552,8 @@
<string name="overview_edu_toast_content" msgid="5797030644017804518">"Ukuze ubuke ama-app akamuva, swayiphela phezulu bese ubambe ngeminwe emithathu ephedini yokuthinta"</string>
<string name="all_apps_edu_toast_content" msgid="8807496014667211562">"Ukuze ubuke wonke ama-app wakho, cindezela inkinobho yokufinyelela kukhibhodi yakho"</string>
<string name="redacted_notification_single_line_title" msgid="212019960919261670">"Kwenziwe iredact"</string>
- <!-- no translation found for public_notification_single_line_text (3576190291791654933) -->
- <skip />
- <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) -->
- <skip />
+ <string name="public_notification_single_line_text" msgid="3576190291791654933">"Vula ukuze ubuke"</string>
+ <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"Vula ukuze ubone ikhodi"</string>
<string name="contextual_education_dialog_title" msgid="4630392552837487324">"Imfundo yokuqukethwe"</string>
<string name="back_edu_notification_title" msgid="5624780717751357278">"Sebenzisa iphedi yokuthinta ukuze ubuyele emuva"</string>
<string name="back_edu_notification_content" msgid="2497557451540954068">"Swayiphela kwesokunxele noma kwesokudla usebenzisa iminwe emithathu. Thepha ukuze ufunde kabanzi ngokunyakazisa umzimba."</string>
@@ -1573,4 +1576,6 @@
<string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Akwaziwa"</string>
<string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Qala kabusha onke amathayela?"</string>
<string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Ithayela Lamasethingi Asheshayo lizosetha kabusha libuyele kumasethingi okuqala edivayisi"</string>
+ <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 4995858..78e719f 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -98,10 +98,6 @@
TODO (b/293252410) - change this comment/resource when flag is enabled -->
<integer name="small_land_lockscreen_quick_settings_max_rows">2</integer>
- <!-- If the dp width of the available space is <= this value, potentially adjust the number
- of media recommendation items-->
- <integer name="default_qs_media_rec_width_dp">380</integer>
-
<!-- The number of columns that the top level tiles span in the QuickSettings -->
<!-- The default tiles to display in QuickSettings -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 7c370d3..c8e3107 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1342,19 +1342,6 @@
<dimen name="qs_media_session_collapsed_legacy_guideline">144dp</dimen>
<dimen name="qs_media_session_collapsed_guideline">168dp</dimen>
- <!-- Size of Smartspace media recommendations cards in the QSPanel carousel -->
- <dimen name="qs_media_rec_default_width">380dp</dimen>
- <dimen name="qs_media_rec_icon_top_margin">16dp</dimen>
- <dimen name="qs_media_rec_album_icon_size">16dp</dimen>
- <dimen name="qs_media_rec_album_size">88dp</dimen>
- <dimen name="qs_media_rec_album_width">110dp</dimen>
- <dimen name="qs_media_rec_album_height_expanded">108dp</dimen>
- <dimen name="qs_media_rec_album_height_collapsed">77dp</dimen>
- <dimen name="qs_media_rec_album_side_margin">16dp</dimen>
- <dimen name="qs_media_rec_album_bottom_margin">8dp</dimen>
- <dimen name="qs_media_rec_album_title_bottom_margin">22dp</dimen>
- <dimen name="qs_media_rec_album_subtitle_height">12dp</dimen>
-
<!-- Chipbar -->
<!-- (Used for media tap-to-transfer chip for sender device and active unlock) -->
<dimen name="chipbar_outer_padding">16dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 43ea2c3..c06c17a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1042,6 +1042,8 @@
<string name="hearing_devices_tools_label">Tools</string>
<!-- QuickSettings: Tool name for hearing devices dialog related tools [CHAR LIMIT=40] [BACKUP_MESSAGE_ID=8916875614623730005]-->
<string name="quick_settings_hearing_devices_live_caption_title">Live Caption</string>
+ <!-- QuickSettings: Label for button to go to hearing devices settings page [CHAR_LIMIT=20] -->
+ <string name="hearing_devices_settings_button">Settings</string>
<!-- QuickSettings: Notes tile. The label of a quick settings tile for launching the default notes taking app. [CHAR LIMIT=NONE] -->
<string name="quick_settings_notes_label">Note</string>
@@ -2599,6 +2601,9 @@
<!-- Accessibility description indicating the currently selected tile's position. Only used for tiles that are currently in use [CHAR LIMIT=NONE] -->
<string name="accessibility_qs_edit_position">Position <xliff:g id="position" example="5">%1$d</xliff:g></string>
+ <!-- Accessibility description indicating the currently selected tile is already added [CHAR LIMIT=NONE] -->
+ <string name="accessibility_qs_edit_tile_already_added">Tile already added</string>
+
<!-- Accessibility announcement after a tile has been added [CHAR LIMIT=NONE] -->
<string name="accessibility_qs_edit_tile_added">Tile added</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 4431dda..7895ff7 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -895,57 +895,6 @@
<item name="android:textColor">@android:color/system_on_primary_dark</item>
</style>
- <style name="MediaPlayer.Recommendation"/>
-
- <style name="MediaPlayer.Recommendation.Header">
- <item name="android:layout_width">wrap_content</item>
- <item name="android:layout_height">wrap_content</item>
- <item name="android:layout_marginTop">@dimen/qs_media_padding</item>
- <item name="android:layout_marginStart">@dimen/qs_media_padding</item>
- <item name="android:fontFamily">=@*android:string/config_headlineFontFamilyMedium</item>
- <item name="android:singleLine">true</item>
- <item name="android:textSize">14sp</item>
- <item name="android:textColor">?android:attr/textColorPrimary</item>
- </style>
-
- <style name="MediaPlayer.Recommendation.AlbumContainer">
- <item name="android:layout_width">@dimen/qs_media_rec_album_size</item>
- <item name="android:layout_height">@dimen/qs_media_rec_album_size</item>
- <item name="android:background">@drawable/qs_media_light_source</item>
- <item name="android:layout_marginTop">@dimen/qs_media_padding</item>
- <item name="android:layout_marginBottom">@dimen/qs_media_rec_album_bottom_margin</item>
- </style>
-
- <style name="MediaPlayer.Recommendation.AlbumContainer.Updated">
- <item name="android:layout_width">@dimen/qs_media_rec_album_width</item>
- <item name="android:minWidth">@dimen/qs_media_rec_album_width</item>
- <item name="android:minHeight">@dimen/qs_media_rec_album_height_collapsed</item>
- <item name="android:background">@drawable/qs_media_light_source</item>
- <item name="android:layout_marginTop">@dimen/qs_media_info_spacing</item>
- </style>
-
- <style name="MediaPlayer.Recommendation.Album">
- <item name="android:backgroundTint">@color/media_player_album_bg</item>
- </style>
-
- <style name="MediaPlayer.Recommendation.Text">
- <item name="android:layout_width">@dimen/qs_media_rec_album_size</item>
- <item name="android:layout_height">wrap_content</item>
- <item name="android:maxLines">1</item>
- <item name="android:ellipsize">end</item>
- <item name="android:textSize">14sp</item>
- <item name="android:gravity">start</item>
- </style>
-
- <style name="MediaPlayer.Recommendation.Text.Title">
- <item name="android:textColor">?android:attr/textColorPrimary</item>
- </style>
-
- <style name="MediaPlayer.Recommendation.Text.Subtitle">
- <item name="android:textColor">?android:attr/textColorSecondary</item>
- </style>
-
-
<!-- Used to style charging animation AVD animation -->
<style name="ChargingAnim" />
diff --git a/packages/SystemUI/res/xml/media_recommendations_collapsed.xml b/packages/SystemUI/res/xml/media_recommendations_collapsed.xml
deleted file mode 100644
index d3be3c7..0000000
--- a/packages/SystemUI/res/xml/media_recommendations_collapsed.xml
+++ /dev/null
@@ -1,64 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 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
- -->
-<ConstraintSet
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- >
-
- <Constraint
- android:id="@+id/sizing_view"
- android:layout_width="match_parent"
- android:layout_height="@dimen/qs_media_session_height_collapsed"
- />
-
- <Constraint
- android:id="@+id/media_rec_title"
- style="@style/MediaPlayer.Recommendation.Header"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent"/>
-
- <Constraint
- android:id="@+id/media_cover1_container"
- style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
- android:layout_height="@dimen/qs_media_rec_album_height_collapsed"
- android:layout_marginEnd="@dimen/qs_media_info_spacing"
- android:layout_marginStart="@dimen/qs_media_padding"
- app:layout_constraintTop_toBottomOf="@+id/media_rec_title"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/media_cover2_container"/>
-
-
- <Constraint
- android:id="@+id/media_cover2_container"
- style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
- android:layout_height="@dimen/qs_media_rec_album_height_collapsed"
- android:layout_marginEnd="@dimen/qs_media_info_spacing"
- app:layout_constraintTop_toBottomOf="@+id/media_rec_title"
- app:layout_constraintStart_toEndOf="@id/media_cover1_container"
- app:layout_constraintEnd_toStartOf="@id/media_cover3_container"/>
-
- <Constraint
- android:id="@+id/media_cover3_container"
- style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
- android:layout_height="@dimen/qs_media_rec_album_height_collapsed"
- android:layout_marginEnd="@dimen/qs_media_padding"
- app:layout_constraintTop_toBottomOf="@+id/media_rec_title"
- app:layout_constraintStart_toEndOf="@id/media_cover2_container"
- app:layout_constraintEnd_toEndOf="parent"/>
-
-
-</ConstraintSet>
diff --git a/packages/SystemUI/res/xml/media_recommendations_expanded.xml b/packages/SystemUI/res/xml/media_recommendations_expanded.xml
deleted file mode 100644
index 88c7055..0000000
--- a/packages/SystemUI/res/xml/media_recommendations_expanded.xml
+++ /dev/null
@@ -1,71 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 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
- -->
-<ConstraintSet
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- >
-
- <Constraint
- android:id="@+id/sizing_view"
- android:layout_width="match_parent"
- android:layout_height="@dimen/qs_media_session_height_expanded"
- />
-
- <Constraint
- android:id="@+id/media_rec_title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/qs_media_padding"
- android:layout_marginStart="@dimen/qs_media_padding"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent"
- android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
- android:singleLine="true"
- android:textSize="14sp"
- android:textColor="@color/notification_primary_text_color"/>
-
- <Constraint
- android:id="@+id/media_cover1_container"
- style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
- android:layout_height="@dimen/qs_media_rec_album_height_expanded"
- android:layout_marginEnd="@dimen/qs_media_info_spacing"
- android:layout_marginStart="@dimen/qs_media_padding"
- app:layout_constraintTop_toBottomOf="@+id/media_rec_title"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/media_cover2_container"/>
-
-
- <Constraint
- android:id="@+id/media_cover2_container"
- style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
- android:layout_height="@dimen/qs_media_rec_album_height_expanded"
- android:layout_marginEnd="@dimen/qs_media_info_spacing"
- app:layout_constraintTop_toBottomOf="@+id/media_rec_title"
- app:layout_constraintStart_toEndOf="@id/media_cover1_container"
- app:layout_constraintEnd_toStartOf="@id/media_cover3_container"/>
-
- <Constraint
- android:id="@+id/media_cover3_container"
- style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
- android:layout_height="@dimen/qs_media_rec_album_height_expanded"
- android:layout_marginEnd="@dimen/qs_media_padding"
- app:layout_constraintTop_toBottomOf="@+id/media_rec_title"
- app:layout_constraintStart_toEndOf="@id/media_cover2_container"
- app:layout_constraintEnd_toEndOf="parent"/>
-
-
-</ConstraintSet>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ILauncherProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ILauncherProxy.aidl
index 10b9303..ade63b1 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ILauncherProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ILauncherProxy.aidl
@@ -66,7 +66,7 @@
/**
* Sent when some system ui state changes.
*/
- void onSystemUiStateChanged(long stateFlags) = 16;
+ void onSystemUiStateChanged(long stateFlags, int displayId) = 16;
/**
* Sent when suggested rotation button could be shown
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
index dcbacec..c880f05 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
@@ -203,7 +203,9 @@
CarrierTextManagerLogger logger) {
mContext = context;
- mIsEmergencyCallCapable = telephonyManager.isVoiceCapable();
+ boolean hasTelephony = mContext.getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
+ mIsEmergencyCallCapable = telephonyManager.isVoiceCapable() && hasTelephony;
mShowAirplaneMode = showAirplaneMode;
mShowMissingSim = showMissingSim;
@@ -221,9 +223,7 @@
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLogger = logger;
mBgExecutor.execute(() -> {
- boolean supported = mContext.getPackageManager()
- .hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
- if (supported && mNetworkSupported.compareAndSet(false, supported)) {
+ if (hasTelephony && mNetworkSupported.compareAndSet(false, hasTelephony)) {
// This will set/remove the listeners appropriately. Note that it will never double
// add the listeners.
handleSetListening(mCarrierTextCallback);
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 1b8282b..e827b2d 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -21,6 +21,7 @@
import android.content.Intent
import android.content.IntentFilter
import android.content.res.Resources
+import android.graphics.RectF
import android.os.Trace
import android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
import android.provider.Settings.Global.ZEN_MODE_OFF
@@ -78,7 +79,7 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.Job
-import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
@@ -147,7 +148,7 @@
val clockStr = clock.toString()
loggers.forEach { it.d({ "New Clock: $str1" }) { str1 = clockStr } }
- clock.initialize(isDarkTheme(), dozeAmount, 0f)
+ clock.initialize(isDarkTheme(), dozeAmount.value, 0f, { onClockBoundsChanged.value = it })
if (!regionSamplingEnabled) {
updateColors()
@@ -240,17 +241,16 @@
private var smallClockFrame: ViewGroup? = null
private var onGlobalLayoutListener: OnGlobalLayoutListener? = null
- private var isDozing = false
- private set
-
private var isCharging = false
- private var dozeAmount = 0f
private var isKeyguardVisible = false
private var isRegistered = false
private var disposableHandle: DisposableHandle? = null
private val regionSamplingEnabled = featureFlags.isEnabled(REGION_SAMPLING)
private var largeClockOnSecondaryDisplay = false
+ val dozeAmount = MutableStateFlow(0f)
+ val onClockBoundsChanged = MutableStateFlow<RectF?>(null)
+
private fun isDarkTheme(): Boolean {
val isLightTheme = TypedValue()
context.theme.resolveAttribute(android.R.attr.isLightTheme, isLightTheme, true)
@@ -306,7 +306,7 @@
var smallTimeListener: TimeListener? = null
var largeTimeListener: TimeListener? = null
val shouldTimeListenerRun: Boolean
- get() = isKeyguardVisible && dozeAmount < DOZE_TICKRATE_THRESHOLD
+ get() = isKeyguardVisible && dozeAmount.value < DOZE_TICKRATE_THRESHOLD
private var weatherData: WeatherData? = null
private var zenData: ZenData? = null
@@ -386,7 +386,7 @@
@VisibleForTesting
internal fun listenForDnd(scope: CoroutineScope): Job {
- ModesUi.assertInNewMode()
+ ModesUi.unsafeAssertInNewMode()
return scope.launch {
zenModeInteractor.dndMode.collect {
val zenMode =
@@ -466,7 +466,6 @@
disposableHandle =
parent.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
- listenForDozing(this)
if (ModesUi.isEnabled) {
listenForDnd(this)
}
@@ -576,17 +575,17 @@
}
private fun handleDoze(doze: Float) {
- dozeAmount = doze
clock?.run {
Trace.beginSection("$TAG#smallClock.animations.doze")
- smallClock.animations.doze(dozeAmount)
+ smallClock.animations.doze(doze)
Trace.endSection()
Trace.beginSection("$TAG#largeClock.animations.doze")
- largeClock.animations.doze(dozeAmount)
+ largeClock.animations.doze(doze)
Trace.endSection()
}
smallTimeListener?.update(doze < DOZE_TICKRATE_THRESHOLD)
largeTimeListener?.update(doze < DOZE_TICKRATE_THRESHOLD)
+ dozeAmount.value = doze
}
@VisibleForTesting
@@ -642,18 +641,6 @@
}
}
- @VisibleForTesting
- internal fun listenForDozing(scope: CoroutineScope): Job {
- return scope.launch {
- combine(keyguardInteractor.dozeAmount, keyguardInteractor.isDozing) {
- localDozeAmount,
- localIsDozing ->
- localDozeAmount > dozeAmount || localIsDozing
- }
- .collect { localIsDozing -> isDozing = localIsDozing }
- }
- }
-
class TimeListener(val clockFace: ClockFaceController, val executor: DelayableExecutor) {
val predrawListener =
ViewTreeObserver.OnPreDrawListener {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 73dc282..e2f3955 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -172,6 +172,7 @@
private boolean mIsDragging;
private float mStartTouchY = -1;
private boolean mDisappearAnimRunning;
+ private boolean mIsAppearAnimationDelayed;
private SwipeListener mSwipeListener;
private ViewMode mViewMode = new DefaultViewMode();
private boolean mIsInteractable;
@@ -583,6 +584,10 @@
return false;
}
+ boolean isAppearAnimationDelayed() {
+ return mIsAppearAnimationDelayed;
+ }
+
void addMotionEventListener(Gefingerpoken listener) {
mMotionEventListeners.add(listener);
}
@@ -624,6 +629,19 @@
mViewMode.startAppearAnimation(securityMode);
}
+ /**
+ * Set view translationY and alpha as we delay bouncer animation.
+ */
+ public void setupForDelayedAppear() {
+ setTranslationY(0f);
+ setAlpha(0f);
+ setIsAppearAnimationDelayed(true);
+ }
+
+ public void setIsAppearAnimationDelayed(boolean isDelayed) {
+ mIsAppearAnimationDelayed = isDelayed;
+ }
+
private void beginJankInstrument(int cuj) {
KeyguardInputView securityView = mSecurityViewFlipper.getSecurityView();
if (securityView == null) return;
@@ -812,6 +830,7 @@
public void reset() {
mViewMode.reset();
mDisappearAnimRunning = false;
+ mIsAppearAnimationDelayed = false;
}
/**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index d10fce4..198c1cb 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -18,6 +18,7 @@
import static android.app.StatusBarManager.SESSION_KEYGUARD;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISSIBLE_KEYGUARD;
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_BIOMETRIC;
@@ -385,6 +386,10 @@
boolean useSplitBouncer = orientation == ORIENTATION_LANDSCAPE;
mSecurityViewFlipperController.updateConstraints(useSplitBouncer);
}
+ if (orientation == ORIENTATION_PORTRAIT) {
+ // If there is any delayed bouncer appear animation it can start now
+ startAppearAnimationIfDelayed();
+ }
}
@Override
@@ -845,6 +850,16 @@
}
}
+ /** Start appear animation which was previously delayed from opening bouncer in landscape. */
+ public void startAppearAnimationIfDelayed() {
+ if (!mView.isAppearAnimationDelayed()) {
+ return;
+ }
+ setAlpha(1f);
+ appear();
+ mView.setIsAppearAnimationDelayed(false);
+ }
+
/** Called when the bouncer changes visibility. */
public void onBouncerVisibilityChanged(boolean isVisible) {
if (!isVisible) {
@@ -1301,4 +1316,13 @@
setAlpha(MathUtils.constrain(1 - scaledFraction, 0f, 1f));
mView.setTranslationY(scaledFraction * mTranslationY);
}
+
+ /** Set up view for delayed appear animation. */
+ public void setupForDelayedAppear() {
+ mView.setupForDelayedAppear();
+ }
+
+ public boolean isLandscapeOrientation() {
+ return mLastOrientation == Configuration.ORIENTATION_LANDSCAPE;
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index bd09e39..d84b034 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -2923,13 +2923,13 @@
private boolean isPrimaryBouncerShowingOrWillBeShowing(
ObservableTransitionState transitionState
) {
- SceneContainerFlag.assertInNewMode();
+ SceneContainerFlag.unsafeAssertInNewMode();
return isPrimaryBouncerFullyShown(transitionState)
|| transitionState.isTransitioning(null, Overlays.Bouncer);
}
private boolean isPrimaryBouncerFullyShown(ObservableTransitionState transitionState) {
- SceneContainerFlag.assertInNewMode();
+ SceneContainerFlag.unsafeAssertInNewMode();
return transitionState.isIdle(Overlays.Bouncer);
}
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 b730c93..08559f2 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
@@ -19,6 +19,8 @@
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
+import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME;
+
import static java.util.Collections.emptyList;
import android.bluetooth.BluetoothHapClient;
@@ -263,6 +265,20 @@
dialog.setTitle(R.string.quick_settings_hearing_devices_dialog_title);
dialog.setView(LayoutInflater.from(dialog.getContext()).inflate(
R.layout.hearing_devices_tile_dialog, null));
+ dialog.setNegativeButton(
+ R.string.hearing_devices_settings_button,
+ (dialogInterface, which) -> {
+ mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_SETTINGS_CLICK,
+ mLaunchSourceId);
+ final Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_DETAILS_SETTINGS)
+ .putExtra(Intent.EXTRA_COMPONENT_NAME,
+ ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString());
+ mActivityStarter.postStartActivityDismissingKeyguard(intent, /* delay= */ 0,
+ mDialogTransitionAnimator.createActivityTransitionController(
+ dialog));
+ },
+ /* dismissOnClick = */ true
+ );
dialog.setPositiveButton(
R.string.quick_settings_done,
/* onClick = */ null,
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt
index fe1d504..4a695d6 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt
@@ -39,7 +39,9 @@
@UiEvent(doc = "Expand the ambient volume controls")
HEARING_DEVICES_AMBIENT_EXPAND_CONTROLS(2153),
@UiEvent(doc = "Collapse the ambient volume controls")
- HEARING_DEVICES_AMBIENT_COLLAPSE_CONTROLS(2154);
+ HEARING_DEVICES_AMBIENT_COLLAPSE_CONTROLS(2154),
+ @UiEvent(doc = "Click on the device settings to enter hearing devices page")
+ HEARING_DEVICES_SETTINGS_CLICK(2172);
override fun getId(): Int = this.id
}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt
index d8e7a16..97de78c 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt
@@ -18,6 +18,7 @@
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
+import android.content.res.Configuration
import android.graphics.Rect
import android.graphics.Region
import android.util.Log
@@ -36,6 +37,7 @@
import com.android.systemui.ambient.touch.scrim.ScrimController
import com.android.systemui.ambient.touch.scrim.ScrimManager
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.plugins.ActivityStarter
@@ -46,6 +48,7 @@
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.wm.shell.animation.FlingAnimationUtils
import java.util.Optional
import javax.inject.Inject
@@ -82,6 +85,8 @@
private val sceneInteractor: SceneInteractor,
private val shadeRepository: ShadeRepository,
private val windowRootViewProvider: Optional<Provider<WindowRootView>>,
+ private val keyguardStateController: KeyguardStateController,
+ communalSettingsInteractor: CommunalSettingsInteractor,
) : TouchHandler {
/** An interface for creating ValueAnimators. */
interface ValueAnimatorCreator {
@@ -101,6 +106,8 @@
private var capture: Boolean? = null
private var expanded: Boolean = false
private var touchSession: TouchSession? = null
+ private var isUserTrackingExpansionDisabled: Boolean = false
+ private var isKeyguardScreenRotationAllowed: Boolean = false
private val scrimManagerCallback =
ScrimManager.Callback { controller ->
currentScrimController?.reset()
@@ -121,6 +128,9 @@
distanceX: Float,
distanceY: Float,
): Boolean {
+ val isLandscape =
+ windowRootView.resources.configuration.orientation ==
+ Configuration.ORIENTATION_LANDSCAPE
if (capture == null) {
capture =
if (Flags.dreamOverlayBouncerSwipeDirectionFiltering()) {
@@ -137,7 +147,9 @@
// reset expanding
expanded = false
// Since the user is dragging the bouncer up, set scrimmed to false.
- currentScrimController?.show()
+ if (isKeyguardScreenRotationAllowed || !isLandscape) {
+ currentScrimController?.show(false)
+ }
if (SceneContainerFlag.isEnabled) {
sceneInteractor.onRemoteUserInputStarted("bouncer touch handler")
@@ -172,6 +184,37 @@
return true
}
+ if (touchSession == null) {
+ return true
+ }
+ val screenTravelPercentage =
+ (abs((y - e2.y).toDouble()) / touchSession!!.bounds.height()).toFloat()
+
+ if (communalSettingsInteractor.isV2FlagEnabled()) {
+ if (isUserTrackingExpansionDisabled) return true
+ // scrolling up in landscape orientation but device doesn't allow keyguard
+ // screen rotation
+ if (y > e2.y && !isKeyguardScreenRotationAllowed && isLandscape) {
+ velocityTracker!!.computeCurrentVelocity(1000)
+ currentExpansion = 1 - screenTravelPercentage
+ expanded =
+ shouldExpandBouncer(
+ velocityTracker!!.yVelocity,
+ velocityTracker!!.xVelocity,
+ EXPANSION_FROM_LANDSCAPE_THRESHOLD,
+ currentExpansion,
+ )
+ if (expanded) {
+ // Once scroll past the percentage threshold, show bouncer scrimmed,
+ // so that user won't be required to drag up and then right to keep
+ // bouncer open after screen rotates to portrait.
+ currentScrimController?.show(true)
+ isUserTrackingExpansionDisabled = true
+ }
+ return true
+ }
+ }
+
if (SceneContainerFlag.isEnabled) {
windowRootView.dispatchTouchEvent(e2)
} else {
@@ -182,12 +225,7 @@
// is fully hidden at full expansion (1) and fully visible when fully
// collapsed
// (0).
- touchSession?.apply {
- val screenTravelPercentage =
- (abs((this@outer.y - e2.y).toDouble()) / getBounds().height())
- .toFloat()
- setPanelExpansion(1 - screenTravelPercentage)
- }
+ touchSession?.apply { setPanelExpansion(1 - screenTravelPercentage) }
}
}
@@ -262,6 +300,7 @@
}
scrimManager.addCallback(scrimManagerCallback)
currentScrimController = scrimManager.currentController
+ isKeyguardScreenRotationAllowed = keyguardStateController.isKeyguardScreenRotationAllowed()
shadeRepository.setLegacyShadeTracking(true)
session.registerCallback {
@@ -271,6 +310,7 @@
scrimManager.removeCallback(scrimManagerCallback)
capture = null
touchSession = null
+ isUserTrackingExpansionDisabled = false
if (!Flags.communalBouncerDoNotModifyPluginOpen()) {
notificationShadeWindowController.setForcePluginOpen(false, this)
}
@@ -299,14 +339,25 @@
return
}
+ // We are already in progress of opening bouncer scrimmed
+ if (isUserTrackingExpansionDisabled) {
+ // User is done scrolling, reset
+ isUserTrackingExpansionDisabled = false
+ return
+ }
+
// We must capture the resulting velocities as resetMonitor() will clear these
// values.
velocityTracker!!.computeCurrentVelocity(1000)
val verticalVelocity = velocityTracker!!.yVelocity
- val horizontalVelocity = velocityTracker!!.xVelocity
- val velocityVector =
- hypot(horizontalVelocity.toDouble(), verticalVelocity.toDouble()).toFloat()
- expanded = !flingRevealsOverlay(verticalVelocity, velocityVector)
+ expanded =
+ shouldExpandBouncer(
+ verticalVelocity,
+ velocityTracker!!.xVelocity,
+ FLING_PERCENTAGE_THRESHOLD,
+ currentExpansion,
+ )
+
val expansion =
if (expanded!!) KeyguardBouncerConstants.EXPANSION_VISIBLE
else KeyguardBouncerConstants.EXPANSION_HIDDEN
@@ -339,11 +390,27 @@
return animator
}
- protected fun flingRevealsOverlay(velocity: Float, velocityVector: Float): Boolean {
+ private fun shouldExpandBouncer(
+ verticalVelocity: Float,
+ horizontalVelocity: Float,
+ threshold: Float,
+ expansion: Float,
+ ): Boolean {
+ val velocityVector =
+ hypot(horizontalVelocity.toDouble(), verticalVelocity.toDouble()).toFloat()
+ return !flingRevealsOverlay(verticalVelocity, velocityVector, threshold, expansion)
+ }
+
+ protected fun flingRevealsOverlay(
+ velocity: Float,
+ velocityVector: Float,
+ threshold: Float,
+ expansion: Float,
+ ): Boolean {
// Fully expand the space above the bouncer, if the user has expanded the bouncer less
// than halfway or final velocity was positive, indicating a downward direction.
return if (abs(velocityVector.toDouble()) < flingAnimationUtils.minVelocityPxPerSecond) {
- currentExpansion > FLING_PERCENTAGE_THRESHOLD
+ expansion > threshold
} else {
velocity > 0
}
@@ -390,6 +457,7 @@
companion object {
const val FLING_PERCENTAGE_THRESHOLD = 0.5f
+ const val EXPANSION_FROM_LANDSCAPE_THRESHOLD = 0.95f
private const val TAG = "BouncerSwipeTouchHandler"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerScrimController.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerScrimController.java
index 94c9982..6f2dd79 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerScrimController.java
@@ -33,8 +33,8 @@
}
@Override
- public void show() {
- mStatusBarKeyguardViewManager.showPrimaryBouncer(false);
+ public void show(boolean scrimmed) {
+ mStatusBarKeyguardViewManager.showPrimaryBouncer(scrimmed);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/ScrimController.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/ScrimController.java
index 0054352..90cbd25 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/ScrimController.java
@@ -25,8 +25,9 @@
public interface ScrimController {
/**
* Called at the start of expansion before any expansion amount updates.
+ * @param scrimmed true when the bouncer should show scrimmed, false when user will be dragging.
*/
- default void show() {
+ default void show(boolean scrimmed) {
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
index 0b578c6..113df20 100644
--- a/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
@@ -122,6 +122,10 @@
return false
}
+ fun isBackCallbackRegistered(): Boolean {
+ return isCallbackRegistered
+ }
+
private fun registerBackCallback() {
if (isCallbackRegistered) {
return
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 4c8a8f1..b8e95ee 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -539,7 +539,8 @@
}
public void show(WindowManager wm) {
- wm.addView(this, getLayoutParams(mWindowToken, mConfig.mPromptInfo.getTitle()));
+ wm.addView(this, getLayoutParams(mWindowToken, mConfig.mPromptInfo.getTitle(),
+ mPromptViewModel.getPromptKind().getValue().isCredential()));
}
private void forceExecuteAnimatedIn() {
@@ -738,7 +739,8 @@
}
@VisibleForTesting
- static WindowManager.LayoutParams getLayoutParams(IBinder windowToken, CharSequence title) {
+ static WindowManager.LayoutParams getLayoutParams(IBinder windowToken, CharSequence title,
+ boolean isCredentialView) {
final int windowFlags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
| WindowManager.LayoutParams.FLAG_SECURE
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
@@ -754,7 +756,7 @@
& ~WindowInsets.Type.systemBars());
lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
lp.setTitle("BiometricPrompt");
- lp.accessibilityTitle = title;
+ lp.accessibilityTitle = isCredentialView ? " " : title;
lp.dimAmount = BACKGROUND_DIM_AMOUNT;
lp.token = windowToken;
return lp;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 88694ae..dfe8eb2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -179,7 +179,6 @@
@NonNull private final PowerInteractor mPowerInteractor;
@NonNull private final CoroutineScope mScope;
@NonNull private final InputManager mInputManager;
- @NonNull private final UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
@NonNull private final SelectedUserInteractor mSelectedUserInteractor;
private final boolean mIgnoreRefreshRate;
private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@@ -292,7 +291,6 @@
mActivityTransitionAnimator,
mPrimaryBouncerInteractor,
mAlternateBouncerInteractor,
- mUdfpsKeyguardAccessibilityDelegate,
mKeyguardTransitionInteractor,
mSelectedUserInteractor,
mDeviceEntryUdfpsTouchOverlayViewModel,
@@ -691,7 +689,6 @@
@NonNull AlternateBouncerInteractor alternateBouncerInteractor,
@NonNull InputManager inputManager,
@NonNull DeviceEntryFaceAuthInteractor deviceEntryFaceAuthInteractor,
- @NonNull UdfpsKeyguardAccessibilityDelegate udfpsKeyguardAccessibilityDelegate,
@NonNull SelectedUserInteractor selectedUserInteractor,
@NonNull KeyguardTransitionInteractor keyguardTransitionInteractor,
Lazy<DeviceEntryUdfpsTouchOverlayViewModel> deviceEntryUdfpsTouchOverlayViewModel,
@@ -742,7 +739,6 @@
mPowerInteractor = powerInteractor;
mScope = scope;
mInputManager = inputManager;
- mUdfpsKeyguardAccessibilityDelegate = udfpsKeyguardAccessibilityDelegate;
mSelectedUserInteractor = selectedUserInteractor;
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 702f237..bdf5827 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -107,7 +107,6 @@
private val primaryBouncerInteractor: PrimaryBouncerInteractor,
private val alternateBouncerInteractor: AlternateBouncerInteractor,
private val isDebuggable: Boolean = Build.IS_DEBUGGABLE,
- private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate,
private val transitionInteractor: KeyguardTransitionInteractor,
private val selectedUserInteractor: SelectedUserInteractor,
private val deviceEntryUdfpsTouchOverlayViewModel: Lazy<DeviceEntryUdfpsTouchOverlayViewModel>,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegate.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegate.kt
deleted file mode 100644
index 99da660..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegate.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2023 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.biometrics
-
-import android.content.res.Resources
-import android.os.Bundle
-import android.view.View
-import android.view.accessibility.AccessibilityNodeInfo
-import com.android.systemui.res.R
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
-import javax.inject.Inject
-
-@SysUISingleton
-class UdfpsKeyguardAccessibilityDelegate
-@Inject
-constructor(
- @Main private val resources: Resources,
- private val keyguardViewManager: StatusBarKeyguardViewManager,
-) : View.AccessibilityDelegate() {
- override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo) {
- super.onInitializeAccessibilityNodeInfo(host, info)
- val clickAction =
- AccessibilityNodeInfo.AccessibilityAction(
- AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id,
- resources.getString(R.string.accessibility_bouncer)
- )
- info.addAction(clickAction)
- }
-
- override fun performAccessibilityAction(host: View, action: Int, args: Bundle?): Boolean {
- // when an a11y service is enabled, double tapping on the fingerprint sensor should
- // show the primary bouncer
- return if (action == AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id) {
- keyguardViewManager.showPrimaryBouncer(/* scrimmed */ true)
- true
- } else super.performAccessibilityAction(host, action, args)
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
index e4c4540..6842c90 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
@@ -43,6 +43,9 @@
// the header info never changes - do it early
val header = viewModel.header.first()
passwordField.setTextOperationUser(UserHandle.of(header.user.userIdForPasswordEntry))
+ viewModel.inputBoxContentDescription.firstOrNull()?.let { descriptionId ->
+ passwordField.contentDescription = view.context.getString(descriptionId)
+ }
viewModel.inputFlags.firstOrNull()?.let { flags -> passwordField.inputType = flags }
if (requestFocusForInput) {
passwordField.requestFocus()
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
index bbf9a1901..30b98a6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
@@ -137,6 +137,9 @@
)
bind(overlayView!!, overlayViewModel, windowManager.get())
overlayView!!.visibility = View.INVISIBLE
+ overlayView!!.setOnClickListener { v ->
+ v.requireViewById<LottieAnimationView>(R.id.sidefps_animation).toggleAnimation()
+ }
Log.d(TAG, "show(): adding overlayView $overlayView")
windowManager.get().addView(overlayView, overlayViewModel.defaultOverlayViewParams)
}
@@ -234,3 +237,11 @@
resumeAnimation()
}
}
+
+fun LottieAnimationView.toggleAnimation() {
+ if (isAnimating) {
+ pauseAnimation()
+ } else {
+ resumeAnimation()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt
index 0c5c723..37cfc33 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt
@@ -59,6 +59,17 @@
}
}
+ /** Input box accessibility description for text based credential views */
+ val inputBoxContentDescription: Flow<Int?> =
+ credentialInteractor.prompt.map {
+ when (it) {
+ is BiometricPromptRequest.Credential.Pin -> R.string.keyguard_accessibility_pin_area
+ is BiometricPromptRequest.Credential.Password ->
+ R.string.keyguard_accessibility_password
+ else -> null
+ }
+ }
+
/** If stealth mode is active (hide user credential input). */
val stealthMode: Flow<Boolean> =
credentialInteractor.prompt.map {
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorImpl.kt
index 94fca21..f8f692d 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorImpl.kt
@@ -124,10 +124,18 @@
when (deviceItem.type) {
DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE -> {
uiEventLogger.log(BluetoothTileDialogUiEvent.CHECK_MARK_ACTION_BUTTON_CLICKED)
+ logger.logAudioSharingButtonClick(
+ AudioSharingButtonClick.CHECK_MARK,
+ deviceItem,
+ )
audioSharingInteractor.stopAudioSharing()
}
DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE -> {
uiEventLogger.log(BluetoothTileDialogUiEvent.PLUS_ACTION_BUTTON_CLICKED)
+ logger.logAudioSharingButtonClick(
+ AudioSharingButtonClick.PLUS_BUTTON,
+ deviceItem,
+ )
audioSharingInteractor.startAudioSharing()
}
else -> {
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt
index 832afb1..7a76eed 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt
@@ -40,6 +40,7 @@
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.withContext
@@ -73,6 +74,7 @@
private val context: Context,
private val localBluetoothManager: LocalBluetoothManager?,
private val audioSharingRepository: AudioSharingRepository,
+ private val logger: BluetoothTileDialogLogger,
@Background private val backgroundDispatcher: CoroutineDispatcher,
) : AudioSharingInteractor {
@@ -92,9 +94,12 @@
override val audioSourceStateUpdate =
isAudioSharingOn
+ .onEach { logger.logAudioSharingStateChanged(it) }
.flatMapLatest {
if (it) {
- audioSharingRepository.audioSourceStateUpdate
+ audioSharingRepository.audioSourceStateUpdate.onEach {
+ logger.logAudioSourceStateUpdate()
+ }
} else {
emptyFlow()
}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepository.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepository.kt
index 44f9769..d84b34a 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepository.kt
@@ -53,6 +53,7 @@
class AudioSharingRepositoryImpl(
private val localBluetoothManager: LocalBluetoothManager,
private val settingsLibAudioSharingRepository: SettingsLibAudioSharingRepository,
+ private val logger: BluetoothTileDialogLogger,
@Background private val backgroundDispatcher: CoroutineDispatcher,
) : AudioSharingRepository {
@@ -79,7 +80,11 @@
}
leAudioBroadcastProfile?.latestBluetoothLeBroadcastMetadata?.let { metadata ->
leAudioBroadcastAssistantProfile?.let {
- it.allConnectedDevices.forEach { sink -> it.addSource(sink, metadata, false) }
+ it.allConnectedDevices.forEach { sink ->
+ it.addSource(sink, metadata, false).also {
+ logger.logAudioSharingRequest(AudioSharingRequest.ADD_SOURCE)
+ }
+ }
}
}
}
@@ -99,7 +104,9 @@
if (!settingsLibAudioSharingRepository.audioSharingAvailable()) {
return@withContext
}
- leAudioBroadcastProfile?.startPrivateBroadcast()
+ leAudioBroadcastProfile?.startPrivateBroadcast().also {
+ logger.logAudioSharingRequest(AudioSharingRequest.START_BROADCAST)
+ }
}
}
@@ -108,7 +115,9 @@
if (!settingsLibAudioSharingRepository.audioSharingAvailable()) {
return@withContext
}
- leAudioBroadcastProfile?.stopLatestBroadcast()
+ leAudioBroadcastProfile?.stopLatestBroadcast().also {
+ logger.logAudioSharingRequest(AudioSharingRequest.STOP_BROADCAST)
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentManager.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentManager.kt
index 576acd2..5a5a51e 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentManager.kt
@@ -218,7 +218,7 @@
scrollViewContent.layoutParams.height = WRAP_CONTENT
lastUiUpdateMs = systemClock.elapsedRealtime()
lastItemRow = itemRow
- logger.logDeviceUiUpdate(lastUiUpdateMs - start)
+ logger.logDeviceUiUpdate(lastUiUpdateMs - start, deviceItem)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentViewModel.kt
index 7ecd276..c5349c8 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentViewModel.kt
@@ -99,7 +99,7 @@
*/
fun bindDetailsView(view: View) {
// If `QsDetailedView` is not enabled, it should show the dialog.
- QsDetailedView.assertInNewMode()
+ QsDetailedView.unsafeAssertInNewMode()
cancelJob()
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogLogger.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogLogger.kt
index 06116f0..5f866c5 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogLogger.kt
@@ -27,18 +27,29 @@
enum class BluetoothStateStage {
USER_TOGGLED,
BLUETOOTH_STATE_VALUE_SET,
- BLUETOOTH_STATE_CHANGE_RECEIVED
+ BLUETOOTH_STATE_CHANGE_RECEIVED,
}
enum class DeviceFetchTrigger {
FIRST_LOAD,
BLUETOOTH_STATE_CHANGE_RECEIVED,
- BLUETOOTH_CALLBACK_RECEIVED
+ BLUETOOTH_CALLBACK_RECEIVED,
+}
+
+enum class AudioSharingButtonClick {
+ PLUS_BUTTON,
+ CHECK_MARK,
+}
+
+enum class AudioSharingRequest {
+ START_BROADCAST,
+ STOP_BROADCAST,
+ ADD_SOURCE,
}
enum class JobStatus {
FINISHED,
- CANCELLED
+ CANCELLED,
}
class BluetoothTileDialogLogger
@@ -53,7 +64,7 @@
str1 = stage.toString()
str2 = state
},
- { "BluetoothState. stage=$str1 state=$str2" }
+ { "BluetoothState. stage=$str1 state=$str2" },
)
fun logDeviceClick(address: String, type: DeviceItemType) =
@@ -64,7 +75,7 @@
str1 = address
str2 = type.toString()
},
- { "DeviceClick. address=$str1 type=$str2" }
+ { "DeviceClick. address=$str1 type=$str2" },
)
fun logActiveDeviceChanged(address: String?, profileId: Int) =
@@ -75,7 +86,7 @@
str1 = address
int1 = profileId
},
- { "ActiveDeviceChanged. address=$str1 profileId=$int1" }
+ { "ActiveDeviceChanged. address=$str1 profileId=$int1" },
)
fun logProfileConnectionStateChanged(address: String, state: String, profileId: Int) =
@@ -87,7 +98,7 @@
str2 = state
int1 = profileId
},
- { "ProfileConnectionStateChanged. address=$str1 state=$str2 profileId=$int1" }
+ { "ProfileConnectionStateChanged. address=$str1 state=$str2 profileId=$int1" },
)
fun logBatteryChanged(address: String, key: Int, value: ByteArray?) =
@@ -99,7 +110,7 @@
int1 = key
str2 = value?.toString() ?: ""
},
- { "BatteryChanged. address=$str1 key=$int1 value=$str2" }
+ { "BatteryChanged. address=$str1 key=$int1 value=$str2" },
)
fun logDeviceFetch(status: JobStatus, trigger: DeviceFetchTrigger, duration: Long) =
@@ -111,18 +122,26 @@
str2 = trigger.toString()
long1 = duration
},
- { "DeviceFetch. status=$str1 trigger=$str2 duration=$long1" }
+ { "DeviceFetch. status=$str1 trigger=$str2 duration=$long1" },
)
- fun logDeviceUiUpdate(duration: Long) =
- logBuffer.log(TAG, DEBUG, { long1 = duration }, { "DeviceUiUpdate. duration=$long1" })
+ fun logDeviceUiUpdate(duration: Long, deviceItem: List<DeviceItem>) =
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ long1 = duration
+ str1 = deviceItem.toString()
+ },
+ { "DeviceUiUpdate. duration=$long1 deviceItem=$str1" },
+ )
fun logDeviceClickInAudioSharingWhenEnabled(inAudioSharing: Boolean) {
logBuffer.log(
TAG,
DEBUG,
{ str1 = inAudioSharing.toString() },
- { "DeviceClick. in audio sharing=$str1" }
+ { "DeviceClick. in audio sharing=$str1" },
)
}
@@ -138,7 +157,36 @@
str1 = criteria
str2 = deviceItem.toString()
},
- { "$str1. deviceItem=$str2" }
+ { "$str1. deviceItem=$str2" },
)
}
+
+ fun logAudioSharingStateChanged(stateOn: Boolean) =
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ { str1 = stateOn.toString() },
+ { "AudioSharingStateChanged. state=$str1" },
+ )
+
+ fun logAudioSourceStateUpdate() = logBuffer.log(TAG, DEBUG, {}, { "AudioSourceStateUpdate" })
+
+ fun logAudioSharingButtonClick(click: AudioSharingButtonClick, deviceItem: DeviceItem) =
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ str1 = click.toString()
+ str2 = deviceItem.toString()
+ },
+ { "AudioSharingButtonClick. click=$str1 deviceItem=$str2" },
+ )
+
+ fun logAudioSharingRequest(apiCall: AudioSharingRequest) =
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ { str1 = apiCall.toString() },
+ { "AudioSharingRequest. apiCall=$str1" },
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/dagger/AudioSharingModule.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/dagger/AudioSharingModule.kt
index afe9a1e..01c4b21 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/dagger/AudioSharingModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/dagger/AudioSharingModule.kt
@@ -30,6 +30,7 @@
import com.android.systemui.bluetooth.qsdialog.AudioSharingRepositoryImpl
import com.android.systemui.bluetooth.qsdialog.AvailableAudioSharingMediaDeviceItemFactory
import com.android.systemui.bluetooth.qsdialog.AvailableMediaDeviceItemFactory
+import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogLogger
import com.android.systemui.bluetooth.qsdialog.ConnectedDeviceItemFactory
import com.android.systemui.bluetooth.qsdialog.DeviceItemActionInteractor
import com.android.systemui.bluetooth.qsdialog.DeviceItemActionInteractorImpl
@@ -53,6 +54,7 @@
fun provideAudioSharingRepository(
localBluetoothManager: LocalBluetoothManager?,
settingsLibAudioSharingRepository: SettingsLibAudioSharingRepository,
+ logger: BluetoothTileDialogLogger,
@Background backgroundDispatcher: CoroutineDispatcher,
): AudioSharingRepository =
if (
@@ -62,6 +64,7 @@
AudioSharingRepositoryImpl(
localBluetoothManager,
settingsLibAudioSharingRepository,
+ logger,
backgroundDispatcher,
)
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index 75503e8..b26a2c0 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -17,7 +17,9 @@
package com.android.systemui.bouncer.domain.interactor
import android.app.StatusBarManager.SESSION_KEYGUARD
+import com.android.app.tracing.FlowTracing.traceAsCounter
import com.android.app.tracing.coroutines.asyncTraced as async
+import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.internal.logging.UiEventLogger
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
@@ -38,9 +40,12 @@
import com.android.systemui.log.SessionTracker
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.domain.interactor.SceneBackInteractor
+import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.ShadeDisplayAware
+import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
@@ -49,7 +54,9 @@
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
/** Encapsulates business logic and application state accessing use-cases. */
@@ -65,6 +72,7 @@
private val powerInteractor: PowerInteractor,
private val uiEventLogger: UiEventLogger,
private val sessionTracker: SessionTracker,
+ sceneInteractor: SceneInteractor,
sceneBackInteractor: SceneBackInteractor,
@ShadeDisplayAware private val configurationInteractor: ConfigurationInteractor,
) {
@@ -149,6 +157,31 @@
val dismissDestination: Flow<SceneKey> =
sceneBackInteractor.backScene.map { it ?: Scenes.Lockscreen }
+ /** The amount [0-1] that the Bouncer Overlay has been transitioned to. */
+ val bouncerExpansion: Flow<Float> =
+ if (SceneContainerFlag.isEnabled) {
+ sceneInteractor.transitionState.flatMapLatestConflated { state ->
+ when (state) {
+ is ObservableTransitionState.Idle ->
+ flowOf(if (Overlays.Bouncer in state.currentOverlays) 1f else 0f)
+ is ObservableTransitionState.Transition ->
+ if (state.toContent == Overlays.Bouncer) {
+ state.progress
+ } else if (state.fromContent == Overlays.Bouncer) {
+ state.progress.map { progress -> 1 - progress }
+ } else {
+ state.currentOverlays().map {
+ if (Overlays.Bouncer in it) 1f else 0f
+ }
+ }
+ }
+ }
+ } else {
+ flowOf()
+ }
+ .distinctUntilChanged()
+ .traceAsCounter("bouncer_expansion") { (it * 100f).toInt() }
+
/** Notifies that the user has places down a pointer, not necessarily dragging just yet. */
fun onDown() {
falsingInteractor.avoidGesture()
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt
index 7f26831..5d64219 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt
@@ -15,6 +15,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToPrimaryBouncerTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
import com.android.systemui.log.BouncerLogger
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
@@ -30,6 +31,8 @@
constructor(
val viewModel: KeyguardBouncerViewModel,
val primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel,
+ val glanceableHubToPrimaryBouncerTransitionViewModel:
+ GlanceableHubToPrimaryBouncerTransitionViewModel,
val componentFactory: KeyguardBouncerComponent.Factory,
val messageAreaControllerFactory: KeyguardMessageAreaController.Factory,
val bouncerMessageInteractor: BouncerMessageInteractor,
@@ -82,6 +85,7 @@
view,
deps.viewModel,
deps.primaryBouncerToGoneTransitionViewModel,
+ deps.glanceableHubToPrimaryBouncerTransitionViewModel,
deps.componentFactory,
deps.messageAreaControllerFactory,
deps.bouncerMessageInteractor,
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
index 7d8945a..45f0e13 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
@@ -33,6 +33,7 @@
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE
import com.android.systemui.bouncer.ui.BouncerViewDelegate
import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
+import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToPrimaryBouncerTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.log.BouncerLogger
@@ -49,6 +50,8 @@
view: ViewGroup,
viewModel: KeyguardBouncerViewModel,
primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel,
+ glanceableHubToPrimaryBouncerTransitionViewModel:
+ GlanceableHubToPrimaryBouncerTransitionViewModel,
componentFactory: KeyguardBouncerComponent.Factory,
messageAreaControllerFactory: KeyguardMessageAreaController.Factory,
bouncerMessageInteractor: BouncerMessageInteractor,
@@ -133,7 +136,20 @@
/* turningOff= */ false
)
securityContainerController.setInitialMessage()
- securityContainerController.appear()
+ // Delay bouncer appearing animation when opening it from the
+ // glanceable hub in landscape, until after orientation changes
+ // to portrait. This prevents bouncer from showing in landscape
+ // layout, if bouncer rotation is not allowed.
+ if (
+ glanceableHubToPrimaryBouncerTransitionViewModel
+ .willDelayAppearAnimation(
+ securityContainerController.isLandscapeOrientation
+ )
+ ) {
+ securityContainerController.setupForDelayedAppear()
+ } else {
+ securityContainerController.appear()
+ }
securityContainerController.onResume(
KeyguardSecurityView.SCREEN_ON
)
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt b/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt
index b13f6df..52204b8 100644
--- a/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt
+++ b/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt
@@ -70,6 +70,7 @@
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.compose.modifiers.padding
import com.android.compose.ui.graphics.drawInOverlay
import com.android.systemui.Flags
import com.android.systemui.biometrics.Utils.toBitmap
@@ -164,20 +165,15 @@
val activeIconColor = colors.activeTickColor
val inactiveIconColor = colors.inactiveTickColor
- val trackIcon: DrawScope.(Offset, Color, Float) -> Unit =
- remember(painter) {
- { offset, color, alpha ->
- translate(offset.x + IconPadding.toPx(), offset.y) {
- with(painter) {
- draw(
- IconSize.toSize(),
- colorFilter = ColorFilter.tint(color),
- alpha = alpha,
- )
- }
+ val trackIcon: DrawScope.(Offset, Color, Float) -> Unit = remember {
+ { offset, color, alpha ->
+ translate(offset.x + IconPadding.toPx(), offset.y) {
+ with(painter) {
+ draw(IconSize.toSize(), colorFilter = ColorFilter.tint(color), alpha = alpha)
}
}
}
+ }
Slider(
value = animatedValue,
@@ -348,7 +344,13 @@
DisposableEffect(Unit) { onDispose { viewModel.setIsDragging(false) } }
- Box(modifier = modifier.fillMaxWidth().sysuiResTag("brightness_slider")) {
+ Box(
+ modifier =
+ modifier
+ .padding(vertical = { SliderBackgroundFrameSize.height.roundToPx() })
+ .fillMaxWidth()
+ .sysuiResTag("brightness_slider")
+ ) {
BrightnessSlider(
gammaValue = gamma,
valueRange = viewModel.minBrightness.value..viewModel.maxBrightness.value,
diff --git a/packages/SystemUI/src/com/android/systemui/common/domain/interactor/SysUIStateDisplaysInteractor.kt b/packages/SystemUI/src/com/android/systemui/common/domain/interactor/SysUIStateDisplaysInteractor.kt
new file mode 100644
index 0000000..9db7b50
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/domain/interactor/SysUIStateDisplaysInteractor.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.common.domain.interactor
+
+import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.data.repository.PerDisplayRepository
+import com.android.systemui.model.StateChange
+import com.android.systemui.model.SysUiState
+import javax.inject.Inject
+
+/** Handles [SysUiState] changes between displays. */
+@SysUISingleton
+class SysUIStateDisplaysInteractor
+@Inject
+constructor(
+ private val sysUIStateRepository: PerDisplayRepository<SysUiState>,
+ private val displayRepository: DisplayRepository,
+) {
+
+ /**
+ * Sets the flags on the given [targetDisplayId] based on the [stateChanges], while making sure
+ * that those flags are not set in any other display.
+ */
+ fun setFlagsExclusivelyToDisplay(targetDisplayId: Int, stateChanges: StateChange) {
+ if (SysUiState.DEBUG) {
+ Log.d(TAG, "Setting flags $stateChanges only for display $targetDisplayId")
+ }
+ displayRepository.displays.value
+ .mapNotNull { sysUIStateRepository[it.displayId] }
+ .apply {
+ // Let's first modify all states, without committing changes ...
+ forEach { displaySysUIState ->
+ if (displaySysUIState.displayId == targetDisplayId) {
+ stateChanges.applyTo(displaySysUIState)
+ } else {
+ stateChanges.clearFrom(displaySysUIState)
+ }
+ }
+ // ... And commit changes at the end
+ forEach { sysuiState -> sysuiState.commitUpdate() }
+ }
+ }
+
+ private companion object {
+ const val TAG = "SysUIStateInteractor"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/Color.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/Color.kt
index d53a737..7215925 100644
--- a/packages/SystemUI/src/com/android/systemui/common/shared/model/Color.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/Color.kt
@@ -19,11 +19,13 @@
import android.annotation.AttrRes
import android.annotation.ColorInt
import android.annotation.ColorRes
+import androidx.compose.runtime.Stable
/**
* Models a color that can be either a specific [Color.Loaded] value or a resolvable theme
* [Color.Attribute]
*/
+@Stable
sealed interface Color {
data class Loaded(@ColorInt val color: Int) : Color
diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/ContentDescription.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/ContentDescription.kt
index d628aca..b7d8ae6 100644
--- a/packages/SystemUI/src/com/android/systemui/common/shared/model/ContentDescription.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/ContentDescription.kt
@@ -18,11 +18,13 @@
import android.annotation.StringRes
import android.content.Context
+import androidx.compose.runtime.Stable
/**
* Models a content description, that can either be already [loaded][ContentDescription.Loaded] or
* be a [reference][ContentDescription.Resource] to a resource.
*/
+@Stable
sealed class ContentDescription {
data class Loaded(val description: String?) : ContentDescription()
diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt
index e6f0245..2adaec2 100644
--- a/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt
@@ -18,11 +18,13 @@
import android.annotation.DrawableRes
import android.graphics.drawable.Drawable
+import androidx.compose.runtime.Stable
/**
* Models an icon, that can either be already [loaded][Icon.Loaded] or be a [reference]
* [Icon.Resource] to a resource. In case of [Loaded], the resource ID [res] is optional.
*/
+@Stable
sealed class Icon {
abstract val contentDescription: ContentDescription?
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index e36e855..49b0bb6 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -29,10 +29,17 @@
import com.android.systemui.communal.shared.model.CommunalScenes.isCommunal
import com.android.systemui.communal.shared.model.CommunalTransitionKeys
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
+import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
import com.android.systemui.util.kotlin.emitOnStart
import com.android.systemui.util.kotlin.sample
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
@@ -45,6 +52,7 @@
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.withContext
@@ -60,10 +68,12 @@
private val communalInteractor: CommunalInteractor,
private val communalSettingsInteractor: CommunalSettingsInteractor,
private val communalSceneInteractor: CommunalSceneInteractor,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val keyguardInteractor: KeyguardInteractor,
private val systemSettings: SystemSettings,
private val notificationShadeWindowController: NotificationShadeWindowController,
@Background private val bgScope: CoroutineScope,
+ @Application private val applicationScope: CoroutineScope,
@Main private val mainDispatcher: CoroutineDispatcher,
private val uiEventLogger: UiEventLogger,
) : CoreStartable {
@@ -154,6 +164,25 @@
}
}
}
+
+ if (communalSettingsInteractor.isV2FlagEnabled()) {
+ applicationScope.launch(context = mainDispatcher) {
+ anyOf(
+ communalSceneInteractor.isTransitioningToOrIdleOnCommunal,
+ // when transitioning from hub to dream, allow hub to stay at the current
+ // orientation, as keyguard doesn't allow rotation by default.
+ keyguardTransitionInteractor.isInTransition(
+ edge = Edge.create(from = Scenes.Communal, to = DREAMING),
+ edgeWithoutSceneContainer =
+ Edge.create(from = GLANCEABLE_HUB, to = DREAMING),
+ ),
+ )
+ .distinctUntilChanged()
+ .collectLatest {
+ notificationShadeWindowController.setGlanceableHubOrientationAware(it)
+ }
+ }
+ }
}
private fun cancelHubTimeout() {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
index 3d9e930..fed99d7 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
@@ -307,6 +307,21 @@
initialValue = false,
)
+ /** Flow that emits a boolean if transitioning to or idle on communal scene. */
+ val isTransitioningToOrIdleOnCommunal: Flow<Boolean> =
+ transitionState
+ .map {
+ (it is ObservableTransitionState.Idle &&
+ it.currentScene == CommunalScenes.Communal) ||
+ (it is ObservableTransitionState.Transition &&
+ it.toContent == CommunalScenes.Communal)
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
+
private companion object {
const val TAG = "CommunalSceneInteractor"
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/PerDisplayRepositoriesModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/PerDisplayRepositoriesModule.kt
new file mode 100644
index 0000000..39708a7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dagger/PerDisplayRepositoriesModule.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.dagger
+
+import com.android.systemui.display.data.repository.DefaultDisplayOnlyInstanceRepositoryImpl
+import com.android.systemui.display.data.repository.PerDisplayInstanceRepositoryImpl
+import com.android.systemui.display.data.repository.PerDisplayRepository
+import com.android.systemui.model.SysUIStateInstanceProvider
+import com.android.systemui.model.SysUiState
+import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
+import dagger.Module
+import dagger.Provides
+
+/** This module is meant to contain all the code to create the various [PerDisplayRepository<>]. */
+@Module
+class PerDisplayRepositoriesModule {
+
+ @SysUISingleton
+ @Provides
+ fun provideSysUiStateRepository(
+ repositoryFactory: PerDisplayInstanceRepositoryImpl.Factory<SysUiState>,
+ instanceProvider: SysUIStateInstanceProvider,
+ ): PerDisplayRepository<SysUiState> {
+ val debugName = "SysUiStatePerDisplayRepo"
+ return if (ShadeWindowGoesAround.isEnabled) {
+ repositoryFactory.create(debugName, instanceProvider)
+ } else {
+ DefaultDisplayOnlyInstanceRepositoryImpl(debugName, instanceProvider)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index f8cf6b0..f08126a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -21,6 +21,7 @@
import android.app.backup.BackupManager;
import android.content.Context;
import android.service.dreams.IDreamManager;
+import android.view.Display;
import androidx.annotation.Nullable;
@@ -64,9 +65,9 @@
import com.android.systemui.demomode.dagger.DemoModeModule;
import com.android.systemui.deviceentry.DeviceEntryModule;
import com.android.systemui.display.DisplayModule;
+import com.android.systemui.display.data.repository.PerDisplayRepository;
import com.android.systemui.doze.dagger.DozeComponent;
import com.android.systemui.dreams.dagger.DreamModule;
-import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.FlagDependenciesModule;
import com.android.systemui.flags.FlagsModule;
@@ -86,7 +87,6 @@
import com.android.systemui.mediaprojection.appselector.MediaProjectionActivitiesModule;
import com.android.systemui.mediaprojection.taskswitcher.MediaProjectionTaskSwitcherModule;
import com.android.systemui.mediarouter.MediaRouterModule;
-import com.android.systemui.model.SceneContainerPlugin;
import com.android.systemui.model.SysUiState;
import com.android.systemui.motiontool.MotionToolModule;
import com.android.systemui.navigationbar.NavigationBarComponent;
@@ -113,7 +113,6 @@
import com.android.systemui.screenrecord.ScreenRecordModule;
import com.android.systemui.screenshot.dagger.ScreenshotModule;
import com.android.systemui.security.data.repository.SecurityRepositoryModule;
-import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeDisplayAwareModule;
@@ -289,7 +288,8 @@
UtilModule.class,
NoteTaskModule.class,
WalletModule.class,
- LowLightModule.class
+ LowLightModule.class,
+ PerDisplayRepositoriesModule.class
},
subcomponents = {
ComplicationComponent.class,
@@ -326,12 +326,8 @@
@SysUISingleton
@Provides
static SysUiState provideSysUiState(
- DisplayTracker displayTracker,
- DumpManager dumpManager,
- SceneContainerPlugin sceneContainerPlugin) {
- final SysUiState state = new SysUiState(displayTracker, sceneContainerPlugin);
- dumpManager.registerDumpable(state);
- return state;
+ PerDisplayRepository<SysUiState> repository) {
+ return repository.get(Display.DEFAULT_DISPLAY);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/AdditionalStartable.java b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/AdditionalStartable.java
deleted file mode 100644
index e69de29..0000000
--- a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/AdditionalStartable.java
+++ /dev/null
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/SystemUser.kt b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/SystemUser.kt
deleted file mode 100644
index 6878a52..0000000
--- a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/SystemUser.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright (C) 2023 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.dagger.qualifiers
-
-import javax.inject.Qualifier
-
-@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class SystemUser
diff --git a/packages/SystemUI/src/com/android/systemui/development/domain/interactor/BuildNumberInteractor.kt b/packages/SystemUI/src/com/android/systemui/development/domain/interactor/BuildNumberInteractor.kt
index fa5556d..cedd516 100644
--- a/packages/SystemUI/src/com/android/systemui/development/domain/interactor/BuildNumberInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/development/domain/interactor/BuildNumberInteractor.kt
@@ -23,6 +23,7 @@
import android.os.UserHandle
import com.android.internal.R as InternalR
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.development.data.repository.DevelopmentSettingRepository
@@ -32,9 +33,12 @@
import com.android.systemui.user.utils.UserScopedService
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted.Companion.WhileSubscribed
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flatMapConcat
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
@SysUISingleton
@@ -46,6 +50,7 @@
private val userRepository: UserRepository,
private val clipboardManagerProvider: UserScopedService<ClipboardManager>,
@Background private val backgroundDispatcher: CoroutineDispatcher,
+ @Application private val applicationScope: CoroutineScope,
) {
/**
@@ -53,10 +58,11 @@
*
* @see DevelopmentSettingRepository.isDevelopmentSettingEnabled
*/
- val buildNumber: Flow<BuildNumber?> =
+ val buildNumber: StateFlow<BuildNumber?> =
userRepository.selectedUserInfo
.flatMapConcat { userInfo -> repository.isDevelopmentSettingEnabled(userInfo) }
.map { enabled -> buildText.takeIf { enabled } }
+ .stateIn(applicationScope, WhileSubscribed(), null)
private val buildText =
BuildNumber(
diff --git a/packages/SystemUI/src/com/android/systemui/development/ui/viewmodel/BuildNumberViewModel.kt b/packages/SystemUI/src/com/android/systemui/development/ui/viewmodel/BuildNumberViewModel.kt
index 68c51ea..31d0471 100644
--- a/packages/SystemUI/src/com/android/systemui/development/ui/viewmodel/BuildNumberViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/development/ui/viewmodel/BuildNumberViewModel.kt
@@ -41,7 +41,6 @@
val buildNumber: BuildNumber? by
hydrator.hydratedStateOf(
traceName = "buildNumber",
- initialValue = null,
source = buildNumberInteractor.buildNumber,
)
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayScopeRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayScopeRepository.kt
index f4d256a5..d912b6a 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayScopeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayScopeRepository.kt
@@ -53,7 +53,7 @@
}
override fun start() {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
backgroundApplicationScope.launch {
displayRepository.displayRemovalEvent.collect { displayId ->
val scope = perDisplayScopes.remove(displayId)
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/FakePerDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/FakePerDisplayRepository.kt
new file mode 100644
index 0000000..083191c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/FakePerDisplayRepository.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.display.data.repository
+
+class FakePerDisplayRepository<T> : PerDisplayRepository<T> {
+
+ private val instances = mutableMapOf<Int, T>()
+
+ fun add(displayId: Int, instance: T) {
+ instances[displayId] = instance
+ }
+
+ fun remove(displayId: Int) {
+ instances.remove(displayId)
+ }
+
+ override fun get(displayId: Int): T? {
+ return instances[displayId]
+ }
+
+ override val displayIds: Set<Int>
+ get() = instances.keys
+
+ override val debugName: String
+ get() = "FakePerDisplayRepository"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt
new file mode 100644
index 0000000..36d3eb51
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.display.data.repository
+
+import android.util.Log
+import android.view.Display
+import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.app.tracing.traceSection
+import com.android.systemui.Dumpable
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dump.DumpManager
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.io.PrintWriter
+import java.util.concurrent.ConcurrentHashMap
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collectLatest
+
+/**
+ * Used to create instances of type `T` for a specific display.
+ *
+ * This is useful for resources or objects that need to be managed independently for each connected
+ * display (e.g., UI state, rendering contexts, or display-specific configurations).
+ *
+ * Note that in most cases this can be implemented by a simple `@AssistedFactory` with `displayId`
+ * parameter
+ *
+ * ```kotlin
+ * class SomeType @AssistedInject constructor(@Assisted displayId: Int,..)
+ * @AssistedFactory
+ * interface Factory {
+ * fun create(displayId: Int): SomeType
+ * }
+ * }
+ * ```
+ *
+ * Then it can be used to create a [PerDisplayRepository] as follows:
+ * ```kotlin
+ * // Injected:
+ * val repositoryFactory: PerDisplayRepositoryImpl.Factory
+ * val instanceFactory: PerDisplayRepositoryImpl.Factory
+ * // repository creation:
+ * repositoryFactory.create(instanceFactory::create)
+ * ```
+ *
+ * @see PerDisplayRepository For how to retrieve and manage instances created by this factory.
+ */
+fun interface PerDisplayInstanceProvider<T> {
+ /** Creates an instance for a display. */
+ fun createInstance(displayId: Int): T?
+}
+
+/**
+ * Extends [PerDisplayInstanceProvider], adding support for destroying the instance.
+ *
+ * This is useful for releasing resources associated with a display when it is disconnected or when
+ * the per-display instance is no longer needed.
+ */
+interface PerDisplayInstanceProviderWithTeardown<T> : PerDisplayInstanceProvider<T> {
+ /** Destroys a previously created instance of `T` forever. */
+ fun destroyInstance(instance: T)
+}
+
+/**
+ * Provides access to per-display instances of type `T`.
+ *
+ * Acts as a repository, managing the caching and retrieval of instances created by a
+ * [PerDisplayInstanceProvider]. It ensures that only one instance of `T` exists per display ID.
+ */
+interface PerDisplayRepository<T> {
+ /** Gets the cached instance or create a new one for a given display. */
+ operator fun get(displayId: Int): T?
+
+ /** List of display ids for which this repository has an instance. */
+ val displayIds: Set<Int>
+
+ /** Debug name for this repository, mainly for tracing and logging. */
+ val debugName: String
+}
+
+/**
+ * Default implementation of [PerDisplayRepository].
+ *
+ * This class manages a cache of per-display instances of type `T`, creating them using a provided
+ * [PerDisplayInstanceProvider] and optionally tearing them down using a
+ * [PerDisplayInstanceProviderWithTeardown] when displays are disconnected.
+ *
+ * It listens to the [DisplayRepository] to detect when displays are added or removed, and
+ * automatically manages the lifecycle of the per-display instances.
+ *
+ * Note that this is a [PerDisplayStoreImpl] 2.0 that doesn't require [CoreStartable] bindings,
+ * providing all args in the constructor.
+ */
+class PerDisplayInstanceRepositoryImpl<T>
+@AssistedInject
+constructor(
+ @Assisted override val debugName: String,
+ @Assisted private val instanceProvider: PerDisplayInstanceProvider<T>,
+ @Background private val backgroundApplicationScope: CoroutineScope,
+ private val displayRepository: DisplayRepository,
+ private val dumpManager: DumpManager,
+) : PerDisplayRepository<T>, Dumpable {
+
+ private val perDisplayInstances = ConcurrentHashMap<Int, T?>()
+
+ init {
+ backgroundApplicationScope.launch("$debugName#start") { start() }
+ }
+
+ override val displayIds: Set<Int>
+ get() = perDisplayInstances.keys
+
+ private suspend fun start() {
+ dumpManager.registerDumpable(this)
+ displayRepository.displayIds.collectLatest { displayIds ->
+ val toRemove = perDisplayInstances.keys - displayIds
+ toRemove.forEach { displayId ->
+ perDisplayInstances.remove(displayId)?.let { instance ->
+ (instanceProvider as? PerDisplayInstanceProviderWithTeardown)?.destroyInstance(
+ instance
+ )
+ }
+ }
+ }
+ }
+
+ override fun get(displayId: Int): T? {
+ if (displayRepository.getDisplay(displayId) == null) {
+ Log.e(TAG, "<$debugName: Display with id $displayId doesn't exist.")
+ return null
+ }
+
+ // If it doesn't exist, create it and put it in the map.
+ return perDisplayInstances.computeIfAbsent(displayId) { key ->
+ val instance =
+ traceSection({ "creating instance of $debugName for displayId=$key" }) {
+ instanceProvider.createInstance(key)
+ }
+ if (instance == null) {
+ Log.e(
+ TAG,
+ "<$debugName> returning null because createInstance($key) returned null.",
+ )
+ }
+ instance
+ }
+ }
+
+ @AssistedFactory
+ interface Factory<T> {
+ fun create(
+ debugName: String,
+ instanceProvider: PerDisplayInstanceProvider<T>,
+ ): PerDisplayInstanceRepositoryImpl<T>
+ }
+
+ companion object {
+ private const val TAG = "PerDisplayInstanceRepo"
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println(perDisplayInstances)
+ }
+}
+
+/**
+ * Provides an instance of a given class **only** for the default display, even if asked for another
+ * display.
+ *
+ * This is useful in case of flag refactors: it can be provided instead of an instance of
+ * [PerDisplayInstanceRepositoryImpl] when a flag related to multi display refactoring is off.
+ */
+class DefaultDisplayOnlyInstanceRepositoryImpl<T>(
+ override val debugName: String,
+ private val instanceProvider: PerDisplayInstanceProvider<T>,
+) : PerDisplayRepository<T> {
+ private val lazyDefaultDisplayInstance by lazy {
+ instanceProvider.createInstance(Display.DEFAULT_DISPLAY)
+ }
+ override val displayIds: Set<Int> = setOf(Display.DEFAULT_DISPLAY)
+
+ override fun get(displayId: Int): T? = lazyDefaultDisplayInstance
+}
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayStore.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayStore.kt
index 564588c..4604886 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayStore.kt
@@ -26,6 +26,7 @@
import kotlinx.coroutines.CoroutineScope
/** Provides per display instances of [T]. */
+@Deprecated("Use PerDisplayInstanceProvider<T> instead")
interface PerDisplayStore<T> {
/**
@@ -43,12 +44,13 @@
fun forDisplay(displayId: Int): T?
}
+@Deprecated("Use PerDisplayRepository<T> instead")
abstract class PerDisplayStoreImpl<T>(
@Background private val backgroundApplicationScope: CoroutineScope,
private val displayRepository: DisplayRepository,
) : PerDisplayStore<T>, CoreStartable {
- private val perDisplayInstances = ConcurrentHashMap<Int, T>()
+ protected val perDisplayInstances = ConcurrentHashMap<Int, T>()
/**
* The instance for the default/main display of the device. For example, on a phone or a tablet,
@@ -106,6 +108,11 @@
* Will be called when the display associated with [instance] was removed. It allows to perform
* any clean up if needed.
*/
+ @Deprecated(
+ "Use PerDisplayInstanceProviderWithTeardown instead, and let " +
+ "PerDisplayInstanceRepositoryImpl decide when to destroy the instance (e.g. on " +
+ "display removal or other conditions."
+ )
open suspend fun onDisplayRemovalAction(instance: T) {}
override fun dump(pw: PrintWriter, args: Array<out String>) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 12718e8b..9edd9dc 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -94,6 +94,7 @@
public class DozeSensors {
private static final String TAG = "DozeSensors";
private static final UiEventLogger UI_EVENT_LOGGER = new UiEventLoggerImpl();
+ private static final String KEY_DOZE_PULSE_ON_AUTH = "doze_pulse_on_auth";
private final AsyncSensorManager mSensorManager;
private final AmbientDisplayConfiguration mConfig;
@@ -241,7 +242,7 @@
),
new TriggerSensor(
findSensor(config.udfpsLongPressSensorType()),
- "doze_pulse_on_auth",
+ KEY_DOZE_PULSE_ON_AUTH,
true /* settingDef */,
udfpsLongPressConfigured(),
DozeLog.REASON_SENSOR_UDFPS_LONG_PRESS,
@@ -421,6 +422,18 @@
&& (!s.mRequiresTouchscreen || mListeningTouchScreenSensors)
&& (!s.mRequiresProx || mListeningProxSensors)
&& (!s.mRequiresAod || mListeningAodOnlySensors);
+
+ //AOD might be turned off in visual because of BetterySaver or isAlwaysOnSuppressed(),
+ //but AOD isn't really turned off, in these cases, udfpsLongPressSensor should be
+ //unregistered.
+ if (!mListeningAodOnlySensors && KEY_DOZE_PULSE_ON_AUTH.equals(s.mSetting)) {
+ if (mConfig.alwaysOnEnabled(mSelectedUserInteractor.getSelectedUserId())
+ && !mConfig.screenOffUdfpsEnabled(
+ mSelectedUserInteractor.getSelectedUserId())) {
+ listen = false;
+ }
+ }
+
s.setListening(listen);
if (listen) {
anyListening = true;
diff --git a/packages/SystemUI/src/com/android/systemui/flags/RefactorFlagUtils.kt b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlagUtils.kt
index 4d89a82..ca157af 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/RefactorFlagUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlagUtils.kt
@@ -86,12 +86,12 @@
* Example usage:
* ```
* public void setSomeNewController(SomeController someController) {
- * SomeRefactor.assertInNewMode();
+ * SomeRefactor.unsafeAssertInNewMode();
* mSomeController = someController;
* }
* ````
*/
- inline fun assertInNewMode(isEnabled: Boolean, flagName: Any) =
+ inline fun unsafeAssertInNewMode(isEnabled: Boolean, flagName: Any) =
check(isEnabled) { "New code path not supported when $flagName is disabled." }
/**
diff --git a/packages/SystemUI/src/com/android/systemui/grid/ui/compose/SpannedGrids.kt b/packages/SystemUI/src/com/android/systemui/grid/ui/compose/SpannedGrids.kt
index 96ef03c..e06c228 100644
--- a/packages/SystemUI/src/com/android/systemui/grid/ui/compose/SpannedGrids.kt
+++ b/packages/SystemUI/src/com/android/systemui/grid/ui/compose/SpannedGrids.kt
@@ -13,9 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.android.systemui.grid.ui.compose
+import androidx.collection.IntIntPair
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
@@ -24,7 +24,6 @@
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
-import androidx.compose.ui.layout.Placeable
import androidx.compose.ui.semantics.CollectionInfo
import androidx.compose.ui.semantics.CollectionItemInfo
import androidx.compose.ui.semantics.collectionInfo
@@ -34,6 +33,8 @@
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.fastForEachIndexed
+import androidx.compose.ui.util.fastMapIndexed
import kotlin.math.max
/**
@@ -65,7 +66,11 @@
spans: List<Int>,
modifier: Modifier = Modifier,
keys: (spanIndex: Int) -> Any = { it },
- composables: @Composable BoxScope.(spanIndex: Int) -> Unit,
+ composables:
+ @Composable
+ BoxScope.(
+ spanIndex: Int, row: Int, isFirstInColumn: Boolean, isLastInColumn: Boolean,
+ ) -> Unit,
) {
SpannedGrid(
primarySpaces = rows,
@@ -80,7 +85,7 @@
}
/**
- * Horizontal (non lazy) grid that supports [spans] for its elements.
+ * Vertical (non lazy) grid that supports [spans] for its elements.
*
* The elements will be laid down horizontally first, and then by rows. So assuming LTR layout, it
* will be (for a span list `[2, 1, 2, 1, 1, 1, 1, 1]` and 4 columns):
@@ -107,7 +112,9 @@
spans: List<Int>,
modifier: Modifier = Modifier,
keys: (spanIndex: Int) -> Any = { it },
- composables: @Composable BoxScope.(spanIndex: Int) -> Unit,
+ composables:
+ @Composable
+ BoxScope.(spanIndex: Int, column: Int, isFirstInRow: Boolean, isLastInRow: Boolean) -> Unit,
) {
SpannedGrid(
primarySpaces = columns,
@@ -130,7 +137,9 @@
isVertical: Boolean,
modifier: Modifier = Modifier,
keys: (spanIndex: Int) -> Any = { it },
- composables: @Composable BoxScope.(spanIndex: Int) -> Unit,
+ composables:
+ @Composable
+ BoxScope.(spanIndex: Int, secondaryAxis: Int, isFirst: Boolean, isLast: Boolean) -> Unit,
) {
val crossAxisArrangement = Arrangement.spacedBy(crossAxisSpacing)
spans.forEachIndexed { index, span ->
@@ -139,7 +148,6 @@
"expected rance of [1, $primarySpaces]"
}
}
-
if (isVertical) {
check(crossAxisSpacing >= 0.dp) { "Negative columnSpacing $crossAxisSpacing" }
check(mainAxisSpacing >= 0.dp) { "Negative rowSpacing $mainAxisSpacing" }
@@ -147,29 +155,30 @@
check(mainAxisSpacing >= 0.dp) { "Negative columnSpacing $mainAxisSpacing" }
check(crossAxisSpacing >= 0.dp) { "Negative rowSpacing $crossAxisSpacing" }
}
-
- val totalMainAxisGroups: Int =
+ // List of primary axis index to secondary axis index
+ // This is keyed to the size of the spans list for performance reasons as we don't expect the
+ // spans value to change outside of edit mode.
+ val positions = remember(spans.size) { Array(spans.size) { IntIntPair(0, 0) } }
+ val totalMainAxisGroups =
remember(primarySpaces, spans) {
- var currentAccumulated = 0
- var groups = 1
- spans.forEach { span ->
- if (currentAccumulated + span <= primarySpaces) {
- currentAccumulated += span
- } else {
- groups += 1
- currentAccumulated = span
+ var mainAxisGroup = 0
+ var currentSlot = 0
+ spans.fastForEachIndexed { index, span ->
+ if (currentSlot + span > primarySpaces) {
+ currentSlot = 0
+ mainAxisGroup += 1
}
+ positions[index] = IntIntPair(mainAxisGroup, currentSlot)
+ currentSlot += span
}
- groups
+ mainAxisGroup + 1
}
-
val slotPositionsAndSizesCache = remember {
object {
var sizes = IntArray(0)
var positions = IntArray(0)
}
}
-
Layout(
{
(0 until spans.size).map { spanIndex ->
@@ -184,7 +193,13 @@
}
}
) {
- composables(spanIndex)
+ val position = positions[spanIndex]
+ composables(
+ spanIndex,
+ position.second,
+ position.second == 0,
+ positions.getOrNull(spanIndex + 1)?.first != position.first,
+ )
}
}
}
@@ -205,7 +220,6 @@
slotPositionsAndSizesCache.sizes,
)
val cellSizesInCrossAxis = slotPositionsAndSizesCache.sizes
-
// with is needed because of the double receiver (Density, Arrangement).
with(crossAxisArrangement) {
arrange(
@@ -216,68 +230,73 @@
)
}
val startPositions = slotPositionsAndSizesCache.positions
-
val mainAxisSpacingPx = mainAxisSpacing.roundToPx()
val mainAxisTotalGaps = (totalMainAxisGroups - 1) * mainAxisSpacingPx
- val mainAxisSize = if (isVertical) constraints.maxHeight else constraints.maxWidth
+ val mainAxisMaxSize = if (isVertical) constraints.maxHeight else constraints.maxWidth
val mainAxisElementConstraint =
- if (mainAxisSize == Constraints.Infinity) {
+ if (mainAxisMaxSize == Constraints.Infinity) {
Constraints.Infinity
} else {
- max(0, (mainAxisSize - mainAxisTotalGaps) / totalMainAxisGroups)
+ max(0, (mainAxisMaxSize - mainAxisTotalGaps) / totalMainAxisGroups)
}
- val mainAxisSizes = IntArray(totalMainAxisGroups) { 0 }
-
- var currentSlot = 0
- var mainAxisGroup = 0
+ var mainAxisTotalSize = mainAxisTotalGaps
+ var currentMainAxis = 0
+ var currentMainAxisMax = 0
val placeables =
- measurables.mapIndexed { index, measurable ->
+ measurables.fastMapIndexed { index, measurable ->
val span = spans[index]
- if (currentSlot + span > primarySpaces) {
- currentSlot = 0
- mainAxisGroup += 1
- }
+ val position = positions[index]
val crossAxisConstraint =
- calculateWidth(cellSizesInCrossAxis, startPositions, currentSlot, span)
- PlaceResult(
- measurable.measure(
- makeConstraint(
- isVertical,
- mainAxisElementConstraint,
- crossAxisConstraint,
- )
- ),
- currentSlot,
- mainAxisGroup,
+ calculateWidth(cellSizesInCrossAxis, startPositions, position.second, span)
+
+ measurable
+ .measure(
+ makeConstraint(isVertical, mainAxisElementConstraint, crossAxisConstraint)
)
.also {
- currentSlot += span
- mainAxisSizes[mainAxisGroup] =
- max(
- mainAxisSizes[mainAxisGroup],
- if (isVertical) it.placeable.height else it.placeable.width,
- )
+ val placeableSize = if (isVertical) it.height else it.width
+ if (position.first != currentMainAxis) {
+ // New row -- Add the max size to the total and reset the max
+ mainAxisTotalSize += currentMainAxisMax
+ currentMainAxisMax = placeableSize
+ currentMainAxis = position.first
+ } else {
+ currentMainAxisMax = max(currentMainAxisMax, placeableSize)
+ }
}
}
+ mainAxisTotalSize += currentMainAxisMax
- val mainAxisTotalSize = mainAxisTotalGaps + mainAxisSizes.sum()
- val mainAxisStartingPoints =
- mainAxisSizes.runningFold(0) { acc, value -> acc + value + mainAxisSpacingPx }
val height = if (isVertical) mainAxisTotalSize else crossAxisSize
val width = if (isVertical) crossAxisSize else mainAxisTotalSize
layout(width, height) {
- placeables.forEach { (placeable, slot, mainAxisGroup) ->
+ var previousMainAxis = 0
+ var currentMainAxisPosition = 0
+ var currentMainAxisMax = 0
+ placeables.forEachIndexed { index, placeable ->
+ val slot = positions[index].second
+ val mainAxisSize = if (isVertical) placeable.height else placeable.width
+
+ if (positions[index].first != previousMainAxis) {
+ // Move up a row + padding
+ currentMainAxisPosition += currentMainAxisMax + mainAxisSpacingPx
+ currentMainAxisMax = mainAxisSize
+ previousMainAxis = positions[index].first
+ } else {
+ currentMainAxisMax = max(currentMainAxisMax, mainAxisSize)
+ }
+
val x =
if (isVertical) {
startPositions[slot]
} else {
- mainAxisStartingPoints[mainAxisGroup]
+ currentMainAxisPosition
}
val y =
if (isVertical) {
- mainAxisStartingPoints[mainAxisGroup]
+ currentMainAxisPosition
} else {
startPositions[slot]
}
@@ -321,9 +340,3 @@
outArray[index] = slotSize + if (index < remainingPixels) 1 else 0
}
}
-
-private data class PlaceResult(
- val placeable: Placeable,
- val slotIndex: Int,
- val mainAxisGroup: Int,
-)
diff --git a/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/SysUIKeyEventHandler.kt b/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/SysUIKeyEventHandler.kt
index 1febc79..bc65ad4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/SysUIKeyEventHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/SysUIKeyEventHandler.kt
@@ -42,7 +42,7 @@
when (event.keyCode) {
KeyEvent.KEYCODE_BACK -> {
- if (event.handleAction()) {
+ if (!backActionInteractor.isBackCallbackRegistered() && event.handleAction()) {
backActionInteractor.onBackRequested()
}
return true
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index cc0efbc..d8fc21a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -1847,6 +1847,7 @@
// explicitly DO NOT want to call
// mKeyguardViewControllerLazy.get().setKeyguardGoingAwayState(false)
// here, since that will mess with the device lock state.
+ mKeyguardStateController.notifyKeyguardGoingAway(false);
mUpdateMonitor.dispatchKeyguardGoingAway(false);
notifyStartedGoingToSleep();
@@ -2489,6 +2490,7 @@
Log.e(TAG,
"doKeyguard: already showing, but re-showing because we're interactive or "
+ "were in the middle of hiding.");
+ notifyLockNowCallback();
}
}
@@ -2993,7 +2995,6 @@
startKeyguardTransition(showing, aodShowing);
} else {
try {
-
mActivityTaskManagerService.setLockScreenShown(showing, aodShowing);
} catch (RemoteException ignored) {
}
@@ -3649,30 +3650,33 @@
return;
}
- try {
- int flags = KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS
- | KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
+ int flags = KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS
+ | KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
- // If we are unlocking to the launcher, clear the snapshot so that any changes as part
- // of the in-window animations are reflected. This is needed even if we're not actually
- // playing in-window animations for this particular unlock since a previous unlock might
- // have changed the Launcher state.
- if (mKeyguardUnlockAnimationControllerLazy.get().isSupportedLauncherUnderneath()) {
- flags |= KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
- }
+ // If we are unlocking to the launcher, clear the snapshot so that any changes as part
+ // of the in-window animations are reflected. This is needed even if we're not actually
+ // playing in-window animations for this particular unlock since a previous unlock might
+ // have changed the Launcher state.
+ if (mKeyguardUnlockAnimationControllerLazy.get().isSupportedLauncherUnderneath()) {
+ flags |= KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
+ }
- mKeyguardStateController.notifyKeyguardGoingAway(true);
+ mKeyguardStateController.notifyKeyguardGoingAway(true);
- if (!KeyguardWmStateRefactor.isEnabled()) {
- // Handled in WmLockscreenVisibilityManager.
- mGoingAwayRequestedForUserId = mSelectedUserInteractor.getSelectedUserId();
+ if (!KeyguardWmStateRefactor.isEnabled()) {
+ // Handled in WmLockscreenVisibilityManager.
+ mGoingAwayRequestedForUserId = mSelectedUserInteractor.getSelectedUserId();
+ final int goingAwayFlags = flags;
+ mUiBgExecutor.execute(() -> {
Log.d(TAG, "keyguardGoingAway requested for userId: "
+ mGoingAwayRequestedForUserId);
- mActivityTaskManagerService.keyguardGoingAway(flags);
- }
- } catch (RemoteException e) {
- mSurfaceBehindRemoteAnimationRequested = false;
- Log.e(TAG, "Failed to report keyguardGoingAway", e);
+ try {
+ mActivityTaskManagerService.keyguardGoingAway(goingAwayFlags);
+ } catch (RemoteException e) {
+ mSurfaceBehindRemoteAnimationRequested = false;
+ Log.e(TAG, "Failed to report keyguardGoingAway", e);
+ }
+ });
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
index 2c5bacb..70e2413 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
@@ -196,6 +196,9 @@
confirmCredentialIntent.putExtra(Intent.EXTRA_INTENT, target.getIntentSender());
}
+ String packageName = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ confirmCredentialIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
+
// WorkLockActivity is started as a task overlay, so unless credential confirmation is also
// started as an overlay, it won't be visible.
final ActivityOptions launchOptions = ActivityOptions.makeBasic();
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
index f11ebee..07ed194 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
@@ -98,7 +98,7 @@
private var settingsValue: Int = 0
private val isAvailable: StateFlow<Boolean> by lazy {
- ModesUi.assertInNewMode()
+ ModesUi.unsafeAssertInNewMode()
interactor.isZenAvailable.stateIn(
scope = backgroundScope,
started = SharingStarted.Eagerly,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index acb98ed..affcd33 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -662,12 +662,12 @@
}
override fun setShowKeyguardWhenReenabled(isShowKeyguardWhenReenabled: Boolean) {
- SceneContainerFlag.assertInNewMode()
+ SceneContainerFlag.unsafeAssertInNewMode()
this.isShowKeyguardWhenReenabled = isShowKeyguardWhenReenabled
}
override fun isShowKeyguardWhenReenabled(): Boolean {
- SceneContainerFlag.assertInNewMode()
+ SceneContainerFlag.unsafeAssertInNewMode()
return isShowKeyguardWhenReenabled
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
index 63cf4f7..ab0efed 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
@@ -85,7 +85,7 @@
val previewClock: Flow<ClockController> = keyguardClockRepository.previewClock
- val clockEventController: ClockEventController by keyguardClockRepository::clockEventController
+ val clockEventController: ClockEventController = keyguardClockRepository.clockEventController
var clock: ClockController? by keyguardClockRepository.clockEventController::clock
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt
index 0b587ae..c031b53 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt
@@ -38,9 +38,15 @@
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
view.alpha = 0f
+
launch("$TAG#viewModel.accessibilityDelegateHint") {
viewModel.accessibilityDelegateHint.collect { hint ->
view.accessibilityHintType = hint
+ if (hint != DeviceEntryIconView.AccessibilityHintType.NONE) {
+ view.setOnClickListener { viewModel.onTapped() }
+ } else {
+ view.setOnClickListener(null)
+ }
}
}
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 def1ac8..e81d535 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
@@ -81,22 +81,92 @@
}
if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
+ val xBuffer =
+ keyguardRootView.context.resources.getDimensionPixelSize(
+ R.dimen.smartspace_padding_horizontal
+ )
+ val yBuffer =
+ keyguardRootView.context.resources.getDimensionPixelSize(
+ R.dimen.smartspace_padding_vertical
+ )
+
+ val smallViewIds =
+ listOf(sharedR.id.date_smartspace_view, sharedR.id.weather_smartspace_view)
+
+ val largeViewIds =
+ listOf(
+ sharedR.id.date_smartspace_view_large,
+ sharedR.id.weather_smartspace_view_large,
+ )
+
launch("$TAG#smartspaceViewModel.burnInLayerVisibility") {
- keyguardRootViewModel.burnInLayerVisibility.collect { visibility ->
- if (clockViewModel.isLargeClockVisible.value) {
- // hide small clock date/weather
- val dateView =
- keyguardRootView.requireViewById<View>(
- sharedR.id.date_smartspace_view
- )
- dateView.visibility = View.GONE
- val weatherView =
- keyguardRootView.requireViewById<View>(
- sharedR.id.weather_smartspace_view
- )
- weatherView.visibility = View.GONE
+ combine(
+ keyguardRootViewModel.burnInLayerVisibility,
+ clockViewModel.isLargeClockVisible,
+ ::Pair,
+ )
+ .collect { (visibility, isLargeClock) ->
+ if (isLargeClock) {
+ // hide small clock date/weather
+ for (viewId in smallViewIds) {
+ keyguardRootView.findViewById<View>(viewId)?.let {
+ it.visibility = View.GONE
+ }
+ }
+ }
}
- }
+ }
+
+ launch("$TAG#clockEventController.onClockBoundsChanged") {
+ // Whenever the doze amount changes, the clock may update it's view bounds.
+ // We need to update our layout position as a result. We could do this via
+ // `requestLayout`, but that's quite expensive when enclosed in since this
+ // recomputes the entire ConstraintLayout, so instead we do it manually. We
+ // would use translationX/Y for this, but that's used by burnin.
+ combine(
+ clockViewModel.isLargeClockVisible,
+ clockViewModel.clockEventController.onClockBoundsChanged,
+ ::Pair,
+ )
+ .collect { (isLargeClock, clockBounds) ->
+ for (id in (if (isLargeClock) smallViewIds else largeViewIds)) {
+ keyguardRootView.findViewById<View>(id)?.let {
+ it.visibility = View.GONE
+ }
+ }
+
+ if (clockBounds == null) return@collect
+ if (isLargeClock) {
+ val largeDateHeight =
+ keyguardRootView
+ .findViewById<View>(
+ sharedR.id.date_smartspace_view_large
+ )
+ ?.height ?: 0
+ for (id in largeViewIds) {
+ keyguardRootView.findViewById<View>(id)?.let { view ->
+ val viewHeight = view.height
+ val offset = (largeDateHeight - viewHeight) / 2
+ view.top =
+ (clockBounds.bottom + yBuffer + offset).toInt()
+ view.bottom = view.top + viewHeight
+ }
+ }
+ } else {
+ for (id in smallViewIds) {
+ keyguardRootView.findViewById<View>(id)?.let { view ->
+ val viewWidth = view.width
+ if (view.isLayoutRtl()) {
+ view.right = (clockBounds.left - xBuffer).toInt()
+ view.left = view.right - viewWidth
+ } else {
+ view.left = (clockBounds.right + xBuffer).toInt()
+ view.right = view.left + viewWidth
+ }
+ }
+ }
+ }
+ }
}
}
}
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 37cc852f..d0b5f74 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
@@ -226,7 +226,7 @@
ConstraintSet.TOP,
customR.id.lockscreen_clock_view_large,
ConstraintSet.BOTTOM,
- dateWeatherPaddingStart,
+ context.resources.getDimensionPixelSize(R.dimen.smartspace_padding_vertical),
)
connect(
@@ -291,7 +291,9 @@
ConstraintSet.START,
customR.id.lockscreen_clock_view,
ConstraintSet.END,
- 20,
+ context.resources.getDimensionPixelSize(
+ R.dimen.smartspace_padding_horizontal
+ ),
)
connect(
sharedR.id.date_smartspace_view,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
index acd381e..9038922 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
@@ -18,6 +18,7 @@
import android.content.Context
import com.android.settingslib.Utils
+import com.android.systemui.accessibility.domain.interactor.AccessibilityInteractor
import com.android.systemui.biometrics.domain.interactor.FingerprintPropertyInteractor
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
@@ -26,6 +27,7 @@
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.shared.recents.utilities.Utilities.clamp
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -46,6 +48,8 @@
fingerprintPropertyInteractor: FingerprintPropertyInteractor,
udfpsOverlayInteractor: UdfpsOverlayInteractor,
alternateBouncerViewModel: AlternateBouncerViewModel,
+ private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
+ private val accessibilityInteractor: AccessibilityInteractor,
) {
private val isSupported: Flow<Boolean> = deviceEntryUdfpsInteractor.isUdfpsSupported
val alpha: Flow<Float> =
@@ -74,7 +78,15 @@
}
}
val accessibilityDelegateHint: Flow<DeviceEntryIconView.AccessibilityHintType> =
- flowOf(DeviceEntryIconView.AccessibilityHintType.ENTER)
+ accessibilityInteractor.isEnabled.flatMapLatest { touchExplorationEnabled ->
+ flowOf(
+ if (touchExplorationEnabled) {
+ DeviceEntryIconView.AccessibilityHintType.BOUNCER
+ } else {
+ DeviceEntryIconView.AccessibilityHintType.NONE
+ }
+ )
+ }
private val fgIconColor: Flow<Int> =
configurationInteractor.onAnyConfigurationChange
@@ -93,6 +105,10 @@
)
}
+ fun onTapped() {
+ statusBarKeyguardViewManager.showPrimaryBouncer(/* scrimmed */ true)
+ }
+
val bgColor: Flow<Int> = deviceEntryBackgroundViewModel.color
val bgAlpha: Flow<Float> = flowOf(1f)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
index 1ca08fe..cff6511 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
@@ -47,7 +47,7 @@
/** Reports the alternate bouncer visible state if the scene container flag is enabled. */
val isVisible: Flow<Boolean> =
- alternateBouncerInteractor.get().isVisible.onEach { SceneContainerFlag.assertInNewMode() }
+ alternateBouncerInteractor.get().isVisible.onEach { SceneContainerFlag.unsafeAssertInNewMode() }
/** Progress to a fully transitioned alternate bouncer. 1f represents fully transitioned. */
val transitionToAlternateBouncerProgress: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModel.kt
index 4001054..c088900 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModel.kt
@@ -17,33 +17,39 @@
package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.Flags
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.communal.shared.model.CommunalBackgroundType
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.FromGlanceableHubTransitionInteractor
import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.BlurConfig
import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition
+import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class GlanceableHubToPrimaryBouncerTransitionViewModel
@Inject
constructor(
private val blurConfig: BlurConfig,
animationFlow: KeyguardTransitionAnimationFlow,
- communalSettingsInteractor: CommunalSettingsInteractor,
+ private val communalSettingsInteractor: CommunalSettingsInteractor,
+ private val communalSceneInteractor: CommunalSceneInteractor,
+ private val keyguardStateController: KeyguardStateController,
) : PrimaryBouncerTransition {
private val transitionAnimation =
animationFlow
.setup(
- duration = FromLockscreenTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
+ duration = FromGlanceableHubTransitionInteractor.TO_BOUNCER_DURATION,
edge = Edge.INVALID,
)
.setupWithoutSceneContainer(edge = Edge.create(GLANCEABLE_HUB, PRIMARY_BOUNCER))
@@ -59,6 +65,13 @@
transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx)
}
+ /** Whether to delay the animation to fade in bouncer elements. */
+ fun willDelayAppearAnimation(isLandscape: Boolean): Boolean =
+ communalSettingsInteractor.isV2FlagEnabled() &&
+ communalSceneInteractor.isIdleOnCommunal.value &&
+ !keyguardStateController.isKeyguardScreenRotationAllowed() &&
+ isLandscape
+
override val notificationBlurRadius: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(0.0f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
index cf5cc26..dcbf7b5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
@@ -19,6 +19,7 @@
import android.content.Context
import android.content.res.Resources
import androidx.constraintlayout.helper.widget.Layer
+import com.android.keyguard.ClockEventController
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.customization.R as customR
import com.android.systemui.dagger.SysUISingleton
@@ -68,6 +69,7 @@
initialValue = true,
)
+ val clockEventController: ClockEventController = keyguardClockInteractor.clockEventController
val currentClock = keyguardClockInteractor.currentClock
val hasCustomWeatherDataDisplay =
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt b/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt
index 9fddbfb..a154694 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt
@@ -16,6 +16,11 @@
package com.android.systemui.log.table
+import com.android.systemui.kairos.BuildScope
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.State
+import com.android.systemui.kairos.changes
+import com.android.systemui.kairos.effect
import com.android.systemui.util.kotlin.pairwiseBy
import kotlinx.coroutines.flow.Flow
@@ -184,3 +189,94 @@
newVal
}
}
+
+/** See [logDiffsForTable(TableLogBuffer, String, T)]. */
+@ExperimentalKairosApi
+@JvmName("logIntDiffsForTable")
+fun BuildScope.logDiffsForTable(
+ intState: State<Int?>,
+ tableLogBuffer: TableLogBuffer,
+ columnPrefix: String = "",
+ columnName: String,
+) {
+ var isInitial = true
+ intState.observe { new ->
+ tableLogBuffer.logChange(columnPrefix, columnName, new, isInitial = isInitial)
+ isInitial = false
+ }
+}
+
+/**
+ * Each time the flow is updated with a new value, logs the differences between the previous value
+ * and the new value to the given [tableLogBuffer].
+ *
+ * The new value's [Diffable.logDiffs] method will be used to log the differences to the table.
+ *
+ * @param columnPrefix a prefix that will be applied to every column name that gets logged.
+ */
+@ExperimentalKairosApi
+fun <T : Diffable<T>> BuildScope.logDiffsForTable(
+ diffableState: State<T>,
+ tableLogBuffer: TableLogBuffer,
+ columnPrefix: String = "",
+) {
+ val initialValue = diffableState.sampleDeferred()
+ effect {
+ // Fully log the initial value to the table.
+ tableLogBuffer.logChange(columnPrefix, isInitial = true) { row ->
+ initialValue.value.logFull(row)
+ }
+ }
+ diffableState.changes.observe { newState ->
+ val prevState = diffableState.sample()
+ tableLogBuffer.logDiffs(columnPrefix, prevVal = prevState, newVal = newState)
+ }
+}
+
+/** See [logDiffsForTable(TableLogBuffer, String, T)]. */
+@ExperimentalKairosApi
+@JvmName("logBooleanDiffsForTable")
+fun BuildScope.logDiffsForTable(
+ booleanState: State<Boolean>,
+ tableLogBuffer: TableLogBuffer,
+ columnPrefix: String = "",
+ columnName: String,
+) {
+ var isInitial = true
+ booleanState.observe { new ->
+ tableLogBuffer.logChange(columnPrefix, columnName, new, isInitial = isInitial)
+ isInitial = false
+ }
+}
+
+/** See [logDiffsForTable(TableLogBuffer, String, T)]. */
+@ExperimentalKairosApi
+@JvmName("logStringDiffsForTable")
+fun BuildScope.logDiffsForTable(
+ stringState: State<String?>,
+ tableLogBuffer: TableLogBuffer,
+ columnPrefix: String = "",
+ columnName: String,
+) {
+ var isInitial = true
+ stringState.observe { new ->
+ tableLogBuffer.logChange(columnPrefix, columnName, new, isInitial = isInitial)
+ isInitial = false
+ }
+}
+
+/** See [logDiffsForTable(TableLogBuffer, String, T)]. */
+@ExperimentalKairosApi
+@JvmName("logListDiffsForTable")
+fun <T> BuildScope.logDiffsForTable(
+ listState: State<List<T>>,
+ tableLogBuffer: TableLogBuffer,
+ columnPrefix: String = "",
+ columnName: String,
+) {
+ var isInitial = true
+ listState.observe { new ->
+ tableLogBuffer.logChange(columnPrefix, columnName, new.toString(), isInitial = isInitial)
+ isInitial = false
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractor.kt
deleted file mode 100644
index 0cb36ed..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractor.kt
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Copyright (C) 2024 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.media.controls.domain.pipeline.interactor
-
-import android.content.Context
-import android.content.Intent
-import android.provider.Settings
-import android.util.Log
-import androidx.annotation.VisibleForTesting
-import com.android.internal.jank.InteractionJankMonitor
-import com.android.systemui.animation.Expandable
-import com.android.systemui.broadcast.BroadcastSender
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.media.controls.data.repository.MediaFilterRepository
-import com.android.systemui.media.controls.domain.pipeline.MediaDataProcessor
-import com.android.systemui.media.controls.shared.model.MediaRecModel
-import com.android.systemui.media.controls.shared.model.MediaRecommendationsModel
-import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
-import com.android.systemui.plugins.ActivityStarter
-import java.net.URISyntaxException
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
-
-/** Encapsulates business logic for media recommendation */
-@SysUISingleton
-class MediaRecommendationsInteractor
-@Inject
-constructor(
- @Application applicationScope: CoroutineScope,
- @Application private val applicationContext: Context,
- private val repository: MediaFilterRepository,
- private val mediaDataProcessor: MediaDataProcessor,
- private val broadcastSender: BroadcastSender,
- private val activityStarter: ActivityStarter,
-) {
-
- val recommendations: Flow<MediaRecommendationsModel> =
- repository.smartspaceMediaData.map { toRecommendationsModel(it) }.distinctUntilChanged()
-
- /** Indicates whether the recommendations card is active. */
- val isActive: StateFlow<Boolean> =
- repository.smartspaceMediaData
- .map { it.isActive }
- .distinctUntilChanged()
- .stateIn(applicationScope, SharingStarted.WhileSubscribed(), false)
-
- fun removeMediaRecommendations(key: String, dismissIntent: Intent?, delayMs: Long) {
- mediaDataProcessor.dismissSmartspaceRecommendation(key, delayMs)
- if (dismissIntent == null) {
- Log.w(TAG, "Cannot create dismiss action click action: extras missing dismiss_intent.")
- return
- }
-
- val className = dismissIntent.component?.className
- if (className == EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME) {
- // Dismiss the card Smartspace data through Smartspace trampoline activity.
- applicationContext.startActivity(dismissIntent)
- } else {
- broadcastSender.sendBroadcast(dismissIntent)
- }
- }
-
- fun startSettings() {
- activityStarter.startActivity(SETTINGS_INTENT, /* dismissShade= */ true)
- }
-
- fun startClickIntent(expandable: Expandable, intent: Intent) {
- if (shouldActivityOpenInForeground(intent)) {
- // Request to unlock the device if the activity needs to be opened in foreground.
- activityStarter.postStartActivityDismissingKeyguard(
- intent,
- 0 /* delay */,
- expandable.activityTransitionController(
- InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER
- ),
- )
- } else {
- // Otherwise, open the activity in background directly.
- applicationContext.startActivity(intent)
- }
- }
-
- /** Returns if the action will open the activity in foreground. */
- private fun shouldActivityOpenInForeground(intent: Intent): Boolean {
- val intentString = intent.extras?.getString(EXTRAS_SMARTSPACE_INTENT) ?: return false
- try {
- val wrapperIntent = Intent.parseUri(intentString, Intent.URI_INTENT_SCHEME)
- return wrapperIntent.getBooleanExtra(KEY_SMARTSPACE_OPEN_IN_FOREGROUND, false)
- } catch (e: URISyntaxException) {
- Log.wtf(TAG, "Failed to create intent from URI: $intentString")
- e.printStackTrace()
- }
- return false
- }
-
- private fun toRecommendationsModel(data: SmartspaceMediaData): MediaRecommendationsModel {
- val mediaRecs = ArrayList<MediaRecModel>()
- data.recommendations.forEach {
- with(it) { mediaRecs.add(MediaRecModel(intent, title, subtitle, icon, extras)) }
- }
- return with(data) {
- MediaRecommendationsModel(
- key = targetId,
- uid = getUid(applicationContext),
- packageName = packageName,
- instanceId = instanceId,
- appName = getAppName(applicationContext),
- dismissIntent = dismissIntent,
- areRecommendationsValid = isValid(),
- mediaRecs = mediaRecs,
- )
- }
- }
-
- fun switchToMediaControl(packageName: String) {
- repository.setMediaFromRecPackageName(packageName)
- }
-
- companion object {
-
- private const val TAG = "MediaRecommendationsInteractor"
-
- // TODO (b/237284176) : move AGSA reference out.
- private const val EXTRAS_SMARTSPACE_INTENT =
- "com.google.android.apps.gsa.smartspace.extra.SMARTSPACE_INTENT"
- @VisibleForTesting
- const val EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME =
- "com.google.android.apps.gsa.staticplugins.opa.smartspace." +
- "ExportedSmartspaceTrampolineActivity"
-
- private const val KEY_SMARTSPACE_OPEN_IN_FOREGROUND = "KEY_OPEN_IN_FOREGROUND"
-
- private val SETTINGS_INTENT = Intent(Settings.ACTION_MEDIA_CONTROLS_SETTINGS)
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaRecommendationsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaRecommendationsViewBinder.kt
deleted file mode 100644
index 4877d18..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaRecommendationsViewBinder.kt
+++ /dev/null
@@ -1,469 +0,0 @@
-/*
- * Copyright (C) 2024 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.media.controls.ui.binder
-
-import android.app.WallpaperColors
-import android.content.Context
-import android.content.res.ColorStateList
-import android.content.res.Configuration
-import android.graphics.Bitmap
-import android.graphics.Color
-import android.graphics.Matrix
-import android.graphics.drawable.BitmapDrawable
-import android.graphics.drawable.ColorDrawable
-import android.graphics.drawable.Drawable
-import android.graphics.drawable.GradientDrawable
-import android.graphics.drawable.Icon
-import android.graphics.drawable.LayerDrawable
-import android.os.Trace
-import android.util.TypedValue
-import android.view.View
-import android.view.ViewGroup
-import androidx.appcompat.content.res.AppCompatResources
-import androidx.constraintlayout.widget.ConstraintSet
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.animation.Expandable
-import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.media.controls.shared.model.NUM_REQUIRED_RECOMMENDATIONS
-import com.android.systemui.media.controls.ui.animation.surfaceFromScheme
-import com.android.systemui.media.controls.ui.animation.textPrimaryFromScheme
-import com.android.systemui.media.controls.ui.animation.textSecondaryFromScheme
-import com.android.systemui.media.controls.ui.controller.MediaViewController
-import com.android.systemui.media.controls.ui.util.MediaArtworkHelper
-import com.android.systemui.media.controls.ui.view.RecommendationViewHolder
-import com.android.systemui.media.controls.ui.viewmodel.MediaRecViewModel
-import com.android.systemui.media.controls.ui.viewmodel.MediaRecommendationsViewModel
-import com.android.systemui.media.controls.ui.viewmodel.MediaRecsCardViewModel
-import com.android.systemui.monet.ColorScheme
-import com.android.systemui.monet.Style
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.res.R
-import com.android.systemui.util.animation.TransitionLayout
-import kotlin.math.min
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.flow.collectLatest
-import com.android.app.tracing.coroutines.launchTraced as launch
-import kotlinx.coroutines.withContext
-
-private const val TAG = "MediaRecommendationsViewBinder"
-private const val MEDIA_REC_SCRIM_START_ALPHA = 0.15f
-private const val MEDIA_REC_SCRIM_END_ALPHA = 1.0f
-
-object MediaRecommendationsViewBinder {
-
- /** Binds recommendations view holder to the given view-model */
- fun bind(
- viewHolder: RecommendationViewHolder,
- viewModel: MediaRecommendationsViewModel,
- mediaViewController: MediaViewController,
- falsingManager: FalsingManager,
- backgroundDispatcher: CoroutineDispatcher,
- mainDispatcher: CoroutineDispatcher,
- ) {
- mediaViewController.recsConfigurationChangeListener = this::updateRecommendationsVisibility
- val cardView = viewHolder.recommendations
- cardView.repeatWhenAttached {
- lifecycleScope.launch {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- launch {
- viewModel.mediaRecsCard.collectLatest { viewModel ->
- viewModel?.let {
- bindRecsCard(
- viewHolder,
- it,
- mediaViewController,
- falsingManager,
- backgroundDispatcher,
- mainDispatcher,
- )
- }
- }
- }
- }
- }
- }
- }
-
- suspend fun bindRecsCard(
- viewHolder: RecommendationViewHolder,
- viewModel: MediaRecsCardViewModel,
- viewController: MediaViewController,
- falsingManager: FalsingManager,
- backgroundDispatcher: CoroutineDispatcher,
- mainDispatcher: CoroutineDispatcher,
- ) {
- // Set up media control location and its listener.
- viewModel.onLocationChanged(viewController.currentEndLocation)
- viewController.locationChangeListener = viewModel.onLocationChanged
-
- // Bind main card.
- viewHolder.recommendations.contentDescription =
- viewModel.contentDescription.invoke(viewController.isGutsVisible)
-
- viewHolder.recommendations.setOnClickListener {
- if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@setOnClickListener
- viewModel.onClicked(Expandable.fromView(it))
- }
-
- viewHolder.recommendations.setOnLongClickListener {
- if (falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY))
- return@setOnLongClickListener true
- if (!viewController.isGutsVisible) {
- openGuts(viewHolder, viewModel, viewController)
- } else {
- closeGuts(viewHolder, viewModel, viewController)
- }
- return@setOnLongClickListener true
- }
-
- // Bind colors
- val appIcon = viewModel.mediaRecs.first().appIcon
- fetchAndUpdateColors(viewHolder, appIcon, backgroundDispatcher, mainDispatcher)
- // Bind all recommendations.
- bindRecommendationsList(
- viewHolder,
- viewModel.mediaRecs,
- falsingManager,
- backgroundDispatcher,
- mainDispatcher,
- )
- updateRecommendationsVisibility(viewController, viewHolder.recommendations)
-
- // Set visibility of recommendations.
- val expandedSet: ConstraintSet = viewController.expandedLayout
- val collapsedSet: ConstraintSet = viewController.collapsedLayout
- viewHolder.mediaTitles.forEach {
- setVisibleAndAlpha(expandedSet, it.id, viewModel.areTitlesVisible)
- setVisibleAndAlpha(collapsedSet, it.id, viewModel.areTitlesVisible)
- }
- viewHolder.mediaSubtitles.forEach {
- setVisibleAndAlpha(expandedSet, it.id, viewModel.areSubtitlesVisible)
- setVisibleAndAlpha(collapsedSet, it.id, viewModel.areSubtitlesVisible)
- }
-
- bindRecommendationsGuts(viewHolder, viewModel, viewController, falsingManager)
-
- viewController.refreshState()
- }
-
- private fun bindRecommendationsGuts(
- viewHolder: RecommendationViewHolder,
- viewModel: MediaRecsCardViewModel,
- viewController: MediaViewController,
- falsingManager: FalsingManager,
- ) {
- val gutsViewHolder = viewHolder.gutsViewHolder
- val gutsViewModel = viewModel.gutsMenu
-
- gutsViewHolder.gutsText.text = gutsViewModel.gutsText
- gutsViewHolder.dismissText.visibility = View.VISIBLE
- gutsViewHolder.dismiss.isEnabled = true
- gutsViewHolder.dismiss.setOnClickListener {
- if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@setOnClickListener
- closeGuts(viewHolder, viewModel, viewController)
- gutsViewModel.onDismissClicked()
- }
-
- gutsViewHolder.cancelText.background = gutsViewModel.cancelTextBackground
- gutsViewHolder.cancel.setOnClickListener {
- if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
- closeGuts(viewHolder, viewModel, viewController)
- }
- }
-
- gutsViewHolder.settings.setOnClickListener {
- if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
- gutsViewModel.onSettingsClicked.invoke()
- }
- }
-
- gutsViewHolder.setDismissible(gutsViewModel.isDismissEnabled)
- }
-
- private suspend fun bindRecommendationsList(
- viewHolder: RecommendationViewHolder,
- mediaRecs: List<MediaRecViewModel>,
- falsingManager: FalsingManager,
- backgroundDispatcher: CoroutineDispatcher,
- mainDispatcher: CoroutineDispatcher,
- ) {
- mediaRecs.forEachIndexed { index, mediaRecViewModel ->
- if (index >= NUM_REQUIRED_RECOMMENDATIONS) return@forEachIndexed
-
- val appIconView = viewHolder.mediaAppIcons[index]
- appIconView.clearColorFilter()
- appIconView.setImageDrawable(mediaRecViewModel.appIcon)
-
- val mediaCoverContainer = viewHolder.mediaCoverContainers[index]
- mediaCoverContainer.setOnClickListener {
- if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@setOnClickListener
- mediaRecViewModel.onClicked(Expandable.fromView(it), index)
- }
- mediaCoverContainer.setOnLongClickListener {
- if (falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY))
- return@setOnLongClickListener true
- (it.parent as View).performLongClick()
- return@setOnLongClickListener true
- }
-
- val mediaCover = viewHolder.mediaCoverItems[index]
- bindRecommendationArtwork(
- mediaCover.context,
- viewHolder,
- mediaRecViewModel,
- index,
- backgroundDispatcher,
- mainDispatcher,
- )
- mediaCover.contentDescription = mediaRecViewModel.contentDescription
-
- val title = viewHolder.mediaTitles[index]
- title.text = mediaRecViewModel.title
-
- val subtitle = viewHolder.mediaSubtitles[index]
- subtitle.text = mediaRecViewModel.subtitle
-
- val progressBar = viewHolder.mediaProgressBars[index]
- progressBar.progress = mediaRecViewModel.progress
- if (mediaRecViewModel.progress == 0) {
- progressBar.visibility = View.GONE
- }
- }
- }
-
- private fun openGuts(
- viewHolder: RecommendationViewHolder,
- viewModel: MediaRecsCardViewModel,
- mediaViewController: MediaViewController,
- ) {
- viewHolder.marquee(true, MediaViewController.GUTS_ANIMATION_DURATION)
- mediaViewController.openGuts()
- viewHolder.recommendations.contentDescription = viewModel.contentDescription.invoke(true)
- viewModel.onLongClicked.invoke()
- }
-
- private fun closeGuts(
- viewHolder: RecommendationViewHolder,
- mediaRecsCardViewModel: MediaRecsCardViewModel,
- mediaViewController: MediaViewController,
- ) {
- viewHolder.marquee(false, MediaViewController.GUTS_ANIMATION_DURATION)
- mediaViewController.closeGuts(false)
- viewHolder.recommendations.contentDescription =
- mediaRecsCardViewModel.contentDescription.invoke(false)
- }
-
- private fun setVisibleAndAlpha(set: ConstraintSet, resId: Int, visible: Boolean) {
- set.setVisibility(resId, if (visible) ConstraintSet.VISIBLE else ConstraintSet.GONE)
- set.setAlpha(resId, if (visible) 1.0f else 0.0f)
- }
-
- fun updateRecommendationsVisibility(
- mediaViewController: MediaViewController,
- cardView: TransitionLayout,
- ) {
- val fittedRecsNum = getNumberOfFittedRecommendations(cardView.context)
- val expandedSet = mediaViewController.expandedLayout
- val collapsedSet = mediaViewController.collapsedLayout
- val mediaCoverContainers = getMediaCoverContainers(cardView)
- // Hide media cover that cannot fit in the recommendation card.
- mediaCoverContainers.forEachIndexed { index, container ->
- setVisibleAndAlpha(expandedSet, container.id, index < fittedRecsNum)
- setVisibleAndAlpha(collapsedSet, container.id, index < fittedRecsNum)
- }
- }
-
- private fun getMediaCoverContainers(cardView: TransitionLayout): List<ViewGroup> {
- return listOf<ViewGroup>(
- cardView.requireViewById(R.id.media_cover1_container),
- cardView.requireViewById(R.id.media_cover2_container),
- cardView.requireViewById(R.id.media_cover3_container),
- )
- }
-
- private fun getNumberOfFittedRecommendations(context: Context): Int {
- val res = context.resources
- val config = res.configuration
- val defaultDpWidth = res.getInteger(R.integer.default_qs_media_rec_width_dp)
- val recCoverWidth =
- (res.getDimensionPixelSize(R.dimen.qs_media_rec_album_width) +
- res.getDimensionPixelSize(R.dimen.qs_media_info_spacing) * 2)
-
- // On landscape, media controls should take half of the screen width.
- val displayAvailableDpWidth =
- if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
- config.screenWidthDp / 2
- } else {
- config.screenWidthDp
- }
- val fittedNum =
- if (displayAvailableDpWidth > defaultDpWidth) {
- val recCoverDefaultWidth =
- res.getDimensionPixelSize(R.dimen.qs_media_rec_default_width)
- recCoverDefaultWidth / recCoverWidth
- } else {
- val displayAvailableWidth =
- TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_DIP,
- displayAvailableDpWidth.toFloat(),
- res.displayMetrics,
- )
- .toInt()
- displayAvailableWidth / recCoverWidth
- }
- return min(fittedNum.toDouble(), NUM_REQUIRED_RECOMMENDATIONS.toDouble()).toInt()
- }
-
- private suspend fun bindRecommendationArtwork(
- context: Context,
- viewHolder: RecommendationViewHolder,
- viewModel: MediaRecViewModel,
- index: Int,
- backgroundDispatcher: CoroutineDispatcher,
- mainDispatcher: CoroutineDispatcher,
- ) {
- val traceCookie = viewHolder.hashCode()
- val traceName = "MediaRecommendationsViewBinder#bindRecommendationArtwork"
- Trace.beginAsyncSection(traceName, traceCookie)
-
- // Capture width & height from views in foreground for artwork scaling in background
- val width = context.resources.getDimensionPixelSize(R.dimen.qs_media_rec_album_width)
- val height =
- context.resources.getDimensionPixelSize(R.dimen.qs_media_rec_album_height_expanded)
-
- withContext(backgroundDispatcher) {
- val artwork =
- getRecCoverBackground(
- context,
- viewModel.albumIcon,
- width,
- height,
- backgroundDispatcher,
- )
- withContext(mainDispatcher) {
- val mediaCover = viewHolder.mediaCoverItems[index]
- val coverMatrix = Matrix(mediaCover.imageMatrix)
- coverMatrix.postScale(1.25f, 1.25f, 0.5f * width, 0.5f * height)
- mediaCover.imageMatrix = coverMatrix
- mediaCover.setImageDrawable(artwork)
- }
- }
- }
-
- /** Returns the recommendation album cover of [width]x[height] size. */
- private suspend fun getRecCoverBackground(
- context: Context,
- icon: Icon?,
- width: Int,
- height: Int,
- backgroundDispatcher: CoroutineDispatcher,
- ): Drawable =
- withContext(backgroundDispatcher) {
- return@withContext MediaArtworkHelper.getWallpaperColor(
- context,
- backgroundDispatcher,
- icon,
- TAG,
- )
- ?.let { wallpaperColors ->
- addGradientToRecommendationAlbum(
- context,
- icon!!,
- ColorScheme(wallpaperColors, true, Style.CONTENT),
- width,
- height,
- )
- } ?: ColorDrawable(Color.TRANSPARENT)
- }
-
- private fun addGradientToRecommendationAlbum(
- context: Context,
- artworkIcon: Icon,
- mutableColorScheme: ColorScheme,
- width: Int,
- height: Int,
- ): LayerDrawable {
- // First try scaling rec card using bitmap drawable.
- // If returns null, set drawable bounds.
- val albumArt =
- getScaledRecommendationCover(context, artworkIcon, width, height)
- ?: MediaArtworkHelper.getScaledBackground(context, artworkIcon, width, height)
- val gradient =
- AppCompatResources.getDrawable(context, R.drawable.qs_media_rec_scrim)?.mutate()
- as GradientDrawable
- return MediaArtworkHelper.setUpGradientColorOnDrawable(
- albumArt,
- gradient,
- mutableColorScheme,
- MEDIA_REC_SCRIM_START_ALPHA,
- MEDIA_REC_SCRIM_END_ALPHA,
- )
- }
-
- /** Returns a [Drawable] of a given [artworkIcon] scaled to [width]x[height] size, . */
- private fun getScaledRecommendationCover(
- context: Context,
- artworkIcon: Icon,
- width: Int,
- height: Int,
- ): Drawable? {
- check(width > 0) { "Width must be a positive number but was $width" }
- check(height > 0) { "Height must be a positive number but was $height" }
-
- return if (
- artworkIcon.type == Icon.TYPE_BITMAP || artworkIcon.type == Icon.TYPE_ADAPTIVE_BITMAP
- ) {
- artworkIcon.bitmap?.let {
- val bitmap = Bitmap.createScaledBitmap(it, width, height, false)
- BitmapDrawable(context.resources, bitmap)
- }
- } else {
- null
- }
- }
-
- private suspend fun fetchAndUpdateColors(
- viewHolder: RecommendationViewHolder,
- appIcon: Drawable,
- backgroundDispatcher: CoroutineDispatcher,
- mainDispatcher: CoroutineDispatcher,
- ) =
- withContext(backgroundDispatcher) {
- val colorScheme =
- ColorScheme(WallpaperColors.fromDrawable(appIcon), /* darkTheme= */ true)
- withContext(mainDispatcher) {
- val backgroundColor = surfaceFromScheme(colorScheme)
- val textPrimaryColor = textPrimaryFromScheme(colorScheme)
- val textSecondaryColor = textSecondaryFromScheme(colorScheme)
-
- viewHolder.cardTitle.setTextColor(textPrimaryColor)
- viewHolder.recommendations.setBackgroundTintList(
- ColorStateList.valueOf(backgroundColor)
- )
-
- viewHolder.mediaTitles.forEach { it.setTextColor(textPrimaryColor) }
- viewHolder.mediaSubtitles.forEach { it.setTextColor(textSecondaryColor) }
- viewHolder.mediaProgressBars.forEach {
- it.progressTintList = ColorStateList.valueOf(textPrimaryColor)
- }
-
- viewHolder.gutsViewHolder.setColors(colorScheme)
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
index 7b1ae57e..ac6343c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
@@ -61,14 +61,12 @@
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
import com.android.systemui.media.controls.ui.binder.MediaControlViewBinder
-import com.android.systemui.media.controls.ui.binder.MediaRecommendationsViewBinder
import com.android.systemui.media.controls.ui.util.MediaViewModelCallback
import com.android.systemui.media.controls.ui.util.MediaViewModelListUpdateCallback
import com.android.systemui.media.controls.ui.view.MediaCarouselScrollHandler
import com.android.systemui.media.controls.ui.view.MediaHostState
import com.android.systemui.media.controls.ui.view.MediaScrollView
import com.android.systemui.media.controls.ui.view.MediaViewHolder
-import com.android.systemui.media.controls.ui.view.RecommendationViewHolder
import com.android.systemui.media.controls.ui.viewmodel.MediaCarouselViewModel
import com.android.systemui.media.controls.ui.viewmodel.MediaCommonViewModel
import com.android.systemui.media.controls.util.MediaUiEventLogger
@@ -478,41 +476,10 @@
MediaPlayerData.isSwipedAway = false
}
- override fun onSmartspaceMediaDataLoaded(
- key: String,
- data: SmartspaceMediaData,
- shouldPrioritize: Boolean,
- ) {
- debugLogger.logRecommendationLoaded(key, data.isActive)
- // Log the case where the hidden media carousel with the existed inactive resume
- // media is shown by the Smartspace signal.
- if (data.isActive) {
- addSmartspaceMediaRecommendations(key, data, shouldPrioritize)
- } else {
- // Handle update to inactive as a removal
- onSmartspaceMediaDataRemoved(data.targetId, immediately = true)
- }
- MediaPlayerData.isSwipedAway = false
- }
-
override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
debugLogger.logMediaRemoved(key, userInitiated)
removePlayer(key, userInitiated = userInitiated)
}
-
- override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) {
- debugLogger.logRecommendationRemoved(key, immediately)
- if (immediately || isReorderingAllowed) {
- removePlayer(key)
- if (!immediately) {
- // Although it wasn't requested, we were able to process the removal
- // immediately since reordering is allowed. So, notify hosts to update
- updateHostVisibility()
- }
- } else {
- keysNeedRemoval.add(key)
- }
- }
}
)
}
@@ -655,22 +622,6 @@
mediaContent.addView(viewHolder.player, position)
controllerById[commonViewModel.instanceId.toString()] = viewController
}
- is MediaCommonViewModel.MediaRecommendations -> {
- val viewHolder =
- RecommendationViewHolder.create(LayoutInflater.from(context), mediaContent)
- viewController.attachRecommendations(viewHolder)
- viewController.recommendationViewHolder?.recommendations?.layoutParams = lp
- MediaRecommendationsViewBinder.bind(
- viewHolder,
- commonViewModel.recsViewModel,
- viewController,
- falsingManager,
- backgroundDispatcher,
- mainDispatcher,
- )
- mediaContent.addView(viewHolder.recommendations, position)
- controllerById[commonViewModel.key] = viewController
- }
}
viewController.setListening(mediaCarouselScrollHandler.visibleToUser && currentlyExpanded)
updateViewControllerToState(viewController, noAnimation = true)
@@ -695,21 +646,10 @@
}
private fun onRemoved(commonViewModel: MediaCommonViewModel) {
- val id =
- when (commonViewModel) {
- is MediaCommonViewModel.MediaControl -> commonViewModel.instanceId.toString()
- is MediaCommonViewModel.MediaRecommendations -> commonViewModel.key
- }
+ val id = (commonViewModel as MediaCommonViewModel.MediaControl).instanceId.toString()
controllerById.remove(id)?.let {
- when (commonViewModel) {
- is MediaCommonViewModel.MediaControl -> {
- mediaCarouselScrollHandler.onPrePlayerRemoved(it.mediaViewHolder!!.player)
- mediaContent.removeView(it.mediaViewHolder!!.player)
- }
- is MediaCommonViewModel.MediaRecommendations -> {
- mediaContent.removeView(it.recommendationViewHolder!!.recommendations)
- }
- }
+ mediaCarouselScrollHandler.onPrePlayerRemoved(it.mediaViewHolder!!.player)
+ mediaContent.removeView(it.mediaViewHolder!!.player)
it.onDestroy()
mediaCarouselScrollHandler.onPlayersChanged()
updatePageIndicator()
@@ -718,21 +658,10 @@
}
private fun onMoved(commonViewModel: MediaCommonViewModel, from: Int, to: Int) {
- val id =
- when (commonViewModel) {
- is MediaCommonViewModel.MediaControl -> commonViewModel.instanceId.toString()
- is MediaCommonViewModel.MediaRecommendations -> commonViewModel.key
- }
+ val id = (commonViewModel as MediaCommonViewModel.MediaControl).instanceId.toString()
controllerById[id]?.let {
mediaContent.removeViewAt(from)
- when (commonViewModel) {
- is MediaCommonViewModel.MediaControl -> {
- mediaContent.addView(it.mediaViewHolder!!.player, to)
- }
- is MediaCommonViewModel.MediaRecommendations -> {
- mediaContent.addView(it.recommendationViewHolder!!.recommendations, to)
- }
- }
+ mediaContent.addView(it.mediaViewHolder!!.player, to)
}
updatePageIndicator()
mediaCarouselScrollHandler.onPlayersChanged()
@@ -746,11 +675,9 @@
val viewIds =
viewModels
.map { mediaCommonViewModel ->
- when (mediaCommonViewModel) {
- is MediaCommonViewModel.MediaControl ->
- mediaCommonViewModel.instanceId.toString()
- is MediaCommonViewModel.MediaRecommendations -> mediaCommonViewModel.key
- }
+ (mediaCommonViewModel as MediaCommonViewModel.MediaControl)
+ .instanceId
+ .toString()
}
.toHashSet()
controllerById
@@ -758,7 +685,6 @@
.forEach {
mediaCarouselScrollHandler.onPrePlayerRemoved(it.value.mediaViewHolder?.player)
mediaContent.removeView(it.value.mediaViewHolder?.player)
- mediaContent.removeView(it.value.recommendationViewHolder?.recommendations)
it.value.onDestroy()
mediaCarouselScrollHandler.onPlayersChanged()
updatePageIndicator()
@@ -808,9 +734,6 @@
mediaContent.removeAllViews()
for (mediaPlayer in MediaPlayerData.players()) {
mediaPlayer.mediaViewHolder?.let { mediaContent.addView(it.player) }
- ?: mediaPlayer.recommendationViewHolder?.let {
- mediaContent.addView(it.recommendations)
- }
}
mediaCarouselScrollHandler.onPlayersChanged()
mediaControlChipInteractor.updateMediaControlChipModelLegacy(
@@ -980,67 +903,6 @@
return MediaViewHolder.create(LayoutInflater.from(context), mediaContent)
}
- private fun addSmartspaceMediaRecommendations(
- key: String,
- data: SmartspaceMediaData,
- shouldPrioritize: Boolean,
- ) =
- traceSection("MediaCarouselController#addSmartspaceMediaRecommendations") {
- if (DEBUG) Log.d(TAG, "Updating smartspace target in carousel")
- MediaPlayerData.getMediaPlayer(key)?.let {
- Log.w(TAG, "Skip adding smartspace target in carousel")
- return
- }
-
- val existingSmartspaceMediaKey = MediaPlayerData.smartspaceMediaKey()
- existingSmartspaceMediaKey?.let {
- val removedPlayer =
- removePlayer(existingSmartspaceMediaKey, dismissMediaData = false)
- removedPlayer?.run {
- debugLogger.logPotentialMemoryLeak(existingSmartspaceMediaKey)
- onDestroy()
- }
- }
-
- val newRecs = mediaControlPanelFactory.get()
- newRecs.attachRecommendation(
- RecommendationViewHolder.create(LayoutInflater.from(context), mediaContent)
- )
- newRecs.mediaViewController.sizeChangedListener = this::updateCarouselDimensions
- val lp =
- LinearLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT,
- )
- newRecs.recommendationViewHolder?.recommendations?.setLayoutParams(lp)
- newRecs.bindRecommendation(data)
- val curVisibleMediaKey =
- MediaPlayerData.visiblePlayerKeys()
- .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
- MediaPlayerData.addMediaRecommendation(
- key,
- data,
- newRecs,
- shouldPrioritize,
- systemClock,
- debugLogger,
- )
- updateViewControllerToState(newRecs.mediaViewController, noAnimation = true)
- reorderAllPlayers(curVisibleMediaKey)
- updatePageIndicator()
- mediaFrame.requiresRemeasuring = true
- // Check postcondition: mediaContent should have the same number of children as there
- // are elements in mediaPlayers.
- if (MediaPlayerData.players().size != mediaContent.childCount) {
- Log.e(
- TAG,
- "Size of players list and number of views in carousel are out of sync. " +
- "Players size is ${MediaPlayerData.players().size}. " +
- "View count is ${mediaContent.childCount}.",
- )
- }
- }
-
fun removePlayer(
key: String,
dismissMediaData: Boolean = true,
@@ -1057,7 +919,6 @@
return removed?.apply {
mediaCarouselScrollHandler.onPrePlayerRemoved(removed.mediaViewHolder?.player)
mediaContent.removeView(removed.mediaViewHolder?.player)
- mediaContent.removeView(removed.recommendationViewHolder?.recommendations)
removed.onDestroy()
mediaCarouselScrollHandler.onPlayersChanged()
mediaControlChipInteractor.updateMediaControlChipModelLegacy(
@@ -1095,31 +956,18 @@
val mediaDataList = MediaPlayerData.mediaData()
// Do not loop through the original list of media data because the re-addition of media data
// is being executed in background thread.
- mediaDataList.forEach { (key, data, isSsMediaRec) ->
- if (isSsMediaRec) {
- val smartspaceMediaData = MediaPlayerData.smartspaceMediaData
+ mediaDataList.forEach { (key, data, _) ->
+ val isSsReactivated = MediaPlayerData.isSsReactivated(key)
+ if (recreateMedia) {
removePlayer(key, dismissMediaData = false, dismissRecommendation = false)
- smartspaceMediaData?.let {
- addSmartspaceMediaRecommendations(
- it.targetId,
- it,
- MediaPlayerData.shouldPrioritizeSs,
- )
- }
- onUiExecutionEnd.run()
- } else {
- val isSsReactivated = MediaPlayerData.isSsReactivated(key)
- if (recreateMedia) {
- removePlayer(key, dismissMediaData = false, dismissRecommendation = false)
- }
- addOrUpdatePlayer(
- key = key,
- oldKey = null,
- data = data,
- isSsReactivated = isSsReactivated,
- onUiExecutionEnd = onUiExecutionEnd,
- )
}
+ addOrUpdatePlayer(
+ key = key,
+ oldKey = null,
+ data = data,
+ isSsReactivated = isSsReactivated,
+ onUiExecutionEnd = onUiExecutionEnd,
+ )
}
}
@@ -1129,12 +977,8 @@
if (recreateMedia) {
mediaContent.removeAllViews()
commonViewModels.forEachIndexed { index, viewModel ->
- when (viewModel) {
- is MediaCommonViewModel.MediaControl ->
- controllerById[viewModel.instanceId.toString()]?.onDestroy()
- is MediaCommonViewModel.MediaRecommendations ->
- controllerById[viewModel.key]?.onDestroy()
- }
+ val mediaControlViewModel = (viewModel as MediaCommonViewModel.MediaControl)
+ controllerById[mediaControlViewModel.instanceId.toString()]?.onDestroy()
onAdded(viewModel, index, configChanged = true)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt
index 5d62c02..3653891 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt
@@ -64,28 +64,6 @@
{ "removing player $str1, by user $bool1" },
)
- fun logRecommendationLoaded(key: String, isActive: Boolean) =
- buffer.log(
- TAG,
- LogLevel.DEBUG,
- {
- str1 = key
- bool1 = isActive
- },
- { "add recommendation $str1, active $bool1" },
- )
-
- fun logRecommendationRemoved(key: String, immediately: Boolean) =
- buffer.log(
- TAG,
- LogLevel.DEBUG,
- {
- str1 = key
- bool1 = immediately
- },
- { "removing recommendation $str1, immediate=$bool1" },
- )
-
fun logCarouselHidden() = buffer.log(TAG, LogLevel.DEBUG, {}, { "hiding carousel" })
fun logCarouselVisible() = buffer.log(TAG, LogLevel.DEBUG, {}, { "showing carousel" })
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
index a6bf5f4..006eb20 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
@@ -22,7 +22,6 @@
import static com.android.systemui.Flags.communalHub;
import static com.android.systemui.Flags.mediaLockscreenLaunchAnimation;
import static com.android.systemui.media.controls.domain.pipeline.MediaActionsKt.getNotificationActions;
-import static com.android.systemui.media.controls.shared.model.SmartspaceMediaDataKt.NUM_REQUIRED_RECOMMENDATIONS;
import static com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.MEDIA_PLAYER_SCRIM_END_ALPHA;
import static com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.MEDIA_PLAYER_SCRIM_END_ALPHA_LEGACY;
import static com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel.MEDIA_PLAYER_SCRIM_START_ALPHA;
@@ -35,24 +34,17 @@
import android.app.BroadcastOptions;
import android.app.PendingIntent;
import android.app.WallpaperColors;
-import android.app.smartspace.SmartspaceAction;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
-import android.content.res.ColorStateList;
-import android.content.res.Configuration;
-import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BlendMode;
import android.graphics.Color;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
-import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Animatable;
-import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
@@ -69,14 +61,12 @@
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
-import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
import android.widget.ImageButton;
import android.widget.ImageView;
-import android.widget.SeekBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
@@ -105,7 +95,6 @@
import com.android.systemui.media.controls.shared.model.MediaButton;
import com.android.systemui.media.controls.shared.model.MediaData;
import com.android.systemui.media.controls.shared.model.MediaDeviceData;
-import com.android.systemui.media.controls.shared.model.SmartspaceMediaData;
import com.android.systemui.media.controls.ui.animation.AnimationBindHandler;
import com.android.systemui.media.controls.ui.animation.ColorSchemeTransition;
import com.android.systemui.media.controls.ui.animation.MediaColorSchemesKt;
@@ -113,7 +102,6 @@
import com.android.systemui.media.controls.ui.binder.SeekBarObserver;
import com.android.systemui.media.controls.ui.view.GutsViewHolder;
import com.android.systemui.media.controls.ui.view.MediaViewHolder;
-import com.android.systemui.media.controls.ui.view.RecommendationViewHolder;
import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel;
import com.android.systemui.media.controls.util.MediaDataUtils;
import com.android.systemui.media.controls.util.MediaUiEventLogger;
@@ -143,14 +131,12 @@
import com.android.systemui.util.animation.TransitionLayout;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.settings.GlobalSettings;
-import com.android.systemui.util.time.SystemClock;
import dagger.Lazy;
import kotlin.Triple;
import kotlin.Unit;
-import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
@@ -165,17 +151,6 @@
protected static final String TAG = "MediaControlPanel";
private static final float DISABLED_ALPHA = 0.38f;
- private static final String EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME = "com.google"
- + ".android.apps.gsa.staticplugins.opa.smartspace.ExportedSmartspaceTrampolineActivity";
- private static final String EXTRAS_SMARTSPACE_INTENT =
- "com.google.android.apps.gsa.smartspace.extra.SMARTSPACE_INTENT";
- private static final String KEY_SMARTSPACE_ARTIST_NAME = "artist_name";
- private static final String KEY_SMARTSPACE_OPEN_IN_FOREGROUND = "KEY_OPEN_IN_FOREGROUND";
-
- private static final float REC_MEDIA_COVER_SCALE_FACTOR = 1.25f;
- private static final float MEDIA_REC_SCRIM_START_ALPHA = 0.15f;
- private static final float MEDIA_REC_SCRIM_END_ALPHA = 1.0f;
-
private static final Intent SETTINGS_INTENT = new Intent(ACTION_MEDIA_CONTROLS_SETTINGS);
// Buttons to show in small player when using semantic actions
@@ -215,17 +190,14 @@
private Context mContext;
private MediaViewHolder mMediaViewHolder;
- private RecommendationViewHolder mRecommendationViewHolder;
private String mKey;
private MediaData mMediaData;
- private SmartspaceMediaData mRecommendationData;
private MediaViewController mMediaViewController;
private MediaSession.Token mToken;
private MediaController mController;
private Lazy<MediaDataManager> mMediaDataManagerLazy;
// Uid for the media app.
protected int mUid = Process.INVALID_UID;
- private int mSmartspaceMediaItemsCount;
private MediaCarouselController mMediaCarouselController;
private final MediaOutputDialogManager mMediaOutputDialogManager;
private final FalsingManager mFalsingManager;
@@ -241,7 +213,6 @@
private final NotificationLockscreenUserManager mLockscreenUserManager;
// Used for logging.
- private SystemClock mSystemClock;
private MediaUiEventLogger mLogger;
private InstanceId mInstanceId;
private String mPackageName;
@@ -310,7 +281,6 @@
MediaOutputDialogManager mediaOutputDialogManager,
MediaCarouselController mediaCarouselController,
FalsingManager falsingManager,
- SystemClock systemClock,
MediaUiEventLogger logger,
KeyguardStateController keyguardStateController,
ActivityIntentHelper activityIntentHelper,
@@ -330,7 +300,6 @@
mMediaOutputDialogManager = mediaOutputDialogManager;
mMediaCarouselController = mediaCarouselController;
mFalsingManager = falsingManager;
- mSystemClock = systemClock;
mLogger = logger;
mKeyguardStateController = keyguardStateController;
mActivityIntentHelper = activityIntentHelper;
@@ -373,16 +342,6 @@
}
/**
- * Get the recommendation view holder used to display Smartspace media recs.
- *
- * @return the recommendation view holder
- */
- @Nullable
- public RecommendationViewHolder getRecommendationViewHolder() {
- return mRecommendationViewHolder;
- }
-
- /**
* Get the view controller used to display media controls
*
* @return the media view controller
@@ -465,7 +424,7 @@
mSeekBarViewModel.attachTouchHandlers(vh.getSeekBar());
mSeekBarViewModel.setScrubbingChangeListener(mScrubbingChangeListener);
mSeekBarViewModel.setEnabledChangeListener(mEnabledChangeListener);
- mMediaViewController.attach(player, MediaViewController.TYPE.PLAYER);
+ mMediaViewController.attach(player);
vh.getPlayer().setOnLongClickListener(v -> {
if (mFalsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) return true;
@@ -522,26 +481,6 @@
return result;
}
- /** Attaches the recommendations to the recommendation view holder. */
- public void attachRecommendation(RecommendationViewHolder vh) {
- mRecommendationViewHolder = vh;
- TransitionLayout recommendations = vh.getRecommendations();
-
- mMediaViewController.attach(recommendations, MediaViewController.TYPE.RECOMMENDATION);
- mMediaViewController.configurationChangeListener = this::updateRecommendationsVisibility;
-
- mRecommendationViewHolder.getRecommendations().setOnLongClickListener(v -> {
- if (mFalsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) return true;
- if (!mMediaViewController.isGutsVisible()) {
- openGuts();
- return true;
- } else {
- closeGuts();
- return true;
- }
- });
- }
-
/** Bind this player view based on the data given. */
public void bindPlayer(@NonNull MediaData data, String key) {
SceneContainerFlag.assertInLegacyMode();
@@ -868,24 +807,6 @@
mMediaViewHolder.getPlayer().setContentDescription(contentDescription);
}
- private void bindRecommendationContentDescription(SmartspaceMediaData data) {
- if (mRecommendationViewHolder == null) {
- return;
- }
-
- CharSequence contentDescription;
- if (mMediaViewController.isGutsVisible()) {
- contentDescription =
- mRecommendationViewHolder.getGutsViewHolder().getGutsText().getText();
- } else if (data != null) {
- contentDescription = mContext.getString(R.string.controls_media_smartspace_rec_header);
- } else {
- contentDescription = null;
- }
-
- mRecommendationViewHolder.getRecommendations().setContentDescription(contentDescription);
- }
-
private void bindArtworkAndColors(MediaData data, String key, boolean updateBackground) {
final int traceCookie = data.hashCode();
final String traceName = "MediaControlPanel#bindArtworkAndColors<" + key + ">";
@@ -993,62 +914,6 @@
});
}
- private void bindRecommendationArtwork(
- SmartspaceAction recommendation,
- String packageName,
- int itemIndex
- ) {
- final int traceCookie = recommendation.hashCode();
- final String traceName =
- "MediaControlPanel#bindRecommendationArtwork<" + packageName + ">";
- Trace.beginAsyncSection(traceName, traceCookie);
-
- // Capture width & height from views in foreground for artwork scaling in background
- int width = mContext.getResources().getDimensionPixelSize(R.dimen.qs_media_rec_album_width);
- int height = mContext.getResources().getDimensionPixelSize(
- R.dimen.qs_media_rec_album_height_expanded);
-
- mBackgroundExecutor.execute(() -> {
- // Album art
- ColorScheme mutableColorScheme = null;
- Drawable artwork;
- Icon artworkIcon = recommendation.getIcon();
- WallpaperColors wallpaperColors = getWallpaperColor(artworkIcon);
- if (wallpaperColors != null) {
- mutableColorScheme = new ColorScheme(wallpaperColors, true, Style.CONTENT);
- artwork = addGradientToRecommendationAlbum(artworkIcon, mutableColorScheme, width,
- height);
- } else {
- artwork = new ColorDrawable(Color.TRANSPARENT);
- }
-
- mMainExecutor.execute(() -> {
- // Bind the artwork drawable to media cover.
- ImageView mediaCover =
- mRecommendationViewHolder.getMediaCoverItems().get(itemIndex);
- // Rescale media cover
- Matrix coverMatrix = new Matrix(mediaCover.getImageMatrix());
- coverMatrix.postScale(REC_MEDIA_COVER_SCALE_FACTOR, REC_MEDIA_COVER_SCALE_FACTOR,
- 0.5f * width, 0.5f * height);
- mediaCover.setImageMatrix(coverMatrix);
- mediaCover.setImageDrawable(artwork);
-
- // Set up the app icon.
- ImageView appIconView = mRecommendationViewHolder.getMediaAppIcons().get(itemIndex);
- appIconView.clearColorFilter();
- try {
- Drawable icon = mContext.getPackageManager()
- .getApplicationIcon(packageName);
- appIconView.setImageDrawable(icon);
- } catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "Cannot find icon for package " + packageName, e);
- appIconView.setImageResource(R.drawable.ic_music_note);
- }
- Trace.endAsyncSection(traceName, traceCookie);
- });
- });
- }
-
// This method should be called from a background thread. WallpaperColors.fromBitmap takes a
// good amount of time. We do that work on the background executor to avoid stalling animations
// on the UI Thread.
@@ -1088,21 +953,6 @@
MEDIA_PLAYER_SCRIM_START_ALPHA_LEGACY, MEDIA_PLAYER_SCRIM_END_ALPHA_LEGACY);
}
- @VisibleForTesting
- protected LayerDrawable addGradientToRecommendationAlbum(Icon artworkIcon,
- ColorScheme mutableColorScheme, int width, int height) {
- // First try scaling rec card using bitmap drawable.
- // If returns null, set drawable bounds.
- Drawable albumArt = getScaledRecommendationCover(artworkIcon, width, height);
- if (albumArt == null) {
- albumArt = getScaledBackground(artworkIcon, width, height);
- }
- GradientDrawable gradient = (GradientDrawable) mContext.getDrawable(
- R.drawable.qs_media_rec_scrim).mutate();
- return setupGradientColorOnDrawable(albumArt, gradient, mutableColorScheme,
- MEDIA_REC_SCRIM_START_ALPHA, MEDIA_REC_SCRIM_END_ALPHA);
- }
-
private LayerDrawable setupGradientColorOnDrawable(Drawable albumArt, GradientDrawable gradient,
ColorScheme mutableColorScheme, float startAlpha, float endAlpha) {
int startColor;
@@ -1465,258 +1315,6 @@
return controller;
}
- /** Bind this recommendation view based on the given data. */
- public void bindRecommendation(@NonNull SmartspaceMediaData data) {
- if (mRecommendationViewHolder == null) {
- return;
- }
-
- if (!data.isValid()) {
- Log.e(TAG, "Received an invalid recommendation list; returning");
- return;
- }
-
- if (Trace.isEnabled()) {
- Trace.traceBegin(Trace.TRACE_TAG_APP,
- "MediaControlPanel#bindRecommendation<" + data.getPackageName() + ">");
- }
-
- mRecommendationData = data;
- mPackageName = data.getPackageName();
- mInstanceId = data.getInstanceId();
-
- // Set up recommendation card's header.
- ApplicationInfo applicationInfo;
- try {
- applicationInfo = mContext.getPackageManager()
- .getApplicationInfo(data.getPackageName(), 0 /* flags */);
- mUid = applicationInfo.uid;
- } catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "Fail to get media recommendation's app info", e);
- Trace.endSection();
- return;
- }
-
- CharSequence appName = data.getAppName(mContext);
- if (appName == null) {
- Log.w(TAG, "Fail to get media recommendation's app name");
- Trace.endSection();
- return;
- }
-
- PackageManager packageManager = mContext.getPackageManager();
- // Set up media source app's logo.
- Drawable icon = packageManager.getApplicationIcon(applicationInfo);
- fetchAndUpdateRecommendationColors(icon);
-
- // Set up media rec card's tap action if applicable.
- TransitionLayout recommendationCard = mRecommendationViewHolder.getRecommendations();
- setSmartspaceRecItemOnClickListener(recommendationCard, data.getCardAction(),
- /* interactedSubcardRank */ -1);
- bindRecommendationContentDescription(data);
-
- List<ImageView> mediaCoverItems = mRecommendationViewHolder.getMediaCoverItems();
- List<ViewGroup> mediaCoverContainers = mRecommendationViewHolder.getMediaCoverContainers();
- List<SmartspaceAction> recommendations = data.getValidRecommendations();
-
- boolean hasTitle = false;
- boolean hasSubtitle = false;
- int fittedRecsNum = getNumberOfFittedRecommendations();
- for (int itemIndex = 0; itemIndex < NUM_REQUIRED_RECOMMENDATIONS; itemIndex++) {
- SmartspaceAction recommendation = recommendations.get(itemIndex);
-
- // Set up media item cover.
- ImageView mediaCoverImageView = mediaCoverItems.get(itemIndex);
- bindRecommendationArtwork(recommendation, data.getPackageName(), itemIndex);
-
- // Set up the media item's click listener if applicable.
- ViewGroup mediaCoverContainer = mediaCoverContainers.get(itemIndex);
- setSmartspaceRecItemOnClickListener(mediaCoverContainer, recommendation, itemIndex);
- // Bubble up the long-click event to the card.
- mediaCoverContainer.setOnLongClickListener(v -> {
- if (mFalsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) return true;
- View parent = (View) v.getParent();
- if (parent != null) {
- parent.performLongClick();
- }
- return true;
- });
-
- // Set up the accessibility label for the media item.
- String artistName = recommendation.getExtras()
- .getString(KEY_SMARTSPACE_ARTIST_NAME, "");
- if (artistName.isEmpty()) {
- mediaCoverImageView.setContentDescription(
- mContext.getString(
- R.string.controls_media_smartspace_rec_item_no_artist_description,
- recommendation.getTitle(), appName));
- } else {
- mediaCoverImageView.setContentDescription(
- mContext.getString(
- R.string.controls_media_smartspace_rec_item_description,
- recommendation.getTitle(), artistName, appName));
- }
-
- // Set up title
- CharSequence title = recommendation.getTitle();
- hasTitle |= !TextUtils.isEmpty(title);
- TextView titleView = mRecommendationViewHolder.getMediaTitles().get(itemIndex);
- titleView.setText(title);
-
- // Set up subtitle
- // It would look awkward to show a subtitle if we don't have a title.
- boolean shouldShowSubtitleText = !TextUtils.isEmpty(title);
- CharSequence subtitle = shouldShowSubtitleText ? recommendation.getSubtitle() : "";
- hasSubtitle |= !TextUtils.isEmpty(subtitle);
- TextView subtitleView = mRecommendationViewHolder.getMediaSubtitles().get(itemIndex);
- subtitleView.setText(subtitle);
-
- // Set up progress bar
- SeekBar mediaProgressBar =
- mRecommendationViewHolder.getMediaProgressBars().get(itemIndex);
- TextView mediaSubtitle = mRecommendationViewHolder.getMediaSubtitles().get(itemIndex);
- // show progress bar if the recommended album is played.
- Double progress = MediaDataUtils.getDescriptionProgress(recommendation.getExtras());
- if (progress == null || progress <= 0.0) {
- mediaProgressBar.setVisibility(View.GONE);
- mediaSubtitle.setVisibility(View.VISIBLE);
- } else {
- mediaProgressBar.setProgress((int) (progress * 100));
- mediaProgressBar.setVisibility(View.VISIBLE);
- mediaSubtitle.setVisibility(View.GONE);
- }
- }
- mSmartspaceMediaItemsCount = NUM_REQUIRED_RECOMMENDATIONS;
-
- // If there's no subtitles and/or titles for any of the albums, hide those views.
- ConstraintSet expandedSet = mMediaViewController.getExpandedLayout();
- ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout();
- final boolean titlesVisible = hasTitle;
- final boolean subtitlesVisible = hasSubtitle;
- mRecommendationViewHolder.getMediaTitles().forEach((titleView) -> {
- setVisibleAndAlpha(expandedSet, titleView.getId(), titlesVisible);
- setVisibleAndAlpha(collapsedSet, titleView.getId(), titlesVisible);
- });
- mRecommendationViewHolder.getMediaSubtitles().forEach((subtitleView) -> {
- setVisibleAndAlpha(expandedSet, subtitleView.getId(), subtitlesVisible);
- setVisibleAndAlpha(collapsedSet, subtitleView.getId(), subtitlesVisible);
- });
-
- // Media covers visibility.
- setMediaCoversVisibility(fittedRecsNum);
-
- // Guts
- Runnable onDismissClickedRunnable = () -> {
- closeGuts();
- mMediaDataManagerLazy.get().dismissSmartspaceRecommendation(
- data.getTargetId(), MediaViewController.GUTS_ANIMATION_DURATION + 100L);
-
- Intent dismissIntent = data.getDismissIntent();
- if (dismissIntent == null) {
- Log.w(TAG, "Cannot create dismiss action click action: "
- + "extras missing dismiss_intent.");
- return;
- }
-
- if (dismissIntent.getComponent() != null
- && dismissIntent.getComponent().getClassName()
- .equals(EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME)) {
- // Dismiss the card Smartspace data through Smartspace trampoline activity.
- mContext.startActivity(dismissIntent);
- } else {
- mBroadcastSender.sendBroadcast(dismissIntent);
- }
- };
- bindGutsMenuCommon(
- /* isDismissible= */ true,
- appName.toString(),
- mRecommendationViewHolder.getGutsViewHolder(),
- onDismissClickedRunnable);
-
- mController = null;
- if (mMetadataAnimationHandler == null || !mMetadataAnimationHandler.isRunning()) {
- mMediaViewController.refreshState();
- }
- Trace.endSection();
- }
-
- private Unit updateRecommendationsVisibility() {
- int fittedRecsNum = getNumberOfFittedRecommendations();
- setMediaCoversVisibility(fittedRecsNum);
- return Unit.INSTANCE;
- }
-
- private void setMediaCoversVisibility(int fittedRecsNum) {
- ConstraintSet expandedSet = mMediaViewController.getExpandedLayout();
- ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout();
- List<ViewGroup> mediaCoverContainers = mRecommendationViewHolder.getMediaCoverContainers();
- // Hide media cover that cannot fit in the recommendation card.
- for (int itemIndex = 0; itemIndex < NUM_REQUIRED_RECOMMENDATIONS; itemIndex++) {
- setVisibleAndAlpha(expandedSet, mediaCoverContainers.get(itemIndex).getId(),
- itemIndex < fittedRecsNum);
- setVisibleAndAlpha(collapsedSet, mediaCoverContainers.get(itemIndex).getId(),
- itemIndex < fittedRecsNum);
- }
- }
-
- @VisibleForTesting
- protected int getNumberOfFittedRecommendations() {
- Resources res = mContext.getResources();
- Configuration config = res.getConfiguration();
- int defaultDpWidth = res.getInteger(R.integer.default_qs_media_rec_width_dp);
- int recCoverWidth = res.getDimensionPixelSize(R.dimen.qs_media_rec_album_width)
- + res.getDimensionPixelSize(R.dimen.qs_media_info_spacing) * 2;
-
- // On landscape, media controls should take half of the screen width.
- int displayAvailableDpWidth = config.screenWidthDp;
- if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
- displayAvailableDpWidth = displayAvailableDpWidth / 2;
- }
- int fittedNum;
- if (displayAvailableDpWidth > defaultDpWidth) {
- int recCoverDefaultWidth = res.getDimensionPixelSize(
- R.dimen.qs_media_rec_default_width);
- fittedNum = recCoverDefaultWidth / recCoverWidth;
- } else {
- int displayAvailableWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- displayAvailableDpWidth, res.getDisplayMetrics());
- fittedNum = displayAvailableWidth / recCoverWidth;
- }
- return Math.min(fittedNum, NUM_REQUIRED_RECOMMENDATIONS);
- }
-
- private void fetchAndUpdateRecommendationColors(Drawable appIcon) {
- mBackgroundExecutor.execute(() -> {
- ColorScheme colorScheme = new ColorScheme(
- WallpaperColors.fromDrawable(appIcon), /* darkTheme= */ true);
- mMainExecutor.execute(() -> setRecommendationColors(colorScheme));
- });
- }
-
- private void setRecommendationColors(ColorScheme colorScheme) {
- if (mRecommendationViewHolder == null) {
- return;
- }
-
- int backgroundColor = MediaColorSchemesKt.surfaceFromScheme(colorScheme);
- int textPrimaryColor = MediaColorSchemesKt.textPrimaryFromScheme(colorScheme);
- int textSecondaryColor = MediaColorSchemesKt.textSecondaryFromScheme(colorScheme);
-
- mRecommendationViewHolder.getCardTitle().setTextColor(textPrimaryColor);
-
- mRecommendationViewHolder.getRecommendations()
- .setBackgroundTintList(ColorStateList.valueOf(backgroundColor));
- mRecommendationViewHolder.getMediaTitles().forEach(
- (title) -> title.setTextColor(textPrimaryColor));
- mRecommendationViewHolder.getMediaSubtitles().forEach(
- (subtitle) -> subtitle.setTextColor(textSecondaryColor));
- mRecommendationViewHolder.getMediaProgressBars().forEach(
- (progressBar) -> progressBar.setProgressTintList(
- ColorStateList.valueOf(textPrimaryColor)));
-
- mRecommendationViewHolder.getGutsViewHolder().setColors(colorScheme);
- }
-
private void bindGutsMenuCommon(
boolean isDismissible,
String appName,
@@ -1772,14 +1370,10 @@
public void closeGuts(boolean immediate) {
if (mMediaViewHolder != null) {
mMediaViewHolder.marquee(false, mMediaViewController.GUTS_ANIMATION_DURATION);
- } else if (mRecommendationViewHolder != null) {
- mRecommendationViewHolder.marquee(false, mMediaViewController.GUTS_ANIMATION_DURATION);
}
mMediaViewController.closeGuts(immediate);
if (mMediaViewHolder != null) {
bindPlayerContentDescription(mMediaData);
- } else if (mRecommendationViewHolder != null) {
- bindRecommendationContentDescription(mRecommendationData);
}
}
@@ -1790,14 +1384,10 @@
private void openGuts() {
if (mMediaViewHolder != null) {
mMediaViewHolder.marquee(true, mMediaViewController.GUTS_ANIMATION_DURATION);
- } else if (mRecommendationViewHolder != null) {
- mRecommendationViewHolder.marquee(true, mMediaViewController.GUTS_ANIMATION_DURATION);
}
mMediaViewController.openGuts();
if (mMediaViewHolder != null) {
bindPlayerContentDescription(mMediaData);
- } else if (mRecommendationViewHolder != null) {
- bindRecommendationContentDescription(mRecommendationData);
}
mLogger.logLongPressOpen(mUid, mPackageName, mInstanceId);
}
@@ -1822,29 +1412,6 @@
}
/**
- * Scale artwork to fill the background of media covers in recommendation card.
- */
- @UiThread
- private Drawable getScaledRecommendationCover(Icon artworkIcon, int width, int height) {
- if (width == 0 || height == 0) {
- return null;
- }
- if (artworkIcon != null) {
- Bitmap bitmap;
- if (artworkIcon.getType() == Icon.TYPE_BITMAP
- || artworkIcon.getType() == Icon.TYPE_ADAPTIVE_BITMAP) {
- Bitmap artworkBitmap = artworkIcon.getBitmap();
- if (artworkBitmap != null) {
- bitmap = Bitmap.createScaledBitmap(artworkIcon.getBitmap(), width,
- height, false);
- return new BitmapDrawable(mContext.getResources(), bitmap);
- }
- }
- }
- return null;
- }
-
- /**
* Get the current media controller
*
* @return the controller
@@ -1896,64 +1463,5 @@
set.setVisibility(actionId, visible ? ConstraintSet.VISIBLE : notVisibleValue);
set.setAlpha(actionId, visible ? 1.0f : 0.0f);
}
-
- private void setSmartspaceRecItemOnClickListener(
- @NonNull View view,
- @NonNull SmartspaceAction action,
- int interactedSubcardRank) {
- if (view == null || action == null || action.getIntent() == null
- || action.getIntent().getExtras() == null) {
- Log.e(TAG, "No tap action can be set up");
- return;
- }
-
- view.setOnClickListener(v -> {
- if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return;
-
- if (interactedSubcardRank == -1) {
- mLogger.logRecommendationCardTap(mPackageName, mInstanceId);
- } else {
- mLogger.logRecommendationItemTap(mPackageName, mInstanceId, interactedSubcardRank);
- }
-
- if (shouldSmartspaceRecItemOpenInForeground(action)) {
- // Request to unlock the device if the activity needs to be opened in foreground.
- mActivityStarter.postStartActivityDismissingKeyguard(
- action.getIntent(),
- 0 /* delay */,
- buildLaunchAnimatorController(
- mRecommendationViewHolder.getRecommendations()));
- } else {
- // Otherwise, open the activity in background directly.
- view.getContext().startActivity(action.getIntent());
- }
-
- // Automatically scroll to the active player once the media is loaded.
- mMediaCarouselController.setShouldScrollToKey(true);
- });
- }
-
- /** Returns if the Smartspace action will open the activity in foreground. */
- private boolean shouldSmartspaceRecItemOpenInForeground(SmartspaceAction action) {
- if (action == null || action.getIntent() == null
- || action.getIntent().getExtras() == null) {
- return false;
- }
-
- String intentString = action.getIntent().getExtras().getString(EXTRAS_SMARTSPACE_INTENT);
- if (intentString == null) {
- return false;
- }
-
- try {
- Intent wrapperIntent = Intent.parseUri(intentString, Intent.URI_INTENT_SCHEME);
- return wrapperIntent.getBooleanExtra(KEY_SMARTSPACE_OPEN_IN_FOREGROUND, false);
- } catch (URISyntaxException e) {
- Log.wtf(TAG, "Failed to create intent from URI: " + intentString);
- e.printStackTrace();
- }
-
- return false;
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
index 69006c6..ec7d332 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
@@ -51,6 +51,7 @@
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.media.dream.MediaDreamComplication
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.qs.flags.QSComposeFragment
import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.ShadeDisplayAware
@@ -289,6 +290,9 @@
updateUserVisibility()
}
+ /** The expansion fraction of notification shade. */
+ var shadeExpandedFraction: Float = 0.0f
+
/**
* distance that the full shade transition takes in order for media to fully transition to the
* shade
@@ -868,7 +872,13 @@
if (isCurrentlyInGuidedTransformation()) {
return false
}
- if (skipQqsOnExpansion) {
+ if (
+ skipQqsOnExpansion ||
+ (QSComposeFragment.isEnabled &&
+ desiredLocation == LOCATION_QQS &&
+ previousLocation == LOCATION_QS &&
+ shadeExpandedFraction == 0.0f)
+ ) {
return false
}
if (isHubTransition) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
index b687dce..dba1900 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
@@ -38,7 +38,6 @@
import com.android.systemui.media.controls.ui.animation.ColorSchemeTransition
import com.android.systemui.media.controls.ui.animation.MetadataAnimationHandler
import com.android.systemui.media.controls.ui.binder.MediaControlViewBinder
-import com.android.systemui.media.controls.ui.binder.MediaRecommendationsViewBinder
import com.android.systemui.media.controls.ui.binder.SeekBarObserver
import com.android.systemui.media.controls.ui.controller.MediaCarouselController.Companion.calculateAlpha
import com.android.systemui.media.controls.ui.view.GutsViewHolder
@@ -48,7 +47,6 @@
import com.android.systemui.media.controls.ui.view.MediaViewHolder.Companion.labelLargeTF
import com.android.systemui.media.controls.ui.view.MediaViewHolder.Companion.labelMediumTF
import com.android.systemui.media.controls.ui.view.MediaViewHolder.Companion.titleMediumTF
-import com.android.systemui.media.controls.ui.view.RecommendationViewHolder
import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel
import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel
import com.android.systemui.res.R
@@ -90,15 +88,6 @@
private val globalSettings: GlobalSettings,
) {
- /**
- * Indicating that the media view controller is for a notification-based player, session-based
- * player, or recommendation
- */
- enum class TYPE {
- PLAYER,
- RECOMMENDATION,
- }
-
companion object {
@JvmField val GUTS_ANIMATION_DURATION = 234L
}
@@ -115,7 +104,6 @@
private var animationDuration: Long = 0
private var animateNextStateChange: Boolean = false
private val measurement = MeasurementOutput(0, 0)
- private var type: TYPE = TYPE.PLAYER
/** A map containing all viewStates for all locations of this mediaState */
private val viewStates: MutableMap<CacheKey, TransitionViewState?> = mutableMapOf()
@@ -203,7 +191,6 @@
private var isNextButtonAvailable = false
/** View holders for controller */
- var recommendationViewHolder: RecommendationViewHolder? = null
var mediaViewHolder: MediaViewHolder? = null
private lateinit var seekBarObserver: SeekBarObserver
@@ -417,13 +404,9 @@
/** Set the height of UMO background constraints. */
private fun setBackgroundHeights(height: Int) {
- val backgroundIds =
- if (type == TYPE.PLAYER) {
- MediaViewHolder.backgroundIds
- } else {
- setOf(RecommendationViewHolder.backgroundId)
- }
- backgroundIds.forEach { id -> expandedLayout.getConstraint(id).layout.mHeight = height }
+ MediaViewHolder.backgroundIds.forEach { id ->
+ expandedLayout.getConstraint(id).layout.mHeight = height
+ }
}
/**
@@ -431,11 +414,7 @@
* [TransitionViewState].
*/
private fun setGutsViewState(viewState: TransitionViewState) {
- val controlsIds =
- when (type) {
- TYPE.PLAYER -> MediaViewHolder.controlsIds
- TYPE.RECOMMENDATION -> RecommendationViewHolder.controlsIds
- }
+ val controlsIds = MediaViewHolder.controlsIds
val gutsIds = GutsViewHolder.ids
controlsIds.forEach { id ->
viewState.widgetStates.get(id)?.let { state ->
@@ -467,7 +446,6 @@
squishedViewState.widgetStates.get(id)?.let { state -> state.height = squishedHeight }
}
- // media player
calculateWidgetGroupAlphaForSquishiness(
MediaViewHolder.expandedBottomActionIds,
squishedViewState.measureHeight.toFloat(),
@@ -480,20 +458,6 @@
squishedViewState,
squishFraction,
)
- // recommendation card
- val titlesTop =
- calculateWidgetGroupAlphaForSquishiness(
- RecommendationViewHolder.mediaTitlesAndSubtitlesIds,
- squishedViewState.measureHeight.toFloat(),
- squishedViewState,
- squishFraction,
- )
- calculateWidgetGroupAlphaForSquishiness(
- RecommendationViewHolder.mediaContainersIds,
- titlesTop,
- squishedViewState,
- squishFraction,
- )
return squishedViewState
}
@@ -661,10 +625,10 @@
* Attach a view to this controller. This may perform measurements if it's not available yet and
* should therefore be done carefully.
*/
- fun attach(transitionLayout: TransitionLayout, type: TYPE) =
+ fun attach(transitionLayout: TransitionLayout) =
traceSection("MediaViewController#attach") {
- loadLayoutForType(type)
- logger.logMediaLocation("attach $type", currentStartLocation, currentEndLocation)
+ loadLayoutConstraints()
+ logger.logMediaLocation("attach", currentStartLocation, currentEndLocation)
this.transitionLayout = transitionLayout
layoutController.attach(transitionLayout)
if (currentEndLocation == MediaHierarchyManager.LOCATION_UNKNOWN) {
@@ -691,7 +655,7 @@
seekBarViewModel.setEnabledChangeListener(enabledChangeListener)
val mediaCard = mediaViewHolder.player
- attach(mediaViewHolder.player, TYPE.PLAYER)
+ attach(mediaViewHolder.player)
val turbulenceNoiseView = mediaViewHolder.turbulenceNoiseView
turbulenceNoiseController = TurbulenceNoiseController(turbulenceNoiseView)
@@ -813,15 +777,6 @@
}
}
- fun attachRecommendations(recommendationViewHolder: RecommendationViewHolder) {
- if (!SceneContainerFlag.isEnabled) return
- this.recommendationViewHolder = recommendationViewHolder
-
- attach(recommendationViewHolder.recommendations, TYPE.RECOMMENDATION)
- recsConfigurationChangeListener =
- MediaRecommendationsViewBinder::updateRecommendationsVisibility
- }
-
fun bindSeekBar(onSeek: () -> Unit, onBindSeekBar: (SeekBarViewModel) -> Unit) {
if (!SceneContainerFlag.isEnabled) return
seekBarViewModel.logSeek = onSeek
@@ -1026,20 +981,10 @@
return result
}
- private fun loadLayoutForType(type: TYPE) {
- this.type = type
-
- // These XML resources contain ConstraintSets that will apply to this player type's layout
- when (type) {
- TYPE.PLAYER -> {
- collapsedLayout.load(context, R.xml.media_session_collapsed)
- expandedLayout.load(context, R.xml.media_session_expanded)
- }
- TYPE.RECOMMENDATION -> {
- collapsedLayout.load(context, R.xml.media_recommendations_collapsed)
- expandedLayout.load(context, R.xml.media_recommendations_expanded)
- }
- }
+ private fun loadLayoutConstraints() {
+ // These XML resources contain ConstraintSets that will apply to this player's layout
+ collapsedLayout.load(context, R.xml.media_session_collapsed)
+ expandedLayout.load(context, R.xml.media_session_expanded)
readjustUIUpdateConstraints()
refreshState()
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaViewModelCallback.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaViewModelCallback.kt
index f28edd6..2fc44ad 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaViewModelCallback.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaViewModelCallback.kt
@@ -42,8 +42,7 @@
) {
oldItem.instanceId == newItem.instanceId
} else {
- oldItem is MediaCommonViewModel.MediaRecommendations &&
- newItem is MediaCommonViewModel.MediaRecommendations
+ false
}
}
@@ -56,11 +55,6 @@
) {
oldItem.immediatelyUpdateUi == newItem.immediatelyUpdateUi &&
oldItem.updateTime == newItem.updateTime
- } else if (
- oldItem is MediaCommonViewModel.MediaRecommendations &&
- newItem is MediaCommonViewModel.MediaRecommendations
- ) {
- oldItem.key == newItem.key && oldItem.loadingEnabled == newItem.loadingEnabled
} else {
false
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/RecommendationViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/RecommendationViewHolder.kt
deleted file mode 100644
index 2d028d0213..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/RecommendationViewHolder.kt
+++ /dev/null
@@ -1,122 +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.media.controls.ui.view
-
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.ImageView
-import android.widget.SeekBar
-import android.widget.TextView
-import com.android.internal.widget.CachingIconView
-import com.android.systemui.media.controls.ui.drawable.IlluminationDrawable
-import com.android.systemui.res.R
-import com.android.systemui.util.animation.TransitionLayout
-
-private const val TAG = "RecommendationViewHolder"
-
-/** ViewHolder for a Smartspace media recommendation. */
-class RecommendationViewHolder private constructor(itemView: View) {
-
- val recommendations = itemView as TransitionLayout
-
- // Recommendation screen
- val cardTitle: TextView = itemView.requireViewById(R.id.media_rec_title)
-
- val mediaCoverContainers =
- listOf<ViewGroup>(
- itemView.requireViewById(R.id.media_cover1_container),
- itemView.requireViewById(R.id.media_cover2_container),
- itemView.requireViewById(R.id.media_cover3_container)
- )
- val mediaAppIcons: List<CachingIconView> =
- mediaCoverContainers.map { it.requireViewById(R.id.media_rec_app_icon) }
- val mediaTitles: List<TextView> =
- mediaCoverContainers.map { it.requireViewById(R.id.media_title) }
- val mediaSubtitles: List<TextView> =
- mediaCoverContainers.map { it.requireViewById(R.id.media_subtitle) }
- val mediaProgressBars: List<SeekBar> =
- mediaCoverContainers.map {
- it.requireViewById<SeekBar?>(R.id.media_progress_bar).apply {
- // Media playback is in the direction of tape, not time, so it stays LTR
- layoutDirection = View.LAYOUT_DIRECTION_LTR
- }
- }
-
- val mediaCoverItems: List<ImageView> =
- mediaCoverContainers.map { it.requireViewById(R.id.media_cover) }
- val gutsViewHolder = GutsViewHolder(itemView)
-
- init {
- (recommendations.background as IlluminationDrawable).let { background ->
- mediaCoverContainers.forEach { background.registerLightSource(it) }
- background.registerLightSource(gutsViewHolder.cancel)
- background.registerLightSource(gutsViewHolder.dismiss)
- background.registerLightSource(gutsViewHolder.settings)
- }
- }
-
- fun marquee(start: Boolean, delay: Long) {
- gutsViewHolder.marquee(start, delay, TAG)
- }
-
- companion object {
- /**
- * Creates a RecommendationViewHolder.
- *
- * @param inflater LayoutInflater to use to inflate the layout.
- * @param parent Parent of inflated view.
- */
- @JvmStatic
- fun create(inflater: LayoutInflater, parent: ViewGroup): RecommendationViewHolder {
- val itemView =
- inflater.inflate(R.layout.media_recommendations, parent, false /* attachToRoot */)
- // Because this media view (a TransitionLayout) is used to measure and layout the views
- // in various states before being attached to its parent, we can't depend on the default
- // LAYOUT_DIRECTION_INHERIT to correctly resolve the ltr direction.
- itemView.layoutDirection = View.LAYOUT_DIRECTION_LOCALE
- return RecommendationViewHolder(itemView)
- }
-
- // Res Ids for the control components on the recommendation view.
- val controlsIds =
- setOf(
- R.id.media_rec_title,
- R.id.media_cover,
- R.id.media_cover1_container,
- R.id.media_cover2_container,
- R.id.media_cover3_container,
- R.id.media_title,
- R.id.media_subtitle,
- )
-
- val mediaTitlesAndSubtitlesIds =
- setOf(
- R.id.media_title,
- R.id.media_subtitle,
- )
-
- val mediaContainersIds =
- setOf(
- R.id.media_cover1_container,
- R.id.media_cover2_container,
- R.id.media_cover3_container
- )
-
- val backgroundId = R.id.sizing_view
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt
index e5f1766..dfaee44 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt
@@ -49,7 +49,6 @@
private val visualStabilityProvider: VisualStabilityProvider,
private val interactor: MediaCarouselInteractor,
private val controlInteractorFactory: MediaControlInteractorFactory,
- private val recommendationsViewModel: MediaRecommendationsViewModel,
private val logger: MediaUiEventLogger,
private val mediaLogger: MediaLogger,
) {
@@ -69,7 +68,7 @@
when (commonModel) {
is MediaCommonModel.MediaControl -> add(toViewModel(commonModel))
is MediaCommonModel.MediaRecommendations ->
- add(toViewModel(commonModel))
+ return@forEach // TODO(b/382680767): remove
}
}
}
@@ -95,8 +94,6 @@
private val mediaControlByInstanceId =
mutableMapOf<InstanceId, MediaCommonViewModel.MediaControl>()
- private var mediaRecs: MediaCommonViewModel.MediaRecommendations? = null
-
private var modelsPendingRemoval: MutableSet<MediaCommonModel> = mutableSetOf()
private var allowReorder = false
@@ -149,37 +146,6 @@
)
}
- private fun toViewModel(
- commonModel: MediaCommonModel.MediaRecommendations
- ): MediaCommonViewModel.MediaRecommendations {
- return mediaRecs?.copy(
- key = commonModel.recsLoadingModel.key,
- loadingEnabled = interactor.isRecommendationActive(),
- )
- ?: MediaCommonViewModel.MediaRecommendations(
- key = commonModel.recsLoadingModel.key,
- loadingEnabled = interactor.isRecommendationActive(),
- recsViewModel = recommendationsViewModel,
- onAdded = { commonViewModel ->
- mediaLogger.logMediaRecommendationCardAdded(
- commonModel.recsLoadingModel.key
- )
- onMediaRecommendationAddedOrUpdated(
- commonViewModel as MediaCommonViewModel.MediaRecommendations
- )
- },
- onRemoved = { immediatelyRemove ->
- onMediaRecommendationRemoved(commonModel, immediatelyRemove)
- },
- onUpdated = { commonViewModel ->
- onMediaRecommendationAddedOrUpdated(
- commonViewModel as MediaCommonViewModel.MediaRecommendations
- )
- },
- )
- .also { mediaRecs = it }
- }
-
private fun onMediaControlAddedOrUpdated(
commonViewModel: MediaCommonViewModel,
commonModel: MediaCommonModel.MediaControl,
@@ -197,32 +163,6 @@
}
}
- private fun onMediaRecommendationAddedOrUpdated(
- commonViewModel: MediaCommonViewModel.MediaRecommendations
- ) {
- if (!interactor.isRecommendationActive()) {
- commonViewModel.onRemoved(true)
- }
- }
-
- private fun onMediaRecommendationRemoved(
- commonModel: MediaCommonModel.MediaRecommendations,
- immediatelyRemove: Boolean,
- ) {
- mediaLogger.logMediaRecommendationCardRemoved(commonModel.recsLoadingModel.key)
- if (immediatelyRemove || isReorderingAllowed()) {
- interactor.dismissSmartspaceRecommendation(commonModel.recsLoadingModel.key, 0L)
- mediaRecs = null
- if (!immediatelyRemove) {
- // Although it wasn't requested, we were able to process the removal
- // immediately since reordering is allowed. So, notify hosts to update
- updateHostVisibility()
- }
- } else {
- modelsPendingRemoval.add(commonModel)
- }
- }
-
private fun isReorderingAllowed(): Boolean {
return visualStabilityProvider.isReorderingAllowed
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCommonViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCommonViewModel.kt
index 52cb173..d493d57 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCommonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCommonViewModel.kt
@@ -35,13 +35,4 @@
val isMediaFromRec: Boolean = false,
val updateTime: Long = 0,
) : MediaCommonViewModel()
-
- data class MediaRecommendations(
- val key: String,
- val loadingEnabled: Boolean,
- val recsViewModel: MediaRecommendationsViewModel,
- override val onAdded: (MediaCommonViewModel) -> Unit,
- override val onRemoved: (Boolean) -> Unit,
- override val onUpdated: (MediaCommonViewModel) -> Unit,
- ) : MediaCommonViewModel()
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecViewModel.kt
deleted file mode 100644
index 77add40..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecViewModel.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2024 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.media.controls.ui.viewmodel
-
-import android.graphics.drawable.Drawable
-import android.graphics.drawable.Icon
-import com.android.systemui.animation.Expandable
-
-/** Models UI state for media recommendation item */
-data class MediaRecViewModel(
- val contentDescription: CharSequence,
- val title: CharSequence = "",
- val subtitle: CharSequence = "",
- /** track progress [0 - 100] for the recommendation album. */
- val progress: Int = 0,
- val albumIcon: Icon? = null,
- val appIcon: Drawable,
- val onClicked: ((Expandable, Int) -> Unit),
-)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt
deleted file mode 100644
index 90313dd..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt
+++ /dev/null
@@ -1,238 +0,0 @@
-/*
- * Copyright (C) 2024 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.media.controls.ui.viewmodel
-
-import android.content.Context
-import android.content.Intent
-import android.content.pm.PackageManager
-import android.graphics.drawable.Drawable
-import android.os.Process
-import android.util.Log
-import com.android.internal.logging.InstanceId
-import com.android.systemui.animation.Expandable
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.media.controls.domain.pipeline.interactor.MediaRecommendationsInteractor
-import com.android.systemui.media.controls.shared.model.MediaRecModel
-import com.android.systemui.media.controls.shared.model.MediaRecommendationsModel
-import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
-import com.android.systemui.media.controls.ui.controller.MediaLocation
-import com.android.systemui.media.controls.ui.controller.MediaViewController.Companion.GUTS_ANIMATION_DURATION
-import com.android.systemui.media.controls.util.MediaDataUtils
-import com.android.systemui.media.controls.util.MediaUiEventLogger
-import com.android.systemui.res.R
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.map
-
-/** Models UI state and handles user input for media recommendations */
-@SysUISingleton
-class MediaRecommendationsViewModel
-@Inject
-constructor(
- @Application private val applicationContext: Context,
- @Background private val backgroundDispatcher: CoroutineDispatcher,
- private val interactor: MediaRecommendationsInteractor,
- private val logger: MediaUiEventLogger,
-) {
-
- val mediaRecsCard: Flow<MediaRecsCardViewModel?> =
- interactor.recommendations
- .map { recsCard -> toRecsViewModel(recsCard) }
- .distinctUntilChanged()
- .flowOn(backgroundDispatcher)
-
- @MediaLocation private var location = MediaHierarchyManager.LOCATION_UNKNOWN
-
- /**
- * Called whenever the recommendation has been expired or removed by the user. This method
- * removes the recommendation card entirely from the carousel.
- */
- private fun onMediaRecommendationsDismissed(
- key: String,
- uid: Int,
- packageName: String,
- dismissIntent: Intent?,
- instanceId: InstanceId?,
- ) {
- logger.logLongPressDismiss(uid, packageName, instanceId)
- interactor.removeMediaRecommendations(key, dismissIntent, GUTS_DISMISS_DELAY_MS_DURATION)
- }
-
- private fun onClicked(
- expandable: Expandable,
- intent: Intent?,
- packageName: String,
- instanceId: InstanceId?,
- index: Int,
- ) {
- if (intent == null || intent.extras == null) {
- Log.e(TAG, "No tap action can be set up")
- return
- }
-
- if (index == -1) {
- logger.logRecommendationCardTap(packageName, instanceId)
- } else {
- logger.logRecommendationItemTap(packageName, instanceId, index)
- }
-
- // set the package name of the player added by recommendation once the media is loaded.
- interactor.switchToMediaControl(packageName)
-
- interactor.startClickIntent(expandable, intent)
- }
-
- private suspend fun toRecsViewModel(model: MediaRecommendationsModel): MediaRecsCardViewModel? {
- if (!model.areRecommendationsValid) {
- Log.e(TAG, "Received an invalid recommendation list")
- return null
- }
- if (model.appName == null || model.uid == Process.INVALID_UID) {
- Log.w(TAG, "Fail to get media recommendation's app info")
- return null
- }
-
- val appIcon = getIconFromApp(model.packageName) ?: return null
-
- var areTitlesVisible = false
- var areSubtitlesVisible = false
- val mediaRecs =
- model.mediaRecs.map { mediaRecModel ->
- areTitlesVisible = areTitlesVisible || !mediaRecModel.title.isNullOrEmpty()
- areSubtitlesVisible = areSubtitlesVisible || !mediaRecModel.subtitle.isNullOrEmpty()
- val progress = MediaDataUtils.getDescriptionProgress(mediaRecModel.extras) ?: 0.0
- MediaRecViewModel(
- contentDescription =
- setUpMediaRecContentDescription(mediaRecModel, model.appName),
- title = mediaRecModel.title ?: "",
- subtitle = mediaRecModel.subtitle ?: "",
- progress = (progress * 100).toInt(),
- albumIcon = mediaRecModel.icon,
- appIcon = appIcon,
- onClicked = { expandable, index ->
- onClicked(
- expandable,
- mediaRecModel.intent,
- model.packageName,
- model.instanceId,
- index,
- )
- },
- )
- }
- // Subtitles should only be visible if titles are visible.
- areSubtitlesVisible = areTitlesVisible && areSubtitlesVisible
-
- return MediaRecsCardViewModel(
- contentDescription = { gutsVisible ->
- if (gutsVisible) {
- applicationContext.getString(
- R.string.controls_media_close_session,
- model.appName,
- )
- } else {
- applicationContext.getString(R.string.controls_media_smartspace_rec_header)
- }
- },
- onClicked = { expandable ->
- onClicked(
- expandable,
- model.dismissIntent,
- model.packageName,
- model.instanceId,
- index = -1,
- )
- },
- onLongClicked = {
- logger.logLongPressOpen(model.uid, model.packageName, model.instanceId)
- },
- mediaRecs = mediaRecs,
- areTitlesVisible = areTitlesVisible,
- areSubtitlesVisible = areSubtitlesVisible,
- gutsMenu = toGutsViewModel(model),
- onLocationChanged = { location = it },
- )
- }
-
- private fun toGutsViewModel(model: MediaRecommendationsModel): GutsViewModel {
- return GutsViewModel(
- gutsText =
- applicationContext.getString(R.string.controls_media_close_session, model.appName),
- onDismissClicked = {
- onMediaRecommendationsDismissed(
- model.key,
- model.uid,
- model.packageName,
- model.dismissIntent,
- model.instanceId,
- )
- },
- cancelTextBackground =
- applicationContext.getDrawable(R.drawable.qs_media_outline_button),
- onSettingsClicked = {
- logger.logLongPressSettings(model.uid, model.packageName, model.instanceId)
- interactor.startSettings()
- },
- )
- }
-
- private fun setUpMediaRecContentDescription(
- mediaRec: MediaRecModel,
- appName: CharSequence?,
- ): CharSequence {
- // Set up the accessibility label for the media item.
- val artistName = mediaRec.extras?.getString(KEY_SMARTSPACE_ARTIST_NAME, "")
- return if (artistName.isNullOrEmpty()) {
- applicationContext.getString(
- R.string.controls_media_smartspace_rec_item_no_artist_description,
- mediaRec.title,
- appName,
- )
- } else {
- applicationContext.getString(
- R.string.controls_media_smartspace_rec_item_description,
- mediaRec.title,
- artistName,
- appName,
- )
- }
- }
-
- private fun getIconFromApp(packageName: String): Drawable? {
- return try {
- applicationContext.packageManager.getApplicationIcon(packageName)
- } catch (e: PackageManager.NameNotFoundException) {
- Log.w(TAG, "Cannot find icon for package $packageName", e)
- null
- }
- }
-
- companion object {
- private const val TAG = "MediaRecommendationsViewModel"
- private const val KEY_SMARTSPACE_ARTIST_NAME = "artist_name"
- /**
- * Delay duration is based on [GUTS_ANIMATION_DURATION], it should have 100 ms increase in
- * order to let the animation end.
- */
- private const val GUTS_DISMISS_DELAY_MS_DURATION = 334L
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecsCardViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecsCardViewModel.kt
deleted file mode 100644
index f1f7dc2..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecsCardViewModel.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2024 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.media.controls.ui.viewmodel
-
-import com.android.systemui.animation.Expandable
-
-/** Models UI state for media recommendations card. */
-data class MediaRecsCardViewModel(
- val contentDescription: (Boolean) -> CharSequence,
- val onClicked: (Expandable) -> Unit,
- val onLongClicked: () -> Unit,
- val mediaRecs: List<MediaRecViewModel>,
- val areTitlesVisible: Boolean,
- val areSubtitlesVisible: Boolean,
- val gutsMenu: GutsViewModel,
- val onLocationChanged: (Int) -> Unit,
-)
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapterLegacy.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapterLegacy.java
index f8e57ef..300a357 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapterLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapterLegacy.java
@@ -186,8 +186,7 @@
mVolumeValueText.setTextColor(mController.getColorItemContent());
mIconAreaLayout.setBackground(null);
updateIconAreaClickListener(null);
- mSeekBar.setProgressTintList(
- ColorStateList.valueOf(mController.getColorSeekbarProgress()));
+ updateSeekBarProgressColor();
updateContainerContentA11yImportance(true /* isImportant */);
renderItem(mediaItem, position);
}
@@ -332,6 +331,16 @@
}
}
+ private void updateSeekBarProgressColor() {
+ mSeekBar.setProgressTintList(
+ ColorStateList.valueOf(mController.getColorSeekbarProgress()));
+ final Drawable contrastDotDrawable =
+ ((LayerDrawable) mSeekBar.getProgressDrawable()).findDrawableByLayerId(
+ R.id.contrast_dot);
+ contrastDotDrawable.setTintList(
+ ColorStateList.valueOf(mController.getColorItemContent()));
+ }
+
void updateSeekbarProgressBackground() {
final ClipDrawable clipDrawable =
(ClipDrawable) ((LayerDrawable) mSeekBar.getProgressDrawable())
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java
index 1f2f571..9d37580 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java
@@ -648,10 +648,6 @@
final MediaDevice connectedMediaDevice =
needToHandleMutingExpectedDevice ? null
: getCurrentConnectedMediaDevice();
-
- Set<String> selectedDevicesIds = getSelectedMediaDevice().stream()
- .map(MediaDevice::getId)
- .collect(Collectors.toSet());
if (oldMediaItems.isEmpty()) {
if (connectedMediaDevice == null) {
if (DEBUG) {
@@ -660,14 +656,12 @@
return categorizeMediaItemsLocked(
/* connectedMediaDevice */ null,
devices,
- selectedDevicesIds,
needToHandleMutingExpectedDevice);
} else {
// selected device exist
return categorizeMediaItemsLocked(
connectedMediaDevice,
devices,
- selectedDevicesIds,
/* needToHandleMutingExpectedDevice */ false);
}
}
@@ -701,20 +695,9 @@
devices.removeAll(targetMediaDevices);
targetMediaDevices.addAll(devices);
}
- List<MediaItem> finalMediaItems = new ArrayList<>();
- boolean shouldAddFirstSeenSelectedDevice =
- com.android.media.flags.Flags.enableOutputSwitcherDeviceGrouping();
- for (MediaDevice targetMediaDevice : targetMediaDevices) {
- if (shouldAddFirstSeenSelectedDevice
- && selectedDevicesIds.contains(targetMediaDevice.getId())) {
- finalMediaItems.add(MediaItem.createDeviceMediaItem(
- targetMediaDevice, /* isFirstDeviceInGroup */ true));
- shouldAddFirstSeenSelectedDevice = false;
- } else {
- finalMediaItems.add(MediaItem.createDeviceMediaItem(
- targetMediaDevice, /* isFirstDeviceInGroup */ false));
- }
- }
+ List<MediaItem> finalMediaItems = targetMediaDevices.stream()
+ .map(MediaItem::createDeviceMediaItem)
+ .collect(Collectors.toList());
dividerItems.forEach(finalMediaItems::add);
attachConnectNewDeviceItemIfNeeded(finalMediaItems);
return finalMediaItems;
@@ -741,9 +724,11 @@
@GuardedBy("mMediaDevicesLock")
private List<MediaItem> categorizeMediaItemsLocked(MediaDevice connectedMediaDevice,
List<MediaDevice> devices,
- Set<String> selectedDevicesIds,
boolean needToHandleMutingExpectedDevice) {
List<MediaItem> finalMediaItems = new ArrayList<>();
+ Set<String> selectedDevicesIds = getSelectedMediaDevice().stream()
+ .map(MediaDevice::getId)
+ .collect(Collectors.toSet());
if (connectedMediaDevice != null) {
selectedDevicesIds.add(connectedMediaDevice.getId());
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/shared/model/MediaSessionState.kt
similarity index 66%
copy from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt
copy to packages/SystemUI/src/com/android/systemui/media/remedia/shared/model/MediaSessionState.kt
index 231fb2d..40d55af 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/remedia/shared/model/MediaSessionState.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2024 The Android Open Source Project
+ * Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,11 +14,12 @@
* limitations under the License.
*/
-package com.android.systemui.dagger.qualifiers
+package com.android.systemui.media.remedia.shared.model
-import javax.inject.Qualifier
+sealed interface MediaSessionState {
+ data object Playing : MediaSessionState
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class NotifInflation
+ data object Paused : MediaSessionState
+
+ data object Buffering : MediaSessionState
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/Media.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/Media.kt
new file mode 100644
index 0000000..de01566
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/Media.kt
@@ -0,0 +1,586 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalMaterial3Api::class)
+
+package com.android.systemui.media.remedia.ui.compose
+
+import androidx.compose.animation.Crossfade
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.RepeatMode
+import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.animation.core.infiniteRepeatable
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.graphics.res.animatedVectorResource
+import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
+import androidx.compose.animation.graphics.vector.AnimatedImageVector
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.hoverable
+import androidx.compose.foundation.interaction.DragInteraction
+import androidx.compose.foundation.interaction.Interaction
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.interaction.PressInteraction
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButtonDefaults
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Slider
+import androidx.compose.material3.SliderColors
+import androidx.compose.material3.SliderDefaults.colors
+import androidx.compose.material3.SliderState
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawWithContent
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.center
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.StrokeCap
+import androidx.compose.ui.graphics.drawscope.Stroke
+import androidx.compose.ui.graphics.drawscope.clipRect
+import androidx.compose.ui.graphics.drawscope.translate
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import com.android.compose.PlatformButton
+import com.android.compose.PlatformIconButton
+import com.android.compose.PlatformOutlinedButton
+import com.android.compose.animation.scene.ContentScope
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.theme.LocalAndroidColorScheme
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.common.ui.compose.load
+import com.android.systemui.communal.ui.compose.extensions.detectLongPressGesture
+import com.android.systemui.media.remedia.shared.model.MediaSessionState
+import com.android.systemui.media.remedia.ui.viewmodel.MediaCardGutsViewModel
+import com.android.systemui.media.remedia.ui.viewmodel.MediaOutputSwitcherChipViewModel
+import com.android.systemui.media.remedia.ui.viewmodel.MediaPlayPauseActionViewModel
+import com.android.systemui.media.remedia.ui.viewmodel.MediaSecondaryActionViewModel
+import com.android.systemui.media.remedia.ui.viewmodel.MediaSeekBarViewModel
+import kotlin.math.max
+
+/** Renders the background of a card, loading the artwork and showing an overlay on top of it. */
+@Composable
+private fun CardBackground(imageLoader: suspend () -> ImageBitmap, modifier: Modifier = Modifier) {
+ var image: ImageBitmap? by remember { mutableStateOf(null) }
+ LaunchedEffect(imageLoader) {
+ image = null
+ image = imageLoader()
+ }
+
+ val gradientBaseColor = MaterialTheme.colorScheme.onSurface
+ Box(
+ modifier =
+ modifier.drawWithContent {
+ // Draw the content of the box (loaded art or placeholder).
+ drawContent()
+
+ if (image != null) {
+ // Then draw the overlay.
+ drawRect(
+ brush =
+ Brush.radialGradient(
+ 0f to gradientBaseColor.copy(alpha = 0.65f),
+ 1f to gradientBaseColor.copy(alpha = 0.75f),
+ center = size.center,
+ radius = max(size.width, size.height) / 2,
+ )
+ )
+ }
+ }
+ ) {
+ image?.let { loadedImage ->
+ // Loaded art.
+ Image(
+ bitmap = loadedImage,
+ contentDescription = null,
+ contentScale = ContentScale.Crop,
+ modifier = Modifier.matchParentSize(),
+ )
+ }
+ ?: run {
+ // Placeholder.
+ Box(Modifier.background(MaterialTheme.colorScheme.onSurface).matchParentSize())
+ }
+ }
+}
+
+/**
+ * Renders the navigation UI (seek bar and/or previous/next buttons).
+ *
+ * If [isSeekBarVisible] is `false`, the seek bar will not be included in the layout, even if it
+ * would otherwise be showing based on the view-model alone. This is meant for callers to decide
+ * whether they'd like to show the seek bar in addition to the prev/next buttons or just show the
+ * buttons.
+ */
+@Composable
+private fun ContentScope.Navigation(
+ viewModel: MediaSeekBarViewModel,
+ isSeekBarVisible: Boolean,
+ modifier: Modifier = Modifier,
+) {
+ when (viewModel) {
+ is MediaSeekBarViewModel.Showing -> {
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = modifier,
+ ) {
+ viewModel.previous?.let {
+ SecondaryAction(viewModel = it, element = Media.Elements.PrevButton)
+ }
+
+ val interactionSource = remember { MutableInteractionSource() }
+ val colors =
+ colors(
+ activeTrackColor = Color.White,
+ inactiveTrackColor = Color.White.copy(alpha = 0.3f),
+ thumbColor = Color.White,
+ )
+ if (isSeekBarVisible) {
+ // To allow the seek bar slider to fade in and out, it's tagged as an element.
+ Element(key = Media.Elements.SeekBarSlider, modifier = Modifier.weight(1f)) {
+ Slider(
+ interactionSource = interactionSource,
+ value = viewModel.progress,
+ onValueChange = { progress -> viewModel.onScrubChange(progress) },
+ onValueChangeFinished = { viewModel.onScrubFinished() },
+ colors = colors,
+ thumb = {
+ SeekBarThumb(interactionSource = interactionSource, colors = colors)
+ },
+ track = { sliderState ->
+ SeekBarTrack(
+ sliderState = sliderState,
+ isSquiggly = viewModel.isSquiggly,
+ colors = colors,
+ modifier = Modifier.fillMaxWidth(),
+ )
+ },
+ modifier = Modifier.fillMaxWidth(),
+ )
+ }
+ }
+
+ viewModel.next?.let {
+ SecondaryAction(viewModel = it, element = Media.Elements.NextButton)
+ }
+ }
+ }
+
+ is MediaSeekBarViewModel.Hidden -> Unit
+ }
+}
+
+/** Renders the thumb of the seek bar. */
+@Composable
+private fun SeekBarThumb(
+ interactionSource: MutableInteractionSource,
+ colors: SliderColors,
+ modifier: Modifier = Modifier,
+) {
+ val interactions = remember { mutableStateListOf<Interaction>() }
+ LaunchedEffect(interactionSource) {
+ interactionSource.interactions.collect { interaction ->
+ when (interaction) {
+ is PressInteraction.Press -> interactions.add(interaction)
+ is PressInteraction.Release -> interactions.remove(interaction.press)
+ is PressInteraction.Cancel -> interactions.remove(interaction.press)
+ is DragInteraction.Start -> interactions.add(interaction)
+ is DragInteraction.Stop -> interactions.remove(interaction.start)
+ is DragInteraction.Cancel -> interactions.remove(interaction.start)
+ }
+ }
+ }
+
+ Spacer(
+ modifier
+ .size(width = 4.dp, height = 16.dp)
+ .hoverable(interactionSource = interactionSource)
+ .background(color = colors.thumbColor, shape = RoundedCornerShape(16.dp))
+ )
+}
+
+/**
+ * Renders the track of the seek bar.
+ *
+ * If [isSquiggly] is `true`, the part to the left of the thumb will animate a squiggly line that
+ * oscillates up and down. The [waveLength] and [amplitude] control the geometry of the squiggle and
+ * the [waveSpeedDpPerSec] controls the speed by which it seems to "move" horizontally.
+ */
+@Composable
+private fun SeekBarTrack(
+ sliderState: SliderState,
+ isSquiggly: Boolean,
+ colors: SliderColors,
+ modifier: Modifier = Modifier,
+ waveLength: Dp = 20.dp,
+ amplitude: Dp = 3.dp,
+ waveSpeedDpPerSec: Dp = 8.dp,
+) {
+ // Animating the amplitude allows the squiggle to gradually grow to its full height or shrink
+ // back to a flat line as needed.
+ val animatedAmplitude by
+ animateDpAsState(
+ targetValue = if (isSquiggly) amplitude else 0.dp,
+ label = "SeekBarTrack.amplitude",
+ )
+
+ // This animates the horizontal movement of the squiggle.
+ val animatedWaveOffset = remember { Animatable(0f) }
+
+ LaunchedEffect(isSquiggly) {
+ if (isSquiggly) {
+ animatedWaveOffset.snapTo(0f)
+ animatedWaveOffset.animateTo(
+ targetValue = 1f,
+ animationSpec =
+ infiniteRepeatable(
+ animation =
+ tween(
+ durationMillis = (1000 * (waveLength / waveSpeedDpPerSec)).toInt(),
+ easing = LinearEasing,
+ ),
+ repeatMode = RepeatMode.Restart,
+ ),
+ )
+ }
+ }
+
+ // Render the track.
+ Canvas(modifier = modifier) {
+ val thumbPositionPx = size.width * sliderState.value
+
+ // The squiggly part before the thumb.
+ if (thumbPositionPx > 0) {
+ val amplitudePx = amplitude.toPx()
+ val animatedAmplitudePx = animatedAmplitude.toPx()
+ val waveLengthPx = waveLength.toPx()
+
+ val path =
+ Path().apply {
+ val halfWaveLengthPx = waveLengthPx / 2
+ val halfWaveCount = (thumbPositionPx / halfWaveLengthPx).toInt()
+
+ repeat(halfWaveCount + 3) { index ->
+ // Draw a half wave (either a hill or a valley shape starting and ending on
+ // the horizontal center).
+ relativeQuadraticTo(
+ // The control point for the bezier curve is on top of the peak of the
+ // hill or the very center bottom of the valley shape.
+ dx1 = halfWaveLengthPx / 2,
+ dy1 = if (index % 2 == 0) -animatedAmplitudePx else animatedAmplitudePx,
+ // Advance horizontally, half a wave length at a time.
+ dx2 = halfWaveLengthPx,
+ dy2 = 0f,
+ )
+ }
+ }
+
+ // Now that the squiggle is rendered a bit past the thumb, clip off the part that passed
+ // the thumb. It's easier to clip the extra squiggle than to figure out the bezier curve
+ // for part of a hill/valley.
+ clipRect(
+ left = 0f,
+ top = -amplitudePx,
+ right = thumbPositionPx,
+ bottom = amplitudePx * 2,
+ ) {
+ translate(left = -waveLengthPx * animatedWaveOffset.value, top = 0f) {
+ // Actually render the squiggle.
+ drawPath(
+ path = path,
+ color = colors.activeTrackColor,
+ style = Stroke(width = 2.dp.toPx(), cap = StrokeCap.Round),
+ )
+ }
+ }
+ }
+
+ // The flat line after the thumb.
+ drawLine(
+ color = colors.inactiveTrackColor,
+ start = Offset(thumbPositionPx, 0f),
+ end = Offset(size.width, 0f),
+ strokeWidth = 2.dp.toPx(),
+ cap = StrokeCap.Round,
+ )
+ }
+}
+
+/** Renders the internal "guts" of a card. */
+@Composable
+private fun CardGuts(viewModel: MediaCardGutsViewModel, modifier: Modifier = Modifier) {
+ Box(
+ modifier =
+ modifier.pointerInput(Unit) { detectLongPressGesture { viewModel.onLongClick() } }
+ ) {
+ // Settings button.
+ Icon(
+ icon = checkNotNull(viewModel.settingsButton.icon),
+ modifier =
+ Modifier.align(Alignment.TopEnd).padding(top = 16.dp, end = 16.dp).clickable {
+ viewModel.settingsButton.onClick()
+ },
+ )
+
+ // Content.
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(16.dp),
+ modifier =
+ Modifier.align(Alignment.BottomCenter)
+ .fillMaxWidth()
+ .padding(start = 16.dp, end = 32.dp, bottom = 40.dp),
+ ) {
+ Text(text = viewModel.text, color = Color.White)
+
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ PlatformButton(
+ onClick = viewModel.primaryAction.onClick,
+ colors = ButtonDefaults.buttonColors(containerColor = Color.White),
+ ) {
+ Text(
+ text = checkNotNull(viewModel.primaryAction.text),
+ color = LocalAndroidColorScheme.current.onPrimaryFixed,
+ )
+ }
+
+ viewModel.secondaryAction?.let { button ->
+ PlatformOutlinedButton(
+ onClick = button.onClick,
+ border = BorderStroke(width = 1.dp, color = Color.White),
+ ) {
+ Text(text = checkNotNull(button.text), color = Color.White)
+ }
+ }
+ }
+ }
+ }
+}
+
+/** Renders the metadata labels of a track. */
+@Composable
+private fun ContentScope.Metadata(
+ title: String,
+ subtitle: String,
+ color: Color,
+ modifier: Modifier = Modifier,
+) {
+ // This element can be animated when switching between scenes inside a media card.
+ Element(key = Media.Elements.Metadata, modifier = modifier) {
+ // When the title and/or subtitle change, crossfade between the old and the new.
+ Crossfade(targetState = title to subtitle, label = "Labels.crossfade") { (title, subtitle)
+ ->
+ Column {
+ Text(
+ text = title,
+ style = MaterialTheme.typography.bodyLarge,
+ color = color,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
+ )
+
+ Text(
+ text = subtitle,
+ style = MaterialTheme.typography.bodyMedium,
+ color = color,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
+ )
+ }
+ }
+ }
+}
+
+/**
+ * Renders a small chip showing the current output device and providing a way to switch to a
+ * different output device.
+ */
+@Composable
+private fun OutputSwitcherChip(
+ viewModel: MediaOutputSwitcherChipViewModel,
+ modifier: Modifier = Modifier,
+) {
+ PlatformButton(
+ onClick = viewModel.onClick,
+ colors =
+ ButtonDefaults.buttonColors(
+ containerColor = LocalAndroidColorScheme.current.primaryFixed
+ ),
+ contentPadding = PaddingValues(start = 8.dp, end = 12.dp, top = 4.dp, bottom = 4.dp),
+ modifier = modifier.height(24.dp),
+ ) {
+ Icon(
+ icon = viewModel.icon,
+ tint = LocalAndroidColorScheme.current.onPrimaryFixed,
+ modifier = Modifier.size(16.dp),
+ )
+ viewModel.text?.let {
+ Spacer(Modifier.size(4.dp))
+ Text(
+ text = viewModel.text,
+ style = MaterialTheme.typography.bodySmall,
+ color = LocalAndroidColorScheme.current.onPrimaryFixed,
+ )
+ }
+ }
+}
+
+/** Renders the primary action of media controls: the play/pause button. */
+@Composable
+private fun ContentScope.PlayPauseAction(
+ viewModel: MediaPlayPauseActionViewModel,
+ buttonWidth: Dp,
+ buttonColor: Color,
+ iconColor: Color,
+ buttonCornerRadius: (isPlaying: Boolean) -> Dp,
+ modifier: Modifier = Modifier,
+) {
+ val cornerRadius: Dp by
+ animateDpAsState(
+ targetValue = buttonCornerRadius(viewModel.state != MediaSessionState.Paused),
+ label = "PlayPauseAction.cornerRadius",
+ )
+ // This element can be animated when switching between scenes inside a media card.
+ Element(key = Media.Elements.PlayPauseButton, modifier = modifier) {
+ PlatformButton(
+ onClick = viewModel.onClick,
+ colors = ButtonDefaults.buttonColors(containerColor = buttonColor),
+ shape = RoundedCornerShape(cornerRadius),
+ modifier = Modifier.size(width = buttonWidth, height = 48.dp),
+ ) {
+ when (viewModel.state) {
+ is MediaSessionState.Playing,
+ is MediaSessionState.Paused -> {
+ // TODO(b/399860531): load this expensive-to-load animated vector drawable off
+ // the main thread.
+ val iconResource = checkNotNull(viewModel.icon)
+ Icon(
+ painter =
+ rememberAnimatedVectorPainter(
+ animatedImageVector =
+ AnimatedImageVector.animatedVectorResource(
+ id = iconResource.res
+ ),
+ atEnd = viewModel.state == MediaSessionState.Playing,
+ ),
+ contentDescription = iconResource.contentDescription?.load(),
+ tint = iconColor,
+ modifier = Modifier.size(24.dp),
+ )
+ }
+ is MediaSessionState.Buffering -> {
+ CircularProgressIndicator(color = iconColor, modifier = Modifier.size(24.dp))
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Renders an icon button for an action that's not the play/pause action.
+ *
+ * If [element] is provided, the secondary action element will be able to animate when switching
+ * between scenes inside a media card.
+ */
+@Composable
+private fun ContentScope.SecondaryAction(
+ viewModel: MediaSecondaryActionViewModel,
+ modifier: Modifier = Modifier,
+ element: ElementKey? = null,
+ iconColor: Color = Color.White,
+) {
+ if (element != null) {
+ Element(key = element, modifier = modifier) {
+ SecondaryActionContent(viewModel = viewModel, iconColor = iconColor)
+ }
+ } else {
+ SecondaryActionContent(viewModel = viewModel, iconColor = iconColor, modifier = modifier)
+ }
+}
+
+/** The content of a [SecondaryAction]. */
+@Composable
+private fun SecondaryActionContent(
+ viewModel: MediaSecondaryActionViewModel,
+ iconColor: Color,
+ modifier: Modifier = Modifier,
+) {
+ PlatformIconButton(
+ onClick = viewModel.onClick,
+ iconResource = (viewModel.icon as Icon.Resource).res,
+ contentDescription = viewModel.icon.contentDescription?.load(),
+ colors = IconButtonDefaults.iconButtonColors(contentColor = iconColor),
+ enabled = viewModel.isEnabled,
+ modifier = modifier.size(48.dp).padding(13.dp),
+ )
+}
+
+private object Media {
+
+ /**
+ * Element keys.
+ *
+ * Composables that are wrapped in [ContentScope.Element] with one of these as their `key`
+ * parameter will automatically be picked up by the STL transition animation framework and will
+ * be animated from their bounds in the original scene to their bounds in the destination scene.
+ *
+ * In addition, tagging such elements with a key allows developers to customize the transition
+ * animations even further.
+ */
+ object Elements {
+ val PlayPauseButton = ElementKey("play_pause")
+ val Metadata = ElementKey("metadata")
+ val PrevButton = ElementKey("prev")
+ val NextButton = ElementKey("next")
+ val SeekBarSlider = ElementKey("seek_bar_slider")
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaCardGutsViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaCardGutsViewModel.kt
new file mode 100644
index 0000000..61e3bdc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaCardGutsViewModel.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.media.remedia.ui.viewmodel
+
+data class MediaCardGutsViewModel(
+ val isVisible: Boolean,
+ val text: String,
+ val primaryAction: MediaGutsButtonViewModel,
+ val secondaryAction: MediaGutsButtonViewModel? = null,
+ val settingsButton: MediaGutsSettingsButtonViewModel,
+ val onLongClick: () -> Unit,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaGutsButtonViewModel.kt
similarity index 70%
copy from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt
copy to packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaGutsButtonViewModel.kt
index 231fb2d..6ce0a01 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaGutsButtonViewModel.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2024 The Android Open Source Project
+ * Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,11 +14,6 @@
* limitations under the License.
*/
-package com.android.systemui.dagger.qualifiers
+package com.android.systemui.media.remedia.ui.viewmodel
-import javax.inject.Qualifier
-
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class NotifInflation
+data class MediaGutsButtonViewModel(val text: String, val onClick: () -> Unit)
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaGutsSettingsButtonViewModel.kt
similarity index 69%
copy from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt
copy to packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaGutsSettingsButtonViewModel.kt
index 231fb2d..fabfe0e 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaGutsSettingsButtonViewModel.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2024 The Android Open Source Project
+ * Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,11 +14,8 @@
* limitations under the License.
*/
-package com.android.systemui.dagger.qualifiers
+package com.android.systemui.media.remedia.ui.viewmodel
-import javax.inject.Qualifier
+import com.android.systemui.common.shared.model.Icon
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class NotifInflation
+data class MediaGutsSettingsButtonViewModel(val icon: Icon, val onClick: () -> Unit)
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaOutputSwitcherChipViewModel.kt
similarity index 65%
copy from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt
copy to packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaOutputSwitcherChipViewModel.kt
index 231fb2d..f2724da 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaOutputSwitcherChipViewModel.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2024 The Android Open Source Project
+ * Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,11 +14,12 @@
* limitations under the License.
*/
-package com.android.systemui.dagger.qualifiers
+package com.android.systemui.media.remedia.ui.viewmodel
-import javax.inject.Qualifier
+import com.android.systemui.common.shared.model.Icon
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class NotifInflation
+data class MediaOutputSwitcherChipViewModel(
+ val icon: Icon,
+ val text: String? = null,
+ val onClick: () -> Unit,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaPlayPauseActionViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaPlayPauseActionViewModel.kt
new file mode 100644
index 0000000..4cb11bc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaPlayPauseActionViewModel.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.media.remedia.ui.viewmodel
+
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.media.remedia.shared.model.MediaSessionState
+
+/** Models UI state for the play/pause action button within media controls. */
+data class MediaPlayPauseActionViewModel(
+ val isVisible: Boolean,
+ val state: MediaSessionState,
+ val icon: Icon.Resource?,
+ val onClick: () -> Unit,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaSecondaryActionViewModel.kt
similarity index 60%
copy from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt
copy to packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaSecondaryActionViewModel.kt
index 231fb2d..a480680 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaSecondaryActionViewModel.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2024 The Android Open Source Project
+ * Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,11 +14,13 @@
* limitations under the License.
*/
-package com.android.systemui.dagger.qualifiers
+package com.android.systemui.media.remedia.ui.viewmodel
-import javax.inject.Qualifier
+import com.android.systemui.common.shared.model.Icon
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class NotifInflation
+/** Models UI state for a secondary action button within media controls. */
+data class MediaSecondaryActionViewModel(
+ val icon: Icon,
+ val isEnabled: Boolean,
+ val onClick: () -> Unit,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaSeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaSeekBarViewModel.kt
new file mode 100644
index 0000000..f1ced6b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaSeekBarViewModel.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.media.remedia.ui.viewmodel
+
+import androidx.annotation.FloatRange
+
+/** Models UI state for the seek bar. */
+sealed interface MediaSeekBarViewModel {
+
+ /** The seek bar should be showing. */
+ data class Showing(
+ /** The progress to show on the seek bar, between `0` and `1`. */
+ @FloatRange(from = 0.0, to = 1.0) val progress: Float,
+ /** The previous button; or `null` if it should be absent in the UI. */
+ val previous: MediaSecondaryActionViewModel?,
+ /** The next button; or `null` if it should be absent in the UI. */
+ val next: MediaSecondaryActionViewModel?,
+ /**
+ * Whether the portion of the seek bar track before the thumb should show the squiggle
+ * animation.
+ */
+ val isSquiggly: Boolean,
+ /**
+ * Whether the UI should show as "scrubbing" because the user is actively moving the thumb
+ * of the seek bar.
+ */
+ val isScrubbing: Boolean,
+ /**
+ * A callback to invoke while the user is "scrubbing" (e.g. actively moving the thumb of the
+ * seek bar). The position/progress of the actual track should not be changed during this
+ * time.
+ */
+ val onScrubChange: (progress: Float) -> Unit,
+ /**
+ * A callback to invoke once the user finishes "scrubbing" (e.g. stopped moving the thumb of
+ * the seek bar). The position/progress should be committed.
+ */
+ val onScrubFinished: () -> Unit,
+ ) : MediaSeekBarViewModel
+
+ /** The seek bar should be hidden. */
+ data object Hidden : MediaSeekBarViewModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt b/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
index ea515c9..4559a7a 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
+++ b/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
@@ -25,6 +25,8 @@
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
+import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_COMMUNAL_HUB_SHOWING
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
@@ -35,6 +37,7 @@
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags
import dagger.Lazy
import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
/**
* A plugin for [SysUiState] that provides overrides for certain state flags that must be pulled
@@ -46,17 +49,28 @@
constructor(
private val sceneInteractor: Lazy<SceneInteractor>,
private val occlusionInteractor: Lazy<SceneContainerOcclusionInteractor>,
+ private val shadeDisplaysRepository: Lazy<ShadeDisplaysRepository>,
) {
+ private val shadeDisplayId: StateFlow<Int> by lazy { shadeDisplaysRepository.get().displayId }
+
/**
* Returns an override value for the given [flag] or `null` if the scene framework isn't enabled
* or if the flag value doesn't need to be overridden.
*/
- fun flagValueOverride(@SystemUiStateFlags flag: Long): Boolean? {
+ fun flagValueOverride(@SystemUiStateFlags flag: Long, displayId: Int): Boolean? {
if (!SceneContainerFlag.isEnabled) {
return null
}
+ if (ShadeWindowGoesAround.isEnabled && shadeDisplayId.value != displayId) {
+ // The shade is in another display. All flags related to the shade container will map to
+ // false on other displays now.
+ //
+ // Note that this assumes there is only one SceneContainer and it is only on the shade
+ // window display. If there will be more, this will need to be revisited
+ return false
+ }
val transitionState = sceneInteractor.get().transitionState.value
val idleTransitionStateOrNull = transitionState as? ObservableTransitionState.Idle
val invisibleDueToOcclusion = occlusionInteractor.get().invisibleDueToOcclusion.value
diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUIStateChange.kt b/packages/SystemUI/src/com/android/systemui/model/SysUIStateChange.kt
new file mode 100644
index 0000000..aaed606
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/model/SysUIStateChange.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.model
+
+import com.android.systemui.shared.system.QuickStepContract.getSystemUiStateString
+
+/**
+ * Represents a set of state changes. A bit can either be set to `true` or `false`.
+ *
+ * This is used in [SysUIStateDisplaysInteractor] to selectively change bits.
+ */
+class StateChange {
+ private var flagsToSet: Long = 0
+ private var flagsToClear: Long = 0
+
+ /**
+ * Sets the [state] of the given [bit].
+ *
+ * @return `this` for chaining purposes
+ */
+ fun setFlag(bit: Long, state: Boolean): StateChange {
+ if (state) {
+ flagsToSet = flagsToSet or bit
+ flagsToClear = flagsToClear and bit.inv()
+ } else {
+ flagsToClear = flagsToClear or bit
+ flagsToSet = flagsToSet and bit.inv()
+ }
+ return this
+ }
+
+ fun hasChanges() = flagsToSet != 0L || flagsToClear != 0L
+
+ /**
+ * Applies all changed flags to [sysUiState].
+ *
+ * Note this doesn't call [SysUiState.commitUpdate].
+ */
+ fun applyTo(sysUiState: SysUiState) {
+ iterateBits(flagsToSet or flagsToClear) { bit ->
+ val isBitSetInNewState = flagsToSet and bit != 0L
+ sysUiState.setFlag(bit, isBitSetInNewState)
+ }
+ }
+
+ fun applyTo(sysUiState: Long): Long {
+ var newState = sysUiState
+ newState = newState or flagsToSet
+ newState = newState and flagsToClear.inv()
+ return newState
+ }
+
+ private inline fun iterateBits(flags: Long, action: (bit: Long) -> Unit) {
+ var remaining = flags
+ while (remaining != 0L) {
+ val lowestBit = remaining and -remaining
+ action(lowestBit)
+
+ remaining -= lowestBit
+ }
+ }
+
+ /**
+ * Clears all the flags changed in a [sysUiState].
+ *
+ * Note this doesn't call [SysUiState.commitUpdate].
+ */
+ fun clearFrom(sysUiState: SysUiState) {
+ iterateBits(flagsToSet or flagsToClear) { bit -> sysUiState.setFlag(bit, false) }
+ }
+
+ fun clear() {
+ flagsToSet = 0
+ flagsToClear = 0
+ }
+
+ override fun toString(): String {
+ return """StateChange(flagsToSet=${getSystemUiStateString(flagsToSet)}, flagsToClear=${
+ getSystemUiStateString(
+ flagsToClear
+ )
+ })"""
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUIStateDispatcher.kt b/packages/SystemUI/src/com/android/systemui/model/SysUIStateDispatcher.kt
new file mode 100644
index 0000000..f95ae25
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/model/SysUIStateDispatcher.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.model
+
+import android.view.Display
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
+import java.util.concurrent.CopyOnWriteArrayList
+import javax.inject.Inject
+
+/**
+ * Channels changes from several [SysUiState]s to a single callback.
+ *
+ * There are several [SysUiState]s (one per display). This class allows for listeners to listen to
+ * sysui state updates from any of those [SysUiState] instances.
+ *
+ * ┌────────────────────┐
+ * │ SysUIStateOverride │
+ * │ displayId=2 │
+ * └┬───────────────────┘
+ * │ ▲
+ * ┌───────────────┐ │ │ ┌────────────────────┐
+ * │ SysUIState │ │ │ │ SysUIStateOverride │
+ * │ displayId=0 │ │ │ │ displayId=1 │
+ * └────────────┬──┘ │ │ └┬───────────────────┘
+ * │ │ │ │ ▲
+ * ▼ ▼ │ ▼ │
+ * ┌─────────────┴─────┴─┐
+ * │SysUiStateDispatcher │
+ * └────────┬────────────┘
+ * │
+ * ▼
+ * ┌──────────────────┐
+ * │ listeners for │
+ * │ all displays │
+ * └──────────────────┘
+ */
+@SysUISingleton
+class SysUIStateDispatcher @Inject constructor() {
+
+ private val listeners = CopyOnWriteArrayList<SysUiState.SysUiStateCallback>()
+
+ /** Called from each [SysUiState] to propagate new state changes. */
+ fun dispatchSysUIStateChange(sysUiFlags: Long, displayId: Int) {
+ if (displayId != Display.DEFAULT_DISPLAY && !ShadeWindowGoesAround.isEnabled) return;
+ listeners.forEach { listener ->
+ listener.onSystemUiStateChanged(sysUiFlags = sysUiFlags, displayId = displayId)
+ }
+ }
+
+ /**
+ * Registers a listener to listen for system UI state changes.
+ *
+ * Listeners will have [SysUiState.SysUiStateCallback.onSystemUiStateChanged] called whenever a
+ * system UI state changes.
+ */
+ fun registerListener(listener: SysUiState.SysUiStateCallback) {
+ listeners += listener
+ }
+
+ fun unregisterListener(listener: SysUiState.SysUiStateCallback) {
+ listeners -= listener
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt b/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt
index ed190a1..6633559 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/model/SysUiState.kt
@@ -18,62 +18,118 @@
import android.util.Log
import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.settings.DisplayTracker
+import com.android.systemui.display.data.repository.PerDisplayInstanceProviderWithTeardown
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.model.SysUiState.SysUiStateCallback
import com.android.systemui.shared.system.QuickStepContract
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
import dalvik.annotation.optimization.NeverCompile
import java.io.PrintWriter
+import javax.inject.Inject
/** Contains sysUi state flags and notifies registered listeners whenever changes happen. */
-@SysUISingleton
-class SysUiState(
- private val displayTracker: DisplayTracker,
+interface SysUiState : Dumpable {
+ /**
+ * Add listener to be notified of changes made to SysUI state.
+ *
+ * The callback will also be called as part of this function.
+ */
+ fun addCallback(callback: SysUiStateCallback)
+
+ /** Removes a callback for state changes. */
+ fun removeCallback(callback: SysUiStateCallback)
+
+ /** Returns whether a flag is enabled in this state. */
+ fun isFlagEnabled(@SystemUiStateFlags flag: Long): Boolean {
+ return (flags and flag) != 0L
+ }
+
+ /** Returns the current sysui state flags. */
+ val flags: Long
+
+ /** Methods to this call can be chained together before calling [commitUpdate]. */
+ fun setFlag(@SystemUiStateFlags flag: Long, enabled: Boolean): SysUiState
+
+ /** Call to save all the flags updated from [setFlag]. */
+ @Deprecated("Each SysUIState instance is now display specific. Just use commitUpdate()")
+ fun commitUpdate(displayId: Int)
+
+ /** Call to save all the flags updated from [setFlag]. */
+ fun commitUpdate()
+
+ /** Callback to be notified whenever system UI state flags are changed. */
+ fun interface SysUiStateCallback {
+
+ /** To be called when any SysUiStateFlag gets updated for a specific [displayId]. */
+ fun onSystemUiStateChanged(@SystemUiStateFlags sysUiFlags: Long, displayId: Int)
+ }
+
+ /**
+ * Destroys an instance. It shouldn't be used anymore afterwards.
+ *
+ * This is mainly used to clean up instances associated with displays that are removed.
+ */
+ fun destroy()
+
+ /** The display ID this instances is associated with */
+ val displayId: Int
+
+ companion object {
+ const val DEBUG: Boolean = false
+ }
+}
+
+private const val TAG = "SysUIState"
+
+class SysUiStateImpl
+@AssistedInject
+constructor(
+ @Assisted override val displayId: Int,
private val sceneContainerPlugin: SceneContainerPlugin?,
-) : Dumpable {
+ private val dumpManager: DumpManager,
+ private val stateDispatcher: SysUIStateDispatcher,
+) : SysUiState {
+
+ private val debugName = "SysUiStateImpl-ForDisplay=$displayId"
+
+ init {
+ dumpManager.registerNormalDumpable(debugName, this)
+ }
+
/** Returns the current sysui state flags. */
@get:SystemUiStateFlags
@SystemUiStateFlags
- var flags: Long = 0
- private set
+ override val flags: Long
+ get() = _flags
- private val callbacks: MutableList<SysUiStateCallback> = ArrayList()
+ private var _flags: Long = 0
private var flagsToSet: Long = 0
private var flagsToClear: Long = 0
/**
* Add listener to be notified of changes made to SysUI state. The callback will also be called
* as part of this function.
+ *
+ * Note that the listener would receive updates for all displays.
*/
- fun addCallback(callback: SysUiStateCallback) {
- callbacks.add(callback)
- callback.onSystemUiStateChanged(flags)
+ override fun addCallback(callback: SysUiStateCallback) {
+ stateDispatcher.registerListener(callback)
+ callback.onSystemUiStateChanged(flags, displayId)
}
/** Callback will no longer receive events on state change */
- fun removeCallback(callback: SysUiStateCallback) {
- callbacks.remove(callback)
- }
-
- fun isFlagEnabled(@SystemUiStateFlags flag: Long): Boolean {
- return (flags and flag) != 0L
+ override fun removeCallback(callback: SysUiStateCallback) {
+ stateDispatcher.unregisterListener(callback)
}
/** Methods to this call can be chained together before calling [.commitUpdate]. */
- fun setFlag(@SystemUiStateFlags flag: Long, enabled: Boolean): SysUiState {
- var enabled = enabled
- val overrideOrNull = sceneContainerPlugin?.flagValueOverride(flag)
- if (overrideOrNull != null && enabled != overrideOrNull) {
- if (DEBUG) {
- Log.d(
- TAG,
- "setFlag for flag $flag and value $enabled overridden to $overrideOrNull by scene container plugin",
- )
- }
+ override fun setFlag(@SystemUiStateFlags flag: Long, enabled: Boolean): SysUiState {
+ val toSet = flagWithOptionalOverrides(flag, enabled, displayId, sceneContainerPlugin)
- enabled = overrideOrNull
- }
-
- if (enabled) {
+ if (toSet) {
flagsToSet = flagsToSet or flag
} else {
flagsToClear = flagsToClear or flag
@@ -81,20 +137,22 @@
return this
}
- /** Call to save all the flags updated from [.setFlag]. */
- fun commitUpdate(displayId: Int) {
- updateFlags(displayId)
+ @Deprecated(
+ "Each SysUIState instance is now display specific. Just use commitUpdate.",
+ ReplaceWith("commitUpdate()"),
+ )
+ override fun commitUpdate(displayId: Int) {
+ // TODO b/398011576 - handle updates for different displays.
+ commitUpdate()
+ }
+
+ override fun commitUpdate() {
+ updateFlags()
flagsToSet = 0
flagsToClear = 0
}
- private fun updateFlags(displayId: Int) {
- if (displayId != displayTracker.defaultDisplayId) {
- // Ignore non-default displays for now
- Log.w(TAG, "Ignoring flag update for display: $displayId", Throwable())
- return
- }
-
+ private fun updateFlags() {
var newState = flags
newState = newState or flagsToSet
newState = newState and flagsToClear.inv()
@@ -103,15 +161,12 @@
/** Notify all those who are registered that the state has changed. */
private fun notifyAndSetSystemUiStateChanged(newFlags: Long, oldFlags: Long) {
- if (DEBUG) {
+ if (SysUiState.DEBUG) {
Log.d(TAG, "SysUiState changed: old=$oldFlags new=$newFlags")
}
if (newFlags != oldFlags) {
- callbacks.forEach { callback: SysUiStateCallback ->
- callback.onSystemUiStateChanged(newFlags)
- }
-
- flags = newFlags
+ _flags = newFlags
+ stateDispatcher.dispatchSysUIStateChange(newFlags, displayId)
}
}
@@ -127,14 +182,53 @@
pw.println(QuickStepContract.isAssistantGestureDisabled(flags))
}
- /** Callback to be notified whenever system UI state flags are changed. */
- interface SysUiStateCallback {
- /** To be called when any SysUiStateFlag gets updated */
- fun onSystemUiStateChanged(@SystemUiStateFlags sysUiFlags: Long)
+ override fun destroy() {
+ dumpManager.unregisterDumpable(debugName)
+ }
+
+ @AssistedFactory
+ interface Factory {
+ /** Creates a new instance of [SysUiStateImpl] for a given [displayId]. */
+ fun create(displayId: Int): SysUiStateImpl
}
companion object {
private val TAG: String = SysUiState::class.java.simpleName
- const val DEBUG: Boolean = false
+ }
+}
+
+/** Returns the flag value taking into account [SceneContainerPlugin] potential overrides. */
+fun flagWithOptionalOverrides(
+ flag: Long,
+ enabled: Boolean,
+ displayId: Int,
+ sceneContainerPlugin: SceneContainerPlugin?,
+): Boolean {
+ var toSet = enabled
+ val overrideOrNull = sceneContainerPlugin?.flagValueOverride(flag = flag, displayId = displayId)
+ if (overrideOrNull != null && toSet != overrideOrNull) {
+ if (SysUiState.DEBUG) {
+ Log.d(
+ TAG,
+ "setFlag for flag $flag and value $toSet overridden to " +
+ "$overrideOrNull by scene container plugin",
+ )
+ }
+
+ toSet = overrideOrNull
+ }
+ return toSet
+}
+
+/** Creates and destroy instances of [SysUiState] */
+@SysUISingleton
+class SysUIStateInstanceProvider @Inject constructor(private val factory: SysUiStateImpl.Factory) :
+ PerDisplayInstanceProviderWithTeardown<SysUiState> {
+ override fun createInstance(displayId: Int): SysUiState {
+ return factory.create(displayId)
+ }
+
+ override fun destroyInstance(instance: SysUiState) {
+ instance.destroy()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/modes/shared/ModesUi.kt b/packages/SystemUI/src/com/android/systemui/modes/shared/ModesUi.kt
index e293e20..a344a5c 100644
--- a/packages/SystemUI/src/com/android/systemui/modes/shared/ModesUi.kt
+++ b/packages/SystemUI/src/com/android/systemui/modes/shared/ModesUi.kt
@@ -42,7 +42,7 @@
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, Flags.FLAG_MODES_UI)
+ inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, Flags.FLAG_MODES_UI)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
diff --git a/packages/SystemUI/src/com/android/systemui/modes/shared/ModesUiIcons.kt b/packages/SystemUI/src/com/android/systemui/modes/shared/ModesUiIcons.kt
index 032b0ac..44a3d77 100644
--- a/packages/SystemUI/src/com/android/systemui/modes/shared/ModesUiIcons.kt
+++ b/packages/SystemUI/src/com/android/systemui/modes/shared/ModesUiIcons.kt
@@ -42,8 +42,8 @@
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() =
- RefactorFlagUtils.assertInNewMode(isEnabled, Flags.FLAG_MODES_UI_ICONS)
+ inline fun unsafeAssertInNewMode() =
+ RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, Flags.FLAG_MODES_UI_ICONS)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index f44c2c0..7af5381 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -355,11 +355,12 @@
private final SysUiState.SysUiStateCallback mSysUiStateCallback =
new SysUiState.SysUiStateCallback() {
- @Override
- public void onSystemUiStateChanged(@SystemUiStateFlags long sysUiFlags) {
- mSysUiFlags = sysUiFlags;
- }
- };
+ @Override
+ public void onSystemUiStateChanged(@SystemUiStateFlags long sysUiFlags,
+ int displayId) {
+ mSysUiFlags = sysUiFlags;
+ }
+ };
private final Consumer<Boolean> mOnIsInPipStateChangedListener =
(isInPip) -> mIsInPip = isInPip;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index fa987dd..9fc1b7a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -255,7 +255,7 @@
* @return size in pixels of QQS
*/
public int getQqsHeight() {
- SceneContainerFlag.assertInNewMode();
+ SceneContainerFlag.unsafeAssertInNewMode();
return mHeader.getMeasuredHeight();
}
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 44c8dc3..5e7e0c9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -42,7 +42,6 @@
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredHeightIn
@@ -128,6 +127,7 @@
import com.android.systemui.qs.panels.ui.compose.TileGrid
import com.android.systemui.qs.shared.ui.ElementKeys
import com.android.systemui.qs.ui.composable.QuickSettingsShade
+import com.android.systemui.qs.ui.composable.QuickSettingsShade.systemGestureExclusionInShade
import com.android.systemui.qs.ui.composable.QuickSettingsTheme
import com.android.systemui.res.R
import com.android.systemui.util.LifecycleFragment
@@ -716,10 +716,7 @@
BrightnessSliderContainer(
viewModel = containerViewModel.brightnessSliderViewModel,
modifier =
- Modifier.fillMaxWidth()
- .height(
- QuickSettingsShade.Dimensions.BrightnessSliderHeight
- ),
+ Modifier.systemGestureExclusionInShade().fillMaxWidth(),
)
}
val TileGrid =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/flags/QsDetailedView.kt b/packages/SystemUI/src/com/android/systemui/qs/flags/QsDetailedView.kt
index 3afaef5..2eba36a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/flags/QsDetailedView.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/flags/QsDetailedView.kt
@@ -81,7 +81,7 @@
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+ inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME)
/** Returns a developer-readable string that describes the current requirement list. */
@JvmStatic
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsButtonViewModel.kt
index 2670787..c45fa97 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsButtonViewModel.kt
@@ -18,6 +18,7 @@
import android.annotation.AttrRes
import android.annotation.ColorInt
+import androidx.compose.runtime.Stable
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
@@ -25,6 +26,7 @@
* A ViewModel for a simple footer actions button. This is used for the user switcher, settings and
* power buttons.
*/
+@Stable
data class FooterActionsButtonViewModel(
val id: Int,
val icon: Icon,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
index 9546e35..aea280c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
@@ -75,6 +75,7 @@
/** The model for the power button. */
val power: Flow<FooterActionsButtonViewModel?>,
+ val initialPower: () -> FooterActionsButtonViewModel?,
/**
* Observe the device monitoring dialog requests and show the dialog accordingly. This function
@@ -181,7 +182,7 @@
fun createFooterActionsViewModel(
@ShadeDisplayAware appContext: Context,
footerActionsInteractor: FooterActionsInteractor,
- shadeMode: Flow<ShadeMode>,
+ shadeMode: StateFlow<ShadeMode>,
falsingManager: FalsingManager,
globalActionsDialogLite: GlobalActionsDialogLite,
activityStarter: ActivityStarter,
@@ -291,6 +292,12 @@
settings = settings,
power = power,
observeDeviceMonitoringDialogRequests = ::observeDeviceMonitoringDialogRequests,
+ initialPower =
+ if (showPowerButton) {
+ { powerButtonViewModel(qsThemedContext, ::onPowerButtonClicked, shadeMode.value) }
+ } else {
+ { null }
+ },
)
}
@@ -411,20 +418,28 @@
shadeMode: Flow<ShadeMode>,
): Flow<FooterActionsButtonViewModel?> {
return shadeMode.map { mode ->
- val isDualShade = mode is ShadeMode.Dual
- FooterActionsButtonViewModel(
- id = R.id.pm_lite,
- Icon.Resource(
- android.R.drawable.ic_lock_power_off,
- ContentDescription.Resource(R.string.accessibility_quick_settings_power_menu),
- ),
- iconTint =
- Utils.getColorAttrDefaultColor(
- qsThemedContext,
- if (isDualShade) R.attr.onShadeInactiveVariant else R.attr.onShadeActive,
- ),
- backgroundColor = if (isDualShade) R.attr.shadeInactive else R.attr.shadeActive,
- onPowerButtonClicked,
- )
+ powerButtonViewModel(qsThemedContext, onPowerButtonClicked, mode)
}
}
+
+fun powerButtonViewModel(
+ qsThemedContext: Context,
+ onPowerButtonClicked: (Expandable) -> Unit,
+ shadeMode: ShadeMode,
+): FooterActionsButtonViewModel {
+ val isDualShade = shadeMode is ShadeMode.Dual
+ return FooterActionsButtonViewModel(
+ id = R.id.pm_lite,
+ Icon.Resource(
+ android.R.drawable.ic_lock_power_off,
+ ContentDescription.Resource(R.string.accessibility_quick_settings_power_menu),
+ ),
+ iconTint =
+ Utils.getColorAttrDefaultColor(
+ qsThemedContext,
+ if (isDualShade) R.attr.onShadeInactiveVariant else R.attr.onShadeActive,
+ ),
+ backgroundColor = if (isDualShade) R.attr.shadeInactive else R.attr.shadeActive,
+ onPowerButtonClicked,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/BounceableInfo.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/BounceableInfo.kt
index eb6f979..b80e6b4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/BounceableInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/BounceableInfo.kt
@@ -35,11 +35,16 @@
index: Int,
column: Int,
columns: Int,
+ isFirstInRow: Boolean,
+ isLastInRow: Boolean,
): BounceableInfo {
- // Only look for neighbor bounceables if they are on the same row
+ // A tile may be the last in the row without being on the last column
val onLastColumn = sizedTile.onLastColumn(column, columns)
- val previousTile = getOrNull(index - 1)?.takeIf { column != 0 }
- val nextTile = getOrNull(index + 1)?.takeIf { !onLastColumn }
+
+ // Only look for neighbor bounceables if they are on the same row
+ val previousTile = getOrNull(index - 1)?.takeIf { !isFirstInRow }
+ val nextTile = getOrNull(index + 1)?.takeIf { !isLastInRow }
+
return BounceableInfo(this[index], previousTile, nextTile, !onLastColumn)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
index 495870f..cdc03bb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
@@ -57,7 +57,6 @@
onDispose { tiles.forEach { it.stopListening(token) } }
}
val columns = viewModel.columns
- var cellIndex = 0
Box(modifier = modifier) {
GridAnchor()
VerticalSpannedGrid(
@@ -67,17 +66,23 @@
spans = spans,
modifier = Modifier.sysuiResTag("qqs_tile_layout"),
keys = { sizedTiles[it].tile.spec },
- ) { spanIndex ->
+ ) { spanIndex, column, isFirstInColumn, isLastInColumn ->
val it = sizedTiles[spanIndex]
- val column = cellIndex % columns
- cellIndex += it.width
Element(it.tile.spec.toElementKey(spanIndex), Modifier) {
Tile(
tile = it.tile,
iconOnly = it.isIcon,
squishiness = { squishiness },
coroutineScope = scope,
- bounceableInfo = bounceables.bounceableInfo(it, spanIndex, column, columns),
+ bounceableInfo =
+ bounceables.bounceableInfo(
+ it,
+ index = spanIndex,
+ column = column,
+ columns = columns,
+ isFirstInRow = isFirstInColumn,
+ isLastInRow = isLastInColumn,
+ ),
tileHapticsViewModelFactoryProvider =
viewModel.tileHapticsViewModelFactoryProvider,
// There should be no QuickQuickSettings when the details view is enabled.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
index 85658bb..50012ab 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
@@ -26,12 +26,14 @@
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
import androidx.compose.animation.graphics.vector.AnimatedImageVector
import androidx.compose.foundation.Image
+import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
@@ -39,6 +41,7 @@
import androidx.compose.foundation.text.BasicText
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
@@ -49,8 +52,16 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawBehind
+import androidx.compose.ui.draw.drawWithContent
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.BlendMode
+import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.ColorProducer
+import androidx.compose.ui.graphics.CompositingStrategy
+import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
@@ -60,7 +71,7 @@
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.stateDescription
import androidx.compose.ui.semantics.toggleableState
-import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.android.compose.modifiers.size
@@ -73,6 +84,9 @@
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.SideIconHeight
import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.SideIconWidth
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.TILE_INITIAL_DELAY_MILLIS
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.TILE_MARQUEE_ITERATIONS
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.TileLabelBlurWidth
import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.longPressLabel
import com.android.systemui.qs.panels.ui.viewmodel.AccessibilityUiState
import com.android.systemui.qs.ui.compose.borderOnFocus
@@ -104,30 +118,31 @@
val focusBorderColor = MaterialTheme.colorScheme.secondary
Box(
modifier =
- Modifier.size(CommonTileDefaults.ToggleTargetSize).thenIf(toggleClick != null) {
- Modifier.borderOnFocus(color = focusBorderColor, iconShape.topEnd)
- .clip(iconShape)
- .verticalSquish(squishiness)
- .drawBehind { drawRect(animatedBackgroundColor) }
- .combinedClickable(
- onClick = toggleClick!!,
- onLongClick = onLongClick,
- onLongClickLabel = longPressLabel,
- hapticFeedbackEnabled = !Flags.msdlFeedback(),
- )
- .thenIf(accessibilityUiState != null) {
- Modifier.semantics {
- accessibilityUiState as AccessibilityUiState
- contentDescription = accessibilityUiState.contentDescription
- stateDescription = accessibilityUiState.stateDescription
- accessibilityUiState.toggleableState?.let {
- toggleableState = it
+ Modifier.size(CommonTileDefaults.ToggleTargetSize)
+ .clip(iconShape)
+ .verticalSquish(squishiness)
+ .drawBehind { drawRect(animatedBackgroundColor) }
+ .thenIf(toggleClick != null) {
+ Modifier.borderOnFocus(color = focusBorderColor, iconShape.topEnd)
+ .combinedClickable(
+ onClick = toggleClick!!,
+ onLongClick = onLongClick,
+ onLongClickLabel = longPressLabel,
+ hapticFeedbackEnabled = !Flags.msdlFeedback(),
+ )
+ .thenIf(accessibilityUiState != null) {
+ Modifier.semantics {
+ accessibilityUiState as AccessibilityUiState
+ contentDescription = accessibilityUiState.contentDescription
+ stateDescription = accessibilityUiState.stateDescription
+ accessibilityUiState.toggleableState?.let {
+ toggleableState = it
+ }
+ role = Role.Switch
}
- role = Role.Switch
- }
- .sysuiResTag(TEST_TAG_TOGGLE)
- }
- }
+ .sysuiResTag(TEST_TAG_TOGGLE)
+ }
+ }
) {
SmallTileContent(
iconProvider = iconProvider,
@@ -167,18 +182,15 @@
val animatedSecondaryLabelColor by
animateColorAsState(colors.secondaryLabel, label = "QSTileSecondaryLabelColor")
Column(verticalArrangement = Arrangement.Center, modifier = modifier.fillMaxHeight()) {
- BasicText(
- label,
+ TileLabel(
+ text = label,
style = MaterialTheme.typography.labelLarge,
color = { animatedLabelColor },
- maxLines = 1,
- overflow = TextOverflow.Ellipsis,
)
if (!TextUtils.isEmpty(secondaryLabel)) {
- BasicText(
+ TileLabel(
secondaryLabel ?: "",
color = { animatedSecondaryLabelColor },
- maxLines = 1,
style = MaterialTheme.typography.bodyMedium,
modifier =
Modifier.thenIf(
@@ -194,9 +206,9 @@
@Composable
fun SmallTileContent(
- modifier: Modifier = Modifier,
iconProvider: Context.() -> Icon,
color: Color,
+ modifier: Modifier = Modifier,
size: () -> Dp = { CommonTileDefaults.IconSize },
animateToEnd: Boolean = false,
) {
@@ -212,31 +224,39 @@
}
}
if (loadedDrawable is Animatable) {
+ // Skip initial animation, icons should animate only as the state change
+ // and not when first composed
+ var shouldSkipInitialAnimation by remember { mutableStateOf(true) }
+ LaunchedEffect(Unit) { shouldSkipInitialAnimation = animateToEnd }
+
val painter =
when (icon) {
is Icon.Resource -> {
val image = AnimatedImageVector.animatedVectorResource(id = icon.res)
key(icon) {
- if (animateToEnd) {
- rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = true)
- } else {
- var atEnd by remember(icon.res) { mutableStateOf(false) }
- LaunchedEffect(key1 = icon.res) { atEnd = true }
- rememberAnimatedVectorPainter(
- animatedImageVector = image,
- atEnd = atEnd,
- )
- }
+ var atEnd by remember(icon) { mutableStateOf(shouldSkipInitialAnimation) }
+ LaunchedEffect(key1 = icon.res) { atEnd = true }
+
+ rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = atEnd)
}
}
is Icon.Loaded -> {
- LaunchedEffect(loadedDrawable) {
+ val painter = rememberDrawablePainter(loadedDrawable)
+
+ // rememberDrawablePainter automatically starts the animation. Using
+ // SideEffect here to immediately stop it if needed
+ DisposableEffect(painter) {
if (loadedDrawable is AnimatedVectorDrawable) {
loadedDrawable.forceAnimationOnUI()
}
+ if (shouldSkipInitialAnimation) {
+ loadedDrawable.stop()
+ }
+ onDispose {}
}
- rememberDrawablePainter(loadedDrawable)
+
+ painter
}
}
@@ -251,6 +271,45 @@
}
}
+@Composable
+private fun TileLabel(
+ text: String,
+ color: ColorProducer,
+ style: TextStyle,
+ modifier: Modifier = Modifier,
+) {
+ BasicText(
+ text = text,
+ color = color,
+ style = style,
+ maxLines = 1,
+ modifier =
+ modifier
+ .fillMaxWidth()
+ .graphicsLayer { compositingStrategy = CompositingStrategy.Offscreen }
+ .drawWithContent {
+ drawContent()
+ // Draw a blur over the end of the text
+ val edgeWidthPx = TileLabelBlurWidth.toPx()
+ drawRect(
+ topLeft = Offset(size.width - edgeWidthPx, 0f),
+ size = Size(edgeWidthPx, size.height),
+ brush =
+ Brush.horizontalGradient(
+ colors = listOf(Color.Transparent, Color.Black),
+ startX = size.width,
+ endX = size.width - edgeWidthPx,
+ ),
+ blendMode = BlendMode.DstIn,
+ )
+ }
+ .basicMarquee(
+ iterations = TILE_MARQUEE_ITERATIONS,
+ initialDelayMillis = TILE_INITIAL_DELAY_MILLIS,
+ ),
+ )
+}
+
object CommonTileDefaults {
val IconSize = 32.dp
val LargeTileIconSize = 28.dp
@@ -258,9 +317,13 @@
val SideIconHeight = 20.dp
val ToggleTargetSize = 56.dp
val TileHeight = 72.dp
- val TilePadding = 8.dp
+ val TileStartPadding = 8.dp
+ val TileEndPadding = 16.dp
val TileArrangementPadding = 6.dp
val InactiveCornerRadius = 50.dp
+ val TileLabelBlurWidth = 32.dp
+ const val TILE_MARQUEE_ITERATIONS = 1
+ const val TILE_INITIAL_DELAY_MILLIS = 2000
@Composable fun longPressLabel() = stringResource(id = R.string.accessibility_long_click_tile)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
index ddadb88..69b967a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
@@ -22,6 +22,7 @@
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
@@ -78,6 +79,7 @@
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
+import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
@@ -140,6 +142,7 @@
import com.android.systemui.qs.panels.ui.compose.selection.rememberResizingState
import com.android.systemui.qs.panels.ui.compose.selection.rememberSelectionState
import com.android.systemui.qs.panels.ui.compose.selection.selectableTile
+import com.android.systemui.qs.panels.ui.model.AvailableTileGridCell
import com.android.systemui.qs.panels.ui.model.GridCell
import com.android.systemui.qs.panels.ui.model.SpacerGridCell
import com.android.systemui.qs.panels.ui.model.TileGridCell
@@ -290,8 +293,19 @@
Text(text = stringResource(id = R.string.drag_to_add_tiles))
}
+ val availableTiles = remember {
+ mutableStateListOf<AvailableTileGridCell>().apply {
+ addAll(toAvailableTiles(listState.tiles, otherTiles))
+ }
+ }
+ LaunchedEffect(listState.tiles, otherTiles) {
+ availableTiles.apply {
+ clear()
+ addAll(toAvailableTiles(listState.tiles, otherTiles))
+ }
+ }
AvailableTileGrid(
- otherTiles,
+ availableTiles,
selectionState,
columns,
onAddTile,
@@ -444,7 +458,7 @@
@Composable
private fun AvailableTileGrid(
- tiles: List<SizedTile<EditTileViewModel>>,
+ tiles: List<AvailableTileGridCell>,
selectionState: MutableSelectionState,
columns: Int,
onAddTile: (TileSpec) -> Unit,
@@ -453,7 +467,7 @@
// Available tiles aren't visible during drag and drop, so the row/col isn't needed
val groupedTiles =
remember(tiles.fastMap { it.tile.category }, tiles.fastMap { it.tile.label }) {
- groupAndSort(tiles.fastMap { TileGridCell(it, 0, 0) })
+ groupAndSort(tiles)
}
val labelColors = EditModeTileDefaults.editTileColors()
@@ -478,11 +492,10 @@
horizontalArrangement = spacedBy(TileArrangementPadding),
modifier = Modifier.fillMaxWidth().height(IntrinsicSize.Max),
) {
- row.forEachIndexed { index, tileGridCell ->
- key(tileGridCell.tile.tileSpec) {
+ row.forEach { tileGridCell ->
+ key(tileGridCell.key) {
AvailableTileGridCell(
cell = tileGridCell,
- index = index,
dragAndDropState = dragAndDropState,
selectionState = selectionState,
onAddTile = onAddTile,
@@ -505,10 +518,7 @@
}
private fun GridCell.key(index: Int): Any {
- return when (this) {
- is TileGridCell -> key
- is SpacerGridCell -> index
- }
+ return if (this is TileGridCell) key else index
}
/**
@@ -687,41 +697,44 @@
@Composable
private fun AvailableTileGridCell(
- cell: TileGridCell,
- index: Int,
+ cell: AvailableTileGridCell,
dragAndDropState: DragAndDropState,
selectionState: MutableSelectionState,
onAddTile: (TileSpec) -> Unit,
modifier: Modifier = Modifier,
) {
- val onClickActionName = stringResource(id = R.string.accessibility_qs_edit_tile_add_action)
- val stateDescription = stringResource(id = R.string.accessibility_qs_edit_position, index + 1)
+ val stateDescription: String? =
+ if (cell.isAvailable) null
+ else stringResource(R.string.accessibility_qs_edit_tile_already_added)
+
+ val alpha by animateFloatAsState(if (cell.isAvailable) 1f else .38f)
val colors = EditModeTileDefaults.editTileColors()
- val onClick = {
- onAddTile(cell.tile.tileSpec)
- selectionState.select(cell.tile.tileSpec)
- }
// Displays the tile as an icon tile with the label underneath
Column(
horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = spacedBy(CommonTileDefaults.TilePadding, Alignment.Top),
- modifier = modifier,
+ verticalArrangement = spacedBy(CommonTileDefaults.TileStartPadding, Alignment.Top),
+ modifier =
+ modifier
+ .graphicsLayer { this.alpha = alpha }
+ .semantics(mergeDescendants = true) {
+ stateDescription?.let { this.stateDescription = it }
+ },
) {
Box(Modifier.fillMaxWidth().height(TileHeight)) {
- Box(
- Modifier.fillMaxSize()
- .clickable(onClick = onClick, onClickLabel = onClickActionName)
- .semantics(mergeDescendants = true) { this.stateDescription = stateDescription }
- .dragAndDropTileSource(
+ val draggableModifier =
+ if (cell.isAvailable) {
+ Modifier.dragAndDropTileSource(
SizedTileImpl(cell.tile, cell.width),
dragAndDropState,
DragType.Add,
) {
selectionState.unSelect()
}
- .tileBackground(colors.background)
- ) {
+ } else {
+ Modifier
+ }
+ Box(draggableModifier.fillMaxSize().tileBackground(colors.background)) {
// Icon
SmallTileContent(
iconProvider = { cell.tile.icon },
@@ -733,9 +746,13 @@
StaticTileBadge(
icon = Icons.Default.Add,
- contentDescription = onClickActionName,
- onClick = onClick,
- )
+ contentDescription =
+ stringResource(id = R.string.accessibility_qs_edit_tile_add_action),
+ enabled = cell.isAvailable,
+ ) {
+ onAddTile(cell.tile.tileSpec)
+ selectionState.select(cell.tile.tileSpec)
+ }
}
Box(Modifier.fillMaxSize()) {
Text(
@@ -796,7 +813,7 @@
placeable.place(startPadding.roundToInt(), 0)
}
}
- .tilePadding(),
+ .largeTilePadding(),
) {
// Icon
Box(Modifier.size(ToggleTargetSize)) {
@@ -819,9 +836,18 @@
}
}
+private fun toAvailableTiles(
+ currentTiles: List<GridCell>,
+ otherTiles: List<SizedTile<EditTileViewModel>>,
+): List<AvailableTileGridCell> {
+ return currentTiles.filterIsInstance<TileGridCell>().fastMap {
+ AvailableTileGridCell(it.tile, isAvailable = false)
+ } + otherTiles.fastMap { AvailableTileGridCell(it.tile) }
+}
+
private fun MeasureScope.iconHorizontalCenter(containerSize: Int): Float {
return (containerSize - ToggleTargetSize.roundToPx()) / 2f -
- CommonTileDefaults.TilePadding.toPx()
+ CommonTileDefaults.TileStartPadding.toPx()
}
private fun Modifier.tileBackground(color: Color): Modifier {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
index dfee4976..0503049 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
@@ -85,8 +85,6 @@
remember(sizedTiles) { List(sizedTiles.size) { BounceableTileViewModel() } }
val squishiness by viewModel.squishinessViewModel.squishiness.collectAsStateWithLifecycle()
val scope = rememberCoroutineScope()
- var cellIndex = 0
-
val spans by remember(sizedTiles) { derivedStateOf { sizedTiles.fastMap { it.width } } }
VerticalSpannedGrid(
@@ -95,10 +93,9 @@
rowSpacing = dimensionResource(R.dimen.qs_tile_margin_vertical),
spans = spans,
keys = { sizedTiles[it].tile.spec },
- ) { spanIndex ->
+ ) { spanIndex, column, isFirstInColumn, isLastInColumn ->
val it = sizedTiles[spanIndex]
- val column = cellIndex % columns
- cellIndex += it.width
+
Element(it.tile.spec.toElementKey(spanIndex), Modifier) {
Tile(
tile = it.tile,
@@ -106,7 +103,15 @@
squishiness = { squishiness },
tileHapticsViewModelFactoryProvider = tileHapticsViewModelFactoryProvider,
coroutineScope = scope,
- bounceableInfo = bounceables.bounceableInfo(it, spanIndex, column, columns),
+ bounceableInfo =
+ bounceables.bounceableInfo(
+ it,
+ index = spanIndex,
+ column = column,
+ columns = columns,
+ isFirstInRow = isFirstInColumn,
+ isLastInRow = isLastInColumn,
+ ),
detailsViewModel = detailsViewModel,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
index d73dc87..a56fabc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
@@ -84,7 +84,9 @@
import com.android.systemui.qs.flags.QsDetailedView
import com.android.systemui.qs.panels.ui.compose.BounceableInfo
import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.InactiveCornerRadius
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.TileEndPadding
import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.TileHeight
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.TileStartPadding
import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.longPressLabel
import com.android.systemui.qs.panels.ui.viewmodel.DetailsViewModel
import com.android.systemui.qs.panels.ui.viewmodel.TileUiState
@@ -270,7 +272,7 @@
iconOnly = iconOnly,
)
.sysuiResTag(if (iconOnly) TEST_TAG_SMALL else TEST_TAG_LARGE)
- .tilePadding(),
+ .thenIf(!iconOnly) { Modifier.largeTilePadding() }, // Icon tiles are center aligned
content = content,
)
}
@@ -284,7 +286,7 @@
.clip(TileDefaults.animateTileShapeAsState(state = uiState.state).value)
.background(colors.background)
.height(TileHeight)
- .tilePadding()
+ .largeTilePadding()
) {
LargeTileContent(
label = uiState.label,
@@ -311,8 +313,8 @@
return spacedBy(space = CommonTileDefaults.TileArrangementPadding, alignment = Alignment.Start)
}
-fun Modifier.tilePadding(): Modifier {
- return padding(CommonTileDefaults.TilePadding)
+fun Modifier.largeTilePadding(): Modifier {
+ return padding(start = TileStartPadding, end = TileEndPadding)
}
@Composable
@@ -356,10 +358,10 @@
val ActiveIconCornerRadius = 16.dp
val ActiveTileCornerRadius = 24.dp
- /** An active tile without dual target uses the active color as background */
+ /** An active icon tile uses the active color as background */
@Composable
@ReadOnlyComposable
- fun activeTileColors(): TileColors =
+ fun activeIconTileColors(): TileColors =
TileColors(
background = MaterialTheme.colorScheme.primary,
iconBackground = MaterialTheme.colorScheme.primary,
@@ -418,10 +420,10 @@
fun getColorForState(uiState: TileUiState, iconOnly: Boolean): TileColors {
return when (uiState.state) {
STATE_ACTIVE -> {
- if (uiState.handlesSecondaryClick && !iconOnly) {
+ if (!iconOnly) {
activeDualTargetTileColors()
} else {
- activeTileColors()
+ activeIconTileColors()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt
index 699e5f6..153238fc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt
@@ -19,6 +19,7 @@
import androidx.compose.animation.animateColor
import androidx.compose.animation.core.Transition
import androidx.compose.animation.core.animateFloat
+import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.animateOffset
import androidx.compose.animation.core.animateSize
import androidx.compose.animation.core.updateTransition
@@ -61,6 +62,7 @@
import androidx.compose.ui.unit.toSize
import androidx.compose.ui.zIndex
import com.android.compose.modifiers.size
+import com.android.compose.modifiers.thenIf
import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.InactiveCornerRadius
import com.android.systemui.qs.panels.ui.compose.selection.SelectionDefaults.BADGE_ANGLE_RAD
import com.android.systemui.qs.panels.ui.compose.selection.SelectionDefaults.BadgeSize
@@ -184,18 +186,37 @@
}
}
+/**
+ * Draws a clickable badge in the top end corner of the parent composable.
+ *
+ * The badge will fade in and fade out based on whether or not it's enabled.
+ *
+ * @param icon the [ImageVector] to display in the badge
+ * @param contentDescription the content description for the icon
+ * @param enabled Whether the badge should be visible and clickable
+ * @param onClick the callback when the badge is clicked
+ */
@Composable
-fun StaticTileBadge(icon: ImageVector, contentDescription: String?, onClick: () -> Unit) {
+fun StaticTileBadge(
+ icon: ImageVector,
+ contentDescription: String?,
+ enabled: Boolean,
+ onClick: () -> Unit,
+) {
val offset = with(LocalDensity.current) { Offset(BadgeXOffset.toPx(), BadgeYOffset.toPx()) }
+ val alpha by animateFloatAsState(if (enabled) 1f else 0f)
MinimumInteractiveSizeComponent(angle = { BADGE_ANGLE_RAD }, offset = { offset }) {
Box(
Modifier.fillMaxSize()
- .clickable(
- interactionSource = null,
- indication = null,
- onClickLabel = contentDescription,
- onClick = onClick,
- )
+ .graphicsLayer { this.alpha = alpha }
+ .thenIf(enabled) {
+ Modifier.clickable(
+ interactionSource = null,
+ indication = null,
+ onClickLabel = contentDescription,
+ onClick = onClick,
+ )
+ }
) {
val secondaryColor = MaterialTheme.colorScheme.secondary
Icon(
@@ -214,7 +235,8 @@
private fun MinimumInteractiveSizeComponent(
angle: () -> Float,
offset: () -> Offset,
- content: @Composable BoxScope.() -> Unit,
+ modifier: Modifier = Modifier,
+ content: @Composable BoxScope.() -> Unit = {},
) {
// Use a higher zIndex than the tile to draw over it, and manually create the touch target
// as we're drawing over neighbor tiles as well.
@@ -222,7 +244,8 @@
Box(
contentAlignment = Alignment.Center,
modifier =
- Modifier.zIndex(2f)
+ modifier
+ .zIndex(2f)
.systemGestureExclusion { Rect(Offset.Zero, it.size.toSize()) }
.layout { measurable, constraints ->
val size = minTouchTargetSize.roundToPx()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/Toolbar.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/Toolbar.kt
index 360266a..99f52c2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/Toolbar.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/Toolbar.kt
@@ -46,8 +46,10 @@
Spacer(modifier = Modifier.weight(1f))
- viewModel.powerButtonViewModel?.let {
- IconButton(it, useModifierBasedExpandable = true, Modifier.sysuiResTag("pm_lite"))
- }
+ IconButton(
+ { viewModel.powerButtonViewModel },
+ useModifierBasedExpandable = true,
+ Modifier.sysuiResTag("pm_lite"),
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt
index c0441f8..78fd8c0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt
@@ -21,13 +21,13 @@
import com.android.systemui.qs.panels.shared.model.SizedTile
import com.android.systemui.qs.panels.shared.model.splitInRowsSequence
import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
+import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.shared.model.CategoryAndName
/** Represents an item from a grid associated with a row and a span */
sealed interface GridCell {
val row: Int
val span: GridItemSpan
- val s: String
}
/**
@@ -40,7 +40,6 @@
override val row: Int,
override val width: Int,
override val span: GridItemSpan = GridItemSpan(width),
- override val s: String = "${tile.tileSpec.spec}-$row-$width",
val column: Int,
) : GridCell, SizedTile<EditTileViewModel>, CategoryAndName by tile {
val key: String = "${tile.tileSpec.spec}-$row"
@@ -52,12 +51,23 @@
) : this(tile = sizedTile.tile, row = row, column = column, width = sizedTile.width)
}
+/**
+ * Represents a [EditTileViewModel] from the edit mode available tiles grid and whether it is
+ * available to add or not.
+ */
+@Immutable
+data class AvailableTileGridCell(
+ override val tile: EditTileViewModel,
+ override val width: Int = 1,
+ val isAvailable: Boolean = true,
+ val key: TileSpec = tile.tileSpec,
+) : SizedTile<EditTileViewModel>, CategoryAndName by tile
+
/** Represents an empty space used to fill incomplete rows. Will always display as a 1x1 tile */
@Immutable
data class SpacerGridCell(
override val row: Int,
override val span: GridItemSpan = GridItemSpan(1),
- override val s: String = "spacer",
) : GridCell
/**
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt
index 5dc8d1b..26148de 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt
@@ -14,7 +14,7 @@
companion object Utils {
fun assertNewTiles() =
- RefactorFlagUtils.assertInNewMode(
+ RefactorFlagUtils.unsafeAssertInNewMode(
AconfigFlags.qsNewTiles(),
AconfigFlags.FLAG_QS_NEW_TILES
)
diff --git a/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java
index 9af4630..7a6426c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/LauncherProxyService.java
@@ -66,6 +66,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
+import android.view.Display;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
@@ -94,6 +95,7 @@
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.ui.view.InWindowLauncherUnlockAnimationManager;
import com.android.systemui.model.SysUiState;
+import com.android.systemui.model.SysUiState.SysUiStateCallback;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.navigationbar.views.NavigationBar;
@@ -584,7 +586,8 @@
// Force-update the systemui state flags
updateSystemUiStateFlags();
- notifySystemUiStateFlags(mSysUiState.getFlags());
+ // TODO b/398011576 - send the state for all displays.
+ notifySystemUiStateFlags(mSysUiState.getFlags(), Display.DEFAULT_DISPLAY);
notifyConnectionChanged();
}
@@ -650,6 +653,13 @@
}
};
+ private final SysUiStateCallback mSysUiStateCallback =
+ new SysUiStateCallback() {
+ @Override
+ public void onSystemUiStateChanged(long sysUiFlags, int displayId) {
+ notifySystemUiStateFlags(sysUiFlags, displayId);
+ }
+ };
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
@Inject
public LauncherProxyService(Context context,
@@ -708,8 +718,10 @@
com.android.internal.R.string.config_recentsComponentName));
mQuickStepIntent = new Intent(ACTION_QUICKSTEP)
.setPackage(mRecentsComponentName.getPackageName());
+ // TODO b/398011576 - Here we're still only handling the default display state. We should
+ // have a callback for any sysuiState change.
mSysUiState = sysUiState;
- mSysUiState.addCallback(this::notifySystemUiStateFlags);
+ mSysUiState.addCallback(mSysUiStateCallback);
mUiEventLogger = uiEventLogger;
mDisplayTracker = displayTracker;
mUnfoldTransitionProgressForwarder = unfoldTransitionProgressForwarder;
@@ -815,14 +827,14 @@
}
}
- private void notifySystemUiStateFlags(@SystemUiStateFlags long flags) {
+ private void notifySystemUiStateFlags(@SystemUiStateFlags long flags, int displayId) {
if (SysUiState.DEBUG) {
Log.d(TAG_OPS, "Notifying sysui state change to launcher service: proxy="
- + mLauncherProxy + " flags=" + flags);
+ + mLauncherProxy + " flags=" + flags + " displayId=" + displayId);
}
try {
if (mLauncherProxy != null) {
- mLauncherProxy.onSystemUiStateChanged(flags);
+ mLauncherProxy.onSystemUiStateChanged(flags, displayId);
}
} catch (RemoteException e) {
Log.e(TAG_OPS, "Failed to notify sysui state change", e);
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/KeyguardStateCallbackStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/KeyguardStateCallbackStartable.kt
index 4044381..ecd93b2 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/KeyguardStateCallbackStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/KeyguardStateCallbackStartable.kt
@@ -69,7 +69,7 @@
}
fun addCallback(callback: IKeyguardStateCallback) {
- SceneContainerFlag.assertInNewMode()
+ SceneContainerFlag.unsafeAssertInNewMode()
callbacks.add(callback)
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt
index 733b421..b0fb619 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt
@@ -83,7 +83,7 @@
* testing.
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, DESCRIPTION)
+ inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, DESCRIPTION)
/** Returns a developer-readable string that describes the current requirement list. */
@JvmStatic
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
index 0dd0e3d..1d470c2 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
@@ -33,6 +33,7 @@
sceneDataSourceDelegator: SceneDataSourceDelegator,
qsSceneAdapter: Provider<QSSceneAdapter>,
sceneJankMonitorFactory: SceneJankMonitor.Factory,
+ windowRootViewKeyEventHandler: WindowRootViewKeyEventHandler,
) {
setLayoutInsetsController(layoutInsetController)
SceneWindowRootViewBinder.bind(
@@ -53,6 +54,7 @@
qsSceneAdapter = qsSceneAdapter,
sceneJankMonitorFactory = sceneJankMonitorFactory,
)
+ setWindowRootViewKeyEventHandler(windowRootViewKeyEventHandler)
}
override fun setVisibility(visibility: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
index 364da5f..42bf753 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
@@ -21,6 +21,7 @@
import android.util.AttributeSet
import android.util.Pair
import android.view.DisplayCutout
+import android.view.KeyEvent
import android.view.View
import android.view.WindowInsets
import android.widget.FrameLayout
@@ -37,6 +38,11 @@
private var rightInset = 0
private var previousInsets: WindowInsets? = null
+ private lateinit var windowRootViewKeyEventHandler: WindowRootViewKeyEventHandler
+
+ fun setWindowRootViewKeyEventHandler(wrvkeh: WindowRootViewKeyEventHandler) {
+ windowRootViewKeyEventHandler = wrvkeh
+ }
override fun onAttachedToWindow() {
super.onAttachedToWindow()
@@ -137,6 +143,24 @@
return parent.let { it !is View || it.id == android.R.id.content }
}
+ override fun dispatchKeyEvent(event: KeyEvent): Boolean {
+ windowRootViewKeyEventHandler.collectKeyEvent(event)
+
+ if (windowRootViewKeyEventHandler.interceptMediaKey(event)) {
+ return true
+ }
+
+ if (super.dispatchKeyEvent(event)) {
+ return true
+ }
+
+ return windowRootViewKeyEventHandler.dispatchKeyEvent(event)
+ }
+
+ override fun dispatchKeyEventPreIme(event: KeyEvent): Boolean {
+ return windowRootViewKeyEventHandler.dispatchKeyEventPreIme(event) ?: false
+ }
+
/** Controller responsible for calculating insets for the shade window. */
interface LayoutInsetsController {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootViewKeyEventHandler.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootViewKeyEventHandler.kt
new file mode 100644
index 0000000..863af92
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootViewKeyEventHandler.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.scene.ui.view
+
+import android.view.KeyEvent
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler
+import dagger.Lazy
+import javax.inject.Inject
+
+@SysUISingleton
+class WindowRootViewKeyEventHandler
+@Inject
+constructor(
+ val sysUIKeyEventHandlerLazy: Lazy<SysUIKeyEventHandler>,
+ val falsingCollector: FalsingCollector,
+) {
+ fun dispatchKeyEvent(event: KeyEvent): Boolean {
+ return sysUIKeyEventHandlerLazy.get().dispatchKeyEvent(event)
+ }
+
+ fun dispatchKeyEventPreIme(event: KeyEvent): Boolean {
+ return sysUIKeyEventHandlerLazy.get().dispatchKeyEventPreIme(event)
+ }
+
+ fun interceptMediaKey(event: KeyEvent): Boolean {
+ return sysUIKeyEventHandlerLazy.get().interceptMediaKey(event)
+ }
+
+ /** Collects the KeyEvent without intercepting it. */
+ fun collectKeyEvent(event: KeyEvent) {
+ falsingCollector.onKeyEvent(event)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
index 9208fc3..271f4dc 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
@@ -23,18 +23,31 @@
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
+import android.content.pm.PackageManager.NameNotFoundException
import android.net.Uri
import android.os.UserHandle
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.res.R
import com.android.systemui.screenshot.scroll.LongScreenshotActivity
import com.android.systemui.shared.Flags.usePreferredImageEditor
+import java.util.function.Consumer
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
@SysUISingleton
class ActionIntentCreator
@Inject
-constructor(private val context: Context, private val packageManager: PackageManager) {
+constructor(
+ private val context: Context,
+ private val packageManager: PackageManager,
+ @Application private val applicationScope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+) {
/** @return a chooser intent to share the given URI. */
fun createShare(uri: Uri): Intent = createShare(uri, subject = null, text = null)
@@ -76,11 +89,16 @@
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
+ // Non-suspend version for java compat
+ fun createEdit(rawUri: Uri, consumer: Consumer<Intent>) {
+ applicationScope.launch { consumer.accept(createEdit(rawUri)) }
+ }
+
/**
* @return an ACTION_EDIT intent for the given URI, directed to config_preferredScreenshotEditor
* if enabled, falling back to config_screenshotEditor if that's non-empty.
*/
- fun createEdit(rawUri: Uri): Intent {
+ suspend fun createEdit(rawUri: Uri): Intent {
val uri = uriWithoutUserId(rawUri)
val editIntent = Intent(Intent.ACTION_EDIT)
@@ -112,22 +130,30 @@
.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
}
- private fun preferredEditor(): ComponentName? =
+ private suspend fun preferredEditor(): ComponentName? =
runCatching {
val preferredEditor = context.getString(R.string.config_preferredScreenshotEditor)
val component = ComponentName.unflattenFromString(preferredEditor) ?: return null
+ return if (isComponentAvailable(component)) component else null
+ }
+ .getOrNull()
+
+ private suspend fun isComponentAvailable(component: ComponentName): Boolean =
+ withContext(backgroundDispatcher) {
+ try {
val info =
packageManager.getPackageInfo(
component.packageName,
PackageManager.GET_ACTIVITIES,
)
-
- return info.activities
- ?.firstOrNull { it.componentName.className.equals(component.className) }
- ?.componentName
+ info.activities?.firstOrNull {
+ it.componentName.className == component.className
+ } != null
+ } catch (e: NameNotFoundException) {
+ false
}
- .getOrNull()
+ }
private fun defaultEditor(): ComponentName? =
runCatching {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt
index 4373389..d91f267 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt
@@ -22,6 +22,7 @@
import android.util.Log
import androidx.appcompat.content.res.AppCompatResources
import com.android.internal.logging.UiEventLogger
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.DebugLogger.debugLog
import com.android.systemui.res.R
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_EDIT_TAPPED
@@ -34,6 +35,8 @@
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import java.util.UUID
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
/**
* Provides actions for screenshots. This class can be overridden by a vendor-specific SysUI
@@ -68,6 +71,7 @@
private val context: Context,
private val uiEventLogger: UiEventLogger,
private val actionIntentCreator: ActionIntentCreator,
+ @Application private val applicationScope: CoroutineScope,
@Assisted val requestId: UUID,
@Assisted val request: ScreenshotData,
@Assisted val actionExecutor: ActionExecutor,
@@ -75,7 +79,7 @@
) : ScreenshotActionsProvider {
private var addedScrollChip = false
private var onScrollClick: Runnable? = null
- private var pendingAction: ((ScreenshotSavedResult) -> Unit)? = null
+ private var pendingAction: (suspend (ScreenshotSavedResult) -> Unit)? = null
private var result: ScreenshotSavedResult? = null
private var webUri: Uri? = null
@@ -166,15 +170,16 @@
return
}
this.result = result
- pendingAction?.invoke(result)
+ pendingAction?.also { applicationScope.launch { it.invoke(result) } }
}
override fun onAssistContent(assistContent: AssistContent?) {
webUri = assistContent?.webUri
}
- private fun onDeferrableActionTapped(onResult: (ScreenshotSavedResult) -> Unit) {
- result?.let { onResult.invoke(it) } ?: run { pendingAction = onResult }
+ private fun onDeferrableActionTapped(onResult: suspend (ScreenshotSavedResult) -> Unit) {
+ result?.let { applicationScope.launch { onResult.invoke(it) } }
+ ?: run { pendingAction = onResult }
}
@AssistedFactory
@@ -188,6 +193,6 @@
}
companion object {
- private const val TAG = "ScreenshotActionsProvider"
+ private const val TAG = "ScreenshotActionsPrvdr"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java
index 48e08a0..88ffd4f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java
@@ -16,6 +16,8 @@
package com.android.systemui.screenshot.scroll;
+import static com.android.systemui.shared.Flags.usePreferredImageEditor;
+
import android.app.Activity;
import android.app.ActivityOptions;
import android.content.ComponentName;
@@ -350,31 +352,57 @@
private void doEdit(Uri uri) {
if (mScreenshotUserHandle != Process.myUserHandle()) {
// TODO: Fix transition for work profile. Omitting it in the meantime.
- mActionExecutor.launchIntentAsync(
- mActionIntentCreator.createEdit(uri),
- mScreenshotUserHandle, false,
- /* activityOptions */ null, /* transitionCoordinator */ null);
- } else {
- String editorPackage = getString(R.string.config_screenshotEditor);
- Intent intent = new Intent(Intent.ACTION_EDIT);
- intent.setDataAndType(uri, "image/png");
- intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
- | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
- Bundle options = null;
+ mActionIntentCreator.createEdit(uri, intent -> {
+ mActionExecutor.launchIntentAsync(
+ intent,
+ mScreenshotUserHandle, false,
+ /* activityOptions */ null, /* transitionCoordinator */ null);
+ });
- // Skip shared element transition for implicit edit intents
- if (!TextUtils.isEmpty(editorPackage)) {
- intent.setComponent(ComponentName.unflattenFromString(editorPackage));
- mTransitionView.setImageBitmap(mOutputBitmap);
- mTransitionView.setVisibility(View.VISIBLE);
- mTransitionView.setTransitionName(
- ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME);
- options = ActivityOptions.makeSceneTransitionAnimation(this, mTransitionView,
- ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME).toBundle();
- // TODO: listen for transition completing instead of finishing onStop
- mTransitionStarted = true;
+ } else {
+ if (usePreferredImageEditor()) {
+ mActionIntentCreator.createEdit(uri, intent -> {
+ Bundle options = null;
+
+ if (intent.getComponent() != null) {
+ // Modify intent for shared transition if we're opening a specific editor.
+ intent.removeFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.removeFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ mTransitionView.setImageBitmap(mOutputBitmap);
+ mTransitionView.setVisibility(View.VISIBLE);
+ mTransitionView.setTransitionName(
+ ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME);
+ options = ActivityOptions.makeSceneTransitionAnimation(this,
+ mTransitionView,
+ ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME).toBundle();
+ // TODO: listen for transition completing instead of finishing onStop
+ mTransitionStarted = true;
+ }
+
+ startActivity(intent, options);
+ });
+ } else {
+ String editorPackage = getString(R.string.config_screenshotEditor);
+ Intent intent = new Intent(Intent.ACTION_EDIT);
+ intent.setDataAndType(uri, "image/png");
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ Bundle options = null;
+
+ // Skip shared element transition for implicit edit intents
+ if (!TextUtils.isEmpty(editorPackage)) {
+ intent.setComponent(ComponentName.unflattenFromString(editorPackage));
+ mTransitionView.setImageBitmap(mOutputBitmap);
+ mTransitionView.setVisibility(View.VISIBLE);
+ mTransitionView.setTransitionName(
+ ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME);
+ options = ActivityOptions.makeSceneTransitionAnimation(this, mTransitionView,
+ ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME).toBundle();
+ // TODO: listen for transition completing instead of finishing onStop
+ mTransitionStarted = true;
+ }
+ startActivity(intent, options);
}
- startActivity(intent, options);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ComposeDialogComposableProvider.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/ComposeDialogComposableProvider.kt
index dde2ebc..9e20055 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ComposeDialogComposableProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ComposeDialogComposableProvider.kt
@@ -17,7 +17,6 @@
package com.android.systemui.settings.brightness
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
@@ -26,7 +25,6 @@
import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer
import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel
import com.android.systemui.lifecycle.rememberViewModel
-import com.android.systemui.qs.ui.composable.QuickSettingsShade
object ComposeDialogComposableProvider {
@@ -46,10 +44,7 @@
rememberViewModel(traceName = "BrightnessDialog.viewModel") {
brightnessSliderViewModelFactory.create(false)
}
- BrightnessSliderContainer(
- viewModel = viewModel,
- Modifier.fillMaxWidth().height(QuickSettingsShade.Dimensions.BrightnessSliderHeight),
- )
+ BrightnessSliderContainer(viewModel = viewModel, Modifier.fillMaxWidth())
}
class ComposableProvider(
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index eea0470..17fb50a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -65,6 +65,7 @@
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.MathUtils;
+import android.view.Display;
import android.view.HapticFeedbackConstants;
import android.view.InputDevice;
import android.view.MotionEvent;
@@ -96,6 +97,7 @@
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.classifier.Classifier;
import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.common.domain.interactor.SysUIStateDisplaysInteractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.dagger.qualifiers.Main;
@@ -119,6 +121,7 @@
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
import com.android.systemui.media.controls.ui.controller.KeyguardMediaController;
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager;
+import com.android.systemui.model.StateChange;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.NavigationModeController;
@@ -450,7 +453,9 @@
private final MediaDataManager mMediaDataManager;
@PanelState
private int mCurrentPanelState = STATE_CLOSED;
+ @Deprecated // Use SysUIStateInteractor instead
private final SysUiState mSysUiState;
+ private final SysUIStateDisplaysInteractor mSysUIStateDisplaysInteractor;
private final NotificationShadeDepthController mDepthController;
private final NavigationBarController mNavigationBarController;
private final int mDisplayId;
@@ -607,6 +612,7 @@
ShadeRepository shadeRepository,
Optional<SysUIUnfoldComponent> unfoldComponent,
SysUiState sysUiState,
+ SysUIStateDisplaysInteractor sysUIStateDisplaysInteractor,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
KeyguardIndicationController keyguardIndicationController,
NotificationListContainer notificationListContainer,
@@ -738,6 +744,7 @@
mMediaDataManager = mediaDataManager;
mTapAgainViewController = tapAgainViewController;
mSysUiState = sysUiState;
+ mSysUIStateDisplaysInteractor = sysUIStateDisplaysInteractor;
mKeyguardBypassController = bypassController;
mUpdateMonitor = keyguardUpdateMonitor;
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
@@ -2253,7 +2260,7 @@
Log.i(TAG, "Ignoring status Bar long press on virtualized test device.");
return;
}
- ShadeExpandsOnStatusBarLongPress.assertInNewMode();
+ ShadeExpandsOnStatusBarLongPress.unsafeAssertInNewMode();
mStatusBarLongPressDowntime = event.getDownTime();
if (isTracking()) {
onTrackingStopped(true);
@@ -2701,13 +2708,41 @@
Log.d(TAG, "Updating panel sysui state flags: fullyExpanded="
+ isFullyExpanded() + " inQs=" + mQsController.getExpanded());
}
+ if (ShadeWindowGoesAround.isEnabled()) {
+ setPerDisplaySysUIStateFlags();
+ } else {
+ setDefaultDisplayFlags();
+ }
+ }
+
+ private int getShadeDisplayId() {
+ if (mView != null && mView.getDisplay() != null) return mView.getDisplay().getDisplayId();
+ return Display.DEFAULT_DISPLAY;
+ }
+
+ private void setPerDisplaySysUIStateFlags() {
+ mSysUIStateDisplaysInteractor.setFlagsExclusivelyToDisplay(
+ getShadeDisplayId(),
+ new StateChange()
+ .setFlag(SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE,
+ isPanelExpanded() && !isCollapsing())
+ .setFlag(SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED,
+ isFullyExpanded() && !mQsController.getExpanded())
+ .setFlag(SYSUI_STATE_QUICK_SETTINGS_EXPANDED,
+ isFullyExpanded() && mQsController.getExpanded())
+ );
+ }
+
+ @Deprecated
+ private void setDefaultDisplayFlags() {
mSysUiState
.setFlag(SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE,
isPanelExpanded() && !isCollapsing())
.setFlag(SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED,
isFullyExpanded() && !mQsController.getExpanded())
.setFlag(SYSUI_STATE_QUICK_SETTINGS_EXPANDED,
- isFullyExpanded() && mQsController.getExpanded()).commitUpdate(mDisplayId);
+ isFullyExpanded() && mQsController.getExpanded()).commitUpdate(
+ mDisplayId);
}
private void debugLog(String fmt, Object... args) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index e4cd7ea..305444f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -451,6 +451,8 @@
} else {
mLpChanged.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
}
+ } else if (state.glanceableHubOrientationAware) {
+ mLpChanged.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER;
} else {
mLpChanged.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
}
@@ -627,6 +629,7 @@
state.shadeOrQsExpanded,
state.notificationShadeFocusable,
state.glanceableHubShowing,
+ state.glanceableHubOrientationAware,
state.bouncerShowing,
state.keyguardFadingAway,
state.keyguardGoingAway,
@@ -763,6 +766,12 @@
}
@Override
+ public void setGlanceableHubOrientationAware(boolean isOrientationAware) {
+ mCurrentState.glanceableHubOrientationAware = isOrientationAware;
+ apply(mCurrentState);
+ }
+
+ @Override
public void setBackdropShowing(boolean showing) {
mCurrentState.mediaBackdropShowing = showing;
apply(mCurrentState);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
index 6a4b52a..a1eac74 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
@@ -36,6 +36,7 @@
@JvmField var notificationShadeFocusable: Boolean = false,
@JvmField var bouncerShowing: Boolean = false,
@JvmField var glanceableHubShowing: Boolean = false,
+ @JvmField var glanceableHubOrientationAware: Boolean = false,
@JvmField var keyguardFadingAway: Boolean = false,
@JvmField var keyguardGoingAway: Boolean = false,
@JvmField var qsExpanded: Boolean = false,
@@ -81,6 +82,7 @@
notificationShadeFocusable.toString(),
bouncerShowing.toString(),
glanceableHubShowing.toString(),
+ glanceableHubOrientationAware.toString(),
keyguardFadingAway.toString(),
keyguardGoingAway.toString(),
qsExpanded.toString(),
@@ -122,6 +124,7 @@
panelExpanded: Boolean,
notificationShadeFocusable: Boolean,
glanceableHubShowing: Boolean,
+ glanceableHubOrientationAware: Boolean,
bouncerShowing: Boolean,
keyguardFadingAway: Boolean,
keyguardGoingAway: Boolean,
@@ -153,6 +156,7 @@
this.shadeOrQsExpanded = panelExpanded
this.notificationShadeFocusable = notificationShadeFocusable
this.glanceableHubShowing = glanceableHubShowing
+ this.glanceableHubOrientationAware = glanceableHubOrientationAware
this.bouncerShowing = bouncerShowing
this.keyguardFadingAway = keyguardFadingAway
this.keyguardGoingAway = keyguardGoingAway
@@ -202,6 +206,7 @@
"panelExpanded",
"notificationShadeFocusable",
"glanceableHubShowing",
+ "glanceableHubOrientationAware",
"bouncerShowing",
"keyguardFadingAway",
"keyguardGoingAway",
@@ -223,7 +228,7 @@
"dozing",
"scrimsVisibility",
"backgroundBlurRadius",
- "communalVisible"
+ "communalVisible",
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
index 1721700..84efdc2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
@@ -105,26 +105,6 @@
}
}
- @Override
- public boolean dispatchKeyEvent(KeyEvent event) {
- mInteractionEventHandler.collectKeyEvent(event);
-
- if (mInteractionEventHandler.interceptMediaKey(event)) {
- return true;
- }
-
- if (super.dispatchKeyEvent(event)) {
- return true;
- }
-
- return mInteractionEventHandler.dispatchKeyEvent(event);
- }
-
- @Override
- public boolean dispatchKeyEventPreIme(KeyEvent event) {
- return mInteractionEventHandler.dispatchKeyEventPreIme(event);
- }
-
protected void setInteractionEventHandler(InteractionEventHandler listener) {
mInteractionEventHandler = listener;
}
@@ -363,17 +343,6 @@
boolean handleTouchEvent(MotionEvent ev);
void didNotHandleTouchEvent(MotionEvent ev);
-
- boolean interceptMediaKey(KeyEvent event);
-
- boolean dispatchKeyEvent(KeyEvent event);
-
- boolean dispatchKeyEventPreIme(KeyEvent event);
-
- /**
- * Collects the KeyEvent without intercepting it
- */
- void collectKeyEvent(KeyEvent event);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 10a9fd2..c74f6c3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -27,7 +27,6 @@
import android.util.Log;
import android.view.Choreographer;
import android.view.GestureDetector;
-import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
@@ -50,7 +49,6 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlagsClassic;
import com.android.systemui.flags.Flags;
-import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.shared.model.Edge;
@@ -60,6 +58,7 @@
import com.android.systemui.qs.flags.QSComposeFragment;
import com.android.systemui.res.R;
import com.android.systemui.scene.shared.flag.SceneContainerFlag;
+import com.android.systemui.scene.ui.view.WindowRootViewKeyEventHandler;
import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor;
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor;
@@ -89,7 +88,6 @@
import com.android.systemui.window.ui.viewmodel.WindowRootViewModel;
import kotlinx.coroutines.CoroutineDispatcher;
-import kotlinx.coroutines.ExperimentalCoroutinesApi;
import kotlinx.coroutines.flow.Flow;
import java.io.PrintWriter;
@@ -117,8 +115,7 @@
private final AmbientState mAmbientState;
private final PulsingGestureListener mPulsingGestureListener;
private final NotificationInsetsController mNotificationInsetsController;
- private final FeatureFlagsClassic mFeatureFlagsClassic;
- private final SysUIKeyEventHandler mSysUIKeyEventHandler;
+ private final WindowRootViewKeyEventHandler mWindowRootViewKeyEventHandler;
private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
private final AlternateBouncerInteractor mAlternateBouncerInteractor;
private final QuickSettingsController mQuickSettingsController;
@@ -202,7 +199,7 @@
NotificationLaunchAnimationInteractor notificationLaunchAnimationInteractor,
FeatureFlagsClassic featureFlagsClassic,
SystemClock clock,
- SysUIKeyEventHandler sysUIKeyEventHandler,
+ WindowRootViewKeyEventHandler windowRootViewKeyEventHandler,
QuickSettingsController quickSettingsController,
PrimaryBouncerInteractor primaryBouncerInteractor,
AlternateBouncerInteractor alternateBouncerInteractor,
@@ -232,8 +229,7 @@
mNotificationInsetsController = notificationInsetsController;
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
mGlanceableHubContainerController = glanceableHubContainerController;
- mFeatureFlagsClassic = featureFlagsClassic;
- mSysUIKeyEventHandler = sysUIKeyEventHandler;
+ mWindowRootViewKeyEventHandler = windowRootViewKeyEventHandler;
mPrimaryBouncerInteractor = primaryBouncerInteractor;
mAlternateBouncerInteractor = alternateBouncerInteractor;
mQuickSettingsController = quickSettingsController;
@@ -370,6 +366,7 @@
mPulsingWakeupGestureHandler = new GestureDetector(mView.getContext(),
mPulsingGestureListener);
mView.setLayoutInsetsController(mNotificationInsetsController);
+ mView.setWindowRootViewKeyEventHandler(mWindowRootViewKeyEventHandler);
mView.setInteractionEventHandler(new NotificationShadeWindowView.InteractionEventHandler() {
boolean mUseDragDownHelperForTouch = false;
boolean mLastInterceptWasDragDownHelper = false;
@@ -605,26 +602,6 @@
mService.setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false);
}
}
-
- @Override
- public boolean interceptMediaKey(KeyEvent event) {
- return mSysUIKeyEventHandler.interceptMediaKey(event);
- }
-
- @Override
- public boolean dispatchKeyEventPreIme(KeyEvent event) {
- return mSysUIKeyEventHandler.dispatchKeyEventPreIme(event);
- }
-
- @Override
- public boolean dispatchKeyEvent(KeyEvent event) {
- return mSysUIKeyEventHandler.dispatchKeyEvent(event);
- }
-
- @Override
- public void collectKeyEvent(KeyEvent event) {
- mFalsingCollector.onKeyEvent(event);
- }
});
mView.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
index 42c63f9..c671f7d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
@@ -959,6 +959,7 @@
void setShadeExpansion(float expandedHeight, float expandedFraction) {
mShadeExpandedHeight = expandedHeight;
mShadeExpandedFraction = expandedFraction;
+ mMediaHierarchyManager.setShadeExpandedFraction(expandedFraction);
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpandsOnStatusBarLongPress.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpandsOnStatusBarLongPress.kt
index 6d8e898..79e6333 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpandsOnStatusBarLongPress.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpandsOnStatusBarLongPress.kt
@@ -50,7 +50,7 @@
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+ inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
index 55ee27d..150ef3a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
@@ -42,6 +42,7 @@
import com.android.systemui.scene.ui.view.SceneJankMonitor
import com.android.systemui.scene.ui.view.SceneWindowRootView
import com.android.systemui.scene.ui.view.WindowRootView
+import com.android.systemui.scene.ui.view.WindowRootViewKeyEventHandler
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.LightRevealScrim
@@ -88,6 +89,7 @@
sceneDataSourceDelegator: Provider<SceneDataSourceDelegator>,
qsSceneAdapter: Provider<QSSceneAdapter>,
sceneJankMonitorFactory: SceneJankMonitor.Factory,
+ windowRootViewKeyEventHandler: WindowRootViewKeyEventHandler,
): WindowRootView {
return if (SceneContainerFlag.isEnabled) {
checkNoSceneDuplicates(scenesProvider.get())
@@ -104,6 +106,7 @@
sceneDataSourceDelegator = sceneDataSourceDelegator.get(),
qsSceneAdapter = qsSceneAdapter,
sceneJankMonitorFactory = sceneJankMonitorFactory,
+ windowRootViewKeyEventHandler = windowRootViewKeyEventHandler,
)
sceneWindowRootView
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDialogContextInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDialogContextInteractor.kt
index 4edba27..3b98708 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDialogContextInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDialogContextInteractor.kt
@@ -47,14 +47,16 @@
constructor(
@Main private val defaultContext: Context,
private val displayWindowPropertyRepository: Provider<DisplayWindowPropertiesRepository>,
- private val shadeDisplaysRepository: ShadeDisplaysRepository,
+ private val shadeDisplaysRepository: Provider<ShadeDisplaysRepository>,
@Background private val bgScope: CoroutineScope,
) : CoreStartable, ShadeDialogContextInteractor {
override fun start() {
if (ShadeWindowGoesAround.isUnexpectedlyInLegacyMode()) return
bgScope.launchTraced(TAG) {
- shadeDisplaysRepository.displayId
+ shadeDisplaysRepository
+ .get()
+ .displayId
// No need for default display pre-warming.
.filter { it != Display.DEFAULT_DISPLAY }
.collectLatest { displayId ->
@@ -70,7 +72,7 @@
if (!ShadeWindowGoesAround.isEnabled) {
return defaultContext
}
- val displayId = shadeDisplaysRepository.displayId.value
+ val displayId = shadeDisplaysRepository.get().displayId.value
return getContextOrDefault(displayId)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
index 9a5c968..930b1cb 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
@@ -108,7 +108,8 @@
val currentDisplay = shadeContext.display ?: error("Current shade display is null")
currentId = currentDisplay.displayId
if (currentId == destinationId) {
- error("Trying to move the shade to a display it was already in")
+ Log.w(TAG, "Trying to move the shade to a display ($currentId) it was already in ")
+ return
}
withContext(mainThreadContext) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
index 246177e..52de0ab 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
@@ -56,7 +56,7 @@
private val shadeModeInteractor: ShadeModeInteractor,
) : BaseShadeInteractor {
init {
- SceneContainerFlag.assertInNewMode()
+ SceneContainerFlag.unsafeAssertInNewMode()
}
override val shadeExpansion: StateFlow<Float> =
@@ -334,7 +334,7 @@
} else if (state.fromContent == overlay) {
state.progress.map { progress -> 1 - progress }
} else {
- flowOf(0f)
+ state.currentOverlays().map { if (overlay in it) 1f else 0f }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt b/packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt
index dc444ff..016f50f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt
@@ -67,7 +67,7 @@
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+ inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
index bfd512f..ef0660f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
@@ -566,12 +566,11 @@
Arrays.asList(
Pair.create(KeyEvent.KEYCODE_TAB, KeyEvent.META_META_ON))),
/* Back: go back to previous state (back button) */
- /* Meta + Escape, Meta + backspace, Meta + left arrow */
+ /* Meta + Escape, Meta + left arrow */
new ShortcutKeyGroupMultiMappingInfo(
context.getString(R.string.group_system_go_back),
Arrays.asList(
Pair.create(KeyEvent.KEYCODE_ESCAPE, KeyEvent.META_META_ON),
- Pair.create(KeyEvent.KEYCODE_DEL, KeyEvent.META_META_ON),
Pair.create(KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.META_META_ON))),
/* Take a full screenshot: Meta + S */
new ShortcutKeyGroupMultiMappingInfo(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
index 6ebe024..c2e355d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
@@ -72,6 +72,7 @@
mRow = row;
final IconComparator iconVisibilityComparator = new IconComparator() {
+ @Override
public boolean compare(View parent, View child, Object parentData,
Object childData) {
if (Flags.notificationsRedesignAppIcons() && mRow.isShowingAppIcon()) {
@@ -83,6 +84,7 @@
}
};
final IconComparator greyComparator = new IconComparator() {
+ @Override
public boolean compare(View parent, View child, Object parentData,
Object childData) {
if (Flags.notificationsRedesignAppIcons() && mRow.isShowingAppIcon()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 32da6ff..ec04edb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -47,6 +47,7 @@
import android.net.Uri;
import android.os.Looper;
import android.os.Process;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -75,6 +76,7 @@
import com.android.systemui.recents.LauncherProxyService;
import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.settings.UserTracker;
+import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
@@ -809,6 +811,10 @@
* lock time.
*/
private boolean shouldShowSensitiveContentRedactedView(NotificationEntry ent) {
+ if (android.app.Flags.redactionOnLockscreenMetrics()) {
+ return shouldShowSensitiveContentRedactedViewWithLog(ent);
+ }
+
if (!LockscreenOtpRedaction.isEnabled()) {
return false;
}
@@ -817,6 +823,7 @@
return false;
}
+ long notificationTime = getEarliestNotificationTime(ent);
if (!mRedactOtpOnWifi.get()) {
if (mConnectedToWifi.get()) {
return false;
@@ -824,7 +831,7 @@
long lastWifiConnectTime = mLastWifiConnectionTime.get();
// If the device has connected to wifi since receiving the notification, do not redact
- if (ent.getSbn().getPostTime() < lastWifiConnectTime) {
+ if (notificationTime < lastWifiConnectTime) {
return false;
}
}
@@ -837,13 +844,87 @@
// when this notification arrived, do not redact
long latestTimeForRedaction = mLastLockTime.get() + mOtpRedactionRequiredLockTimeMs.get();
- if (ent.getSbn().getPostTime() < latestTimeForRedaction) {
+ if (notificationTime < latestTimeForRedaction) {
return false;
}
return true;
}
+ /*
+ * We show the sensitive content redaction view if
+ * 1. The feature is enabled
+ * 2. The notification has the `hasSensitiveContent` ranking variable set to true
+ * 3. The device is locked
+ * 4. The device is NOT connected to Wifi
+ * 5. The device has not connected to Wifi since receiving the notification
+ * 6. The notification arrived at least LOCK_TIME_FOR_SENSITIVE_REDACTION_MS after the last
+ * lock time.
+ *
+ * This version of the method logs a metric about the request.
+ */
+ private boolean shouldShowSensitiveContentRedactedViewWithLog(NotificationEntry ent) {
+ if (!LockscreenOtpRedaction.isEnabled()) {
+ return false;
+ }
+
+ if (ent.getRanking() == null || !ent.getRanking().hasSensitiveContent()) {
+ return false;
+ }
+
+ long notificationWhen = ent.getSbn().getNotification().getWhen();
+ long notificationTime = getEarliestNotificationTime(ent);
+ boolean locked = mLocked.get();
+ long lockTime = mLastLockTime.get();
+ boolean wifiConnected = mConnectedToWifi.get();
+ long wifiConnectionTime = mLastWifiConnectionTime.get();
+
+ boolean shouldRedact = true;
+ if (!locked) {
+ shouldRedact = false;
+ }
+
+ if (!mRedactOtpOnWifi.get()) {
+ if (wifiConnected) {
+ shouldRedact = false;
+ }
+
+ // If the device has connected to wifi since receiving the notification, do not redact
+ if (notificationTime < wifiConnectionTime) {
+ shouldRedact = false;
+ }
+ }
+
+ // If the lock screen was not already locked for at least mOtpRedactionRequiredLockTimeMs
+ // when this notification arrived, do not redact
+ long latestTimeForRedaction = lockTime + mOtpRedactionRequiredLockTimeMs.get();
+
+ if (notificationTime < latestTimeForRedaction) {
+ shouldRedact = false;
+ }
+
+ int whenAndEarliestDiff = clampLongToIntRange(notificationWhen - notificationTime);
+ int earliestAndLockDiff = clampLongToIntRange(lockTime - notificationTime);
+ int earliestAndWifiDiff = clampLongToIntRange(wifiConnectionTime - notificationTime);
+ SysUiStatsLog.write(SysUiStatsLog.OTP_NOTIFICATION_DISPLAYED, shouldRedact,
+ whenAndEarliestDiff, locked, earliestAndLockDiff, wifiConnected,
+ earliestAndWifiDiff);
+ return shouldRedact;
+ }
+
+ private int clampLongToIntRange(long toConvert) {
+ return (int) Math.min(Integer.MAX_VALUE, Math.max(Integer.MIN_VALUE, toConvert));
+ }
+
+ // Get the earliest time the user might have seen this notification. This is either the
+ // notification's "when" time, or the notification entry creation time
+ private long getEarliestNotificationTime(NotificationEntry notif) {
+ long notifWhenWallClock = notif.getSbn().getNotification().getWhen();
+ long creationTimeDelta = SystemClock.elapsedRealtime() - notif.getCreationTime();
+ long creationTimeWallClock = System.currentTimeMillis() - creationTimeDelta;
+ return Math.min(notifWhenWallClock, creationTimeWallClock);
+ }
+
private boolean packageHasVisibilityOverride(String key) {
if (mCommonNotifCollectionLazy.get() == null) {
Log.wtf(TAG, "mEntryManager was null!", new Throwable());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 240953d..18f4b4a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar;
import static com.android.systemui.Flags.mediaControlsUserInitiatedDeleteintent;
-import static com.android.systemui.Flags.notificationMediaManagerBackgroundExecution;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -116,12 +115,7 @@
if (DEBUG_MEDIA) {
Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata);
}
- if (notificationMediaManagerBackgroundExecution()) {
- mBackgroundExecutor.execute(() -> setMediaMetadata(metadata));
- } else {
- setMediaMetadata(metadata);
- }
-
+ mBackgroundExecutor.execute(() -> setMediaMetadata(metadata));
dispatchUpdateMediaMetaData();
}
};
@@ -283,11 +277,7 @@
public void addCallback(MediaListener callback) {
mMediaListeners.add(callback);
- if (notificationMediaManagerBackgroundExecution()) {
- mBackgroundExecutor.execute(() -> updateMediaMetaData(callback));
- } else {
- updateMediaMetaData(callback);
- }
+ mBackgroundExecutor.execute(() -> updateMediaMetaData(callback));
}
private void updateMediaMetaData(MediaListener callback) {
@@ -303,55 +293,13 @@
public void findAndUpdateMediaNotifications() {
// TODO(b/169655907): get the semi-filtered notifications for current user
Collection<NotificationEntry> allNotifications = mNotifPipeline.getAllNotifs();
- if (notificationMediaManagerBackgroundExecution()) {
- // Create new sbn list to be accessed in background thread.
- List<StatusBarNotification> statusBarNotifications = new ArrayList<>();
- for (NotificationEntry entry: allNotifications) {
- statusBarNotifications.add(entry.getSbn());
- }
- mBackgroundExecutor.execute(() -> findPlayingMediaNotification(statusBarNotifications));
- } else {
- findPlayingMediaNotification(allNotifications);
- }
- dispatchUpdateMediaMetaData();
- }
-
- /**
- * Find a notification and media controller associated with the playing media session, and
- * update this manager's internal state.
- * TODO(b/273443374) check this method
- */
- void findPlayingMediaNotification(@NonNull Collection<NotificationEntry> allNotifications) {
- // Promote the media notification with a controller in 'playing' state, if any.
- NotificationEntry mediaNotification = null;
- MediaController controller = null;
+ // Create new sbn list to be accessed in background thread.
+ List<StatusBarNotification> statusBarNotifications = new ArrayList<>();
for (NotificationEntry entry : allNotifications) {
- Notification notif = entry.getSbn().getNotification();
- if (notif.isMediaNotification()) {
- final MediaSession.Token token =
- entry.getSbn().getNotification().extras.getParcelable(
- Notification.EXTRA_MEDIA_SESSION, MediaSession.Token.class);
- if (token != null) {
- MediaController aController = new MediaController(mContext, token);
- if (PlaybackState.STATE_PLAYING
- == getMediaControllerPlaybackState(aController)) {
- if (DEBUG_MEDIA) {
- Log.v(TAG, "DEBUG_MEDIA: found mediastyle controller matching "
- + entry.getSbn().getKey());
- }
- mediaNotification = entry;
- controller = aController;
- break;
- }
- }
- }
+ statusBarNotifications.add(entry.getSbn());
}
-
- StatusBarNotification statusBarNotification = null;
- if (mediaNotification != null) {
- statusBarNotification = mediaNotification.getSbn();
- }
- setUpControllerAndKey(controller, statusBarNotification);
+ mBackgroundExecutor.execute(() -> findPlayingMediaNotification(statusBarNotifications));
+ dispatchUpdateMediaMetaData();
}
/**
@@ -415,11 +363,7 @@
}
public void clearCurrentMediaNotification() {
- if (notificationMediaManagerBackgroundExecution()) {
- mBackgroundExecutor.execute(this::clearMediaNotification);
- } else {
- clearMediaNotification();
- }
+ mBackgroundExecutor.execute(this::clearMediaNotification);
}
private void clearMediaNotification() {
@@ -429,11 +373,7 @@
private void dispatchUpdateMediaMetaData() {
ArrayList<MediaListener> callbacks = new ArrayList<>(mMediaListeners);
- if (notificationMediaManagerBackgroundExecution()) {
- mBackgroundExecutor.execute(() -> updateMediaMetaData(callbacks));
- } else {
- updateMediaMetaData(callbacks);
- }
+ mBackgroundExecutor.execute(() -> updateMediaMetaData(callbacks));
}
private void updateMediaMetaData(List<MediaListener> callbacks) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index 84266e8..3db0048 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -38,13 +38,13 @@
import com.android.systemui.Flags.spatialModelAppPushback
import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.shade.ShadeExpansionChangeEvent
import com.android.systemui.shade.ShadeExpansionListener
+import com.android.systemui.shared.Flags.ambientAod
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK
import com.android.systemui.statusbar.phone.DozeParameters
@@ -53,11 +53,8 @@
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.statusbar.policy.SplitShadeStateController
import com.android.systemui.util.WallpaperController
-import com.android.systemui.wallpapers.domain.interactor.WallpaperInteractor
import com.android.systemui.window.domain.interactor.WindowRootViewBlurInteractor
import com.android.wm.shell.appzoomout.AppZoomOut
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
import java.io.PrintWriter
import java.util.Optional
import javax.inject.Inject
@@ -79,14 +76,12 @@
private val keyguardInteractor: KeyguardInteractor,
private val choreographer: Choreographer,
private val wallpaperController: WallpaperController,
- private val wallpaperInteractor: WallpaperInteractor,
private val notificationShadeWindowController: NotificationShadeWindowController,
private val dozeParameters: DozeParameters,
@ShadeDisplayAware private val context: Context,
private val splitShadeStateController: SplitShadeStateController,
private val windowRootViewBlurInteractor: WindowRootViewBlurInteractor,
private val appZoomOutOptional: Optional<AppZoomOut>,
- @Application private val applicationScope: CoroutineScope,
dumpManager: DumpManager,
configurationController: ConfigurationController,
) : ShadeExpansionListener, Dumpable {
@@ -115,8 +110,6 @@
private var prevTimestamp: Long = -1
private var prevShadeDirection = 0
private var prevShadeVelocity = 0f
- private var prevDozeAmount: Float = 0f
- @VisibleForTesting var wallpaperSupportsAmbientMode: Boolean = false
// tracks whether app launch transition is in progress. This involves two independent factors
// that control blur, shade expansion and app launch animation from outside sysui.
// They can complete out of order, this flag will be reset by the animation that finishes later.
@@ -226,15 +219,7 @@
}
/** Blur radius of the wake-up animation on this frame. */
- private var wakeBlurRadius = 0f
- set(value) {
- if (field == value) return
- field = value
- scheduleUpdate()
- }
-
- /** Blur radius of the unlock animation on this frame. */
- private var unlockBlurRadius = 0f
+ private var wakeAndUnlockBlurRadius = 0f
set(value) {
if (field == value) return
field = value
@@ -261,16 +246,14 @@
ShadeInterpolation.getNotificationScrimAlpha(qsPanelExpansion) * shadeExpansion
combinedBlur = max(combinedBlur, blurUtils.blurRadiusOfRatio(qsExpandedRatio))
combinedBlur = max(combinedBlur, blurUtils.blurRadiusOfRatio(transitionToFullShadeProgress))
- var shadeRadius = max(combinedBlur, max(wakeBlurRadius, unlockBlurRadius))
+ var shadeRadius = max(combinedBlur, wakeAndUnlockBlurRadius)
if (areBlursDisabledForAppLaunch || blursDisabledForUnlock) {
shadeRadius = 0f
}
var blur = shadeRadius.toInt()
- // If the blur comes from waking up, we don't want to zoom out the background
- val zoomOut =
- if (shadeRadius != wakeBlurRadius) blurRadiusToZoomOut(blurRadius = shadeRadius) else 0f
+ val zoomOut = blurRadiusToZoomOut(blurRadius = shadeRadius)
// Make blur be 0 if it is necessary to stop blur effect.
if (scrimsVisible) {
if (!Flags.notificationShadeBlur()) {
@@ -355,14 +338,14 @@
startDelay = keyguardStateController.keyguardFadingAwayDelay
interpolator = Interpolators.FAST_OUT_SLOW_IN
addUpdateListener { animation: ValueAnimator ->
- unlockBlurRadius =
+ wakeAndUnlockBlurRadius =
blurUtils.blurRadiusOfRatio(animation.animatedValue as Float)
}
addListener(
object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
keyguardAnimator = null
- unlockBlurRadius = 0f
+ wakeAndUnlockBlurRadius = 0f
}
}
)
@@ -398,20 +381,15 @@
}
override fun onDozeAmountChanged(linear: Float, eased: Float) {
- prevDozeAmount = eased
- updateWakeBlurRadius(prevDozeAmount)
+ wakeAndUnlockBlurRadius =
+ if (ambientAod()) {
+ 0f
+ } else {
+ blurUtils.blurRadiusOfRatio(eased)
+ }
}
}
- private fun updateWakeBlurRadius(ratio: Float) {
- wakeBlurRadius =
- if (!wallpaperSupportsAmbientMode) {
- 0f
- } else {
- blurUtils.blurRadiusOfRatio(ratio)
- }
- }
-
init {
dumpManager.registerCriticalDumpable(javaClass.name, this)
if (WAKE_UP_ANIMATION_ENABLED) {
@@ -433,12 +411,6 @@
}
}
)
- applicationScope.launch {
- wallpaperInteractor.wallpaperSupportsAmbientMode.collect { supported ->
- wallpaperSupportsAmbientMode = supported
- updateWakeBlurRadius(prevDozeAmount)
- }
- }
initBlurListeners()
}
@@ -613,8 +585,7 @@
it.println("shouldApplyShadeBlur: ${shouldApplyShadeBlur()}")
it.println("shadeAnimation: ${shadeAnimation.radius}")
it.println("brightnessMirrorRadius: ${brightnessMirrorSpring.radius}")
- it.println("wakeBlur: $wakeBlurRadius")
- it.println("unlockBlur: $wakeBlurRadius")
+ it.println("wakeAndUnlockBlur: $wakeAndUnlockBlurRadius")
it.println("blursDisabledForAppLaunch: $blursDisabledForAppLaunch")
it.println("appLaunchTransitionIsInProgress: $appLaunchTransitionIsInProgress")
it.println("qsPanelExpansion: $qsPanelExpansion")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
index 85fad42..50cf015 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
@@ -89,6 +89,9 @@
/** Sets the state of whether the glanceable hub is showing or not. */
default void setGlanceableHubShowing(boolean showing) {}
+ /** Sets the state of whether the glanceable hub can change with user's orientation or not. */
+ default void setGlanceableHubOrientationAware(boolean isOrientationAware) {}
+
/** Sets the state of whether the backdrop is showing or not. */
default void setBackdropShowing(boolean showing) {}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
index f466278..be3afad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
@@ -144,7 +144,7 @@
} else {
OngoingActivityChipModel.ClickBehavior.ExpandAction(
onClick = { expandable ->
- StatusBarChipsModernization.assertInNewMode()
+ StatusBarChipsModernization.unsafeAssertInNewMode()
val animationController =
expandable.activityTransitionController(
Cuj.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt
index edb4418..35e622b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt
@@ -73,7 +73,7 @@
_promotedNotificationChipTapEvent.asSharedFlow()
suspend fun onPromotedNotificationChipTapped(key: String) {
- StatusBarNotifChips.assertInNewMode()
+ StatusBarNotifChips.unsafeAssertInNewMode()
_promotedNotificationChipTapEvent.emit(key)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/shared/StatusBarNotifChips.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/shared/StatusBarNotifChips.kt
index 47ffbaf..947e93c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/shared/StatusBarNotifChips.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/shared/StatusBarNotifChips.kt
@@ -50,7 +50,7 @@
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+ inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
index 994357f..2fe6270 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
@@ -71,7 +71,7 @@
private fun NotificationChipModel.toActivityChipModel(
headsUpState: TopPinnedState
): OngoingActivityChipModel.Active {
- StatusBarNotifChips.assertInNewMode()
+ StatusBarNotifChips.unsafeAssertInNewMode()
val contentDescription = getContentDescription(this.appName)
val icon =
if (this.statusBarChipIconView != null) {
@@ -81,7 +81,7 @@
contentDescription,
)
} else {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(
this.key,
contentDescription,
@@ -103,7 +103,7 @@
}
val clickBehavior =
OngoingActivityChipModel.ClickBehavior.ShowHeadsUpNotification({
- StatusBarChipsModernization.assertInNewMode()
+ StatusBarChipsModernization.unsafeAssertInNewMode()
clickListener.invoke()
})
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
index 20dec11..02e4ba4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
@@ -202,7 +202,7 @@
)
}
is OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon -> {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
val iconView = fetchStatusBarIconView(iconViewStore, icon)
if (iconView == null) {
// This means that the notification key doesn't exist anymore.
@@ -223,7 +223,7 @@
iconViewStore: IconViewStore?,
icon: OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon,
): StatusBarIconView? {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
if (iconViewStore == null) {
throw IllegalStateException("Store should always be non-null when flag is enabled.")
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt
index 95e454a..e8ab396 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt
@@ -195,7 +195,7 @@
StatusBarIcon(colors, viewModel.impl.notification?.key, modifier) { viewModel.impl }
}
is OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon -> {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
check(iconViewStore != null)
StatusBarIcon(colors, viewModel.notificationKey, modifier) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
index cf0342b..0cfc321 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
@@ -148,7 +148,7 @@
shouldAnimate,
) {
init {
- StatusBarNotifChips.assertInNewMode()
+ StatusBarNotifChips.unsafeAssertInNewMode()
}
override val logName = "Active.ShortTimeDelta"
@@ -227,7 +227,7 @@
val contentDescription: ContentDescription,
) : ChipIcon {
init {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt
index a978c04..229d1a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt
@@ -71,7 +71,7 @@
tag: String,
): (Expandable) -> Unit {
return { expandable ->
- StatusBarChipsModernization.assertInNewMode()
+ StatusBarChipsModernization.unsafeAssertInNewMode()
logger.log(tag, LogLevel.INFO, {}, { "Chip clicked" })
val dialog = dialogDelegate.createDialog()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
index 1a30caf..eae2c25 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
@@ -350,6 +350,26 @@
.stateIn(scope, SharingStarted.Lazily, MultipleOngoingActivityChipsModelLegacy())
}
+ private val activeChips =
+ if (StatusBarChipsModernization.isEnabled) {
+ chips.map { it.active }
+ } else {
+ chipsLegacy.map {
+ val list = mutableListOf<OngoingActivityChipModel.Active>()
+ if (it.primary is OngoingActivityChipModel.Active) {
+ list.add(it.primary)
+ }
+ if (it.secondary is OngoingActivityChipModel.Active) {
+ list.add(it.secondary)
+ }
+ list
+ }
+ }
+
+ /** A flow modeling just the keys for the currently visible chips. */
+ val visibleChipKeys: Flow<List<String>> =
+ activeChips.map { chips -> chips.filter { !it.isHidden }.map { it.key } }
+
/**
* Sort the given chip [bundle] in order of priority, and divide the chips between active,
* overflow, and inactive (see [MultipleOngoingActivityChipsModel] for a description of each).
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/CommandQueueInitializer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/CommandQueueInitializer.kt
index d25ca28..601d105 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/CommandQueueInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/CommandQueueInitializer.kt
@@ -47,7 +47,7 @@
) : CoreStartable {
override fun start() {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
val resultPerDisplay: Map<String, RegisterStatusBarResult> =
try {
barService.registerStatusBarForAllDisplays(commandQueue)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarOrchestratorStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarOrchestratorStore.kt
index 7069791..7964950 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarOrchestratorStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarOrchestratorStore.kt
@@ -16,25 +16,19 @@
package com.android.systemui.statusbar.core
-import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.DisplayScopeRepository
-import com.android.systemui.display.data.repository.PerDisplayStoreImpl
import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
+import com.android.systemui.statusbar.data.repository.StatusBarPerDisplayStoreImpl
import com.android.systemui.statusbar.phone.AutoHideControllerStore
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
import com.android.systemui.statusbar.window.data.repository.StatusBarWindowStateRepositoryStore
-import dagger.Lazy
-import dagger.Module
-import dagger.Provides
-import dagger.multibindings.ClassKey
-import dagger.multibindings.IntoMap
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
-/** [PerDisplayStoreImpl] for providing display specific [StatusBarOrchestrator]. */
+/** [StatusBarPerDisplayStoreImpl] for providing display specific [StatusBarOrchestrator]. */
@SysUISingleton
class MultiDisplayStatusBarOrchestratorStore
@Inject
@@ -48,10 +42,14 @@
private val autoHideControllerStore: AutoHideControllerStore,
private val displayScopeRepository: DisplayScopeRepository,
private val statusBarWindowStateRepositoryStore: StatusBarWindowStateRepositoryStore,
-) : PerDisplayStoreImpl<StatusBarOrchestrator>(backgroundApplicationScope, displayRepository) {
+) :
+ StatusBarPerDisplayStoreImpl<StatusBarOrchestrator>(
+ backgroundApplicationScope,
+ displayRepository,
+ ) {
init {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
}
override fun createInstanceForDisplay(displayId: Int): StatusBarOrchestrator? {
@@ -79,21 +77,3 @@
instance.stop()
}
}
-
-@Module
-interface MultiDisplayStatusBarOrchestratorStoreModule {
-
- @Provides
- @SysUISingleton
- @IntoMap
- @ClassKey(MultiDisplayStatusBarOrchestratorStore::class)
- fun storeAsCoreStartable(
- multiDisplayLazy: Lazy<MultiDisplayStatusBarOrchestratorStore>
- ): CoreStartable {
- return if (StatusBarConnectedDisplays.isEnabled) {
- multiDisplayLazy.get()
- } else {
- CoreStartable.NOP
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt
index dfb3763..30c4ca5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.core
import android.view.Display
-import android.view.IWindowManager
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
@@ -44,28 +43,21 @@
private val statusBarInitializerStore: StatusBarInitializerStore,
private val privacyDotWindowControllerStore: PrivacyDotWindowControllerStore,
private val lightBarControllerStore: LightBarControllerStore,
- private val windowManager: IWindowManager,
) : CoreStartable {
init {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
}
override fun start() {
applicationScope.launch {
- displayRepository.displays
+ displayRepository.displayIdsWithSystemDecorations
.pairwiseBy { previousDisplays, currentDisplays ->
currentDisplays - previousDisplays
}
- .onStart { emit(displayRepository.displays.value) }
+ .onStart { emit(displayRepository.displayIdsWithSystemDecorations.value) }
.collect { newDisplays ->
- newDisplays.forEach {
- // TODO(b/393191204): Split navbar, status bar, etc. functionality
- // from WindowManager#shouldShowSystemDecors.
- if (windowManager.shouldShowSystemDecors(it.displayId)) {
- createAndStartComponentsForDisplay(it.displayId)
- }
- }
+ newDisplays.forEach { createAndStartComponentsForDisplay(it) }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/NewStatusBarIcons.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/NewStatusBarIcons.kt
index 402881d..9e1686a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/NewStatusBarIcons.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/NewStatusBarIcons.kt
@@ -49,7 +49,7 @@
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+ inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarConnectedDisplays.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarConnectedDisplays.kt
index 06474b0..cb1827d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarConnectedDisplays.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarConnectedDisplays.kt
@@ -50,7 +50,7 @@
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+ inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt
index de6cd07..48955cc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt
@@ -20,11 +20,11 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.PerDisplayStore
-import com.android.systemui.display.data.repository.PerDisplayStoreImpl
import com.android.systemui.display.data.repository.SingleDisplayStore
import com.android.systemui.statusbar.data.repository.DarkIconDispatcherStore
import com.android.systemui.statusbar.data.repository.StatusBarConfigurationControllerStore
import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
+import com.android.systemui.statusbar.data.repository.StatusBarPerDisplayStoreImpl
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -45,10 +45,13 @@
private val darkIconDispatcherStore: DarkIconDispatcherStore,
) :
StatusBarInitializerStore,
- PerDisplayStoreImpl<StatusBarInitializer>(backgroundApplicationScope, displayRepository) {
+ StatusBarPerDisplayStoreImpl<StatusBarInitializer>(
+ backgroundApplicationScope,
+ displayRepository,
+ ) {
init {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
}
override fun createInstanceForDisplay(displayId: Int): StatusBarInitializer? {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt
index 73ada54..c4c25c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt
@@ -146,7 +146,7 @@
/** Starts status bar orchestration. To be called when status bar is created. */
fun start() {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
startJob =
coroutineScope
// Perform animations on the main thread to prevent crashes.
@@ -280,7 +280,7 @@
* Called when the [StatusBarOrchestrator] should stop doing any work and clean up if needed.
*/
fun stop() {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
dumpManager.unregisterDumpable(dumpableName)
startJob?.cancel()
startJob = null
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarRootModernization.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarRootModernization.kt
index 3c30f3c..4ee49d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarRootModernization.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarRootModernization.kt
@@ -52,7 +52,7 @@
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+ inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/DarkIconDispatcherStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/DarkIconDispatcherStore.kt
index 041f3c8..f353c01 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/DarkIconDispatcherStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/DarkIconDispatcherStore.kt
@@ -24,7 +24,6 @@
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
import com.android.systemui.display.data.repository.PerDisplayStore
-import com.android.systemui.display.data.repository.PerDisplayStoreImpl
import com.android.systemui.display.data.repository.SingleDisplayStore
import com.android.systemui.plugins.DarkIconDispatcher
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
@@ -59,10 +58,13 @@
private val displayWindowPropertiesRepository: DisplayWindowPropertiesRepository,
) :
SysuiDarkIconDispatcherStore,
- PerDisplayStoreImpl<SysuiDarkIconDispatcher>(backgroundApplicationScope, displayRepository) {
+ StatusBarPerDisplayStoreImpl<SysuiDarkIconDispatcher>(
+ backgroundApplicationScope,
+ displayRepository,
+ ) {
init {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
}
override fun createInstanceForDisplay(displayId: Int): SysuiDarkIconDispatcher? {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/LightBarControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/LightBarControllerStore.kt
index c629d10..3c0d6c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/LightBarControllerStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/LightBarControllerStore.kt
@@ -22,7 +22,6 @@
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.DisplayScopeRepository
import com.android.systemui.display.data.repository.PerDisplayStore
-import com.android.systemui.display.data.repository.PerDisplayStoreImpl
import com.android.systemui.statusbar.phone.LightBarController
import com.android.systemui.statusbar.phone.LightBarControllerImpl
import dagger.Binds
@@ -47,7 +46,10 @@
private val darkIconDispatcherStore: DarkIconDispatcherStore,
) :
LightBarControllerStore,
- PerDisplayStoreImpl<LightBarController>(backgroundApplicationScope, displayRepository) {
+ StatusBarPerDisplayStoreImpl<LightBarController>(
+ backgroundApplicationScope,
+ displayRepository,
+ ) {
override fun createInstanceForDisplay(displayId: Int): LightBarController? {
val darkIconDispatcher = darkIconDispatcherStore.forDisplay(displayId) ?: return null
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotViewControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotViewControllerStore.kt
index d48c94b..32dc840 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotViewControllerStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotViewControllerStore.kt
@@ -22,7 +22,6 @@
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.DisplayScopeRepository
import com.android.systemui.display.data.repository.PerDisplayStore
-import com.android.systemui.display.data.repository.PerDisplayStoreImpl
import com.android.systemui.display.data.repository.SingleDisplayStore
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.events.PrivacyDotViewController
@@ -50,7 +49,10 @@
private val contentInsetsProviderStore: StatusBarContentInsetsProviderStore,
) :
PrivacyDotViewControllerStore,
- PerDisplayStoreImpl<PrivacyDotViewController>(backgroundApplicationScope, displayRepository) {
+ StatusBarPerDisplayStoreImpl<PrivacyDotViewController>(
+ backgroundApplicationScope,
+ displayRepository,
+ ) {
override fun createInstanceForDisplay(displayId: Int): PrivacyDotViewController? {
val configurationController =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStore.kt
index 86c3c48..7fc5e8a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStore.kt
@@ -25,7 +25,6 @@
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
import com.android.systemui.display.data.repository.PerDisplayStore
-import com.android.systemui.display.data.repository.PerDisplayStoreImpl
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.events.PrivacyDotWindowController
import dagger.Binds
@@ -52,10 +51,13 @@
private val viewCaptureAwareWindowManagerFactory: ViewCaptureAwareWindowManager.Factory,
) :
PrivacyDotWindowControllerStore,
- PerDisplayStoreImpl<PrivacyDotWindowController>(backgroundApplicationScope, displayRepository) {
+ StatusBarPerDisplayStoreImpl<PrivacyDotWindowController>(
+ backgroundApplicationScope,
+ displayRepository,
+ ) {
init {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
}
override fun createInstanceForDisplay(displayId: Int): PrivacyDotWindowController? {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarConfigurationControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarConfigurationControllerStore.kt
index 38cea83..36b11ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarConfigurationControllerStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarConfigurationControllerStore.kt
@@ -24,7 +24,6 @@
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
import com.android.systemui.display.data.repository.PerDisplayStore
-import com.android.systemui.display.data.repository.PerDisplayStoreImpl
import com.android.systemui.display.data.repository.SingleDisplayStore
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
@@ -53,13 +52,13 @@
private val configurationControllerFactory: ConfigurationControllerImpl.Factory,
) :
StatusBarConfigurationControllerStore,
- PerDisplayStoreImpl<StatusBarConfigurationController>(
+ StatusBarPerDisplayStoreImpl<StatusBarConfigurationController>(
backgroundApplicationScope,
displayRepository,
) {
init {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
}
override fun createInstanceForDisplay(displayId: Int): StatusBarConfigurationController? {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarContentInsetsProviderStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarContentInsetsProviderStore.kt
index 5ea1211..3cd4b5c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarContentInsetsProviderStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarContentInsetsProviderStore.kt
@@ -25,7 +25,6 @@
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
import com.android.systemui.display.data.repository.PerDisplayStore
-import com.android.systemui.display.data.repository.PerDisplayStoreImpl
import com.android.systemui.display.data.repository.SingleDisplayStore
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.layout.StatusBarContentInsetsProvider
@@ -54,7 +53,7 @@
private val cameraProtectionLoaderFactory: CameraProtectionLoaderImpl.Factory,
) :
StatusBarContentInsetsProviderStore,
- PerDisplayStoreImpl<StatusBarContentInsetsProvider>(
+ StatusBarPerDisplayStoreImpl<StatusBarContentInsetsProvider>(
backgroundApplicationScope,
displayRepository,
) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt
index 143e998..5980c9c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt
@@ -22,7 +22,6 @@
import com.android.systemui.dagger.qualifiers.DisplayId
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.PerDisplayStore
-import com.android.systemui.display.data.repository.PerDisplayStoreImpl
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.core.StatusBarInitializer
import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent
@@ -48,13 +47,13 @@
displayRepository: DisplayRepository,
) :
StatusBarModeRepositoryStore,
- PerDisplayStoreImpl<StatusBarModePerDisplayRepository>(
+ StatusBarPerDisplayStoreImpl<StatusBarModePerDisplayRepository>(
backgroundApplicationScope,
displayRepository,
) {
init {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
}
override fun createInstanceForDisplay(displayId: Int): StatusBarModePerDisplayRepository {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayStoreImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayStoreImpl.kt
new file mode 100644
index 0000000..e873c02
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayStoreImpl.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.data.repository
+
+import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.data.repository.PerDisplayStoreImpl
+import com.android.systemui.util.kotlin.pairwiseBy
+import kotlinx.coroutines.CoroutineScope
+
+/** [PerDisplayStoreImpl] for Status Bar related classes. */
+abstract class StatusBarPerDisplayStoreImpl<T>(
+ @Background private val backgroundApplicationScope: CoroutineScope,
+ private val displayRepository: DisplayRepository,
+) : PerDisplayStoreImpl<T>(backgroundApplicationScope, displayRepository) {
+
+ override fun start() {
+ val instanceType = instanceClass.simpleName
+ backgroundApplicationScope.launch("StatusBarPerDisplayStore#<$instanceType>start") {
+ displayRepository.displayIdsWithSystemDecorations
+ .pairwiseBy { previousDisplays, currentDisplays ->
+ previousDisplays - currentDisplays
+ }
+ .collect { removedDisplayIds ->
+ removedDisplayIds.forEach { removedDisplayId ->
+ val removedInstance = perDisplayInstances.remove(removedDisplayId)
+ removedInstance?.let { onDisplayRemovalAction(it) }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStore.kt
index ffc1255..7b9ea69 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStore.kt
@@ -23,7 +23,6 @@
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
import com.android.systemui.display.data.repository.PerDisplayStore
-import com.android.systemui.display.data.repository.PerDisplayStoreImpl
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.events.SystemEventChipAnimationController
import com.android.systemui.statusbar.events.SystemEventChipAnimationControllerImpl
@@ -53,13 +52,13 @@
private val statusBarContentInsetsProviderStore: StatusBarContentInsetsProviderStore,
) :
SystemEventChipAnimationControllerStore,
- PerDisplayStoreImpl<SystemEventChipAnimationController>(
+ StatusBarPerDisplayStoreImpl<SystemEventChipAnimationController>(
backgroundApplicationScope,
displayRepository,
) {
init {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
}
override fun createInstanceForDisplay(displayId: Int): SystemEventChipAnimationController? {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationController.kt
index 4b9721e..b881c92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationController.kt
@@ -37,7 +37,7 @@
) : SystemEventChipAnimationController {
init {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
}
override fun prepareChipAnimation(viewCreator: ViewCreator) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
index 63410d746..3200ca1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
@@ -190,7 +190,7 @@
}
override fun stop() {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
contentInsetsProvider.removeCallback(insetsChangedListener)
configurationController.removeCallback(configurationListener)
stateController.removeCallback(statusBarStateListener)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/ui/view/BatteryStatusEventComposeChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/ui/view/BatteryStatusEventComposeChip.kt
index a90e3ff..05d3256 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/ui/view/BatteryStatusEventComposeChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/ui/view/BatteryStatusEventComposeChip.kt
@@ -54,7 +54,7 @@
get() = composeInner
init {
- NewStatusBarIcons.assertInNewMode()
+ NewStatusBarIcons.unsafeAssertInNewMode()
inflate(context, R.layout.status_bar_event_chip_compose, this)
roundedContainer = requireViewById(R.id.rounded_container)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModel.kt
index 90f97df..ec65087 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModel.kt
@@ -17,51 +17,51 @@
package com.android.systemui.statusbar.featurepods.media.ui.viewmodel
import android.content.Context
+import androidx.compose.runtime.getValue
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.statusbar.featurepods.media.domain.interactor.MediaControlChipInteractor
import com.android.systemui.statusbar.featurepods.media.shared.model.MediaControlChipModel
import com.android.systemui.statusbar.featurepods.popups.shared.model.HoverBehavior
import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipId
import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel
import com.android.systemui.statusbar.featurepods.popups.ui.viewmodel.StatusBarPopupChipViewModel
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
/**
* [StatusBarPopupChipViewModel] for a media control chip in the status bar. This view model is
* responsible for converting the [MediaControlChipModel] to a [PopupChipModel] that can be used to
* display a media control chip.
*/
-@SysUISingleton
class MediaControlChipViewModel
-@Inject
+@AssistedInject
constructor(
- @Background private val backgroundScope: CoroutineScope,
@Application private val applicationContext: Context,
mediaControlChipInteractor: MediaControlChipInteractor,
-) : StatusBarPopupChipViewModel {
-
+) : StatusBarPopupChipViewModel, ExclusiveActivatable() {
+ private val hydrator: Hydrator = Hydrator("MediaControlChipViewModel.hydrator")
/**
- * A [StateFlow] of the current [PopupChipModel]. This flow emits a new [PopupChipModel]
+ * A snapshot [State] of the current [PopupChipModel]. This emits a new [PopupChipModel]
* whenever the underlying [MediaControlChipModel] changes.
*/
- override val chip: StateFlow<PopupChipModel> =
- mediaControlChipInteractor.mediaControlChipModel
- .map { mediaControlChipModel -> toPopupChipModel(mediaControlChipModel) }
- .stateIn(
- backgroundScope,
- SharingStarted.WhileSubscribed(),
- PopupChipModel.Hidden(PopupChipId.MediaControl),
- )
+ override val chip: PopupChipModel by
+ hydrator.hydratedStateOf(
+ traceName = "chip",
+ initialValue = PopupChipModel.Hidden(PopupChipId.MediaControl),
+ source =
+ mediaControlChipInteractor.mediaControlChipModel.map { model ->
+ toPopupChipModel(model)
+ },
+ )
+
+ override suspend fun onActivated(): Nothing {
+ hydrator.activate()
+ }
private fun toPopupChipModel(model: MediaControlChipModel?): PopupChipModel {
if (model == null || model.songName.isNullOrEmpty()) {
@@ -96,7 +96,12 @@
return HoverBehavior.Button(
icon = Icon.Loaded(drawable = icon, contentDescription = contentDescription),
- onIconPressed = { backgroundScope.launch { action.run() } },
+ onIconPressed = { action.run() },
)
}
+
+ @AssistedFactory
+ interface Factory {
+ fun create(): MediaControlChipViewModel
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/StatusBarPopupChips.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/StatusBarPopupChips.kt
index a9f72ff2..08a8960 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/StatusBarPopupChips.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/StatusBarPopupChips.kt
@@ -50,7 +50,7 @@
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+ inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipViewModel.kt
index 5712be3..38f2413 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipViewModel.kt
@@ -16,14 +16,14 @@
package com.android.systemui.statusbar.featurepods.popups.ui.viewmodel
+import com.android.systemui.lifecycle.Activatable
import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel
-import kotlinx.coroutines.flow.StateFlow
/**
* Interface for a view model that knows the display requirements for a single type of status bar
* popup chip.
*/
-interface StatusBarPopupChipViewModel {
- /** A flow modeling the popup chip that should be shown (or not shown). */
- val chip: StateFlow<PopupChipModel>
+interface StatusBarPopupChipViewModel : Activatable {
+ /** A snapshot [State] modeling the popup chip that should be shown (or not shown). */
+ val chip: PopupChipModel
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModel.kt
index 33bf90d..35f1a99 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModel.kt
@@ -21,7 +21,6 @@
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import com.android.systemui.lifecycle.ExclusiveActivatable
-import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.statusbar.featurepods.media.ui.viewmodel.MediaControlChipViewModel
import com.android.systemui.statusbar.featurepods.popups.StatusBarPopupChips
import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipId
@@ -36,18 +35,17 @@
*/
class StatusBarPopupChipsViewModel
@AssistedInject
-constructor(mediaControlChip: MediaControlChipViewModel) : ExclusiveActivatable() {
- private val hydrator: Hydrator = Hydrator("StatusBarPopupChipsViewModel.hydrator")
+constructor(mediaControlChipFactory: MediaControlChipViewModel.Factory) : ExclusiveActivatable() {
+
+ private val mediaControlChip by lazy { mediaControlChipFactory.create() }
/** The ID of the current chip that is showing its popup, or `null` if no chip is shown. */
private var currentShownPopupChipId by mutableStateOf<PopupChipId?>(null)
- private val incomingPopupChipBundle: PopupChipBundle by
- hydrator.hydratedStateOf(
- traceName = "incomingPopupChipBundle",
- initialValue = PopupChipBundle(),
- source = mediaControlChip.chip.map { chip -> PopupChipBundle(media = chip) },
- )
+ private val incomingPopupChipBundle: PopupChipBundle by derivedStateOf {
+ val mediaChip = mediaControlChip.chip
+ PopupChipBundle(media = mediaChip)
+ }
val shownPopupChips: List<PopupChipModel.Shown> by derivedStateOf {
if (StatusBarPopupChips.isEnabled) {
@@ -66,7 +64,7 @@
}
override suspend fun onActivated(): Nothing {
- hydrator.activate()
+ mediaControlChip.activate()
}
private data class PopupChipBundle(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/headsup/shared/StatusBarNoHunBehavior.kt b/packages/SystemUI/src/com/android/systemui/statusbar/headsup/shared/StatusBarNoHunBehavior.kt
index c9024d9..7a985e7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/headsup/shared/StatusBarNoHunBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/headsup/shared/StatusBarNoHunBehavior.kt
@@ -50,7 +50,7 @@
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+ inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/layout/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/layout/StatusBarContentInsetsProvider.kt
index 9d5d87f..e588dd3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/layout/StatusBarContentInsetsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/layout/StatusBarContentInsetsProvider.kt
@@ -201,7 +201,7 @@
}
override fun stop() {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
configurationController.removeCallback(this)
dumpManager.unregisterDumpable(dumpableName)
commandRegistry.unregisterCommand(commandName)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModelStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModelStore.kt
index d2dccc4..d7e9bb2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModelStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/layout/ui/viewmodel/StatusBarContentInsetsViewModelStore.kt
@@ -21,10 +21,10 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.PerDisplayStore
-import com.android.systemui.display.data.repository.PerDisplayStoreImpl
import com.android.systemui.display.data.repository.SingleDisplayStore
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore
+import com.android.systemui.statusbar.data.repository.StatusBarPerDisplayStoreImpl
import dagger.Lazy
import dagger.Module
import dagger.Provides
@@ -45,7 +45,7 @@
private val statusBarContentInsetsProviderStore: StatusBarContentInsetsProviderStore,
) :
StatusBarContentInsetsViewModelStore,
- PerDisplayStoreImpl<StatusBarContentInsetsViewModel>(
+ StatusBarPerDisplayStoreImpl<StatusBarContentInsetsViewModel>(
backgroundApplicationScope,
displayRepository,
) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PhysicsPropertyAnimator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PhysicsPropertyAnimator.kt
index 0a24d7a..68c13af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PhysicsPropertyAnimator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PhysicsPropertyAnimator.kt
@@ -88,8 +88,8 @@
@JvmStatic
fun createDefaultSpring(): SpringForce {
return SpringForce()
- .setStiffness(380f) // MEDIUM LOW STIFFNESS
- .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY) // LOW BOUNCINESS
+ .setStiffness(380f)
+ .setDampingRatio(0.68f);
}
@JvmStatic
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index 611e0ef..fdb8cd8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -27,10 +27,10 @@
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.notification.NotifPipelineFlags
import com.android.systemui.statusbar.notification.collection.GroupEntry
-import com.android.systemui.statusbar.notification.collection.PipelineEntry
import com.android.systemui.statusbar.notification.collection.NotifCollection
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.PipelineEntry
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
@@ -125,7 +125,7 @@
* Must be run on the main thread.
*/
private fun onPromotedNotificationChipTapEvent(key: String) {
- StatusBarNotifChips.assertInNewMode()
+ StatusBarNotifChips.unsafeAssertInNewMode()
val entry = notifCollection.getEntry(key)
if (entry == null) {
@@ -459,7 +459,12 @@
} else {
if (posted.isHeadsUpEntry) {
// We don't want this to be interrupting anymore, let's remove it
- hunMutator.removeNotification(posted.key, false /*removeImmediately*/)
+ // If the notification is pinned by the user, the only way a user can un-pin
+ // it is by tapping the status bar notification chip. Since that's a clear
+ // user action, we should remove the HUN immediately instead of waiting for
+ // any sort of minimum timeout.
+ val shouldRemoveImmediately = posted.isPinnedByUser
+ hunMutator.removeNotification(posted.key, shouldRemoveImmediately)
} else {
// Don't let the bind finish
cancelHeadsUpBind(posted.entry)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StabilizeHeadsUpGroup.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StabilizeHeadsUpGroup.kt
index a0e44bf..179a87d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StabilizeHeadsUpGroup.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StabilizeHeadsUpGroup.kt
@@ -50,7 +50,7 @@
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+ inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
index c5ae875..0d4207b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
@@ -162,7 +162,7 @@
@Override
public boolean isGroupExpanded(EntryAdapter entry) {
- NotificationBundleUi.assertInNewMode();
+ NotificationBundleUi.unsafeAssertInNewMode();
ExpandableNotificationRow parent = entry.getRow().getNotificationParent();
return mExpandedCollections.contains(entry)
|| (parent != null && mExpandedCollections.contains(parent.getEntryAdapter()));
@@ -170,7 +170,7 @@
@Override
public void setGroupExpanded(EntryAdapter groupRoot, boolean expanded) {
- NotificationBundleUi.assertInNewMode();
+ NotificationBundleUi.unsafeAssertInNewMode();
if (!groupRoot.isAttached()) {
if (expanded) {
Log.wtf(TAG, "Cannot expand group that is not attached");
@@ -192,7 +192,7 @@
@Override
public boolean toggleGroupExpansion(EntryAdapter groupRoot) {
- NotificationBundleUi.assertInNewMode();
+ NotificationBundleUi.unsafeAssertInNewMode();
setGroupExpanded(groupRoot, !isGroupExpanded(groupRoot));
return isGroupExpanded(groupRoot);
}
@@ -231,7 +231,7 @@
}
private void sendOnGroupExpandedChange(EntryAdapter entry, boolean expanded) {
- NotificationBundleUi.assertInNewMode();
+ NotificationBundleUi.unsafeAssertInNewMode();
for (OnGroupExpansionChangeListener listener : mOnGroupChangeListeners) {
listener.onGroupExpansionChange(entry.getRow(), expanded);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java
index aec0d70..66a4f9f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java
@@ -59,7 +59,7 @@
@Override
public boolean isGroupRoot(@NonNull EntryAdapter entry) {
- NotificationBundleUi.assertInNewMode();
+ NotificationBundleUi.unsafeAssertInNewMode();
return entry.isGroupRoot();
}
@@ -85,7 +85,7 @@
@Override
public boolean isChildInGroup(@NonNull EntryAdapter entry) {
- NotificationBundleUi.assertInNewMode();
+ NotificationBundleUi.unsafeAssertInNewMode();
// An entry is a child if it's not a group root or top level entry, but it is attached.
return entry.isAttached() && !entry.isGroupRoot() && !entry.isTopLevelEntry();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/shared/ModesEmptyShadeFix.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/shared/ModesEmptyShadeFix.kt
index f1fc275..d3a44f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/shared/ModesEmptyShadeFix.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/shared/ModesEmptyShadeFix.kt
@@ -50,7 +50,7 @@
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+ inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt
index 7e6c605..9bb9b9a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt
@@ -124,7 +124,7 @@
}
val footer: FooterMessageViewModel by lazy {
- ModesEmptyShadeFix.assertInNewMode()
+ ModesEmptyShadeFix.unsafeAssertInNewMode()
FooterMessageViewModel(
messageId = R.string.unlock_to_see_notif_text,
iconId = R.drawable.ic_friction_lock_closed,
@@ -133,7 +133,7 @@
}
val onClick: Flow<SettingsIntent> by lazy {
- ModesEmptyShadeFix.assertInNewMode()
+ ModesEmptyShadeFix.unsafeAssertInNewMode()
combine(
zenModeInteractor.modesHidingNotifications,
notificationSettingsInteractor.isNotificationHistoryEnabled,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/NotifRedesignFooter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/NotifRedesignFooter.kt
index 0307d90..dbe046f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/NotifRedesignFooter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/NotifRedesignFooter.kt
@@ -50,7 +50,7 @@
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+ inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt
index 8eca166..c401d82 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.notification.headsup
import android.os.Handler
-import android.util.Log
import androidx.annotation.VisibleForTesting
import com.android.internal.logging.UiEvent
import com.android.internal.logging.UiEventLogger
@@ -24,9 +23,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.notification.headsup.HeadsUpManagerImpl.HeadsUpEntry
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
-import com.android.systemui.util.Compile
import java.io.PrintWriter
import javax.inject.Inject
@@ -155,6 +154,7 @@
} else if (entry in nextMap) {
outcome = "update next"
nextMap[entry]?.add(runnable)
+ checkNextPinnedByUser(entry)?.let { outcome = "$outcome & $it" }
} else if (headsUpEntryShowing == null) {
outcome = "show now"
showNow(entry, arrayListOf(runnable))
@@ -166,17 +166,22 @@
outcome = "add next"
addToNext(entry, runnable)
- // Shorten headsUpEntryShowing display time
- val nextIndex = nextList.indexOf(entry)
- val isOnlyNextEntry = nextIndex == 0 && nextList.size == 1
- if (isOnlyNextEntry) {
- // HeadsUpEntry.updateEntry recursively calls AvalancheController#update
- // and goes to the isShowing case above
- headsUpEntryShowing!!.updateEntry(
- /* updatePostTime= */ false,
- /* updateEarliestRemovalTime= */ false,
- /* reason= */ "shorten duration of previously-last HUN",
- )
+ val nextIsPinnedByUserResult = checkNextPinnedByUser(entry)
+ if (nextIsPinnedByUserResult != null) {
+ outcome = "$outcome & $nextIsPinnedByUserResult"
+ } else {
+ // Shorten headsUpEntryShowing display time
+ val nextIndex = nextList.indexOf(entry)
+ val isOnlyNextEntry = nextIndex == 0 && nextList.size == 1
+ if (isOnlyNextEntry) {
+ // HeadsUpEntry.updateEntry recursively calls AvalancheController#update
+ // and goes to the isShowing case above
+ headsUpEntryShowing!!.updateEntry(
+ /* updatePostTime= */ false,
+ /* updateEarliestRemovalTime= */ false,
+ /* reason= */ "shorten duration of previously-last HUN",
+ )
+ }
}
}
outcome += getStateStr()
@@ -190,6 +195,28 @@
}
/**
+ * Checks if the given entry is requesting [PinnedStatus.PinnedByUser] status and makes the
+ * correct updates if needed.
+ *
+ * @return a string representing the outcome, or null if nothing changed.
+ */
+ private fun checkNextPinnedByUser(entry: HeadsUpEntry): String? {
+ if (
+ StatusBarNotifChips.isEnabled &&
+ entry.requestedPinnedStatus == PinnedStatus.PinnedByUser
+ ) {
+ val string = "next is PinnedByUser"
+ headsUpEntryShowing?.updateEntry(
+ /* updatePostTime= */ false,
+ /* updateEarliestRemovalTime= */ false,
+ /* reason= */ string,
+ )
+ return string
+ }
+ return null
+ }
+
+ /**
* Run or ignore Runnable for given HeadsUpEntry. If entry was never shown, ignore and delete
* all Runnables associated with that entry.
*/
@@ -243,19 +270,22 @@
outcome = "remove showing. ${getStateStr()}"
} else {
runnable.run()
- outcome = "run runnable for untracked HUN " +
+ outcome =
+ "run runnable for untracked HUN " +
"(was dropped or shown when AC was disabled). ${getStateStr()}"
}
headsUpManagerLogger.logAvalancheDelete(caller, isEnabled(), getKey(entry), outcome)
}
/**
- * Returns duration based on
+ * Returns how much longer the given entry should show based on:
* 1) Whether HeadsUpEntry is the last one tracked by AvalancheController
- * 2) The priority of the top HUN in the next batch Used by
- * BaseHeadsUpManager.HeadsUpEntry.calculateFinishTime to shorten display duration.
+ * 2) The priority of the top HUN in the next batch
+ *
+ * Used by [HeadsUpManagerImpl.HeadsUpEntry]'s finishTimeCalculator to shorten display duration.
*/
- fun getDurationMs(entry: HeadsUpEntry?, autoDismissMs: Int): Int {
+ fun getDuration(entry: HeadsUpEntry?, autoDismissMsValue: Int): RemainingDuration {
+ val autoDismissMs = RemainingDuration.UpdatedDuration(autoDismissMsValue)
if (!isEnabled()) {
// Use default duration, like we did before AvalancheController existed
return autoDismissMs
@@ -273,7 +303,11 @@
val thisKey = getKey(entry)
if (entryList.isEmpty()) {
headsUpManagerLogger.logAvalancheDuration(
- thisKey, autoDismissMs, "No avalanche HUNs, use default", nextKey = "")
+ thisKey,
+ autoDismissMs,
+ "No avalanche HUNs, use default",
+ nextKey = "",
+ )
return autoDismissMs
}
// entryList.indexOf(entry) returns -1 even when the entry is in entryList
@@ -285,28 +319,64 @@
}
if (thisEntryIndex == -1) {
headsUpManagerLogger.logAvalancheDuration(
- thisKey, autoDismissMs, "Untracked entry, use default", nextKey = "")
+ thisKey,
+ autoDismissMs,
+ "Untracked entry, use default",
+ nextKey = "",
+ )
return autoDismissMs
}
val nextEntryIndex = thisEntryIndex + 1
if (nextEntryIndex >= entryList.size) {
headsUpManagerLogger.logAvalancheDuration(
- thisKey, autoDismissMs, "Last entry, use default", nextKey = "")
+ thisKey,
+ autoDismissMs,
+ "Last entry, use default",
+ nextKey = "",
+ )
return autoDismissMs
}
val nextEntry = entryList[nextEntryIndex]
val nextKey = getKey(nextEntry)
+
+ if (
+ StatusBarNotifChips.isEnabled &&
+ nextEntry.requestedPinnedStatus == PinnedStatus.PinnedByUser
+ ) {
+ return RemainingDuration.HideImmediately.also {
+ headsUpManagerLogger.logAvalancheDuration(
+ thisKey,
+ duration = it,
+ "next is PinnedByUser",
+ nextKey,
+ )
+ }
+ }
if (nextEntry.compareNonTimeFields(entry) == -1) {
- headsUpManagerLogger.logAvalancheDuration(
- thisKey, 500, "LOWER priority than next: ", nextKey)
- return 500
+ return RemainingDuration.UpdatedDuration(500).also {
+ headsUpManagerLogger.logAvalancheDuration(
+ thisKey,
+ duration = it,
+ "LOWER priority than next: ",
+ nextKey,
+ )
+ }
} else if (nextEntry.compareNonTimeFields(entry) == 0) {
- headsUpManagerLogger.logAvalancheDuration(
- thisKey, 1000, "SAME priority as next: ", nextKey)
- return 1000
+ return RemainingDuration.UpdatedDuration(1000).also {
+ headsUpManagerLogger.logAvalancheDuration(
+ thisKey,
+ duration = it,
+ "SAME priority as next: ",
+ nextKey,
+ )
+ }
} else {
headsUpManagerLogger.logAvalancheDuration(
- thisKey, autoDismissMs, "HIGHER priority than next: ", nextKey)
+ thisKey,
+ autoDismissMs,
+ "HIGHER priority than next: ",
+ nextKey,
+ )
return autoDismissMs
}
}
@@ -377,11 +447,11 @@
}
private fun showNext() {
- headsUpManagerLogger.logAvalancheStage("show next", key = "")
+ headsUpManagerLogger.logAvalancheStage("show next", key = "")
headsUpEntryShowing = null
if (nextList.isEmpty()) {
- headsUpManagerLogger.logAvalancheStage("no more", key = "")
+ headsUpManagerLogger.logAvalancheStage("no more", key = "")
previousHunKey = ""
return
}
@@ -432,10 +502,12 @@
private fun getStateStr(): String {
return "\n[AC state]" +
- "\nshow: ${getKey(headsUpEntryShowing)}" +
- "\nprevious: $previousHunKey" +
- "\n$nextStr" +
- "\n[HeadsUpManagerImpl.mHeadsUpEntryMap] " + baseEntryMapStr() + "\n"
+ "\nshow: ${getKey(headsUpEntryShowing)}" +
+ "\nprevious: $previousHunKey" +
+ "\n$nextStr" +
+ "\n[HeadsUpManagerImpl.mHeadsUpEntryMap] " +
+ baseEntryMapStr() +
+ "\n"
}
private val nextStr: String
@@ -447,7 +519,7 @@
// This should never happen
val nextMapStr = nextMap.keys.joinToString("\n ") { getKey(it) }
return "next list (${nextList.size}):\n $nextListStr" +
- "\nnext map (${nextMap.size}):\n $nextMapStr"
+ "\nnext map (${nextMap.size}):\n $nextMapStr"
}
fun getKey(entry: HeadsUpEntry?): String {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpAnimationEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpAnimationEvent.kt
new file mode 100644
index 0000000..ab84896
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpAnimationEvent.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.statusbar.notification.row.ExpandableNotificationRow
+
+/** Models the data needed for a heads-up notification animation. */
+data class HeadsUpAnimationEvent(
+ /** The row corresponding to the heads-up notification. */
+ val row: ExpandableNotificationRow,
+ /**
+ * True if this notification should do a appearance animation, false if this notification should
+ * do a disappear animation.
+ */
+ val isHeadsUpAppearance: Boolean,
+ /** True if the status bar is showing a chip corresponding to this notification. */
+ val hasStatusBarChip: Boolean,
+)
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
index f9bd805..b5d7321 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpAnimator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpAnimator.kt
@@ -17,21 +17,27 @@
package com.android.systemui.statusbar.notification.headsup
import android.content.Context
+import com.android.internal.policy.SystemBarUtils
import com.android.systemui.res.R
+import com.android.systemui.statusbar.ui.SystemBarUtilsProxy
/**
* A class shared between [StackScrollAlgorithm] and [StackStateAnimator] to ensure all heads up
* animations use the same animation values.
+ *
+ * @param systemBarUtilsProxy optional utility class to provide the status bar height. Typically
+ * null in production code and non-null in tests.
*/
-class HeadsUpAnimator(context: Context) {
+class HeadsUpAnimator(context: Context, private val systemBarUtilsProxy: SystemBarUtilsProxy?) {
init {
- NotificationsHunSharedAnimationValues.assertInNewMode()
+ NotificationsHunSharedAnimationValues.unsafeAssertInNewMode()
}
var headsUpAppearHeightBottom: Int = 0
var stackTopMargin: Int = 0
private var headsUpAppearStartAboveScreen = context.fetchHeadsUpAppearStartAboveScreen()
+ private var statusBarHeight = fetchStatusBarHeight(context)
/**
* Returns the Y translation for a heads-up notification animation.
@@ -40,14 +46,20 @@
* animation. For a disappear animation, the returned Y translation should be the ending value
* of the animation.
*/
- fun getHeadsUpYTranslation(isHeadsUpFromBottom: Boolean): Int {
- NotificationsHunSharedAnimationValues.assertInNewMode()
+ fun getHeadsUpYTranslation(isHeadsUpFromBottom: Boolean, hasStatusBarChip: Boolean): Int {
+ NotificationsHunSharedAnimationValues.unsafeAssertInNewMode()
if (isHeadsUpFromBottom) {
// start from or end at the bottom of the screen
return headsUpAppearHeightBottom + headsUpAppearStartAboveScreen
}
+ if (hasStatusBarChip) {
+ // If this notification is also represented by a chip in the status bar, we don't want
+ // any HUN transitions to obscure that chip.
+ return statusBarHeight - stackTopMargin
+ }
+
// start from or end at the top of the screen
return -stackTopMargin - headsUpAppearStartAboveScreen
}
@@ -55,9 +67,15 @@
/** Should be invoked when resource values may have changed. */
fun updateResources(context: Context) {
headsUpAppearStartAboveScreen = context.fetchHeadsUpAppearStartAboveScreen()
+ statusBarHeight = fetchStatusBarHeight(context)
}
private fun Context.fetchHeadsUpAppearStartAboveScreen(): Int {
return this.resources.getDimensionPixelSize(R.dimen.heads_up_appear_y_above_screen)
}
+
+ private fun fetchStatusBarHeight(context: Context): Int {
+ return systemBarUtilsProxy?.getStatusBarHeight()
+ ?: SystemBarUtils.getStatusBarHeight(context)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java
index 5157e7a..ca94655 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java
@@ -46,7 +46,6 @@
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips;
-import com.android.systemui.statusbar.notification.collection.EntryAdapter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.coordinator.HeadsUpCoordinator;
import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener;
@@ -320,15 +319,17 @@
mLogger.logShowNotificationRequest(entry, isPinnedByUser);
+ PinnedStatus requestedPinnedStatus =
+ isPinnedByUser
+ ? PinnedStatus.PinnedByUser
+ : PinnedStatus.PinnedBySystem;
+ headsUpEntry.setRequestedPinnedStatus(requestedPinnedStatus);
+
Runnable runnable = () -> {
mLogger.logShowNotification(entry, isPinnedByUser);
// Add new entry and begin managing it
mHeadsUpEntryMap.put(entry.getKey(), headsUpEntry);
- PinnedStatus requestedPinnedStatus =
- isPinnedByUser
- ? PinnedStatus.PinnedByUser
- : PinnedStatus.PinnedBySystem;
onEntryAdded(headsUpEntry, requestedPinnedStatus);
// TODO(b/328390331) move accessibility events to the view layer
entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
@@ -1096,7 +1097,7 @@
@Override
public void setExpanded(@NonNull String entryKey, @NonNull ExpandableNotificationRow row,
boolean expanded) {
- NotificationBundleUi.assertInNewMode();
+ NotificationBundleUi.unsafeAssertInNewMode();
HeadsUpEntry headsUpEntry = getHeadsUpEntry(entryKey);
if (headsUpEntry != null && row.getPinnedStatus().isPinned()) {
headsUpEntry.setExpanded(expanded);
@@ -1289,10 +1290,17 @@
@Nullable private Runnable mCancelRemoveRunnable;
private boolean mGutsShownPinned;
+ /** The *current* pinned status of this HUN. */
private final MutableStateFlow<PinnedStatus> mPinnedStatus =
StateFlowKt.MutableStateFlow(PinnedStatus.NotPinned);
/**
+ * The *requested* pinned status of this HUN. {@link AvalancheController} uses this value to
+ * know if the current HUN needs to be removed so that a pinned-by-user HUN can show.
+ */
+ private PinnedStatus mRequestedPinnedStatus = PinnedStatus.NotPinned;
+
+ /**
* If the time this entry has been on was extended
*/
private boolean extended;
@@ -1352,6 +1360,20 @@
}
}
+ /** Sets what pinned status this HUN is requesting. */
+ void setRequestedPinnedStatus(PinnedStatus pinnedStatus) {
+ if (!StatusBarNotifChips.isEnabled() && pinnedStatus == PinnedStatus.PinnedByUser) {
+ Log.w(TAG, "PinnedByUser status not allowed if StatusBarNotifChips is disabled");
+ mRequestedPinnedStatus = PinnedStatus.NotPinned;
+ } else {
+ mRequestedPinnedStatus = pinnedStatus;
+ }
+ }
+
+ PinnedStatus getRequestedPinnedStatus() {
+ return mRequestedPinnedStatus;
+ }
+
@VisibleForTesting
void setRowPinnedStatus(PinnedStatus pinnedStatus) {
if (mEntry != null) mEntry.setRowPinnedStatus(pinnedStatus);
@@ -1410,11 +1432,29 @@
}
FinishTimeUpdater finishTimeCalculator = () -> {
- final long finishTime = calculateFinishTime();
+ RemainingDuration remainingDuration =
+ mAvalancheController.getDuration(this, mAutoDismissTime);
+
+ if (remainingDuration instanceof RemainingDuration.HideImmediately) {
+ /* Check if */ StatusBarNotifChips.isUnexpectedlyInLegacyMode();
+ return 0;
+ }
+
+ int remainingTimeoutMs;
+ if (isStickyForSomeTime()) {
+ remainingTimeoutMs = mStickyForSomeTimeAutoDismissTime;
+ } else {
+ remainingTimeoutMs =
+ ((RemainingDuration.UpdatedDuration) remainingDuration).getDuration();
+ }
+ final long duration = getRecommendedHeadsUpTimeoutMs(remainingTimeoutMs);
+ final long timeoutTimestamp =
+ mPostTime + duration + (extended ? mExtensionTime : 0);
+
final long now = mSystemClock.elapsedRealtime();
return NotificationThrottleHun.isEnabled()
- ? Math.max(finishTime, mEarliestRemovalTime) - now
- : Math.max(finishTime - now, mMinimumDisplayTimeDefault);
+ ? Math.max(timeoutTimestamp, mEarliestRemovalTime) - now
+ : Math.max(timeoutTimestamp - now, mMinimumDisplayTimeDefault);
};
scheduleAutoRemovalCallback(finishTimeCalculator, "updateEntry (not sticky)");
@@ -1696,21 +1736,6 @@
}
/**
- * @return When the notification should auto-dismiss itself, based on
- * {@link SystemClock#elapsedRealtime()}
- */
- private long calculateFinishTime() {
- int requestedTimeOutMs;
- if (isStickyForSomeTime()) {
- requestedTimeOutMs = mStickyForSomeTimeAutoDismissTime;
- } else {
- requestedTimeOutMs = mAvalancheController.getDurationMs(this, mAutoDismissTime);
- }
- final long duration = getRecommendedHeadsUpTimeoutMs(requestedTimeOutMs);
- return mPostTime + duration + (extended ? mExtensionTime : 0);
- }
-
- /**
* Get user-preferred or default timeout duration. The larger one will be returned.
* @return milliseconds before auto-dismiss
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerLogger.kt
index 388d357..00b05cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerLogger.kt
@@ -106,13 +106,23 @@
)
}
- fun logAvalancheDuration(thisKey: String, duration: Int, reason: String, nextKey: String) {
+ fun logAvalancheDuration(
+ thisKey: String,
+ duration: RemainingDuration,
+ reason: String,
+ nextKey: String,
+ ) {
+ val durationMs =
+ when (duration) {
+ is RemainingDuration.UpdatedDuration -> duration.duration
+ is RemainingDuration.HideImmediately -> 0
+ }
buffer.log(
TAG,
INFO,
{
str1 = thisKey
- int1 = duration
+ int1 = durationMs
str2 = reason
str3 = nextKey
},
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
index ca9d498..c538316 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/NotificationsHunSharedAnimationValues.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/NotificationsHunSharedAnimationValues.kt
@@ -50,7 +50,7 @@
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+ inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/RemainingDuration.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/RemainingDuration.kt
new file mode 100644
index 0000000..fd7f4e8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/RemainingDuration.kt
@@ -0,0 +1,26 @@
+/*
+ * 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
+
+/** Models how much longer a HUN should be displayed. */
+sealed interface RemainingDuration {
+ /** This HUN should be hidden immediately, regardless of any minimum time enforcements. */
+ data object HideImmediately : RemainingDuration
+
+ /** This HUN should hide after [duration] milliseconds have occurred. */
+ data class UpdatedDuration(val duration: Int) : RemainingDuration
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
index c512b43..294b8d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
@@ -90,12 +90,12 @@
ConcurrentHashMap<String, Job>()
fun addIconsUpdateListener(listener: OnIconUpdateRequiredListener) {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
onIconUpdateRequiredListeners += listener
}
fun removeIconsUpdateListener(listener: OnIconUpdateRequiredListener) {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
onIconUpdateRequiredListeners -= listener
}
@@ -140,7 +140,7 @@
*/
fun createSbIconView(context: Context, entry: NotificationEntry): StatusBarIconView =
traceSection("IconManager.createSbIconView") {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
val sbIcon = iconBuilder.createIconView(entry, context)
sbIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
@@ -200,7 +200,7 @@
/** Update the [StatusBarIconView] for the given [NotificationEntry]. */
fun updateSbIcon(entry: NotificationEntry, iconView: StatusBarIconView) =
traceSection("IconManager.updateSbIcon") {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
val (normalIconDescriptor, _) = getIconDescriptors(entry)
val notificationContentDescription =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUi.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUi.kt
index 6324219..5bf5766 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUi.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUi.kt
@@ -50,7 +50,7 @@
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+ inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiAod.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiAod.kt
index fa1f32c..8679975 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiAod.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiAod.kt
@@ -49,7 +49,7 @@
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+ inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiForceExpanded.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiForceExpanded.kt
index cb0d674..287e002 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiForceExpanded.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiForceExpanded.kt
@@ -50,7 +50,7 @@
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+ inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractor.kt
index 4bc6854..ec4ee45 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractor.kt
@@ -19,7 +19,6 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.Style
import com.android.systemui.util.kotlin.FlowDumperImpl
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -34,10 +33,7 @@
) : FlowDumperImpl(dumpManager) {
/** The content to show as the promoted notification on AOD */
val content: Flow<PromotedNotificationContentModel?> =
- promotedNotificationsInteractor.topPromotedNotificationContent
+ promotedNotificationsInteractor.aodPromotedNotification
- val isPresent: Flow<Boolean> =
- content
- .map { (it != null) && (it.style != Style.Ineligible) }
- .dumpWhileCollecting("isPresent")
+ val isPresent: Flow<Boolean> = content.map { it != null }.dumpWhileCollecting("isPresent")
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractor.kt
index 1015cfb..1abd48c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractor.kt
@@ -22,6 +22,8 @@
import com.android.systemui.statusbar.chips.notification.domain.interactor.StatusBarNotificationChipsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.Style.Ineligible
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -83,13 +85,13 @@
.map { list -> list.firstNotNullOfOrNull { it.promotedContent } }
.distinctUntilNewInstance()
- /** This is the top-most promoted notification, which should avoid regular changing. */
- val topPromotedNotificationContent: Flow<PromotedNotificationContentModel?> =
+ /** This is the AOD promoted notification, which should avoid regular changing. */
+ val aodPromotedNotification: Flow<PromotedNotificationContentModel?> =
combine(
topPromotedChipNotification,
activeNotificationsInteractor.topLevelRepresentativeNotifications,
) { topChipNotif, topLevelNotifs ->
- topChipNotif ?: topLevelNotifs.firstNotNullOfOrNull { it.promotedContent }
+ topChipNotif?.takeIfAodEligible() ?: topLevelNotifs.firstAodEligibleOrNull()
}
// #equals() can be a bit expensive on this object, but this flow will regularly try to
// emit the same immutable instance over and over, so just prevent that.
@@ -105,6 +107,16 @@
.distinctUntilChanged()
.flowOn(backgroundDispatcher)
+ private fun List<ActiveNotificationModel>.firstAodEligibleOrNull():
+ PromotedNotificationContentModel? {
+ return this.firstNotNullOfOrNull { it.promotedContent?.takeIfAodEligible() }
+ }
+
+ private fun PromotedNotificationContentModel.takeIfAodEligible():
+ PromotedNotificationContentModel? {
+ return this.takeUnless { it.style == Ineligible }
+ }
+
/**
* Returns flow where all subsequent repetitions of the same object instance are filtered out.
*/
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 3ef1fd2..987068d 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
@@ -99,6 +99,7 @@
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.StatusBarIconView;
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips;
import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
import com.android.systemui.statusbar.notification.ColorUpdateLogger;
import com.android.systemui.statusbar.notification.FeedbackIcon;
@@ -179,6 +180,7 @@
private boolean mIsFaded;
private boolean mIsPromotedOngoing = false;
+ private boolean mHasStatusBarChipDuringHeadsUpAnimation = false;
@Nullable
public ImageModelIndex mImageModelIndex = null;
@@ -947,7 +949,7 @@
@Nullable
public EntryAdapter getEntryAdapter() {
- NotificationBundleUi.assertInNewMode();
+ NotificationBundleUi.unsafeAssertInNewMode();
return mEntryAdapter;
}
@@ -1679,7 +1681,7 @@
view.setBackgroundTintColor(color);
}
if (notificationRowTransparency() && mBackgroundNormal != null) {
- if (NotificationBundleUi.isEnabled()) {
+ if (NotificationBundleUi.isEnabled() && mEntryAdapter != null) {
mBackgroundNormal.setBgIsColorized(
usesTransparentBackground() && mEntryAdapter.isColorized());
} else {
@@ -2074,7 +2076,7 @@
*/
public ExpandableNotificationRow(Context context, AttributeSet attrs, UserHandle user) {
this(context, attrs, userContextForEntry(context, user));
- NotificationBundleUi.assertInNewMode();
+ NotificationBundleUi.unsafeAssertInNewMode();
}
/**
@@ -2943,6 +2945,30 @@
setExpandable(!mIsPromotedOngoing);
}
+ /**
+ * Sets whether the status bar is showing a chip corresponding to this notification.
+ *
+ * Only set when this notification's heads-up status changes since that's the only time it's
+ * relevant.
+ */
+ public void setHasStatusBarChipDuringHeadsUpAnimation(boolean hasStatusBarChip) {
+ if (StatusBarNotifChips.isUnexpectedlyInLegacyMode()) {
+ return;
+ }
+ mHasStatusBarChipDuringHeadsUpAnimation = hasStatusBarChip;
+ }
+
+ /**
+ * Returns true if the status bar is showing a chip corresponding to this notification during a
+ * heads-up appear or disappear animation.
+ *
+ * Note that this value is only set when this notification's heads-up status changes since
+ * that's the only time it's relevant.
+ */
+ public boolean hasStatusBarChipDuringHeadsUpAnimation() {
+ return StatusBarNotifChips.isEnabled() && mHasStatusBarChipDuringHeadsUpAnimation;
+ }
+
@Override
public void setClipToActualHeight(boolean clipToActualHeight) {
super.setClipToActualHeight(clipToActualHeight || isUserLocked());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/BundleHeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/BundleHeaderViewModel.kt
new file mode 100644
index 0000000..d02ae43
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/BundleHeaderViewModel.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.row.ui.viewmodel
+
+import android.graphics.drawable.Drawable
+import android.view.View
+import androidx.compose.animation.core.tween
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.MotionScheme
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
+import com.android.compose.animation.scene.SceneTransitionLayoutState
+import com.android.compose.animation.scene.transitions
+import com.android.systemui.notifications.ui.composable.row.BundleHeader
+import kotlinx.coroutines.CoroutineScope
+
+interface BundleHeaderViewModel {
+ val titleText: String
+ val numberOfChildren: Int?
+ val bundleIcon: Drawable?
+ val previewIcons: List<Drawable>
+
+ val state: SceneTransitionLayoutState
+
+ val hasUnreadMessages: Boolean
+ val backgroundDrawable: Drawable?
+
+ fun onHeaderClicked(scope: CoroutineScope)
+}
+
+class BundleHeaderViewModelImpl : BundleHeaderViewModel {
+ override var titleText by mutableStateOf("")
+ override var numberOfChildren by mutableStateOf<Int?>(1)
+ override var hasUnreadMessages by mutableStateOf(true)
+ override var bundleIcon by mutableStateOf<Drawable?>(null)
+ override var previewIcons by mutableStateOf(listOf<Drawable>())
+ override var backgroundDrawable by mutableStateOf<Drawable?>(null)
+
+ var onExpandClickListener: View.OnClickListener? = null
+
+ @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+ override var state: MutableSceneTransitionLayoutState =
+ MutableSceneTransitionLayoutState(
+ BundleHeader.Scenes.Collapsed,
+ MotionScheme.standard(),
+ transitions {
+ from(BundleHeader.Scenes.Collapsed, to = BundleHeader.Scenes.Expanded) {
+ spec = tween(500)
+ translate(BundleHeader.Elements.PreviewIcon3, x = 32.dp)
+ translate(BundleHeader.Elements.PreviewIcon2, x = 16.dp)
+ fade(BundleHeader.Elements.PreviewIcon1)
+ fade(BundleHeader.Elements.PreviewIcon2)
+ fade(BundleHeader.Elements.PreviewIcon3)
+ }
+ },
+ )
+
+ override fun onHeaderClicked(scope: CoroutineScope) {
+ val targetScene =
+ when (state.currentScene) {
+ BundleHeader.Scenes.Collapsed -> BundleHeader.Scenes.Expanded
+ BundleHeader.Scenes.Expanded -> BundleHeader.Scenes.Collapsed
+ else -> error("Unknown Scene")
+ }
+ state.setTargetScene(targetScene, scope)
+
+ onExpandClickListener?.onClick(null)
+ hasUnreadMessages = false
+ }
+}
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 4146a94..8176d2a 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
@@ -211,14 +211,28 @@
rightIconLP.setMarginEnd(horizontalMargin);
mRightIcon.setLayoutParams(rightIconLP);
+ // if there is no title and topline view, there is nothing to adjust.
+ if (mNotificationTopLine == null && mTitle == null) {
+ return;
+ }
+
// align top line view to start of the right icon.
final int iconSize = mView.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.notification_right_icon_size);
final int marginEnd = 2 * horizontalMargin + iconSize;
- mNotificationTopLine.setHeaderTextMarginEnd(marginEnd);
+ final boolean isTitleInTopLine;
+ // set margin end for the top line view if it exists
+ if (mNotificationTopLine != null) {
+ mNotificationTopLine.setHeaderTextMarginEnd(marginEnd);
+ isTitleInTopLine = mNotificationTopLine.isTitlePresent();
+ } else {
+ isTitleInTopLine = false;
+ }
+ // Margin is to be applied to the title only when it is in the body,
+ // but not in the title.
// title has too much margin on the right, so we need to reduce it
- if (mTitle != null) {
+ if (!isTitleInTopLine && mTitle != null) {
final ViewGroup.MarginLayoutParams titleLP =
(ViewGroup.MarginLayoutParams) mTitle.getLayoutParams();
titleLP.setMarginEnd(marginEnd);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationBundleUi.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationBundleUi.kt
index 7237231..11c942c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationBundleUi.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationBundleUi.kt
@@ -49,7 +49,7 @@
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+ inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
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 4390f1b..531baa8d 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
@@ -104,6 +104,7 @@
import com.android.systemui.shade.TouchLogger;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips;
import com.android.systemui.statusbar.headsup.shared.StatusBarNoHunBehavior;
import com.android.systemui.statusbar.notification.ColorUpdateLogger;
import com.android.systemui.statusbar.notification.FakeShadowView;
@@ -117,6 +118,7 @@
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.HeadsUpAnimationEvent;
import com.android.systemui.statusbar.notification.headsup.HeadsUpAnimator;
import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper;
import com.android.systemui.statusbar.notification.headsup.HeadsUpUtil;
@@ -139,6 +141,7 @@
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.policy.ScrollAdapter;
import com.android.systemui.statusbar.policy.SplitShadeStateController;
+import com.android.systemui.statusbar.ui.SystemBarUtilsProxy;
import com.android.systemui.util.Assert;
import com.android.systemui.util.ColorUtilKt;
import com.android.systemui.util.DumpUtilsKt;
@@ -351,10 +354,11 @@
private final int[] mTempInt2 = new int[2];
private final HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>();
private final HashSet<ExpandableView> mClearTransientViewsWhenFinished = new HashSet<>();
- private final HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations
- = new HashSet<>();
+ private final Map<ExpandableNotificationRow, HeadsUpAnimationEvent> mHeadsUpChangeAnimations
+ = new HashMap<>();
private boolean mForceNoOverlappingRendering;
- private final ArrayList<Pair<ExpandableNotificationRow, Boolean>> mTmpList = new ArrayList<>();
+ private final ArrayList<ExpandableNotificationRow> mTmpHeadsUpChangeAnimations =
+ new ArrayList<>();
private boolean mAnimationRunning;
private final ViewTreeObserver.OnPreDrawListener mRunningAnimationUpdater
= new ViewTreeObserver.OnPreDrawListener() {
@@ -673,7 +677,7 @@
mExpandHelper.setScrollAdapter(mScrollAdapter);
if (NotificationsHunSharedAnimationValues.isEnabled()) {
- mHeadsUpAnimator = new HeadsUpAnimator(context);
+ mHeadsUpAnimator = new HeadsUpAnimator(context, /* systemBarUtilsProxy= */ null);
} else {
mHeadsUpAnimator = null;
}
@@ -1226,6 +1230,14 @@
}
@Override
+ public void setOccluded(boolean isOccluded) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
+ return;
+ }
+ this.setVisibility(isOccluded ? View.INVISIBLE : View.VISIBLE);
+ }
+
+ @Override
public void setScrollState(@NonNull ShadeScrollState scrollState) {
if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
return;
@@ -3066,20 +3078,20 @@
*/
private boolean removeRemovedChildFromHeadsUpChangeAnimations(View child) {
boolean hasAddEvent = false;
- for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
- ExpandableNotificationRow row = eventPair.first;
- boolean isHeadsUp = eventPair.second;
+ for (HeadsUpAnimationEvent event : mHeadsUpChangeAnimations.values()) {
+ ExpandableNotificationRow row = event.getRow();
+ boolean isHeadsUp = event.isHeadsUpAppearance();
if (child == row) {
- mTmpList.add(eventPair);
+ mTmpHeadsUpChangeAnimations.add(event.getRow());
hasAddEvent |= isHeadsUp;
}
}
if (hasAddEvent) {
// This child was just added lets remove all events.
- mHeadsUpChangeAnimations.removeAll(mTmpList);
+ mTmpHeadsUpChangeAnimations.forEach((row) -> mHeadsUpChangeAnimations.remove(row));
((ExpandableNotificationRow) child).setHeadsUpAnimatingAway(false);
}
- mTmpList.clear();
+ mTmpHeadsUpChangeAnimations.clear();
return hasAddEvent && mAddedHeadsUpChildren.contains(child);
}
@@ -3365,9 +3377,9 @@
}
private void generateHeadsUpAnimationEvents() {
- for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
- ExpandableNotificationRow row = eventPair.first;
- boolean isHeadsUp = eventPair.second;
+ for (HeadsUpAnimationEvent headsUpEvent : mHeadsUpChangeAnimations.values()) {
+ ExpandableNotificationRow row = headsUpEvent.getRow();
+ boolean isHeadsUp = headsUpEvent.isHeadsUpAppearance();
if (isHeadsUp != row.isHeadsUp()) {
// For cases where we have a heads up showing and appearing again we shouldn't
// do the animations at all.
@@ -3425,6 +3437,10 @@
}
AnimationEvent event = new AnimationEvent(row, type);
event.headsUpFromBottom = onBottom;
+
+ boolean hasStatusBarChip =
+ StatusBarNotifChips.isEnabled() && headsUpEvent.getHasStatusBarChip();
+ event.headsUpHasStatusBarChip = hasStatusBarChip;
// TODO(b/283084712) remove this and update the HUN filters at creation
event.filter.animateHeight = false;
mAnimationEvents.add(event);
@@ -5060,10 +5076,11 @@
mAnimationFinishedRunnables.add(runnable);
}
- public void generateHeadsUpAnimation(NotificationEntry entry, boolean isHeadsUp) {
+ public void generateHeadsUpAnimation(
+ NotificationEntry entry, boolean isHeadsUp, boolean hasStatusBarChip) {
SceneContainerFlag.assertInLegacyMode();
ExpandableNotificationRow row = entry.getHeadsUpAnimationView();
- generateHeadsUpAnimation(row, isHeadsUp);
+ generateHeadsUpAnimation(row, isHeadsUp, hasStatusBarChip);
}
/**
@@ -5072,8 +5089,11 @@
*
* @param row to animate
* @param isHeadsUp true for appear, false for disappear animations
+ * @param hasStatusBarChip true if the status bar is currently displaying a chip for the given
+ * notification
*/
- public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) {
+ public void generateHeadsUpAnimation(
+ ExpandableNotificationRow row, boolean isHeadsUp, boolean hasStatusBarChip) {
boolean addAnimation =
mAnimationsEnabled && (isHeadsUp || mHeadsUpGoingAwayAnimationsAllowed);
if (NotificationThrottleHun.isEnabled()) {
@@ -5088,19 +5108,26 @@
: " isSeenInShade=" + row.getEntry().isSeenInShade()
+ " row=" + row.getKey())
+ " mIsExpanded=" + mIsExpanded
- + " isHeadsUp=" + isHeadsUp);
+ + " isHeadsUp=" + isHeadsUp
+ + " hasStatusBarChip=" + hasStatusBarChip);
}
+
if (addAnimation) {
// If we're hiding a HUN we just started showing THIS FRAME, then remove that event,
// and do not add the disappear event either.
- if (!isHeadsUp && mHeadsUpChangeAnimations.remove(new Pair<>(row, true))) {
+ boolean showingHunThisFrame =
+ mHeadsUpChangeAnimations.containsKey(row)
+ && mHeadsUpChangeAnimations.get(row).isHeadsUpAppearance();
+ if (!isHeadsUp && showingHunThisFrame) {
+ mHeadsUpChangeAnimations.remove(row);
if (SPEW) {
Log.v(TAG, "generateHeadsUpAnimation: previous hun appear animation cancelled");
}
logHunAnimationSkipped(row, "previous hun appear animation cancelled");
return;
}
- mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp));
+ mHeadsUpChangeAnimations.put(
+ row, new HeadsUpAnimationEvent(row, isHeadsUp, hasStatusBarChip));
mNeedsAnimation = true;
if (!mIsExpanded && !mWillExpand && !isHeadsUp) {
row.setHeadsUpAnimatingAway(true);
@@ -5108,6 +5135,9 @@
setHeadsUpAnimatingAway(true);
}
}
+ if (StatusBarNotifChips.isEnabled()) {
+ row.setHasStatusBarChipDuringHeadsUpAnimation(hasStatusBarChip);
+ }
requestChildrenUpdate();
}
}
@@ -6461,30 +6491,50 @@
static AnimationFilter[] FILTERS = new AnimationFilter[]{
// ANIMATION_TYPE_ADD
- new AnimationFilter()
- .animateAlpha()
- .animateHeight()
- .animateTopInset()
- .animateY()
- .animateZ()
- .hasDelays(),
+ physicalNotificationMovement()
+ ? new AnimationFilter()
+ .animateAlpha()
+ .animateHeight()
+ .animateTopInset()
+ .animateY()
+ .animateZ()
+ : new AnimationFilter()
+ .animateAlpha()
+ .animateHeight()
+ .animateTopInset()
+ .animateY()
+ .animateZ()
+ .hasDelays(),
// ANIMATION_TYPE_REMOVE
- new AnimationFilter()
- .animateAlpha()
- .animateHeight()
- .animateTopInset()
- .animateY()
- .animateZ()
- .hasDelays(),
+ physicalNotificationMovement()
+ ? new AnimationFilter()
+ .animateAlpha()
+ .animateHeight()
+ .animateTopInset()
+ .animateY()
+ .animateZ()
+ : new AnimationFilter()
+ .animateAlpha()
+ .animateHeight()
+ .animateTopInset()
+ .animateY()
+ .animateZ()
+ .hasDelays(),
// ANIMATION_TYPE_REMOVE_SWIPED_OUT
- new AnimationFilter()
- .animateHeight()
- .animateTopInset()
- .animateY()
- .animateZ()
- .hasDelays(),
+ physicalNotificationMovement()
+ ? new AnimationFilter()
+ .animateHeight()
+ .animateTopInset()
+ .animateY()
+ .animateZ()
+ : new AnimationFilter()
+ .animateHeight()
+ .animateTopInset()
+ .animateY()
+ .animateZ()
+ .hasDelays(),
// ANIMATION_TYPE_TOP_PADDING_CHANGED
new AnimationFilter()
@@ -6674,6 +6724,7 @@
final long length;
View viewAfterChangingView;
boolean headsUpFromBottom;
+ boolean headsUpHasStatusBarChip;
AnimationEvent(ExpandableView view, int type) {
this(view, type, LENGTHS[type]);
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 124e6f5..bb3abc1 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
@@ -92,6 +92,7 @@
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips;
import com.android.systemui.statusbar.notification.ColorUpdateLogger;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
@@ -325,6 +326,14 @@
*/
private float mMaxAlphaForGlanceableHub = 1.0f;
+ /**
+ * A list of keys for the visible status bar chips.
+ *
+ * Note that this list can contain both notification keys, as well as keys for other types of
+ * chips like screen recording.
+ */
+ private List<String> mVisibleStatusBarChipKeys = new ArrayList<>();
+
private final NotificationListViewBinder mViewBinder;
private void updateResources() {
@@ -1580,8 +1589,16 @@
return mView.getFirstChildNotGone();
}
+ /** Sets the list of keys that have currently visible status bar chips. */
+ public void updateStatusBarChipKeys(List<String> visibleStatusBarChipKeys) {
+ mVisibleStatusBarChipKeys = visibleStatusBarChipKeys;
+ }
+
public void generateHeadsUpAnimation(NotificationEntry entry, boolean isHeadsUp) {
- mView.generateHeadsUpAnimation(entry, isHeadsUp);
+ boolean hasStatusBarChip =
+ StatusBarNotifChips.isEnabled()
+ && mVisibleStatusBarChipKeys.contains(entry.getKey());
+ mView.generateHeadsUpAnimation(entry, isHeadsUp, hasStatusBarChip);
}
public void setMaxTopPadding(int padding) {
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 4e91680..fcb63df 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
@@ -409,12 +409,12 @@
if (counter != null) {
if (NotificationBundleUi.isEnabled) {
- val entry = (currentNotification as? ExpandableNotificationRow)?.entry
- counter.incrementForBucket(entry?.bucket)
- } else {
val entryAdapter =
(currentNotification as? ExpandableNotificationRow)?.entryAdapter
counter.incrementForBucket(entryAdapter?.sectionBucket)
+ } else {
+ val entry = (currentNotification as? ExpandableNotificationRow)?.entry
+ counter.incrementForBucket(entry?.bucket)
}
}
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 4effb76..d23a4c6 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
@@ -1059,7 +1059,9 @@
shouldHunAppearFromBottom(ambientState, childState);
if (NotificationsHunSharedAnimationValues.isEnabled()) {
int yTranslation =
- mHeadsUpAnimator.getHeadsUpYTranslation(shouldHunAppearFromBottom);
+ mHeadsUpAnimator.getHeadsUpYTranslation(
+ shouldHunAppearFromBottom,
+ row.hasStatusBarChipDuringHeadsUpAnimation());
childState.setYTranslation(yTranslation);
} else {
if (shouldHunAppearFromBottom) {
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 19abfa8..5414318 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
@@ -38,6 +38,7 @@
import com.android.systemui.res.R;
import com.android.systemui.shared.clocks.AnimatableClockView;
import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips;
import com.android.systemui.statusbar.notification.PhysicsPropertyAnimator;
import com.android.systemui.statusbar.notification.headsup.HeadsUpAnimator;
import com.android.systemui.statusbar.notification.headsup.NotificationsHunSharedAnimationValues;
@@ -289,6 +290,10 @@
long delayPerElement = ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING;
switch (event.animationType) {
case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD: {
+ if (physicalNotificationMovement()) {
+ // We don't want any delays when adding anymore
+ continue;
+ }
int ownIndex = viewState.notGoneIndex;
int changingIndex =
((ExpandableView) (event.mChangingView)).getViewState().notGoneIndex;
@@ -302,6 +307,10 @@
case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT:
delayPerElement = ANIMATION_DELAY_PER_ELEMENT_MANUAL;
case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE: {
+ if (physicalNotificationMovement()) {
+ // We don't want any delays when removing anymore
+ continue;
+ }
int ownIndex = viewState.notGoneIndex;
boolean noNextView = event.viewAfterChangingView == null;
ExpandableView viewAfterChangingView = noNextView
@@ -552,7 +561,9 @@
mHeadsUpAppearChildren.add(changingView);
mTmpState.copyFrom(changingView.getViewState());
- mTmpState.setYTranslation(getHeadsUpYTranslationStart(event.headsUpFromBottom));
+ mTmpState.setYTranslation(
+ getHeadsUpYTranslationStart(
+ event.headsUpFromBottom, event.headsUpHasStatusBarChip));
// set the height and the initial position
mTmpState.applyToView(changingView);
mAnimationProperties.setCustomInterpolator(View.TRANSLATION_Y,
@@ -664,7 +675,9 @@
// StackScrollAlgorithm cannot find this view because it has been removed
// from the NSSL. To correctly translate the view to the top or bottom of
// the screen (where it animated from), we need to update its translation.
- mTmpState.setYTranslation(getHeadsUpYTranslationStart(event.headsUpFromBottom));
+ mTmpState.setYTranslation(
+ getHeadsUpYTranslationStart(
+ event.headsUpFromBottom, event.headsUpHasStatusBarChip));
endRunnable = changingView::removeFromTransientContainer;
}
@@ -735,9 +748,9 @@
return needsCustomAnimation;
}
- private float getHeadsUpYTranslationStart(boolean headsUpFromBottom) {
+ private float getHeadsUpYTranslationStart(boolean headsUpFromBottom, boolean hasStatusBarChip) {
if (NotificationsHunSharedAnimationValues.isEnabled()) {
- return mHeadsUpAnimator.getHeadsUpYTranslation(headsUpFromBottom);
+ return mHeadsUpAnimator.getHeadsUpYTranslation(headsUpFromBottom, hasStatusBarChip);
}
if (headsUpFromBottom) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
index a7305f7..9c855e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
@@ -49,6 +49,9 @@
/** Max alpha for this view */
fun setMaxAlpha(alpha: Float)
+ /** Set whether this view is occluded by something else. */
+ fun setOccluded(isOccluded: Boolean)
+
/** Sets a clipping shape, which defines the drawable area of this view. */
fun setClippingShape(shape: ShadeScrimShape?)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
index 10b665d..facb894 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
@@ -33,6 +33,7 @@
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.NotificationShelf
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.notification.NotificationActivityStarter
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController
import com.android.systemui.statusbar.notification.dagger.SilentHeader
@@ -133,6 +134,14 @@
}
}
+ if (StatusBarNotifChips.isEnabled) {
+ launch {
+ viewModel.visibleStatusBarChipKeys.collect { keys ->
+ viewController.updateStatusBarChipKeys(keys)
+ }
+ }
+ }
+
launch { bindLogger(view) }
}
}
@@ -231,7 +240,7 @@
emptyShadeViewModel: EmptyShadeViewModel,
parentView: NotificationStackScrollLayout,
) {
- ModesEmptyShadeFix.assertInNewMode()
+ ModesEmptyShadeFix.unsafeAssertInNewMode()
// The empty shade needs to be re-inflated every time the theme or the font size
// changes.
configuration
@@ -269,7 +278,7 @@
emptyShadeView: EmptyShadeView,
emptyShadeViewModel: EmptyShadeViewModel,
): Unit = coroutineScope {
- ModesEmptyShadeFix.assertInNewMode()
+ ModesEmptyShadeFix.unsafeAssertInNewMode()
launch {
emptyShadeView.repeatWhenAttachedToWindow {
EmptyShadeViewBinder.bind(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
index a4e39cb..653344a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
@@ -86,6 +86,8 @@
.collectTraced { view.setClippingShape(it) }
}
+ launch { viewModel.isOccluded.collectTraced { view.setOccluded(it) } }
+
launch { viewModel.maxAlpha.collectTraced { view.setMaxAlpha(it) } }
launch { viewModel.shadeScrollState.collect { view.setScrollState(it) } }
launch {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
index 5ed1889..c1eb70e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
@@ -20,6 +20,7 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModel
import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
@@ -56,6 +57,7 @@
constructor(
val shelf: NotificationShelfViewModel,
val hideListViewModel: HideListViewModel,
+ val ongoingActivityChipsViewModel: OngoingActivityChipsViewModel,
val footerViewModelFactory: FooterViewModel.Factory,
val emptyShadeViewModelFactory: EmptyShadeViewModel.Factory,
val logger: Optional<NotificationLoggerViewModel>,
@@ -364,6 +366,14 @@
}
}
+ /**
+ * A list of keys for the visible status bar chips.
+ *
+ * Note that this list can contain both notification keys, as well as keys for other types of
+ * chips like screen recording.
+ */
+ val visibleStatusBarChipKeys = ongoingActivityChipsViewModel.visibleChipKeys
+
// TODO(b/325936094) use it for the text displayed in the StatusBar
fun headsUpRow(key: HeadsUpRowKey): HeadsUpRowViewModel =
HeadsUpRowViewModel(headsUpNotificationInteractor.headsUpRow(key))
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
index 1dbaf2f..a277597 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
@@ -24,7 +24,9 @@
import com.android.compose.animation.scene.ObservableTransitionState.Idle
import com.android.compose.animation.scene.ObservableTransitionState.Transition
import com.android.compose.animation.scene.ObservableTransitionState.Transition.ChangeScene
+import com.android.compose.animation.scene.OverlayKey
import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.lifecycle.ExclusiveActivatable
@@ -70,6 +72,7 @@
private val stackAppearanceInteractor: NotificationStackAppearanceInteractor,
shadeInteractor: ShadeInteractor,
shadeModeInteractor: ShadeModeInteractor,
+ bouncerInteractor: BouncerInteractor,
private val remoteInputInteractor: RemoteInputInteractor,
private val sceneInteractor: SceneInteractor,
// TODO(b/336364825) Remove Lazy when SceneContainerFlag is released -
@@ -131,12 +134,15 @@
private fun expandFractionDuringOverlayTransition(
transition: Transition,
currentScene: SceneKey,
+ currentOverlays: Set<OverlayKey>,
shadeExpansion: Float,
): Float {
return if (currentScene == Scenes.Lockscreen) {
1f
} else if (transition.isTransitioningFromOrTo(Overlays.NotificationsShade)) {
shadeExpansion
+ } else if (Overlays.NotificationsShade in currentOverlays) {
+ 1f
} else {
0f
}
@@ -161,12 +167,13 @@
shadeInteractor.qsExpansion,
shadeModeInteractor.shadeMode,
sceneInteractor.transitionState,
- ) { shadeExpansion, qsExpansion, _, transitionState ->
+ sceneInteractor.currentOverlays,
+ ) { shadeExpansion, qsExpansion, _, transitionState, currentOverlays ->
when (transitionState) {
is Idle ->
if (
expandedInScene(transitionState.currentScene) ||
- Overlays.NotificationsShade in transitionState.currentOverlays
+ Overlays.NotificationsShade in currentOverlays
) {
1f
} else {
@@ -182,12 +189,14 @@
expandFractionDuringOverlayTransition(
transition = transitionState,
currentScene = transitionState.currentScene,
+ currentOverlays = currentOverlays,
shadeExpansion = shadeExpansion,
)
is Transition.ReplaceOverlay ->
expandFractionDuringOverlayTransition(
transition = transitionState,
currentScene = transitionState.currentScene,
+ currentOverlays = currentOverlays,
shadeExpansion = shadeExpansion,
)
}
@@ -198,6 +207,12 @@
val qsExpandFraction: Flow<Float> =
shadeInteractor.qsExpansion.dumpWhileCollecting("qsExpandFraction")
+ val isOccluded: Flow<Boolean> =
+ bouncerInteractor.bouncerExpansion
+ .map { it == 1f }
+ .distinctUntilChanged()
+ .dumpWhileCollecting("isOccluded")
+
/** Blur radius to be applied to Notifications. */
fun blurRadius(maxBlurRadius: Flow<Int>) =
combine(blurFraction, maxBlurRadius) { fraction, maxRadius -> fraction * maxRadius }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 33cc62c..9d55e1d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -21,6 +21,7 @@
import androidx.annotation.VisibleForTesting
import com.android.app.tracing.coroutines.flow.flowName
import com.android.systemui.Flags.glanceableHubV2
+import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
@@ -106,7 +107,6 @@
import kotlinx.coroutines.flow.combineTransform
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emptyFlow
-import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
@@ -132,6 +132,7 @@
private val keyguardInteractor: KeyguardInteractor,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val shadeInteractor: ShadeInteractor,
+ private val bouncerInteractor: BouncerInteractor,
shadeModeInteractor: ShadeModeInteractor,
notificationStackAppearanceInteractor: NotificationStackAppearanceInteractor,
private val alternateBouncerToGoneTransitionViewModel:
@@ -516,8 +517,13 @@
combineTransform(
shadeInteractor.shadeExpansion,
shadeInteractor.qsExpansion,
- ) { shadeExpansion, qsExpansion ->
- if (qsExpansion == 1f) {
+ bouncerInteractor.bouncerExpansion,
+ ) { shadeExpansion, qsExpansion, bouncerExpansion ->
+ if (bouncerExpansion == 1f) {
+ emit(0f)
+ } else if (bouncerExpansion > 0f) {
+ emit(1 - bouncerExpansion)
+ } else if (qsExpansion == 1f) {
// Ensure HUNs will be visible in QS shade (at least while
// unlocked)
emit(1f)
@@ -526,19 +532,36 @@
emit(1f - qsExpansion)
}
}
- Split -> isAnyExpanded.filter { it }.map { 1f }
+ Split ->
+ combineTransform(isAnyExpanded, bouncerInteractor.bouncerExpansion) {
+ isAnyExpanded,
+ bouncerExpansion ->
+ if (bouncerExpansion == 1f) {
+ emit(0f)
+ } else if (bouncerExpansion > 0f) {
+ emit(1 - bouncerExpansion)
+ } else if (isAnyExpanded) {
+ emit(1f)
+ }
+ }
Dual ->
combineTransform(
shadeModeInteractor.isShadeLayoutWide,
headsUpNotificationInteractor.get().isHeadsUpOrAnimatingAway,
shadeInteractor.shadeExpansion,
shadeInteractor.qsExpansion,
+ bouncerInteractor.bouncerExpansion,
) {
isShadeLayoutWide,
isHeadsUpOrAnimatingAway,
shadeExpansion,
- qsExpansion ->
- if (isShadeLayoutWide) {
+ qsExpansion,
+ bouncerExpansion ->
+ if (bouncerExpansion == 1f) {
+ emit(0f)
+ } else if (bouncerExpansion > 0f) {
+ emit(1 - bouncerExpansion)
+ } else if (isShadeLayoutWide) {
if (shadeExpansion > 0f) {
emit(1f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt
index feb7409..bc53314 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.ui.viewbinder
+import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModel
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.shared.HeadsUpRowKey
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
@@ -27,20 +29,29 @@
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
-import com.android.app.tracing.coroutines.launchTraced as launch
class HeadsUpNotificationViewBinder
@Inject
-constructor(private val viewModel: NotificationListViewModel) {
+constructor(
+ private val viewModel: NotificationListViewModel,
+ private val ongoingActivityChipsViewModel: OngoingActivityChipsViewModel,
+) {
suspend fun bindHeadsUpNotifications(parentView: NotificationStackScrollLayout): Unit =
coroutineScope {
launch {
var previousKeys = emptySet<HeadsUpRowKey>()
- combine(viewModel.pinnedHeadsUpRowKeys, viewModel.activeHeadsUpRowKeys, ::Pair)
+ combine(
+ viewModel.pinnedHeadsUpRowKeys,
+ viewModel.activeHeadsUpRowKeys,
+ ongoingActivityChipsViewModel.visibleChipKeys,
+ ::Triple,
+ )
.sample(viewModel.headsUpAnimationsEnabled, ::Pair)
.collect { (newKeys, animationsEnabled) ->
val pinned = newKeys.first
val all = newKeys.second
+ val statusBarChips: List<String> = newKeys.third
+
val added = all.union(pinned) - previousKeys
val removed = previousKeys - pinned
previousKeys = pinned
@@ -48,15 +59,23 @@
if (animationsEnabled) {
added.forEach { key ->
+ val row = obtainView(key)
+ val hasStatusBarChip = statusBarChips.contains(row.entry.key)
parentView.generateHeadsUpAnimation(
- obtainView(key),
+ row,
/* isHeadsUp = */ true,
+ hasStatusBarChip,
)
}
removed.forEach { key ->
val row = obtainView(key)
+ val hasStatusBarChip = statusBarChips.contains(row.entry.key)
if (!parentView.isBeingDragged()) {
- parentView.generateHeadsUpAnimation(row, /* isHeadsUp= */ false)
+ parentView.generateHeadsUpAnimation(
+ row,
+ /* isHeadsUp= */ false,
+ hasStatusBarChip,
+ )
}
row.markHeadsUpSeen()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideControllerStore.kt
index 2ae38dd4..616bab6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideControllerStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideControllerStore.kt
@@ -22,9 +22,9 @@
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
import com.android.systemui.display.data.repository.PerDisplayStore
-import com.android.systemui.display.data.repository.PerDisplayStoreImpl
import com.android.systemui.display.data.repository.SingleDisplayStore
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.data.repository.StatusBarPerDisplayStoreImpl
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -41,10 +41,13 @@
private val autoHideControllerFactory: AutoHideControllerImpl.Factory,
) :
AutoHideControllerStore,
- PerDisplayStoreImpl<AutoHideController>(backgroundApplicationScope, displayRepository) {
+ StatusBarPerDisplayStoreImpl<AutoHideController>(
+ backgroundApplicationScope,
+ displayRepository,
+ ) {
init {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
}
override fun createInstanceForDisplay(displayId: Int): AutoHideController? {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
index 323b7d8..ba55700 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
@@ -21,6 +21,7 @@
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.core.CommandQueueInitializer
import com.android.systemui.statusbar.core.MultiDisplayStatusBarInitializerStore
+import com.android.systemui.statusbar.core.MultiDisplayStatusBarOrchestratorStore
import com.android.systemui.statusbar.core.MultiDisplayStatusBarStarter
import com.android.systemui.statusbar.core.SingleDisplayStatusBarInitializerStore
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
@@ -197,5 +198,19 @@
CoreStartable.NOP
}
}
+
+ @Provides
+ @SysUISingleton
+ @IntoMap
+ @ClassKey(MultiDisplayStatusBarOrchestratorStore::class)
+ fun orchestratorStoreAsCoreStartable(
+ multiDisplayLazy: Lazy<MultiDisplayStatusBarOrchestratorStore>
+ ): CoreStartable {
+ return if (StatusBarConnectedDisplays.isEnabled) {
+ multiDisplayLazy.get()
+ } else {
+ CoreStartable.NOP
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index c541cff..d8d6979 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -875,7 +875,7 @@
}
private void showSecondaryOngoingActivityChip(boolean animate) {
- StatusBarNotifChips.assertInNewMode();
+ StatusBarNotifChips.unsafeAssertInNewMode();
StatusBarRootModernization.assertInLegacyMode();
animateShow(mSecondaryOngoingActivityChip, animate);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarChipsModernization.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarChipsModernization.kt
index b77e8f2..6afcd8a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarChipsModernization.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/StatusBarChipsModernization.kt
@@ -52,7 +52,7 @@
* the flag is not enabled to ensure that the refactor author catches issues in testing.
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+ inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/domain/interactor/BatteryInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/domain/interactor/BatteryInteractor.kt
index d53cbab..342adc6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/domain/interactor/BatteryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/battery/domain/interactor/BatteryInteractor.kt
@@ -65,12 +65,12 @@
*/
val batteryAttributionType =
combine(isCharging, powerSave, isBatteryDefenderEnabled) { charging, powerSave, defend ->
- if (charging) {
- BatteryAttributionModel.Charging
- } else if (powerSave) {
+ if (powerSave) {
BatteryAttributionModel.PowerSave
} else if (defend) {
BatteryAttributionModel.Defend
+ } else if (charging) {
+ BatteryAttributionModel.Charging
} else {
null
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index c71162a..2952850 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -18,7 +18,9 @@
import android.net.wifi.WifiManager
import com.android.systemui.CoreStartable
+import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.kairos.ExperimentalKairosApi
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
import com.android.systemui.log.table.TableLogBuffer
@@ -35,9 +37,15 @@
import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigRepositoryImpl
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepositoryKairosAdapter
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileRepositorySwitcher
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileRepositorySwitcherKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoModeMobileConnectionDataSourceKairosImpl
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileConnectionRepositoryKairosFactoryImpl
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileConnectionsRepositoryKairosImpl
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorKairosImpl
import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
@@ -74,9 +82,20 @@
import dagger.multibindings.IntoMap
import java.util.function.Supplier
import javax.inject.Named
+import javax.inject.Provider
import kotlinx.coroutines.flow.Flow
-@Module
+@OptIn(ExperimentalKairosApi::class)
+@Module(
+ includes =
+ [
+ DemoModeMobileConnectionDataSourceKairosImpl.Module::class,
+ MobileRepositorySwitcherKairos.Module::class,
+ MobileConnectionsRepositoryKairosImpl.Module::class,
+ MobileConnectionRepositoryKairosFactoryImpl.Module::class,
+ MobileConnectionsRepositoryKairosAdapter.Module::class,
+ ]
+)
abstract class StatusBarPipelineModule {
@Binds
abstract fun airplaneModeRepository(impl: AirplaneModeRepositoryImpl): AirplaneModeRepository
@@ -117,11 +136,6 @@
@Binds abstract fun wifiInteractor(impl: WifiInteractorImpl): WifiInteractor
- @Binds
- abstract fun mobileConnectionsRepository(
- impl: MobileRepositorySwitcher
- ): MobileConnectionsRepository
-
@Binds abstract fun userSetupRepository(impl: UserSetupRepositoryImpl): UserSetupRepository
@Binds abstract fun mobileMappingsProxy(impl: MobileMappingsProxyImpl): MobileMappingsProxy
@@ -135,9 +149,6 @@
): SubscriptionManagerProxy
@Binds
- abstract fun mobileIconsInteractor(impl: MobileIconsInteractorImpl): MobileIconsInteractor
-
- @Binds
@IntoMap
@ClassKey(MobileUiAdapter::class)
abstract fun bindFeature(impl: MobileUiAdapter): CoreStartable
@@ -158,6 +169,30 @@
companion object {
@Provides
+ fun mobileIconsInteractor(
+ impl: Provider<MobileIconsInteractorImpl>,
+ kairosImpl: Provider<MobileIconsInteractorKairosImpl>,
+ ): MobileIconsInteractor {
+ return if (Flags.statusBarMobileIconKairos()) {
+ kairosImpl.get()
+ } else {
+ impl.get()
+ }
+ }
+
+ @Provides
+ fun mobileConnectionsRepository(
+ impl: Provider<MobileRepositorySwitcher>,
+ kairosImpl: Provider<MobileConnectionsRepositoryKairosAdapter>,
+ ): MobileConnectionsRepository {
+ return if (Flags.statusBarMobileIconKairos()) {
+ kairosImpl.get()
+ } else {
+ impl.get()
+ }
+ }
+
+ @Provides
@SysUISingleton
fun provideRealWifiRepository(
wifiManager: WifiManager?,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ethernet/shared/StatusBarSignalPolicyRefactorEthernet.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ethernet/shared/StatusBarSignalPolicyRefactorEthernet.kt
index 48747df..1d1cfd6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ethernet/shared/StatusBarSignalPolicyRefactorEthernet.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ethernet/shared/StatusBarSignalPolicyRefactorEthernet.kt
@@ -50,7 +50,7 @@
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+ inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryKairos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryKairos.kt
new file mode 100644
index 0000000..2e79626
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryKairos.kt
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2024 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.pipeline.mobile.data.repository
+
+import android.telephony.CellSignalStrength
+import android.telephony.TelephonyManager
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.State
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+
+/**
+ * Every mobile line of service can be identified via a [SubscriptionInfo] object. We set up a
+ * repository for each individual, tracked subscription via [MobileConnectionsRepository], and this
+ * repository is responsible for setting up a [TelephonyManager] object tied to its subscriptionId
+ *
+ * There should only ever be one [MobileConnectionRepository] per subscription, since
+ * [TelephonyManager] limits the number of callbacks that can be registered per process.
+ *
+ * This repository should have all of the relevant information for a single line of service, which
+ * eventually becomes a single icon in the status bar.
+ */
+@ExperimentalKairosApi
+interface MobileConnectionRepositoryKairos {
+ /** The subscriptionId that this connection represents */
+ val subId: Int
+
+ /** The carrierId for this connection. See [TelephonyManager.getSimCarrierId] */
+ val carrierId: State<Int>
+
+ /** Reflects the value from the carrier config INFLATE_SIGNAL_STRENGTH for this connection */
+ val inflateSignalStrength: State<Boolean>
+
+ /** Carrier config KEY_SHOW_5G_SLICE_ICON_BOOL for this connection */
+ val allowNetworkSliceIndicator: State<Boolean>
+
+ /**
+ * The table log buffer created for this connection. Will have the name "MobileConnectionLog
+ * [subId]"
+ */
+ val tableLogBuffer: TableLogBuffer
+
+ /** True if the [android.telephony.ServiceState] says this connection is emergency calls only */
+ val isEmergencyOnly: State<Boolean>
+
+ /** True if [android.telephony.ServiceState] says we are roaming */
+ val isRoaming: State<Boolean>
+
+ /**
+ * See [android.telephony.ServiceState.getOperatorAlphaShort], this value is defined as the
+ * current registered operator name in short alphanumeric format. In some cases this name might
+ * be preferred over other methods of calculating the network name
+ */
+ val operatorAlphaShort: State<String?>
+
+ /**
+ * TODO (b/263167683): Clarify this field
+ *
+ * This check comes from [com.android.settingslib.Utils.isInService]. It is intended to be a
+ * mapping from a ServiceState to a notion of connectivity. Notably, it will consider a
+ * connection to be in-service if either the voice registration state is IN_SERVICE or the data
+ * registration state is IN_SERVICE and NOT IWLAN.
+ */
+ val isInService: State<Boolean>
+
+ /**
+ * True if this subscription is actively connected to a non-terrestrial network and false
+ * otherwise. Reflects [android.telephony.ServiceState.isUsingNonTerrestrialNetwork].
+ *
+ * Notably: This value reflects that this subscription is **currently** using a non-terrestrial
+ * network, because some subscriptions can switch between terrestrial and non-terrestrial
+ * networks. [SubscriptionModel.isExclusivelyNonTerrestrial] reflects whether a subscription is
+ * configured to exclusively connect to non-terrestrial networks. [isNonTerrestrial] can change
+ * during the lifetime of a subscription but [SubscriptionModel.isExclusivelyNonTerrestrial]
+ * will stay constant.
+ */
+ val isNonTerrestrial: State<Boolean>
+
+ /** True if [android.telephony.SignalStrength] told us that this connection is using GSM */
+ val isGsm: State<Boolean>
+
+ /**
+ * There is still specific logic in the pipeline that calls out CDMA level explicitly. This
+ * field is not completely orthogonal to [primaryLevel], because CDMA could be primary.
+ */
+ // @IntRange(from = 0, to = 4)
+ val cdmaLevel: State<Int>
+
+ /** [android.telephony.SignalStrength]'s concept of the overall signal level */
+ // @IntRange(from = 0, to = 4)
+ val primaryLevel: State<Int>
+
+ /**
+ * This level can be used to reflect the signal strength when in carrier roaming NTN mode
+ * (carrier-based satellite)
+ */
+ val satelliteLevel: State<Int>
+
+ /** The current data connection state. See [DataConnectionState] */
+ val dataConnectionState: State<DataConnectionState>
+
+ /** The current data activity direction. See [DataActivityModel] */
+ val dataActivityDirection: State<DataActivityModel>
+
+ /** True if there is currently a carrier network change in process */
+ val carrierNetworkChangeActive: State<Boolean>
+
+ /**
+ * [resolvedNetworkType] is the [TelephonyDisplayInfo.getOverrideNetworkType] if it exists or
+ * [TelephonyDisplayInfo.getNetworkType]. This is used to look up the proper network type icon
+ */
+ val resolvedNetworkType: State<ResolvedNetworkType>
+
+ /** The total number of levels. Used with [SignalDrawable]. */
+ val numberOfLevels: State<Int>
+
+ /** Observable tracking [TelephonyManager.isDataConnectionAllowed] */
+ val dataEnabled: State<Boolean>
+
+ /**
+ * See [TelephonyManager.getCdmaEnhancedRoamingIndicatorDisplayNumber]. This bit only matters if
+ * the connection type is CDMA.
+ *
+ * True if the Enhanced Roaming Indicator (ERI) display number is not [TelephonyManager.ERI_OFF]
+ */
+ val cdmaRoaming: State<Boolean>
+
+ /** The service provider name for this network connection, or the default name. */
+ val networkName: State<NetworkNameModel>
+
+ /**
+ * The service provider name for this network connection, or the default name.
+ *
+ * TODO(b/296600321): De-duplicate this field with [networkName] after determining the data
+ * provided is identical
+ */
+ val carrierName: State<NetworkNameModel>
+
+ /**
+ * True if this type of connection is allowed while airplane mode is on, and false otherwise.
+ */
+ val isAllowedDuringAirplaneMode: State<Boolean>
+
+ /**
+ * True if this network has NET_CAPABILITIY_PRIORITIZE_LATENCY, and can be considered to be a
+ * network slice
+ */
+ val hasPrioritizedNetworkCapabilities: State<Boolean>
+
+ /**
+ * True if this connection is in emergency callback mode.
+ *
+ * @see [TelephonyManager.getEmergencyCallbackMode]
+ */
+ val isInEcmMode: State<Boolean>
+
+ companion object {
+ /** The default number of levels to use for [numberOfLevels]. */
+ val DEFAULT_NUM_LEVELS = CellSignalStrength.getNumSignalStrengthLevels()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryKairos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryKairos.kt
new file mode 100644
index 0000000..79bfb6e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryKairos.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2024 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.pipeline.mobile.data.repository
+
+import android.telephony.CarrierConfigManager
+import android.telephony.SubscriptionManager
+import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.settingslib.mobile.MobileMappings
+import com.android.settingslib.mobile.MobileMappings.Config
+import com.android.systemui.kairos.Events
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.Incremental
+import com.android.systemui.kairos.State
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+
+/**
+ * Repo for monitoring the complete active subscription info list, to be consumed and filtered based
+ * on various policy
+ */
+@ExperimentalKairosApi
+interface MobileConnectionsRepositoryKairos {
+
+ /** All active mobile connections. */
+ val mobileConnectionsBySubId: Incremental<Int, MobileConnectionRepositoryKairos>
+
+ /** Observable list of current mobile subscriptions */
+ val subscriptions: State<Collection<SubscriptionModel>>
+
+ /**
+ * Observable for the subscriptionId of the current mobile data connection. Null if we don't
+ * have a valid subscription id
+ */
+ val activeMobileDataSubscriptionId: State<Int?>
+
+ /** Repo that tracks the current [activeMobileDataSubscriptionId] */
+ val activeMobileDataRepository: State<MobileConnectionRepositoryKairos?>
+
+ /**
+ * Observable event for when the active data sim switches but the group stays the same. E.g.,
+ * CBRS switching would trigger this
+ */
+ val activeSubChangedInGroupEvent: Events<Unit>
+
+ /** Tracks [SubscriptionManager.getDefaultDataSubscriptionId]. `null` if there is no default. */
+ val defaultDataSubId: State<Int?>
+
+ /**
+ * True if the default network connection is a mobile-like connection and false otherwise.
+ *
+ * This is typically shown by having [android.net.NetworkCapabilities.TRANSPORT_CELLULAR], but
+ * there are edge cases (like carrier merged wifi) that could also result in the default
+ * connection being mobile-like.
+ */
+ val mobileIsDefault: State<Boolean>
+
+ /**
+ * True if the device currently has a carrier merged connection.
+ *
+ * See [CarrierMergedConnectionRepository] for more info.
+ */
+ val hasCarrierMergedConnection: State<Boolean>
+
+ /** True if the default network connection is validated and false otherwise. */
+ val defaultConnectionIsValidated: State<Boolean>
+
+ /**
+ * [Config] is an object that tracks relevant configuration flags for a given subscription ID.
+ * In the case of [MobileMappings], it's hard-coded to check the default data subscription's
+ * config, so this will apply to every icon that we care about.
+ *
+ * Relevant bits in the config are things like
+ * [CarrierConfigManager.KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL]
+ *
+ * This flow will produce whenever the default data subscription or the carrier config changes.
+ */
+ val defaultDataSubRatConfig: State<Config>
+
+ /** The icon mapping from network type to [MobileIconGroup] for the default subscription */
+ val defaultMobileIconMapping: State<Map<String, MobileIconGroup>>
+
+ /** Fallback [MobileIconGroup] in the case where there is no icon in the mapping */
+ val defaultMobileIconGroup: State<MobileIconGroup>
+
+ /**
+ * Can the device make emergency calls using the device-based service state? This field is only
+ * useful when all known active subscriptions are OOS and not emergency call capable.
+ *
+ * Specifically, this checks every [ServiceState] of the device, and looks for any that report
+ * [ServiceState.isEmergencyOnly].
+ *
+ * This is an eager flow, and re-evaluates whenever ACTION_SERVICE_STATE is sent for subId = -1.
+ */
+ val isDeviceEmergencyCallCapable: State<Boolean>
+
+ /**
+ * If any active SIM on the device is in
+ * [android.telephony.TelephonyManager.SIM_STATE_PIN_REQUIRED] or
+ * [android.telephony.TelephonyManager.SIM_STATE_PUK_REQUIRED] or
+ * [android.telephony.TelephonyManager.SIM_STATE_PERM_DISABLED]
+ */
+ val isAnySimSecure: State<Boolean>
+
+ /**
+ * Checks if any subscription has [android.telephony.TelephonyManager.getEmergencyCallbackMode]
+ * == true
+ */
+ val isInEcmMode: State<Boolean>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryKairosAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryKairosAdapter.kt
new file mode 100644
index 0000000..64144d9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryKairosAdapter.kt
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2024 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.pipeline.mobile.data.repository
+
+import android.content.Context
+import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.MobileMappings
+import com.android.systemui.Flags
+import com.android.systemui.KairosActivatable
+import com.android.systemui.KairosBuilder
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.KairosNetwork
+import com.android.systemui.kairos.buildSpec
+import com.android.systemui.kairos.combine
+import com.android.systemui.kairos.map
+import com.android.systemui.kairos.mapValues
+import com.android.systemui.kairos.toColdConflatedFlow
+import com.android.systemui.kairosBuilder
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileConnectionRepositoryKairosAdapter
+import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
+import dagger.Provides
+import dagger.multibindings.ElementsIntoSet
+import javax.inject.Inject
+import javax.inject.Provider
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.stateIn
+
+@ExperimentalKairosApi
+@SysUISingleton
+class MobileConnectionsRepositoryKairosAdapter
+@Inject
+constructor(
+ private val kairosRepo: MobileConnectionsRepositoryKairos,
+ private val kairosNetwork: KairosNetwork,
+ @Application scope: CoroutineScope,
+ connectivityRepository: ConnectivityRepository,
+ context: Context,
+ carrierConfigRepo: CarrierConfigRepository,
+) : MobileConnectionsRepository, KairosBuilder by kairosBuilder() {
+ override val subscriptions: StateFlow<List<SubscriptionModel>> =
+ kairosRepo.subscriptions
+ .map { it.toList() }
+ .toColdConflatedFlow(kairosNetwork)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), emptyList())
+
+ override val activeMobileDataSubscriptionId: StateFlow<Int?> =
+ kairosRepo.activeMobileDataSubscriptionId
+ .toColdConflatedFlow(kairosNetwork)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
+ private val reposBySubIdK = buildIncremental {
+ kairosRepo.mobileConnectionsBySubId
+ .mapValues { (subId, repo) ->
+ buildSpec {
+ MobileConnectionRepositoryKairosAdapter(
+ kairosRepo = repo,
+ carrierConfig = carrierConfigRepo.getOrCreateConfigForSubId(subId),
+ )
+ }
+ }
+ .applyLatestSpecForKey()
+ }
+
+ private val reposBySubId =
+ reposBySubIdK
+ .toColdConflatedFlow(kairosNetwork)
+ .stateIn(scope, SharingStarted.Eagerly, emptyMap())
+
+ override val activeMobileDataRepository: StateFlow<MobileConnectionRepository?> =
+ combine(kairosRepo.activeMobileDataSubscriptionId, reposBySubIdK) { id, repos -> repos[id] }
+ .toColdConflatedFlow(kairosNetwork)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
+ override val activeSubChangedInGroupEvent: Flow<Unit> =
+ kairosRepo.activeSubChangedInGroupEvent.toColdConflatedFlow(kairosNetwork)
+
+ override val defaultDataSubId: StateFlow<Int?> =
+ kairosRepo.defaultDataSubId
+ .toColdConflatedFlow(kairosNetwork)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
+ override val mobileIsDefault: StateFlow<Boolean> =
+ kairosRepo.mobileIsDefault
+ .toColdConflatedFlow(kairosNetwork)
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ connectivityRepository.defaultConnections.value.mobile.isDefault,
+ )
+
+ override val hasCarrierMergedConnection: Flow<Boolean> =
+ kairosRepo.hasCarrierMergedConnection.toColdConflatedFlow(kairosNetwork)
+
+ override val defaultConnectionIsValidated: StateFlow<Boolean> =
+ kairosRepo.defaultConnectionIsValidated
+ .toColdConflatedFlow(kairosNetwork)
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ connectivityRepository.defaultConnections.value.isValidated,
+ )
+
+ override fun getRepoForSubId(subId: Int): MobileConnectionRepository =
+ reposBySubId.value[subId] ?: error("Unknown subscription id: $subId")
+
+ override val defaultDataSubRatConfig: StateFlow<MobileMappings.Config> =
+ kairosRepo.defaultDataSubRatConfig
+ .toColdConflatedFlow(kairosNetwork)
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ MobileMappings.Config.readConfig(context),
+ )
+
+ override val defaultMobileIconMapping: Flow<Map<String, SignalIcon.MobileIconGroup>> =
+ kairosRepo.defaultMobileIconMapping.toColdConflatedFlow(kairosNetwork)
+
+ override val defaultMobileIconGroup: Flow<SignalIcon.MobileIconGroup> =
+ kairosRepo.defaultMobileIconGroup.toColdConflatedFlow(kairosNetwork)
+
+ override val isDeviceEmergencyCallCapable: StateFlow<Boolean> =
+ kairosRepo.isDeviceEmergencyCallCapable
+ .toColdConflatedFlow(kairosNetwork)
+ .stateIn(scope, SharingStarted.Eagerly, false)
+
+ override val isAnySimSecure: StateFlow<Boolean> =
+ kairosRepo.isAnySimSecure
+ .toColdConflatedFlow(kairosNetwork)
+ .stateIn(scope, SharingStarted.Eagerly, false)
+
+ override fun getIsAnySimSecure(): Boolean = isAnySimSecure.value
+
+ override suspend fun isInEcmMode(): Boolean =
+ kairosNetwork.transact { kairosRepo.isInEcmMode.sample() }
+
+ @dagger.Module
+ object Module {
+ @Provides
+ @ElementsIntoSet
+ fun kairosActivatable(
+ impl: Provider<MobileConnectionsRepositoryKairosAdapter>
+ ): Set<@JvmSuppressWildcards KairosActivatable> =
+ if (Flags.statusBarMobileIconKairos()) setOf(impl.get()) else emptySet()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
index 66587c7..caf4bf5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
@@ -48,9 +48,9 @@
* something like this:
* ```
* RealRepository
- * │
- * ├──►RepositorySwitcher──►RealInteractor──►RealViewModel
- * │
+ * │
+ * ├──►RepositorySwitcher──►RealInteractor──►RealViewModel
+ * │
* DemoRepository
* ```
*
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherKairos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherKairos.kt
new file mode 100644
index 0000000..1f5b849
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherKairos.kt
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2024 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.pipeline.mobile.data.repository
+
+import android.os.Bundle
+import androidx.annotation.VisibleForTesting
+import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.MobileMappings
+import com.android.systemui.Flags
+import com.android.systemui.KairosActivatable
+import com.android.systemui.KairosBuilder
+import com.android.systemui.activated
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.demomode.DemoMode
+import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.kairos.Events
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.Incremental
+import com.android.systemui.kairos.State
+import com.android.systemui.kairos.flatMap
+import com.android.systemui.kairos.map
+import com.android.systemui.kairos.switchEvents
+import com.android.systemui.kairos.switchIncremental
+import com.android.systemui.kairosBuilder
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileConnectionsRepositoryKairosImpl
+import dagger.Binds
+import dagger.Provides
+import dagger.multibindings.ElementsIntoSet
+import javax.inject.Inject
+import javax.inject.Provider
+import kotlinx.coroutines.channels.awaitClose
+
+/**
+ * A provider for the [MobileConnectionsRepository] interface that can choose between the Demo and
+ * Prod concrete implementations at runtime. It works by defining a base flow, [activeRepo], which
+ * switches based on the latest information from [DemoModeController], and switches every flow in
+ * the interface to point to the currently-active provider. This allows us to put the demo mode
+ * interface in its own repository, completely separate from the real version, while still using all
+ * of the prod implementations for the rest of the pipeline (interactors and onward). Looks
+ * something like this:
+ * ```
+ * RealRepository
+ * │
+ * ├──►RepositorySwitcher──►RealInteractor──►RealViewModel
+ * │
+ * DemoRepository
+ * ```
+ *
+ * NOTE: because the UI layer for mobile icons relies on a nested-repository structure, it is likely
+ * that we will have to drain the subscription list whenever demo mode changes. Otherwise if a real
+ * subscription list [1] is replaced with a demo subscription list [1], the view models will not see
+ * a change (due to `distinctUntilChanged`) and will not refresh their data providers to the demo
+ * implementation.
+ */
+@ExperimentalKairosApi
+@SysUISingleton
+class MobileRepositorySwitcherKairos
+@Inject
+constructor(
+ private val realRepository: MobileConnectionsRepositoryKairosImpl,
+ private val demoRepositoryFactory: DemoMobileConnectionsRepositoryKairos.Factory,
+ demoModeController: DemoModeController,
+) : MobileConnectionsRepositoryKairos, KairosBuilder by kairosBuilder() {
+
+ private val isDemoMode: State<Boolean> = buildState {
+ conflatedCallbackFlow {
+ val callback =
+ object : DemoMode {
+ override fun dispatchDemoCommand(command: String?, args: Bundle?) {
+ // Nothing, we just care about on/off
+ }
+
+ override fun onDemoModeStarted() {
+ trySend(true)
+ }
+
+ override fun onDemoModeFinished() {
+ trySend(false)
+ }
+ }
+
+ demoModeController.addCallback(callback)
+ awaitClose { demoModeController.removeCallback(callback) }
+ }
+ .toState(demoModeController.isInDemoMode)
+ }
+
+ // Convenient definition flow for the currently active repo (based on demo mode or not)
+ @VisibleForTesting
+ val activeRepo: State<MobileConnectionsRepositoryKairos> = buildState {
+ isDemoMode.mapLatestBuild { demoMode ->
+ if (demoMode) {
+ activated { demoRepositoryFactory.create() }
+ } else {
+ realRepository
+ }
+ }
+ }
+
+ override val mobileConnectionsBySubId: Incremental<Int, MobileConnectionRepositoryKairos> =
+ activeRepo.map { it.mobileConnectionsBySubId }.switchIncremental()
+
+ override val subscriptions: State<Collection<SubscriptionModel>> =
+ activeRepo.flatMap { it.subscriptions }
+
+ override val activeMobileDataSubscriptionId: State<Int?> =
+ activeRepo.flatMap { it.activeMobileDataSubscriptionId }
+
+ override val activeMobileDataRepository: State<MobileConnectionRepositoryKairos?> =
+ activeRepo.flatMap { it.activeMobileDataRepository }
+
+ override val activeSubChangedInGroupEvent: Events<Unit> =
+ activeRepo.map { it.activeSubChangedInGroupEvent }.switchEvents()
+
+ override val defaultDataSubRatConfig: State<MobileMappings.Config> =
+ activeRepo.flatMap { it.defaultDataSubRatConfig }
+
+ override val defaultMobileIconMapping: State<Map<String, SignalIcon.MobileIconGroup>> =
+ activeRepo.flatMap { it.defaultMobileIconMapping }
+
+ override val defaultMobileIconGroup: State<SignalIcon.MobileIconGroup> =
+ activeRepo.flatMap { it.defaultMobileIconGroup }
+
+ override val isDeviceEmergencyCallCapable: State<Boolean> =
+ activeRepo.flatMap { it.isDeviceEmergencyCallCapable }
+
+ override val isAnySimSecure: State<Boolean> = activeRepo.flatMap { it.isAnySimSecure }
+
+ override val defaultDataSubId: State<Int?> = activeRepo.flatMap { it.defaultDataSubId }
+
+ override val mobileIsDefault: State<Boolean> = activeRepo.flatMap { it.mobileIsDefault }
+
+ override val hasCarrierMergedConnection: State<Boolean> =
+ activeRepo.flatMap { it.hasCarrierMergedConnection }
+
+ override val defaultConnectionIsValidated: State<Boolean> =
+ activeRepo.flatMap { it.defaultConnectionIsValidated }
+
+ override val isInEcmMode: State<Boolean> = activeRepo.flatMap { it.isInEcmMode }
+
+ @dagger.Module
+ interface Module {
+ @Binds fun bindImpl(impl: MobileRepositorySwitcherKairos): MobileConnectionsRepositoryKairos
+
+ companion object {
+ @Provides
+ @ElementsIntoSet
+ fun kairosActivatable(
+ impl: Provider<MobileRepositorySwitcherKairos>
+ ): Set<@JvmSuppressWildcards KairosActivatable> =
+ if (Flags.statusBarMobileIconKairos()) setOf(impl.get()) else emptySet()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepositoryKairos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepositoryKairos.kt
new file mode 100644
index 0000000..a244feb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepositoryKairos.kt
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2024 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.pipeline.mobile.data.repository.demo
+
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
+import android.telephony.TelephonyManager
+import com.android.settingslib.SignalIcon
+import com.android.systemui.KairosBuilder
+import com.android.systemui.kairos.Events
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.State
+import com.android.systemui.kairos.TransactionScope
+import com.android.systemui.kairos.map
+import com.android.systemui.kairos.mapCheap
+import com.android.systemui.kairos.mergeLeft
+import com.android.systemui.kairos.stateOf
+import com.android.systemui.kairos.util.Either
+import com.android.systemui.kairos.util.Either.First
+import com.android.systemui.kairos.util.Either.Second
+import com.android.systemui.kairos.util.firstOrNull
+import com.android.systemui.kairosBuilder
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.Mobile as FakeMobileEvent
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepositoryKairos.Companion.COL_CARRIER_ID
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepositoryKairos.Companion.COL_CARRIER_NETWORK_CHANGE
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepositoryKairos.Companion.COL_CDMA_LEVEL
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepositoryKairos.Companion.COL_IS_IN_SERVICE
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepositoryKairos.Companion.COL_IS_NTN
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepositoryKairos.Companion.COL_OPERATOR
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepositoryKairos.Companion.COL_PRIMARY_LEVEL
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepositoryKairos.Companion.COL_ROAMING
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepositoryKairos.Companion.COL_SATELLITE_LEVEL
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel.CarrierMerged as FakeCarrierMergedEvent
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/**
+ * Demo version of [MobileConnectionRepository]. Note that this class shares all of its flows using
+ * [SharingStarted.WhileSubscribed()] to give the same semantics as using a regular
+ * [MutableStateFlow] while still logging all of the inputs in the same manor as the production
+ * repos.
+ */
+@ExperimentalKairosApi
+class DemoMobileConnectionRepositoryKairos(
+ override val subId: Int,
+ override val tableLogBuffer: TableLogBuffer,
+ mobileEvents: Events<FakeMobileEvent>,
+ carrierMergedResetEvents: Events<Any?>,
+ wifiEvents: Events<FakeCarrierMergedEvent>,
+ private val mobileMappingsReverseLookup: State<Map<SignalIcon.MobileIconGroup, String>>,
+) : MobileConnectionRepositoryKairos, KairosBuilder by kairosBuilder() {
+
+ private val initialState =
+ FakeMobileEvent(
+ level = null,
+ dataType = null,
+ subId = subId,
+ carrierId = null,
+ activity = null,
+ carrierNetworkChange = false,
+ roaming = false,
+ name = DEMO_CARRIER_NAME,
+ )
+
+ private val lastMobileEvent: State<FakeMobileEvent> = buildState {
+ mobileEvents.holdState(initialState)
+ }
+
+ private val lastEvent: State<Either<FakeMobileEvent, FakeCarrierMergedEvent>> = buildState {
+ mergeLeft(
+ mobileEvents.mapCheap { First(it) },
+ wifiEvents.mapCheap { Second(it) },
+ carrierMergedResetEvents.mapCheap { First(lastMobileEvent.sample()) },
+ )
+ .holdState(First(initialState))
+ }
+
+ override val carrierId: State<Int> =
+ lastEvent
+ .map { it.firstOrNull()?.carrierId ?: INVALID_SUBSCRIPTION_ID }
+ .also {
+ onActivated {
+ logDiffsForTable(
+ intState = it,
+ tableLogBuffer = tableLogBuffer,
+ columnName = COL_CARRIER_ID,
+ )
+ }
+ }
+
+ override val inflateSignalStrength: State<Boolean> = buildState {
+ mobileEvents
+ .map { ev -> ev.inflateStrength }
+ .holdState(false)
+ .also { logDiffsForTable(it, tableLogBuffer, "", columnName = "inflate") }
+ }
+
+ // I don't see a reason why we would turn the config off for demo mode.
+ override val allowNetworkSliceIndicator: State<Boolean> = stateOf(true)
+
+ // TODO(b/261029387): not yet supported
+ override val isEmergencyOnly: State<Boolean> = stateOf(false)
+
+ override val isRoaming: State<Boolean> =
+ lastEvent
+ .map { it.firstOrNull()?.roaming ?: false }
+ .also { onActivated { logDiffsForTable(it, tableLogBuffer, columnName = COL_ROAMING) } }
+
+ override val operatorAlphaShort: State<String?> =
+ lastEvent
+ .map { it.firstOrNull()?.name }
+ .also {
+ onActivated { logDiffsForTable(it, tableLogBuffer, columnName = COL_OPERATOR) }
+ }
+
+ override val isInService: State<Boolean> =
+ lastEvent
+ .map {
+ when (it) {
+ is First -> it.value.level?.let { level -> level > 0 } ?: false
+ is Second -> true
+ }
+ }
+ .also {
+ onActivated { logDiffsForTable(it, tableLogBuffer, columnName = COL_IS_IN_SERVICE) }
+ }
+
+ override val isNonTerrestrial: State<Boolean> = buildState {
+ mobileEvents
+ .map { it.ntn }
+ .holdState(false)
+ .also { logDiffsForTable(it, tableLogBuffer, columnName = COL_IS_NTN) }
+ }
+
+ // TODO(b/261029387): not yet supported
+ override val isGsm: State<Boolean> = stateOf(false)
+
+ override val cdmaLevel: State<Int> =
+ lastEvent
+ .map {
+ when (it) {
+ is First -> it.value.level ?: 0
+ is Second -> it.value.level
+ }
+ }
+ .also {
+ onActivated { logDiffsForTable(it, tableLogBuffer, columnName = COL_CDMA_LEVEL) }
+ }
+
+ override val primaryLevel: State<Int> =
+ lastEvent
+ .map {
+ when (it) {
+ is First -> it.value.level ?: 0
+ is Second -> it.value.level
+ }
+ }
+ .also {
+ onActivated { logDiffsForTable(it, tableLogBuffer, columnName = COL_PRIMARY_LEVEL) }
+ }
+
+ override val satelliteLevel: State<Int> =
+ stateOf(0).also {
+ onActivated { logDiffsForTable(it, tableLogBuffer, columnName = COL_SATELLITE_LEVEL) }
+ }
+
+ // TODO(b/261029387): not yet supported
+ override val dataConnectionState: State<DataConnectionState> =
+ buildState {
+ mergeLeft(mobileEvents, wifiEvents)
+ .map { DataConnectionState.Connected }
+ .holdState(DataConnectionState.Disconnected)
+ }
+ .also {
+ onActivated {
+ logDiffsForTable(diffableState = it, tableLogBuffer = tableLogBuffer)
+ }
+ }
+
+ override val dataActivityDirection: State<DataActivityModel> =
+ lastEvent
+ .map {
+ val activity =
+ when (it) {
+ is First -> it.value.activity ?: TelephonyManager.DATA_ACTIVITY_NONE
+ is Second -> it.value.activity
+ }
+ activity.toMobileDataActivityModel()
+ }
+ .also { onActivated { logDiffsForTable(it, tableLogBuffer, columnPrefix = "") } }
+
+ override val carrierNetworkChangeActive: State<Boolean> =
+ lastEvent
+ .map { it.firstOrNull()?.carrierNetworkChange ?: false }
+ .also {
+ onActivated {
+ logDiffsForTable(it, tableLogBuffer, columnName = COL_CARRIER_NETWORK_CHANGE)
+ }
+ }
+
+ override val resolvedNetworkType: State<ResolvedNetworkType> = buildState {
+ lastEvent
+ .mapTransactionally {
+ it.firstOrNull()?.dataType?.let { resolvedNetworkTypeForIconGroup(it) }
+ ?: ResolvedNetworkType.CarrierMergedNetworkType
+ }
+ .also { logDiffsForTable(it, tableLogBuffer, columnPrefix = "") }
+ }
+
+ override val numberOfLevels: State<Int> =
+ inflateSignalStrength.map { shouldInflate ->
+ if (shouldInflate) DEFAULT_NUM_LEVELS + 1 else DEFAULT_NUM_LEVELS
+ }
+
+ override val dataEnabled: State<Boolean> = stateOf(true)
+
+ override val cdmaRoaming: State<Boolean> = lastEvent.map { it.firstOrNull()?.roaming ?: false }
+
+ override val networkName: State<NetworkNameModel.IntentDerived> =
+ lastEvent.map {
+ NetworkNameModel.IntentDerived(it.firstOrNull()?.name ?: CARRIER_MERGED_NAME)
+ }
+
+ override val carrierName: State<NetworkNameModel.SubscriptionDerived> =
+ lastEvent.map {
+ NetworkNameModel.SubscriptionDerived(
+ it.firstOrNull()?.let { event -> "${event.name} ${event.subId}" }
+ ?: CARRIER_MERGED_NAME
+ )
+ }
+
+ override val isAllowedDuringAirplaneMode: State<Boolean> = lastEvent.map { it is Second }
+
+ override val hasPrioritizedNetworkCapabilities: State<Boolean> =
+ lastEvent.map { it.firstOrNull()?.slice ?: false }
+
+ override val isInEcmMode: State<Boolean> = stateOf(false)
+
+ private fun TransactionScope.resolvedNetworkTypeForIconGroup(
+ iconGroup: SignalIcon.MobileIconGroup?
+ ) = DefaultNetworkType(mobileMappingsReverseLookup.sample()[iconGroup] ?: "dis")
+
+ companion object {
+ private const val DEMO_CARRIER_NAME = "Demo Carrier"
+ private const val CARRIER_MERGED_NAME = "Carrier Merged Network"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryKairos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryKairos.kt
new file mode 100644
index 0000000..925ee54
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryKairos.kt
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2024 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.pipeline.mobile.data.repository.demo
+
+import android.content.Context
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
+import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET
+import android.util.Log
+import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.MobileMappings
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.KairosBuilder
+import com.android.systemui.activated
+import com.android.systemui.kairos.BuildScope
+import com.android.systemui.kairos.Events
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.GroupedEvents
+import com.android.systemui.kairos.Incremental
+import com.android.systemui.kairos.State
+import com.android.systemui.kairos.TransactionScope
+import com.android.systemui.kairos.asIncremental
+import com.android.systemui.kairos.buildSpec
+import com.android.systemui.kairos.combine
+import com.android.systemui.kairos.emptyEvents
+import com.android.systemui.kairos.filter
+import com.android.systemui.kairos.filterIsInstance
+import com.android.systemui.kairos.groupBy
+import com.android.systemui.kairos.groupByKey
+import com.android.systemui.kairos.map
+import com.android.systemui.kairos.mapCheap
+import com.android.systemui.kairos.mapNotNull
+import com.android.systemui.kairos.mapValues
+import com.android.systemui.kairos.mergeLeft
+import com.android.systemui.kairos.stateOf
+import com.android.systemui.kairosBuilder
+import com.android.systemui.log.table.TableLogBufferFactory
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.Mobile
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.MobileDisabled
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Factory.Companion.MOBILE_CONNECTION_BUFFER_SIZE
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+/** This repository vends out data based on demo mode commands */
+@ExperimentalKairosApi
+class DemoMobileConnectionsRepositoryKairos
+@AssistedInject
+constructor(
+ mobileDataSource: DemoModeMobileConnectionDataSourceKairos,
+ private val wifiDataSource: DemoModeWifiDataSource,
+ context: Context,
+ private val logFactory: TableLogBufferFactory,
+) : MobileConnectionsRepositoryKairos, KairosBuilder by kairosBuilder() {
+
+ @AssistedFactory
+ fun interface Factory {
+ fun create(): DemoMobileConnectionsRepositoryKairos
+ }
+
+ private val wifiEvents: Events<FakeWifiEventModel?> = buildEvents {
+ wifiDataSource.wifiEvents.toEvents()
+ }
+
+ private val mobileEventsWithSubId: Events<Pair<Int, FakeNetworkEventModel>> =
+ mobileDataSource.mobileEvents.mapNotNull { event ->
+ event?.let { (event.subId ?: lastSeenSubId.sample())?.let { it to event } }
+ }
+
+ private val mobileEventsBySubId: GroupedEvents<Int, FakeNetworkEventModel> =
+ mobileEventsWithSubId.map { mapOf(it) }.groupByKey()
+
+ private val carrierMergedEvents: Events<FakeWifiEventModel.CarrierMerged> =
+ wifiEvents.filterIsInstance<FakeWifiEventModel.CarrierMerged>()
+
+ private val wifiEventsBySubId: GroupedEvents<Int, FakeWifiEventModel.CarrierMerged> =
+ carrierMergedEvents.groupBy { it.subscriptionId }
+
+ private val lastSeenSubId: State<Int?> = buildState {
+ mergeLeft(
+ mobileEventsWithSubId.mapCheap { it.first },
+ carrierMergedEvents.mapCheap { it.subscriptionId },
+ )
+ .holdState(null)
+ }
+
+ private val activeCarrierMergedSubscription: State<Int?> = buildState {
+ mergeLeft(
+ carrierMergedEvents.mapCheap { it.subscriptionId },
+ wifiEvents
+ .filter {
+ it is FakeWifiEventModel.Wifi || it is FakeWifiEventModel.WifiDisabled
+ }
+ .map { null },
+ )
+ .holdState(null)
+ }
+
+ private val activeMobileSubscriptions: State<Set<Int>> = buildState {
+ mobileDataSource.mobileEvents
+ .mapNotNull { event ->
+ when (event) {
+ null -> null
+ is Mobile -> event.subId?.let { subId -> { subs: Set<Int> -> subs + subId } }
+ is MobileDisabled ->
+ (event.subId ?: maybeGetOnlySubIdForRemoval())?.let { subId ->
+ { subs: Set<Int> -> subs - subId }
+ }
+ }
+ }
+ .foldState(emptySet()) { f, s -> f(s) }
+ }
+
+ private val subscriptionIds: State<Set<Int>> =
+ combine(activeMobileSubscriptions, activeCarrierMergedSubscription) { mobile, carrierMerged
+ ->
+ carrierMerged?.let { mobile + carrierMerged } ?: mobile
+ }
+
+ private val subscriptionsById: State<Map<Int, SubscriptionModel>> =
+ subscriptionIds.map { subs ->
+ subs.associateWith { subId ->
+ SubscriptionModel(
+ subscriptionId = subId,
+ isOpportunistic = false,
+ carrierName = DEFAULT_CARRIER_NAME,
+ profileClass = PROFILE_CLASS_UNSET,
+ )
+ }
+ }
+
+ override val subscriptions: State<Collection<SubscriptionModel>> =
+ subscriptionsById.map { it.values }
+
+ private fun TransactionScope.maybeGetOnlySubIdForRemoval(): Int? {
+ val subIds = activeMobileSubscriptions.sample()
+ return if (subIds.size == 1) {
+ subIds.first()
+ } else {
+ Log.d(
+ TAG,
+ "processDisabledMobileState: Unable to infer subscription to " +
+ "disable. Specify subId using '-e slot <subId>'. " +
+ "Known subIds: [${subIds.joinToString(",")}]",
+ )
+ null
+ }
+ }
+
+ private val reposBySubId: Incremental<Int, DemoMobileConnectionRepositoryKairos> =
+ buildIncremental {
+ subscriptionsById
+ .asIncremental()
+ .mapValues { (id, _) -> buildSpec { newRepo(id) } }
+ .applyLatestSpecForKey()
+ }
+
+ // TODO(b/261029387): add a command for this value
+ override val activeMobileDataSubscriptionId: State<Int> =
+ // For now, active is just the first in the list
+ subscriptions.map { infos ->
+ infos.firstOrNull()?.subscriptionId ?: INVALID_SUBSCRIPTION_ID
+ }
+
+ override val activeMobileDataRepository: State<DemoMobileConnectionRepositoryKairos?> =
+ combine(activeMobileDataSubscriptionId, reposBySubId) { subId, repoMap -> repoMap[subId] }
+
+ // TODO(b/261029387): consider adding a demo command for this
+ override val activeSubChangedInGroupEvent: Events<Unit> = emptyEvents
+
+ /** Demo mode doesn't currently support modifications to the mobile mappings */
+ override val defaultDataSubRatConfig: State<MobileMappings.Config> =
+ stateOf(MobileMappings.Config.readConfig(context))
+
+ override val defaultMobileIconGroup: State<SignalIcon.MobileIconGroup> =
+ stateOf(TelephonyIcons.THREE_G)
+
+ // TODO(b/339023069): demo command for device-based emergency calls state
+ override val isDeviceEmergencyCallCapable: State<Boolean> = stateOf(false)
+
+ override val isAnySimSecure: State<Boolean> = stateOf(false)
+
+ override val defaultMobileIconMapping: State<Map<String, SignalIcon.MobileIconGroup>> =
+ stateOf(TelephonyIcons.ICON_NAME_TO_ICON)
+
+ /**
+ * In order to maintain compatibility with the old demo mode shell command API, reverse the
+ * [MobileMappings] lookup from (NetworkType: String -> Icon: MobileIconGroup), so that we can
+ * parse the string from the command line into a preferred icon group, and send _a_ valid
+ * network type for that icon through the pipeline.
+ *
+ * Note: collisions don't matter here, because the data source (the command line) only cares
+ * about the resulting icon, not the underlying network type.
+ */
+ private val mobileMappingsReverseLookup: State<Map<SignalIcon.MobileIconGroup, String>> =
+ defaultMobileIconMapping.map { networkToIconMap -> networkToIconMap.reverse() }
+
+ private fun <K, V> Map<K, V>.reverse() = entries.associate { (k, v) -> v to k }
+
+ // TODO(b/261029387): add a command for this value
+ override val defaultDataSubId: State<Int?> = stateOf(null)
+
+ // TODO(b/261029387): not yet supported
+ override val mobileIsDefault: State<Boolean> = stateOf(true)
+
+ // TODO(b/261029387): not yet supported
+ override val hasCarrierMergedConnection: State<Boolean> = stateOf(false)
+
+ // TODO(b/261029387): not yet supported
+ override val defaultConnectionIsValidated: State<Boolean> = stateOf(true)
+
+ override val isInEcmMode: State<Boolean> = stateOf(false)
+
+ override val mobileConnectionsBySubId: Incremental<Int, DemoMobileConnectionRepositoryKairos>
+ get() = reposBySubId
+
+ private fun BuildScope.newRepo(subId: Int) = activated {
+ DemoMobileConnectionRepositoryKairos(
+ subId = subId,
+ tableLogBuffer =
+ logFactory.getOrCreate(
+ "DemoMobileConnectionLog[$subId]",
+ MOBILE_CONNECTION_BUFFER_SIZE,
+ ),
+ mobileEvents = mobileEventsBySubId[subId].filterIsInstance(),
+ carrierMergedResetEvents =
+ wifiEvents.mapNotNull { it?.takeIf { it !is FakeWifiEventModel.CarrierMerged } },
+ wifiEvents = wifiEventsBySubId[subId],
+ mobileMappingsReverseLookup = mobileMappingsReverseLookup,
+ )
+ }
+
+ companion object {
+ private const val TAG = "DemoMobileConnectionsRepo"
+
+ private const val DEFAULT_CARRIER_NAME = "demo carrier"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSourceKairos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSourceKairos.kt
new file mode 100644
index 0000000..f329383
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSourceKairos.kt
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2024 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.pipeline.mobile.data.repository.demo
+
+import android.os.Bundle
+import android.telephony.Annotation.DataActivityType
+import android.telephony.TelephonyManager.DATA_ACTIVITY_IN
+import android.telephony.TelephonyManager.DATA_ACTIVITY_INOUT
+import android.telephony.TelephonyManager.DATA_ACTIVITY_NONE
+import android.telephony.TelephonyManager.DATA_ACTIVITY_OUT
+import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.Flags
+import com.android.systemui.KairosActivatable
+import com.android.systemui.KairosBuilder
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.demomode.DemoMode.COMMAND_NETWORK
+import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.kairos.Events
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairosBuilder
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.Mobile
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.MobileDisabled
+import dagger.Binds
+import dagger.Provides
+import dagger.multibindings.ElementsIntoSet
+import javax.inject.Inject
+import javax.inject.Provider
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/**
+ * Data source that can map from demo mode commands to inputs into the
+ * [DemoMobileConnectionsRepositoryKairos]
+ */
+@ExperimentalKairosApi
+interface DemoModeMobileConnectionDataSourceKairos {
+ val mobileEvents: Events<FakeNetworkEventModel?>
+}
+
+@ExperimentalKairosApi
+@SysUISingleton
+class DemoModeMobileConnectionDataSourceKairosImpl
+@Inject
+constructor(demoModeController: DemoModeController) :
+ KairosBuilder by kairosBuilder(), DemoModeMobileConnectionDataSourceKairos {
+ private val demoCommandStream: Flow<Bundle> =
+ demoModeController.demoFlowForCommand(COMMAND_NETWORK)
+
+ // If the args contains "mobile", then all of the args are relevant. It's just the way demo mode
+ // commands work and it's a little silly
+ private val _mobileCommands: Flow<FakeNetworkEventModel?> =
+ demoCommandStream.map { args -> args.toMobileEvent() }
+ override val mobileEvents: Events<FakeNetworkEventModel?> = buildEvents {
+ _mobileCommands.toEvents()
+ }
+
+ private fun Bundle.toMobileEvent(): FakeNetworkEventModel? {
+ val mobile = getString("mobile") ?: return null
+ return if (mobile == "show") {
+ activeMobileEvent()
+ } else {
+ MobileDisabled(subId = getString("slot")?.toInt())
+ }
+ }
+
+ /** Parse a valid mobile command string into a network event */
+ private fun Bundle.activeMobileEvent(): Mobile {
+ // There are many key/value pairs supported by mobile demo mode. Bear with me here
+ val level = getString("level")?.toInt()
+ val dataType = getString("datatype")?.toDataType()
+ val slot = getString("slot")?.toInt()
+ val carrierId = getString("carrierid")?.toInt()
+ val inflateStrength = getString("inflate").toBoolean()
+ val activity = getString("activity")?.toActivity()
+ val carrierNetworkChange = getString("carriernetworkchange") == "show"
+ val roaming = getString("roam") == "show"
+ val name = getString("networkname") ?: "demo mode"
+ val slice = getString("slice").toBoolean()
+ val ntn = getString("ntn").toBoolean()
+
+ return Mobile(
+ level = level,
+ dataType = dataType,
+ subId = slot,
+ carrierId = carrierId,
+ inflateStrength = inflateStrength,
+ activity = activity,
+ carrierNetworkChange = carrierNetworkChange,
+ roaming = roaming,
+ name = name,
+ slice = slice,
+ ntn = ntn,
+ )
+ }
+
+ @dagger.Module
+ interface Module {
+ @Binds
+ fun bindImpl(
+ impl: DemoModeMobileConnectionDataSourceKairosImpl
+ ): DemoModeMobileConnectionDataSourceKairos
+
+ companion object {
+ @Provides
+ @ElementsIntoSet
+ fun kairosActivatable(
+ impl: Provider<DemoModeMobileConnectionDataSourceKairosImpl>
+ ): Set<@JvmSuppressWildcards KairosActivatable> =
+ if (Flags.statusBarMobileIconKairos()) setOf(impl.get()) else emptySet()
+ }
+ }
+}
+
+private fun String.toDataType(): MobileIconGroup =
+ when (this) {
+ "1x" -> TelephonyIcons.ONE_X
+ "3g" -> TelephonyIcons.THREE_G
+ "4g" -> TelephonyIcons.FOUR_G
+ "4g+" -> TelephonyIcons.FOUR_G_PLUS
+ "5g" -> TelephonyIcons.NR_5G
+ "5ge" -> TelephonyIcons.LTE_CA_5G_E
+ "5g+" -> TelephonyIcons.NR_5G_PLUS
+ "e" -> TelephonyIcons.E
+ "g" -> TelephonyIcons.G
+ "h" -> TelephonyIcons.H
+ "h+" -> TelephonyIcons.H_PLUS
+ "lte" -> TelephonyIcons.LTE
+ "lte+" -> TelephonyIcons.LTE_PLUS
+ "dis" -> TelephonyIcons.DATA_DISABLED
+ "not" -> TelephonyIcons.NOT_DEFAULT_DATA
+ else -> TelephonyIcons.UNKNOWN
+ }
+
+@DataActivityType
+private fun String.toActivity(): Int =
+ when (this) {
+ "inout" -> DATA_ACTIVITY_INOUT
+ "in" -> DATA_ACTIVITY_IN
+ "out" -> DATA_ACTIVITY_OUT
+ else -> DATA_ACTIVITY_NONE
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt
index 42171d0..54162bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt
@@ -25,11 +25,13 @@
* Nullable fields represent optional command line arguments
*/
sealed interface FakeNetworkEventModel {
+ // Null means the default (chosen by the repository)
+ val subId: Int?
+
data class Mobile(
val level: Int?,
val dataType: SignalIcon.MobileIconGroup?,
- // Null means the default (chosen by the repository)
- val subId: Int?,
+ override val subId: Int?,
val carrierId: Int?,
val inflateStrength: Boolean = false,
@DataActivityType val activity: Int?,
@@ -40,8 +42,5 @@
val ntn: Boolean = false,
) : FakeNetworkEventModel
- data class MobileDisabled(
- // Null means the default (chosen by the repository)
- val subId: Int?
- ) : FakeNetworkEventModel
+ data class MobileDisabled(override val subId: Int?) : FakeNetworkEventModel
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryKairos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryKairos.kt
new file mode 100644
index 0000000..d61d11b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryKairos.kt
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2024 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.pipeline.mobile.data.repository.prod
+
+import android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
+import android.telephony.TelephonyManager
+import android.util.Log
+import com.android.systemui.KairosBuilder
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.State
+import com.android.systemui.kairos.combine
+import com.android.systemui.kairos.map
+import com.android.systemui.kairos.stateOf
+import com.android.systemui.kairosBuilder
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepositoryKairos.Companion.DEFAULT_NUM_LEVELS
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import javax.inject.Inject
+
+/**
+ * A repository implementation for a carrier merged (aka VCN) network. A carrier merged network is
+ * delivered to SysUI as a wifi network (see [WifiNetworkModel.CarrierMerged], but is visually
+ * displayed as a mobile network triangle.
+ *
+ * See [android.net.wifi.WifiInfo.isCarrierMerged] for more information.
+ *
+ * See [MobileConnectionRepositoryImpl] for a repository implementation of a typical mobile
+ * connection.
+ */
+@ExperimentalKairosApi
+class CarrierMergedConnectionRepositoryKairos(
+ override val subId: Int,
+ override val tableLogBuffer: TableLogBuffer,
+ private val telephonyManager: TelephonyManager,
+ val wifiRepository: WifiRepository,
+ override val isInEcmMode: State<Boolean>,
+) : MobileConnectionRepositoryKairos, KairosBuilder by kairosBuilder() {
+ init {
+ if (telephonyManager.subscriptionId != subId) {
+ error(
+ """CarrierMergedRepo: TelephonyManager should be created with subId($subId).
+ | Found ${telephonyManager.subscriptionId} instead."""
+ .trimMargin()
+ )
+ }
+ }
+
+ private val isWifiEnabled: State<Boolean> = buildState {
+ wifiRepository.isWifiEnabled.toState()
+ }
+ private val isWifiDefault: State<Boolean> = buildState {
+ wifiRepository.isWifiDefault.toState()
+ }
+ private val wifiNetwork: State<WifiNetworkModel> = buildState {
+ wifiRepository.wifiNetwork.toState()
+ }
+
+ /**
+ * Outputs the carrier merged network to use, or null if we don't have a valid carrier merged
+ * network.
+ */
+ private val network: State<WifiNetworkModel.CarrierMerged?> =
+ combine(isWifiEnabled, isWifiDefault, wifiNetwork) { isEnabled, isDefault, network ->
+ when {
+ !isEnabled -> null
+ !isDefault -> null
+ network !is WifiNetworkModel.CarrierMerged -> null
+ network.subscriptionId != subId -> {
+ Log.w(
+ TAG,
+ """Connection repo subId=$subId does not equal wifi repo
+ | subId=${network.subscriptionId}; not showing carrier merged"""
+ .trimMargin(),
+ )
+ null
+ }
+ else -> network
+ }
+ }
+
+ override val cdmaRoaming: State<Boolean> = stateOf(ROAMING)
+
+ override val networkName: State<NetworkNameModel> =
+ // The SIM operator name should be the same throughout the lifetime of a subId, **but**
+ // it may not be available when this repo is created because it takes time to load. To
+ // be safe, we re-fetch it each time the network has changed.
+ network.map { NetworkNameModel.SimDerived(telephonyManager.simOperatorName) }
+
+ override val carrierName: State<NetworkNameModel>
+ get() = networkName
+
+ override val numberOfLevels: State<Int> =
+ wifiNetwork.map {
+ if (it is WifiNetworkModel.CarrierMerged) {
+ it.numberOfLevels
+ } else {
+ DEFAULT_NUM_LEVELS
+ }
+ }
+
+ override val primaryLevel: State<Int> =
+ network.map { it?.level ?: SIGNAL_STRENGTH_NONE_OR_UNKNOWN }
+
+ override val cdmaLevel: State<Int> =
+ network.map { it?.level ?: SIGNAL_STRENGTH_NONE_OR_UNKNOWN }
+
+ override val dataActivityDirection: State<DataActivityModel> = buildState {
+ wifiRepository.wifiActivity.toState()
+ }
+
+ override val resolvedNetworkType: State<ResolvedNetworkType> =
+ network.map {
+ if (it != null) {
+ ResolvedNetworkType.CarrierMergedNetworkType
+ } else {
+ ResolvedNetworkType.UnknownNetworkType
+ }
+ }
+
+ override val dataConnectionState: State<DataConnectionState> =
+ network.map {
+ if (it != null) {
+ DataConnectionState.Connected
+ } else {
+ DataConnectionState.Disconnected
+ }
+ }
+
+ override val isRoaming: State<Boolean> = stateOf(false)
+ override val carrierId: State<Int> = stateOf(INVALID_SUBSCRIPTION_ID)
+ override val inflateSignalStrength: State<Boolean> = stateOf(false)
+ override val allowNetworkSliceIndicator: State<Boolean> = stateOf(false)
+ override val isEmergencyOnly: State<Boolean> = stateOf(false)
+ override val operatorAlphaShort: State<String?> = stateOf(null)
+ override val isInService: State<Boolean> = stateOf(true)
+ override val isNonTerrestrial: State<Boolean> = stateOf(false)
+ override val isGsm: State<Boolean> = stateOf(false)
+ override val carrierNetworkChangeActive: State<Boolean> = stateOf(false)
+ override val satelliteLevel: State<Int> = stateOf(0)
+
+ /**
+ * Carrier merged connections happen over wifi but are displayed as a mobile triangle. Because
+ * they occur over wifi, it's possible to have a valid carrier merged connection even during
+ * airplane mode. See b/291993542.
+ */
+ override val isAllowedDuringAirplaneMode: State<Boolean> = stateOf(true)
+
+ /**
+ * It's not currently considered possible that a carrier merged network can have these
+ * prioritized capabilities. If we need to track them, we can add the same check as is in
+ * [MobileConnectionRepositoryImpl].
+ */
+ override val hasPrioritizedNetworkCapabilities: State<Boolean> = stateOf(false)
+
+ override val dataEnabled: State<Boolean>
+ get() = isWifiEnabled
+
+ companion object {
+ // Carrier merged is never roaming
+ private const val ROAMING = false
+ }
+
+ @SysUISingleton
+ class Factory
+ @Inject
+ constructor(
+ private val telephonyManager: TelephonyManager,
+ private val wifiRepository: WifiRepository,
+ ) {
+ fun build(
+ subId: Int,
+ mobileLogger: TableLogBuffer,
+ mobileRepo: MobileConnectionRepositoryKairos,
+ ): CarrierMergedConnectionRepositoryKairos {
+ return CarrierMergedConnectionRepositoryKairos(
+ subId,
+ mobileLogger,
+ telephonyManager.createForSubscriptionId(subId),
+ wifiRepository,
+ mobileRepo.isInEcmMode,
+ )
+ }
+ }
+}
+
+private const val TAG = "CarrierMergedConnectionRepository"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryKairos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryKairos.kt
new file mode 100644
index 0000000..1a8ca95
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryKairos.kt
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2024 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.pipeline.mobile.data.repository.prod
+
+import android.util.IndentingPrintWriter
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.KairosBuilder
+import com.android.systemui.kairos.BuildSpec
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.State
+import com.android.systemui.kairos.flatMap
+import com.android.systemui.kairosBuilder
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepositoryKairos
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.io.PrintWriter
+
+/**
+ * A repository that fully implements a mobile connection.
+ *
+ * This connection could either be a typical mobile connection (see [MobileConnectionRepositoryImpl]
+ * or a carrier merged connection (see [CarrierMergedConnectionRepository]). This repository
+ * switches between the two types of connections based on whether the connection is currently
+ * carrier merged.
+ */
+@ExperimentalKairosApi
+class FullMobileConnectionRepositoryKairos
+@AssistedInject
+constructor(
+ @Assisted override val subId: Int,
+ @Assisted override val tableLogBuffer: TableLogBuffer,
+ @Assisted private val mobileRepo: MobileConnectionRepositoryKairos,
+ @Assisted private val carrierMergedRepoSpec: BuildSpec<MobileConnectionRepositoryKairos>,
+ @Assisted private val isCarrierMerged: State<Boolean>,
+) : MobileConnectionRepositoryKairos, KairosBuilder by kairosBuilder() {
+
+ init {
+ onActivated {
+ logDiffsForTable(isCarrierMerged, tableLogBuffer, columnName = "isCarrierMerged")
+ }
+ }
+
+ @VisibleForTesting
+ val activeRepo: State<MobileConnectionRepositoryKairos> = buildState {
+ isCarrierMerged.mapLatestBuild { merged ->
+ if (merged) {
+ carrierMergedRepoSpec.applySpec()
+ } else {
+ mobileRepo
+ }
+ }
+ }
+
+ override val carrierId: State<Int> = activeRepo.flatMap { it.carrierId }
+
+ override val cdmaRoaming: State<Boolean> = activeRepo.flatMap { it.cdmaRoaming }
+
+ override val isEmergencyOnly: State<Boolean> =
+ activeRepo
+ .flatMap { it.isEmergencyOnly }
+ .also {
+ onActivated { logDiffsForTable(it, tableLogBuffer, columnName = COL_EMERGENCY) }
+ }
+
+ override val isRoaming: State<Boolean> =
+ activeRepo
+ .flatMap { it.isRoaming }
+ .also { onActivated { logDiffsForTable(it, tableLogBuffer, columnName = COL_ROAMING) } }
+
+ override val operatorAlphaShort: State<String?> =
+ activeRepo
+ .flatMap { it.operatorAlphaShort }
+ .also {
+ onActivated { logDiffsForTable(it, tableLogBuffer, columnName = COL_OPERATOR) }
+ }
+
+ override val isInService: State<Boolean> =
+ activeRepo
+ .flatMap { it.isInService }
+ .also {
+ onActivated { logDiffsForTable(it, tableLogBuffer, columnName = COL_IS_IN_SERVICE) }
+ }
+
+ override val isNonTerrestrial: State<Boolean> =
+ activeRepo
+ .flatMap { it.isNonTerrestrial }
+ .also { onActivated { logDiffsForTable(it, tableLogBuffer, columnName = COL_IS_NTN) } }
+
+ override val isGsm: State<Boolean> =
+ activeRepo
+ .flatMap { it.isGsm }
+ .also { onActivated { logDiffsForTable(it, tableLogBuffer, columnName = COL_IS_GSM) } }
+
+ override val cdmaLevel: State<Int> =
+ activeRepo
+ .flatMap { it.cdmaLevel }
+ .also {
+ onActivated { logDiffsForTable(it, tableLogBuffer, columnName = COL_CDMA_LEVEL) }
+ }
+
+ override val primaryLevel: State<Int> =
+ activeRepo
+ .flatMap { it.primaryLevel }
+ .also {
+ onActivated { logDiffsForTable(it, tableLogBuffer, columnName = COL_PRIMARY_LEVEL) }
+ }
+
+ override val satelliteLevel: State<Int> =
+ activeRepo
+ .flatMap { it.satelliteLevel }
+ .also {
+ onActivated {
+ logDiffsForTable(it, tableLogBuffer, columnName = COL_SATELLITE_LEVEL)
+ }
+ }
+
+ override val dataConnectionState: State<DataConnectionState> =
+ activeRepo
+ .flatMap { it.dataConnectionState }
+ .also { onActivated { logDiffsForTable(it, tableLogBuffer, columnPrefix = "") } }
+
+ override val dataActivityDirection: State<DataActivityModel> =
+ activeRepo
+ .flatMap { it.dataActivityDirection }
+ .also { onActivated { logDiffsForTable(it, tableLogBuffer, columnPrefix = "") } }
+
+ override val carrierNetworkChangeActive: State<Boolean> =
+ activeRepo
+ .flatMap { it.carrierNetworkChangeActive }
+ .also {
+ onActivated {
+ logDiffsForTable(it, tableLogBuffer, columnName = COL_CARRIER_NETWORK_CHANGE)
+ }
+ }
+
+ override val resolvedNetworkType: State<ResolvedNetworkType> =
+ activeRepo
+ .flatMap { it.resolvedNetworkType }
+ .also { onActivated { logDiffsForTable(it, tableLogBuffer, columnPrefix = "") } }
+
+ override val dataEnabled: State<Boolean> =
+ activeRepo
+ .flatMap { it.dataEnabled }
+ .also {
+ onActivated { logDiffsForTable(it, tableLogBuffer, columnName = "dataEnabled") }
+ }
+
+ override val inflateSignalStrength: State<Boolean> =
+ activeRepo
+ .flatMap { it.inflateSignalStrength }
+ .also { onActivated { logDiffsForTable(it, tableLogBuffer, columnName = "inflate") } }
+
+ override val allowNetworkSliceIndicator: State<Boolean> =
+ activeRepo
+ .flatMap { it.allowNetworkSliceIndicator }
+ .also {
+ onActivated { logDiffsForTable(it, tableLogBuffer, columnName = "allowSlice") }
+ }
+
+ override val numberOfLevels: State<Int> = activeRepo.flatMap { it.numberOfLevels }
+
+ override val networkName: State<NetworkNameModel> =
+ activeRepo
+ .flatMap { it.networkName }
+ .also { onActivated { logDiffsForTable(it, tableLogBuffer, columnPrefix = "intent") } }
+
+ override val carrierName: State<NetworkNameModel> =
+ activeRepo
+ .flatMap { it.carrierName }
+ .also { onActivated { logDiffsForTable(it, tableLogBuffer, columnPrefix = "sub") } }
+
+ override val isAllowedDuringAirplaneMode: State<Boolean> =
+ activeRepo.flatMap { it.isAllowedDuringAirplaneMode }
+
+ override val hasPrioritizedNetworkCapabilities: State<Boolean> =
+ activeRepo.flatMap { it.hasPrioritizedNetworkCapabilities }
+
+ override val isInEcmMode: State<Boolean> = activeRepo.flatMap { it.isInEcmMode }
+
+ private var dumpCache: DumpCache? = null
+
+ private data class DumpCache(
+ val isCarrierMerged: Boolean,
+ val activeRepo: MobileConnectionRepositoryKairos,
+ )
+
+ fun dump(pw: PrintWriter) {
+ val cache = dumpCache ?: return
+ val ipw = IndentingPrintWriter(pw, " ")
+
+ ipw.println("MobileConnectionRepository[$subId]")
+ ipw.increaseIndent()
+
+ ipw.println("carrierMerged=${cache.isCarrierMerged}")
+
+ ipw.print("Type (cellular or carrier merged): ")
+ when (cache.activeRepo) {
+ is CarrierMergedConnectionRepositoryKairos -> ipw.println("Carrier merged")
+ is MobileConnectionRepositoryKairosImpl -> ipw.println("Cellular")
+ }
+
+ ipw.increaseIndent()
+ ipw.println("Provider: ${cache.activeRepo}")
+ ipw.decreaseIndent()
+
+ ipw.decreaseIndent()
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(
+ subId: Int,
+ mobileLogger: TableLogBuffer,
+ isCarrierMerged: State<Boolean>,
+ mobileRepo: MobileConnectionRepositoryKairos,
+ mergedRepoSpec: BuildSpec<MobileConnectionRepositoryKairos>,
+ ): FullMobileConnectionRepositoryKairos
+ }
+
+ companion object {
+ const val COL_CARRIER_ID = "carrierId"
+ const val COL_CARRIER_NETWORK_CHANGE = "carrierNetworkChangeActive"
+ const val COL_CDMA_LEVEL = "cdmaLevel"
+ const val COL_EMERGENCY = "emergencyOnly"
+ const val COL_IS_NTN = "isNtn"
+ const val COL_IS_GSM = "isGsm"
+ const val COL_IS_IN_SERVICE = "isInService"
+ const val COL_OPERATOR = "operatorName"
+ const val COL_PRIMARY_LEVEL = "primaryLevel"
+ const val COL_SATELLITE_LEVEL = "satelliteLevel"
+ const val COL_ROAMING = "roaming"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index b4a45e2..bf7c299 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -542,6 +542,10 @@
data class OnCarrierRoamingNtnSignalStrengthChanged(val signalStrength: NtnSignalStrength) :
CallbackEvent
+
+ data class OnCallBackModeStarted(val type: Int) : CallbackEvent
+
+ data class OnCallBackModeStopped(val type: Int) : CallbackEvent
}
/**
@@ -560,6 +564,8 @@
val onCarrierRoamingNtnSignalStrengthChanged:
CallbackEvent.OnCarrierRoamingNtnSignalStrengthChanged? =
null,
+ val addedCallbackModes: Set<Int> = emptySet(),
+ val removedCallbackModes: Set<Int> = emptySet(),
) {
fun applyEvent(event: CallbackEvent): TelephonyCallbackState {
return when (event) {
@@ -578,6 +584,37 @@
is CallbackEvent.OnSignalStrengthChanged -> copy(onSignalStrengthChanged = event)
is CallbackEvent.OnCarrierRoamingNtnSignalStrengthChanged ->
copy(onCarrierRoamingNtnSignalStrengthChanged = event)
+ is CallbackEvent.OnCallBackModeStarted -> {
+ copy(
+ addedCallbackModes =
+ if (event.type !in removedCallbackModes) {
+ addedCallbackModes + event.type
+ } else {
+ addedCallbackModes
+ },
+ removedCallbackModes =
+ if (event.type !in addedCallbackModes) {
+ removedCallbackModes - event.type
+ } else {
+ removedCallbackModes
+ },
+ )
+ }
+ is CallbackEvent.OnCallBackModeStopped ->
+ copy(
+ addedCallbackModes =
+ if (event.type !in removedCallbackModes) {
+ addedCallbackModes - event.type
+ } else {
+ addedCallbackModes
+ },
+ removedCallbackModes =
+ if (event.type !in addedCallbackModes) {
+ removedCallbackModes + event.type
+ } else {
+ removedCallbackModes
+ },
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryKairosAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryKairosAdapter.kt
new file mode 100644
index 0000000..9b37f48
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryKairosAdapter.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2024 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.pipeline.mobile.data.repository.prod
+
+import com.android.systemui.kairos.BuildScope
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepositoryKairos
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.util.kotlin.Producer
+import kotlinx.coroutines.flow.StateFlow
+
+@ExperimentalKairosApi
+fun BuildScope.MobileConnectionRepositoryKairosAdapter(
+ kairosRepo: MobileConnectionRepositoryKairos,
+ carrierConfig: SystemUiCarrierConfig,
+) =
+ MobileConnectionRepositoryKairosAdapter(
+ subId = kairosRepo.subId,
+ carrierId = kairosRepo.carrierId.toStateFlow(),
+ inflateSignalStrength = carrierConfig.shouldInflateSignalStrength,
+ allowNetworkSliceIndicator = carrierConfig.allowNetworkSliceIndicator,
+ tableLogBuffer = kairosRepo.tableLogBuffer,
+ isEmergencyOnly = kairosRepo.isEmergencyOnly.toStateFlow(),
+ isRoaming = kairosRepo.isRoaming.toStateFlow(),
+ operatorAlphaShort = kairosRepo.operatorAlphaShort.toStateFlow(),
+ isInService = kairosRepo.isInService.toStateFlow(),
+ isNonTerrestrial = kairosRepo.isNonTerrestrial.toStateFlow(),
+ isGsm = kairosRepo.isGsm.toStateFlow(),
+ cdmaLevel = kairosRepo.cdmaLevel.toStateFlow(),
+ primaryLevel = kairosRepo.primaryLevel.toStateFlow(),
+ satelliteLevel = kairosRepo.satelliteLevel.toStateFlow(),
+ dataConnectionState = kairosRepo.dataConnectionState.toStateFlow(),
+ dataActivityDirection = kairosRepo.dataActivityDirection.toStateFlow(),
+ carrierNetworkChangeActive = kairosRepo.carrierNetworkChangeActive.toStateFlow(),
+ resolvedNetworkType = kairosRepo.resolvedNetworkType.toStateFlow(),
+ numberOfLevels = kairosRepo.numberOfLevels.toStateFlow(),
+ dataEnabled = kairosRepo.dataEnabled.toStateFlow(),
+ cdmaRoaming = kairosRepo.cdmaRoaming.toStateFlow(),
+ networkName = kairosRepo.networkName.toStateFlow(),
+ carrierName = kairosRepo.carrierName.toStateFlow(),
+ isAllowedDuringAirplaneMode = kairosRepo.isAllowedDuringAirplaneMode.toStateFlow(),
+ hasPrioritizedNetworkCapabilities =
+ kairosRepo.hasPrioritizedNetworkCapabilities.toStateFlow(),
+ isInEcmMode = { kairosNetwork.transact { kairosRepo.isInEcmMode.sample() } },
+ )
+
+@ExperimentalKairosApi
+class MobileConnectionRepositoryKairosAdapter(
+ override val subId: Int,
+ override val carrierId: StateFlow<Int>,
+ override val inflateSignalStrength: StateFlow<Boolean>,
+ override val allowNetworkSliceIndicator: StateFlow<Boolean>,
+ override val tableLogBuffer: TableLogBuffer,
+ override val isEmergencyOnly: StateFlow<Boolean>,
+ override val isRoaming: StateFlow<Boolean>,
+ override val operatorAlphaShort: StateFlow<String?>,
+ override val isInService: StateFlow<Boolean>,
+ override val isNonTerrestrial: StateFlow<Boolean>,
+ override val isGsm: StateFlow<Boolean>,
+ override val cdmaLevel: StateFlow<Int>,
+ override val primaryLevel: StateFlow<Int>,
+ override val satelliteLevel: StateFlow<Int>,
+ override val dataConnectionState: StateFlow<DataConnectionState>,
+ override val dataActivityDirection: StateFlow<DataActivityModel>,
+ override val carrierNetworkChangeActive: StateFlow<Boolean>,
+ override val resolvedNetworkType: StateFlow<ResolvedNetworkType>,
+ override val numberOfLevels: StateFlow<Int>,
+ override val dataEnabled: StateFlow<Boolean>,
+ override val cdmaRoaming: StateFlow<Boolean>,
+ override val networkName: StateFlow<NetworkNameModel>,
+ override val carrierName: StateFlow<NetworkNameModel>,
+ override val isAllowedDuringAirplaneMode: StateFlow<Boolean>,
+ override val hasPrioritizedNetworkCapabilities: StateFlow<Boolean>,
+ private val isInEcmMode: Producer<Boolean>,
+) : MobileConnectionRepository {
+ override suspend fun isInEcmMode(): Boolean = isInEcmMode.get()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryKairosImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryKairosImpl.kt
new file mode 100644
index 0000000..abe72e1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryKairosImpl.kt
@@ -0,0 +1,485 @@
+/*
+ * Copyright (C) 2024 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.pipeline.mobile.data.repository.prod
+
+import android.annotation.SuppressLint
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.net.ConnectivityManager
+import android.net.ConnectivityManager.NetworkCallback
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkRequest
+import android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
+import android.telephony.CellSignalStrengthCdma
+import android.telephony.ServiceState
+import android.telephony.SignalStrength
+import android.telephony.SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyDisplayInfo
+import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
+import android.telephony.TelephonyManager
+import android.telephony.TelephonyManager.ERI_FLASH
+import android.telephony.TelephonyManager.ERI_ON
+import android.telephony.TelephonyManager.EXTRA_SUBSCRIPTION_ID
+import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
+import android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID
+import com.android.settingslib.Utils
+import com.android.systemui.KairosBuilder
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO
+import com.android.systemui.kairos.Events
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.State
+import com.android.systemui.kairos.Transactional
+import com.android.systemui.kairos.awaitClose
+import com.android.systemui.kairos.coalescingEvents
+import com.android.systemui.kairos.conflatedEvents
+import com.android.systemui.kairos.map
+import com.android.systemui.kairos.mapNotNull
+import com.android.systemui.kairos.stateOf
+import com.android.systemui.kairos.transactionally
+import com.android.systemui.kairosBuilder
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Disconnected
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.OverrideNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.UnknownNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig
+import com.android.systemui.statusbar.pipeline.mobile.data.model.toDataConnectionType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.toNetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.time.Duration
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.scan
+import kotlinx.coroutines.withContext
+
+/**
+ * A repository implementation for a typical mobile connection (as opposed to a carrier merged
+ * connection -- see [CarrierMergedConnectionRepository]).
+ */
+@ExperimentalKairosApi
+class MobileConnectionRepositoryKairosImpl
+@AssistedInject
+constructor(
+ @Assisted override val subId: Int,
+ private val context: Context,
+ @Assisted subscriptionModel: State<SubscriptionModel?>,
+ @Assisted defaultNetworkName: NetworkNameModel,
+ @Assisted networkNameSeparator: String,
+ connectivityManager: ConnectivityManager,
+ @Assisted private val telephonyManager: TelephonyManager,
+ @Assisted systemUiCarrierConfig: SystemUiCarrierConfig,
+ broadcastDispatcher: BroadcastDispatcher,
+ private val mobileMappingsProxy: MobileMappingsProxy,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ logger: MobileInputLogger,
+ @Assisted override val tableLogBuffer: TableLogBuffer,
+ flags: FeatureFlagsClassic,
+) : MobileConnectionRepositoryKairos, KairosBuilder by kairosBuilder() {
+
+ init {
+ if (telephonyManager.subscriptionId != subId) {
+ throw IllegalStateException(
+ "MobileRepo: TelephonyManager should be created with subId($subId). " +
+ "Found ${telephonyManager.subscriptionId} instead."
+ )
+ }
+ }
+
+ /**
+ * This flow defines the single shared connection to system_server via TelephonyCallback. Any
+ * new callback should be added to this listener and funneled through callbackEvents via a data
+ * class. See [CallbackEvent] for defining new callbacks.
+ *
+ * The reason we need to do this is because TelephonyManager limits the number of registered
+ * listeners per-process, so we don't want to create a new listener for every callback.
+ *
+ * A note on the design for back pressure here: We don't control _which_ telephony callback
+ * comes in first, since we register every relevant bit of information as a batch. E.g., if a
+ * downstream starts collecting on a field which is backed by
+ * [TelephonyCallback.ServiceStateListener], it's not possible for us to guarantee that _that_
+ * callback comes in -- the first callback could very well be
+ * [TelephonyCallback.DataActivityListener], which would promptly be dropped if we didn't keep
+ * it tracked. We use the [scan] operator here to track the most recent callback of _each type_
+ * here. See [TelephonyCallbackState] to see how the callbacks are stored.
+ */
+ private val callbackEvents: Events<TelephonyCallbackState> = buildEvents {
+ coalescingEvents(
+ initialValue = TelephonyCallbackState(),
+ coalesce = TelephonyCallbackState::applyEvent,
+ ) {
+ val callback =
+ object :
+ TelephonyCallback(),
+ TelephonyCallback.CarrierNetworkListener,
+ TelephonyCallback.CarrierRoamingNtnListener,
+ TelephonyCallback.DataActivityListener,
+ TelephonyCallback.DataConnectionStateListener,
+ TelephonyCallback.DataEnabledListener,
+ TelephonyCallback.DisplayInfoListener,
+ TelephonyCallback.ServiceStateListener,
+ TelephonyCallback.SignalStrengthsListener,
+ TelephonyCallback.EmergencyCallbackModeListener {
+
+ override fun onCarrierNetworkChange(active: Boolean) {
+ logger.logOnCarrierNetworkChange(active, subId)
+ emit(CallbackEvent.OnCarrierNetworkChange(active))
+ }
+
+ override fun onCarrierRoamingNtnModeChanged(active: Boolean) {
+ logger.logOnCarrierRoamingNtnModeChanged(active)
+ emit(CallbackEvent.OnCarrierRoamingNtnModeChanged(active))
+ }
+
+ override fun onDataActivity(direction: Int) {
+ logger.logOnDataActivity(direction, subId)
+ emit(CallbackEvent.OnDataActivity(direction))
+ }
+
+ override fun onDataEnabledChanged(enabled: Boolean, reason: Int) {
+ logger.logOnDataEnabledChanged(enabled, subId)
+ emit(CallbackEvent.OnDataEnabledChanged(enabled))
+ }
+
+ override fun onDataConnectionStateChanged(dataState: Int, networkType: Int) {
+ logger.logOnDataConnectionStateChanged(dataState, networkType, subId)
+ emit(CallbackEvent.OnDataConnectionStateChanged(dataState))
+ }
+
+ override fun onDisplayInfoChanged(telephonyDisplayInfo: TelephonyDisplayInfo) {
+ logger.logOnDisplayInfoChanged(telephonyDisplayInfo, subId)
+ emit(CallbackEvent.OnDisplayInfoChanged(telephonyDisplayInfo))
+ }
+
+ override fun onServiceStateChanged(serviceState: ServiceState) {
+ logger.logOnServiceStateChanged(serviceState, subId)
+ emit(CallbackEvent.OnServiceStateChanged(serviceState))
+ }
+
+ override fun onSignalStrengthsChanged(signalStrength: SignalStrength) {
+ logger.logOnSignalStrengthsChanged(signalStrength, subId)
+ emit(CallbackEvent.OnSignalStrengthChanged(signalStrength))
+ }
+
+ override fun onCallbackModeStarted(
+ type: Int,
+ timerDuration: Duration,
+ subId: Int,
+ ) {
+ // logger.logOnCallBackModeStarted(type, subId)
+ emit(CallbackEvent.OnCallBackModeStarted(type))
+ }
+
+ override fun onCallbackModeRestarted(
+ type: Int,
+ timerDuration: Duration,
+ subId: Int,
+ ) {
+ // no-op
+ }
+
+ override fun onCallbackModeStopped(type: Int, reason: Int, subId: Int) {
+ // logger.logOnCallBackModeStopped(type, reason, subId)
+ emit(CallbackEvent.OnCallBackModeStopped(type))
+ }
+ }
+ withContext(bgDispatcher) {
+ telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
+ }
+ awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
+ }
+ }
+
+ private val serviceState: State<ServiceState?> = buildState {
+ callbackEvents.mapNotNull { it.onServiceStateChanged?.serviceState }.holdState(null)
+ }
+
+ override val isEmergencyOnly: State<Boolean> = serviceState.map { it?.isEmergencyOnly == true }
+
+ private val displayInfo: State<TelephonyDisplayInfo?> = buildState {
+ callbackEvents.mapNotNull { it.onDisplayInfoChanged?.telephonyDisplayInfo }.holdState(null)
+ }
+
+ override val isRoaming: State<Boolean> =
+ if (flags.isEnabled(ROAMING_INDICATOR_VIA_DISPLAY_INFO)) {
+ displayInfo.map { it?.isRoaming == true }
+ } else {
+ serviceState.map { it?.roaming == true }
+ }
+
+ override val operatorAlphaShort: State<String?> = serviceState.map { it?.operatorAlphaShort }
+
+ override val isInService: State<Boolean> =
+ serviceState.map { it?.let(Utils::isInService) == true }
+
+ private val carrierRoamingNtnActive: State<Boolean> = buildState {
+ callbackEvents.mapNotNull { it.onCarrierRoamingNtnModeChanged?.active }.holdState(false)
+ }
+
+ override val isNonTerrestrial: State<Boolean>
+ get() = carrierRoamingNtnActive
+
+ private val signalStrength: State<SignalStrength?> = buildState {
+ callbackEvents.mapNotNull { it.onSignalStrengthChanged?.signalStrength }.holdState(null)
+ }
+
+ override val isGsm: State<Boolean> = signalStrength.map { it?.isGsm == true }
+
+ override val cdmaLevel: State<Int> =
+ signalStrength.map {
+ it?.getCellSignalStrengths(CellSignalStrengthCdma::class.java)?.firstOrNull()?.level
+ ?: SIGNAL_STRENGTH_NONE_OR_UNKNOWN
+ }
+
+ override val primaryLevel: State<Int> =
+ signalStrength.map { it?.level ?: SIGNAL_STRENGTH_NONE_OR_UNKNOWN }
+
+ override val satelliteLevel: State<Int> = buildState {
+ callbackEvents
+ .mapNotNull { it.onCarrierRoamingNtnSignalStrengthChanged?.signalStrength?.level }
+ .holdState(0)
+ }
+
+ override val dataConnectionState: State<DataConnectionState> = buildState {
+ callbackEvents
+ .mapNotNull { it.onDataConnectionStateChanged?.dataState?.toDataConnectionType() }
+ .holdState(Disconnected)
+ }
+
+ override val dataActivityDirection: State<DataActivityModel> = buildState {
+ callbackEvents
+ .mapNotNull { it.onDataActivity?.direction?.toMobileDataActivityModel() }
+ .holdState(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
+ }
+
+ override val carrierNetworkChangeActive: State<Boolean> = buildState {
+ callbackEvents.mapNotNull { it.onCarrierNetworkChange?.active }.holdState(false)
+ }
+
+ private val telephonyDisplayInfo: State<TelephonyDisplayInfo?> = buildState {
+ callbackEvents.mapNotNull { it.onDisplayInfoChanged?.telephonyDisplayInfo }.holdState(null)
+ }
+
+ override val resolvedNetworkType: State<ResolvedNetworkType> =
+ telephonyDisplayInfo.map { displayInfo ->
+ displayInfo
+ ?.overrideNetworkType
+ ?.takeIf { it != OVERRIDE_NETWORK_TYPE_NONE }
+ ?.let { OverrideNetworkType(mobileMappingsProxy.toIconKeyOverride(it)) }
+ ?: displayInfo
+ ?.networkType
+ ?.takeIf { it != NETWORK_TYPE_UNKNOWN }
+ ?.let { DefaultNetworkType(mobileMappingsProxy.toIconKey(it)) }
+ ?: UnknownNetworkType
+ }
+
+ override val inflateSignalStrength: State<Boolean> = buildState {
+ systemUiCarrierConfig.shouldInflateSignalStrength.toState()
+ }
+
+ override val allowNetworkSliceIndicator: State<Boolean> = buildState {
+ systemUiCarrierConfig.allowNetworkSliceIndicator.toState()
+ }
+
+ override val numberOfLevels: State<Int> =
+ inflateSignalStrength.map { shouldInflate ->
+ if (shouldInflate) {
+ DEFAULT_NUM_LEVELS + 1
+ } else {
+ DEFAULT_NUM_LEVELS
+ }
+ }
+
+ override val carrierName: State<NetworkNameModel> =
+ subscriptionModel.map {
+ it?.let { model -> NetworkNameModel.SubscriptionDerived(model.carrierName) }
+ ?: defaultNetworkName
+ }
+
+ /**
+ * There are a few cases where we will need to poll [TelephonyManager] so we can update some
+ * internal state where callbacks aren't provided. Any of those events should be merged into
+ * this flow, which can be used to trigger the polling.
+ */
+ private val telephonyPollingEvent: Events<Unit> = callbackEvents.map {}
+
+ private val cdmaEnhancedRoamingIndicatorDisplayNumber: Transactional<Int?> = transactionally {
+ try {
+ telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber
+ } catch (e: UnsupportedOperationException) {
+ // Handles the same as a function call failure
+ null
+ }
+ }
+
+ override val cdmaRoaming: State<Boolean> = buildState {
+ telephonyPollingEvent
+ .map {
+ val cdmaEri = cdmaEnhancedRoamingIndicatorDisplayNumber.sample()
+ cdmaEri == ERI_ON || cdmaEri == ERI_FLASH
+ }
+ .holdState(false)
+ }
+
+ override val carrierId: State<Int> = buildState {
+ broadcastDispatcher
+ .broadcastFlow(
+ filter =
+ IntentFilter(TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED),
+ map = { intent, _ -> intent },
+ )
+ .filter { intent ->
+ intent.getIntExtra(EXTRA_SUBSCRIPTION_ID, INVALID_SUBSCRIPTION_ID) == subId
+ }
+ .map { it.carrierId() }
+ .toState(telephonyManager.simCarrierId)
+ }
+
+ /**
+ * BroadcastDispatcher does not handle sticky broadcasts, so we can't use it here. Note that we
+ * now use the [SharingStarted.Eagerly] strategy, because there have been cases where the sticky
+ * broadcast does not represent the correct state.
+ *
+ * See b/322432056 for context.
+ */
+ @SuppressLint("RegisterReceiverViaContext")
+ override val networkName: State<NetworkNameModel> = buildState {
+ conflatedEvents {
+ val receiver =
+ object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ if (
+ intent.getIntExtra(
+ EXTRA_SUBSCRIPTION_INDEX,
+ INVALID_SUBSCRIPTION_ID,
+ ) == subId
+ ) {
+ logger.logServiceProvidersUpdatedBroadcast(intent)
+ emit(
+ intent.toNetworkNameModel(networkNameSeparator)
+ ?: defaultNetworkName
+ )
+ }
+ }
+ }
+
+ context.registerReceiver(
+ receiver,
+ IntentFilter(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED),
+ )
+
+ awaitClose { context.unregisterReceiver(receiver) }
+ }
+ .holdState(defaultNetworkName)
+ }
+
+ override val dataEnabled: State<Boolean> = buildState {
+ callbackEvents
+ .mapNotNull { it.onDataEnabledChanged?.enabled }
+ .holdState(telephonyManager.isDataConnectionAllowed)
+ }
+
+ override val isInEcmMode: State<Boolean> = buildState {
+ callbackEvents
+ .mapNotNull {
+ (it.addedCallbackModes to it.removedCallbackModes).takeIf { (added, removed) ->
+ added.isNotEmpty() || removed.isNotEmpty()
+ }
+ }
+ .foldState(emptySet<Int>()) { (added, removed), acc -> acc - removed + added }
+ .mapTransactionally { it.isNotEmpty() }
+ }
+
+ /** Typical mobile connections aren't available during airplane mode. */
+ override val isAllowedDuringAirplaneMode: State<Boolean> = stateOf(false)
+
+ /**
+ * Currently, a network with NET_CAPABILITY_PRIORITIZE_LATENCY is the only type of network that
+ * we consider to be a "network slice". _PRIORITIZE_BANDWIDTH may be added in the future. Any of
+ * these capabilities that are used here must also be represented in the
+ * self_certified_network_capabilities.xml config file
+ */
+ @SuppressLint("WrongConstant")
+ private val networkSliceRequest: NetworkRequest =
+ NetworkRequest.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+ .setSubscriptionIds(setOf(subId))
+ .build()
+
+ @SuppressLint("MissingPermission")
+ override val hasPrioritizedNetworkCapabilities: State<Boolean> = buildState {
+ conflatedEvents {
+ // Our network callback listens only for this.subId && net_cap_prioritize_latency
+ // therefore our state is a simple mapping of whether or not that network exists
+ val callback =
+ object : NetworkCallback() {
+ override fun onAvailable(network: Network) {
+ logger.logPrioritizedNetworkAvailable(network.netId)
+ emit(true)
+ }
+
+ override fun onLost(network: Network) {
+ logger.logPrioritizedNetworkLost(network.netId)
+ emit(false)
+ }
+ }
+
+ connectivityManager.registerNetworkCallback(networkSliceRequest, callback)
+
+ awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
+ }
+ .holdState(false)
+ }
+
+ @AssistedFactory
+ fun interface Factory {
+ fun create(
+ subId: Int,
+ mobileLogger: TableLogBuffer,
+ subscriptionModel: State<SubscriptionModel?>,
+ defaultNetworkName: NetworkNameModel,
+ networkNameSeparator: String,
+ systemUiCarrierConfig: SystemUiCarrierConfig,
+ telephonyManager: TelephonyManager,
+ ): MobileConnectionRepositoryKairosImpl
+ }
+}
+
+private fun Intent.carrierId(): Int =
+ getIntExtra(TelephonyManager.EXTRA_CARRIER_ID, UNKNOWN_CARRIER_ID)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index 2efc057..d6105c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -192,20 +192,16 @@
override val isDeviceEmergencyCallCapable: StateFlow<Boolean> =
serviceStateChangedEvent
.mapLatest {
- val modems = telephonyManager.activeModemCount
-
- // Assume false for automotive devices which don't have the calling feature.
- // TODO: b/398045526 to revisit the below.
- val isAutomotive: Boolean =
- context.packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
- val hasFeatureCalling: Boolean =
+ // TODO(b/400460777): check for hasSystemFeature only once
+ val hasRadioAccess: Boolean =
context.packageManager.hasSystemFeature(
- PackageManager.FEATURE_TELEPHONY_CALLING
+ PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS
)
- if (isAutomotive && !hasFeatureCalling) {
+ if (!hasRadioAccess) {
return@mapLatest false
}
+ val modems = telephonyManager.activeModemCount
// Check the service state for every modem. If any state reports emergency calling
// capable, then consider the device to have emergency call capabilities
(0..<modems)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryKairosImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryKairosImpl.kt
new file mode 100644
index 0000000..e468159
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryKairosImpl.kt
@@ -0,0 +1,584 @@
+/*
+ * Copyright (C) 2024 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.pipeline.mobile.data.repository.prod
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.PackageManager
+import android.telephony.CarrierConfigManager
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
+import android.telephony.TelephonyCallback.EmergencyCallbackModeListener
+import android.telephony.TelephonyManager
+import android.util.IndentingPrintWriter
+import com.android.internal.telephony.PhoneConstants
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.settingslib.mobile.MobileMappings.Config
+import com.android.systemui.Dumpable
+import com.android.systemui.Flags
+import com.android.systemui.KairosActivatable
+import com.android.systemui.KairosBuilder
+import com.android.systemui.activated
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.kairos.BuildSpec
+import com.android.systemui.kairos.Events
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.Incremental
+import com.android.systemui.kairos.State
+import com.android.systemui.kairos.StateSelector
+import com.android.systemui.kairos.asIncremental
+import com.android.systemui.kairos.asyncEvent
+import com.android.systemui.kairos.buildSpec
+import com.android.systemui.kairos.changes
+import com.android.systemui.kairos.combine
+import com.android.systemui.kairos.effect
+import com.android.systemui.kairos.filterNotNull
+import com.android.systemui.kairos.flatMap
+import com.android.systemui.kairos.map
+import com.android.systemui.kairos.mapNotNull
+import com.android.systemui.kairos.mapValues
+import com.android.systemui.kairos.mergeLeft
+import com.android.systemui.kairos.onEach
+import com.android.systemui.kairos.rebuildOn
+import com.android.systemui.kairos.selector
+import com.android.systemui.kairos.stateOf
+import com.android.systemui.kairos.switchEvents
+import com.android.systemui.kairos.transitions
+import com.android.systemui.kairos.util.WithPrev
+import com.android.systemui.kairosBuilder
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.TableLogBufferFactory
+import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.dagger.MobileSummaryLog
+import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.mobile.util.SubscriptionManagerProxy
+import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel
+import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import dagger.Binds
+import dagger.Lazy
+import dagger.Provides
+import dagger.multibindings.ElementsIntoSet
+import java.io.PrintWriter
+import java.time.Duration
+import javax.inject.Inject
+import javax.inject.Provider
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.withContext
+
+@ExperimentalKairosApi
+@SysUISingleton
+class MobileConnectionsRepositoryKairosImpl
+@Inject
+constructor(
+ connectivityRepository: ConnectivityRepository,
+ private val subscriptionManager: SubscriptionManager,
+ private val subscriptionManagerProxy: SubscriptionManagerProxy,
+ private val telephonyManager: TelephonyManager,
+ private val logger: MobileInputLogger,
+ @MobileSummaryLog private val tableLogger: TableLogBuffer,
+ mobileMappingsProxy: MobileMappingsProxy,
+ broadcastDispatcher: BroadcastDispatcher,
+ private val context: Context,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ @Main private val mainDispatcher: CoroutineDispatcher,
+ airplaneModeRepository: AirplaneModeRepository,
+ // Some "wifi networks" should be rendered as a mobile connection, which is why the wifi
+ // repository is an input to the mobile repository.
+ // See [CarrierMergedConnectionRepositoryKairos] for details.
+ wifiRepository: WifiRepository,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ dumpManager: DumpManager,
+ private val mobileRepoFactory: Lazy<ConnectionRepoFactory>,
+) : MobileConnectionsRepositoryKairos, Dumpable, KairosBuilder by kairosBuilder() {
+
+ init {
+ dumpManager.registerNormalDumpable("MobileConnectionsRepositoryKairos", this)
+ }
+
+ private val carrierMergedSubId: State<Int?> = buildState {
+ combine(
+ wifiRepository.wifiNetwork.toState(),
+ connectivityRepository.defaultConnections.toState(),
+ airplaneModeRepository.isAirplaneMode.toState(),
+ ) { wifiNetwork, defaultConnections, isAirplaneMode ->
+ // The carrier merged connection should only be used if it's also the default
+ // connection or mobile connections aren't available because of airplane mode.
+ val defaultConnectionIsNonMobile =
+ defaultConnections.carrierMerged.isDefault ||
+ defaultConnections.wifi.isDefault ||
+ isAirplaneMode
+
+ if (wifiNetwork is WifiNetworkModel.CarrierMerged && defaultConnectionIsNonMobile) {
+ wifiNetwork.subscriptionId
+ } else {
+ null
+ }
+ }
+ .also {
+ logDiffsForTable(it, tableLogger, LOGGING_PREFIX, columnName = "carrierMergedSubId")
+ }
+ }
+
+ private val mobileSubscriptionsChangeEvent: Events<Unit> = buildEvents {
+ conflatedCallbackFlow {
+ val callback =
+ object : SubscriptionManager.OnSubscriptionsChangedListener() {
+ override fun onSubscriptionsChanged() {
+ logger.logOnSubscriptionsChanged()
+ trySend(Unit)
+ }
+ }
+ subscriptionManager.addOnSubscriptionsChangedListener(Runnable::run, callback)
+ awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) }
+ }
+ .flowOn(bgDispatcher)
+ .toEvents()
+ }
+
+ /** Turn ACTION_SERVICE_STATE (for subId = -1) into an event */
+ private val serviceStateChangedEvent: Events<Unit> = buildEvents {
+ broadcastDispatcher
+ .broadcastFlow(IntentFilter(Intent.ACTION_SERVICE_STATE)) { intent, _ ->
+ val subId =
+ intent.getIntExtra(
+ SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
+ INVALID_SUBSCRIPTION_ID,
+ )
+
+ // Only emit if the subId is not associated with an active subscription
+ if (subId == INVALID_SUBSCRIPTION_ID) {
+ Unit
+ }
+ }
+ .toEvents()
+ }
+
+ /** Eager flow to determine the device-based emergency calls only state */
+ override val isDeviceEmergencyCallCapable: State<Boolean> = buildState {
+ rebuildOn(serviceStateChangedEvent) { asyncEvent { doAnyModemsSupportEmergencyCalls() } }
+ .switchEvents()
+ .holdState(false)
+ .also {
+ logDiffsForTable(
+ it,
+ tableLogger,
+ LOGGING_PREFIX,
+ columnName = "deviceEmergencyOnly",
+ )
+ }
+ }
+
+ private suspend fun doAnyModemsSupportEmergencyCalls(): Boolean =
+ withContext(bgDispatcher) {
+ val modems = telephonyManager.activeModemCount
+
+ // Assume false for automotive devices which don't have the calling feature.
+ // TODO: b/398045526 to revisit the below.
+ val isAutomotive: Boolean =
+ context.packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
+ val hasFeatureCalling: Boolean =
+ context.packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
+ if (isAutomotive && !hasFeatureCalling) {
+ return@withContext false
+ }
+
+ // Check the service state for every modem. If any state reports emergency calling
+ // capable, then consider the device to have emergency call capabilities
+ (0..<modems)
+ .map { telephonyManager.getServiceStateForSlot(it) }
+ .any { it?.isEmergencyOnly == true }
+ }
+
+ /**
+ * State flow that emits the set of mobile data subscriptions, each represented by its own
+ * [SubscriptionModel].
+ */
+ override val subscriptions: State<List<SubscriptionModel>> = buildState {
+ rebuildOn(mergeLeft(mobileSubscriptionsChangeEvent, carrierMergedSubId.changes)) {
+ asyncEvent { fetchSubscriptionModels() }
+ }
+ .switchEvents()
+ .holdState(emptyList())
+ .also {
+ logDiffsForTable(it, tableLogger, LOGGING_PREFIX, columnName = "subscriptions")
+ }
+ }
+
+ val subscriptionsById: State<Map<Int, SubscriptionModel>> =
+ subscriptions.map { subs -> subs.associateBy { it.subscriptionId } }
+
+ override val mobileConnectionsBySubId: Incremental<Int, MobileConnectionRepositoryKairos> =
+ buildIncremental {
+ subscriptionsById
+ .asIncremental()
+ .mapValues { (subId, sub) -> mobileRepoFactory.get().create(subId) }
+ .applyLatestSpecForKey()
+ }
+
+ private val telephonyManagerState: State<Pair<Int?, Set<Int>>> = buildState {
+ callbackFlow {
+ val callback =
+ object :
+ TelephonyCallback(),
+ ActiveDataSubscriptionIdListener,
+ EmergencyCallbackModeListener {
+ override fun onActiveDataSubscriptionIdChanged(subId: Int) {
+ if (subId != INVALID_SUBSCRIPTION_ID) {
+ trySend { (_, set): Pair<Int?, Set<Int>> -> subId to set }
+ } else {
+ trySend { (_, set): Pair<Int?, Set<Int>> -> null to set }
+ }
+ }
+
+ override fun onCallbackModeStarted(
+ type: Int,
+ timerDuration: Duration,
+ subId: Int,
+ ) {
+ trySend { (id, set): Pair<Int?, Set<Int>> -> id to (set + type) }
+ }
+
+ override fun onCallbackModeRestarted(
+ type: Int,
+ timerDuration: Duration,
+ subId: Int,
+ ) {
+ // no-op
+ }
+
+ override fun onCallbackModeStopped(type: Int, reason: Int, subId: Int) {
+ trySend { (id, set): Pair<Int?, Set<Int>> -> id to (set - type) }
+ }
+ }
+ telephonyManager.registerTelephonyCallback(Runnable::run, callback)
+ awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
+ }
+ .flowOn(bgDispatcher)
+ .scanToState(null to emptySet())
+ }
+
+ override val activeMobileDataSubscriptionId: State<Int?> =
+ telephonyManagerState
+ .map { it.first }
+ .also {
+ onActivated {
+ logDiffsForTable(it, tableLogger, LOGGING_PREFIX, columnName = "activeSubId")
+ }
+ }
+
+ override val activeMobileDataRepository: State<MobileConnectionRepositoryKairos?> =
+ combine(activeMobileDataSubscriptionId, mobileConnectionsBySubId) { id, cache -> cache[id] }
+
+ override val defaultDataSubId: State<Int?> = buildState {
+ broadcastDispatcher
+ .broadcastFlow(
+ IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
+ ) { intent, _ ->
+ intent
+ .getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, INVALID_SUBSCRIPTION_ID)
+ .takeIf { it != INVALID_SUBSCRIPTION_ID }
+ }
+ .onStart {
+ emit(
+ subscriptionManagerProxy.getDefaultDataSubscriptionId().takeIf {
+ it != INVALID_SUBSCRIPTION_ID
+ }
+ )
+ }
+ .toState(initialValue = null)
+ .also { logDiffsForTable(it, tableLogger, LOGGING_PREFIX, columnName = "defaultSubId") }
+ }
+
+ private val carrierConfigChangedEvent: Events<Unit> =
+ buildEvents {
+ broadcastDispatcher
+ .broadcastFlow(IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED))
+ .toEvents()
+ }
+ .onEach { logger.logActionCarrierConfigChanged() }
+
+ override val defaultDataSubRatConfig: State<Config> = buildState {
+ rebuildOn(mergeLeft(defaultDataSubId.changes, carrierConfigChangedEvent)) {
+ Config.readConfig(context).also { effect { logger.logDefaultDataSubRatConfig(it) } }
+ }
+ }
+
+ override val defaultMobileIconMapping: State<Map<String, MobileIconGroup>> = buildState {
+ defaultDataSubRatConfig
+ .map { mobileMappingsProxy.mapIconSets(it) }
+ .apply { observe { logger.logDefaultMobileIconMapping(it) } }
+ }
+
+ override val defaultMobileIconGroup: State<MobileIconGroup> = buildState {
+ defaultDataSubRatConfig
+ .map { mobileMappingsProxy.getDefaultIcons(it) }
+ .apply { observe { logger.logDefaultMobileIconGroup(it) } }
+ }
+
+ override val isAnySimSecure: State<Boolean> = buildState {
+ conflatedCallbackFlow {
+ val callback =
+ object : KeyguardUpdateMonitorCallback() {
+ override fun onSimStateChanged(subId: Int, slotId: Int, simState: Int) {
+ logger.logOnSimStateChanged()
+ trySend(keyguardUpdateMonitor.isSimPinSecure)
+ }
+ }
+ keyguardUpdateMonitor.registerCallback(callback)
+ awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
+ }
+ .flowOn(mainDispatcher)
+ .toState(false)
+ .also {
+ logDiffsForTable(it, tableLogger, LOGGING_PREFIX, columnName = "isAnySimSecure")
+ }
+ }
+
+ private val defaultConnections: State<DefaultConnectionModel> = buildState {
+ connectivityRepository.defaultConnections.toState()
+ }
+
+ override val mobileIsDefault: State<Boolean> =
+ defaultConnections
+ .map { it.mobile.isDefault }
+ .also {
+ onActivated {
+ logDiffsForTable(
+ it,
+ tableLogger,
+ columnPrefix = LOGGING_PREFIX,
+ columnName = "mobileIsDefault",
+ )
+ }
+ }
+
+ override val hasCarrierMergedConnection: State<Boolean> =
+ carrierMergedSubId
+ .map { it != null }
+ .also {
+ onActivated {
+ logDiffsForTable(
+ it,
+ tableLogger,
+ columnPrefix = LOGGING_PREFIX,
+ columnName = "hasCarrierMergedConnection",
+ )
+ }
+ }
+
+ override val defaultConnectionIsValidated: State<Boolean> =
+ defaultConnections
+ .map { it.isValidated }
+ .also {
+ onActivated {
+ logDiffsForTable(
+ it,
+ tableLogger,
+ columnPrefix = LOGGING_PREFIX,
+ columnName = "defaultConnectionIsValidated",
+ )
+ }
+ }
+
+ /**
+ * Flow that tracks the active mobile data subscriptions. Emits `true` whenever the active data
+ * subscription Id changes but the subscription group remains the same. In these cases, we want
+ * to retain the previous subscription's validation status for up to 2s to avoid flickering the
+ * icon.
+ *
+ * TODO(b/265164432): we should probably expose all change events, not just same group
+ */
+ @SuppressLint("MissingPermission")
+ override val activeSubChangedInGroupEvent: Events<Unit> = buildEvents {
+ activeMobileDataSubscriptionId.transitions
+ .mapNotNull { (prevVal, newVal) ->
+ prevVal?.let { newVal?.let { WithPrev(prevVal, newVal) } }
+ }
+ .mapAsyncLatest { (prevVal, newVal) ->
+ if (isActiveSubChangeInGroup(prevVal, newVal)) Unit else null
+ }
+ .filterNotNull()
+ }
+
+ private suspend fun isActiveSubChangeInGroup(prevId: Int, newId: Int): Boolean =
+ withContext(bgDispatcher) {
+ val prevSub = subscriptionManager.getActiveSubscriptionInfo(prevId)?.groupUuid
+ val nextSub = subscriptionManager.getActiveSubscriptionInfo(newId)?.groupUuid
+ prevSub != null && prevSub == nextSub
+ }
+
+ private val isInEcmModeTopLevel: State<Boolean> =
+ telephonyManagerState.map { it.second.isNotEmpty() }
+
+ override val isInEcmMode: State<Boolean> =
+ isInEcmModeTopLevel.flatMap { isInEcm ->
+ if (isInEcm) {
+ stateOf(true)
+ } else {
+ mobileConnectionsBySubId.flatMap {
+ it.mapValues { it.value.isInEcmMode }.combine().map { it.values.any { it } }
+ }
+ }
+ }
+
+ /** Determines which subId is currently carrier-merged. */
+ val carrierMergedSelector: StateSelector<Int?> = carrierMergedSubId.selector()
+
+ private suspend fun fetchSubscriptionModels(): List<SubscriptionModel> =
+ withContext(bgDispatcher) {
+ subscriptionManager.completeActiveSubscriptionInfoList.map { it.toSubscriptionModel() }
+ }
+
+ private fun SubscriptionInfo.toSubscriptionModel(): SubscriptionModel =
+ SubscriptionModel(
+ subscriptionId = subscriptionId,
+ isOpportunistic = isOpportunistic,
+ isExclusivelyNonTerrestrial = isOnlyNonTerrestrialNetwork,
+ groupUuid = groupUuid,
+ carrierName = carrierName.toString(),
+ profileClass = profileClass,
+ )
+
+ private var dumpCache: DumpCache? = null
+
+ private data class DumpCache(val repos: Map<Int, FullMobileConnectionRepositoryKairos>)
+
+ override fun dump(pw: PrintWriter, args: Array<String>) {
+ val cache = dumpCache ?: return
+ val ipw = IndentingPrintWriter(pw, " ")
+ ipw.println("Connection cache:")
+
+ ipw.increaseIndent()
+ cache.repos.forEach { (subId, repo) -> ipw.println("$subId: $repo") }
+ ipw.decreaseIndent()
+
+ ipw.println("Connections (${cache.repos.size} total):")
+ ipw.increaseIndent()
+ cache.repos.values.forEach { it.dump(ipw) }
+ ipw.decreaseIndent()
+ }
+
+ fun interface ConnectionRepoFactory {
+ fun create(subId: Int): BuildSpec<MobileConnectionRepositoryKairos>
+ }
+
+ @dagger.Module
+ object Module {
+ @Provides
+ @ElementsIntoSet
+ fun kairosActivatable(
+ impl: Provider<MobileConnectionsRepositoryKairosImpl>
+ ): Set<@JvmSuppressWildcards KairosActivatable> =
+ if (Flags.statusBarMobileIconKairos()) setOf(impl.get()) else emptySet()
+ }
+
+ companion object {
+ private const val LOGGING_PREFIX = "Repo"
+ }
+}
+
+@ExperimentalKairosApi
+class MobileConnectionRepositoryKairosFactoryImpl
+@Inject
+constructor(
+ context: Context,
+ private val connectionsRepo: MobileConnectionsRepositoryKairosImpl,
+ private val logFactory: TableLogBufferFactory,
+ private val carrierConfigRepo: CarrierConfigRepository,
+ private val telephonyManager: TelephonyManager,
+ private val mobileRepoFactory: MobileConnectionRepositoryKairosImpl.Factory,
+ private val mergedRepoFactory: CarrierMergedConnectionRepositoryKairos.Factory,
+) : MobileConnectionsRepositoryKairosImpl.ConnectionRepoFactory {
+
+ private val networkNameSeparator: String =
+ context.getString(R.string.status_bar_network_name_separator)
+
+ private val defaultNetworkName =
+ NetworkNameModel.Default(
+ context.getString(com.android.internal.R.string.lockscreen_carrier_default)
+ )
+
+ override fun create(subId: Int): BuildSpec<MobileConnectionRepositoryKairos> = buildSpec {
+ activated {
+ val mobileLogger =
+ logFactory.getOrCreate(tableBufferLogName(subId), MOBILE_CONNECTION_BUFFER_SIZE)
+ val mobileRepo = activated {
+ mobileRepoFactory.create(
+ subId,
+ mobileLogger,
+ connectionsRepo.subscriptionsById.map { subs -> subs[subId] },
+ defaultNetworkName,
+ networkNameSeparator,
+ carrierConfigRepo.getOrCreateConfigForSubId(subId),
+ telephonyManager.createForSubscriptionId(subId),
+ )
+ }
+ FullMobileConnectionRepositoryKairos(
+ subId = subId,
+ tableLogBuffer = mobileLogger,
+ mobileRepo = mobileRepo,
+ carrierMergedRepoSpec =
+ buildSpec {
+ activated { mergedRepoFactory.build(subId, mobileLogger, mobileRepo) }
+ },
+ isCarrierMerged = connectionsRepo.carrierMergedSelector[subId],
+ )
+ }
+ }
+
+ companion object {
+ /** The buffer size to use for logging. */
+ private const val MOBILE_CONNECTION_BUFFER_SIZE = 100
+
+ /** Returns a log buffer name for a mobile connection with the given [subId]. */
+ fun tableBufferLogName(subId: Int): String = "MobileConnectionLog[$subId]"
+ }
+
+ @dagger.Module
+ interface Module {
+ @Binds
+ fun bindImpl(
+ impl: MobileConnectionRepositoryKairosFactoryImpl
+ ): MobileConnectionsRepositoryKairosImpl.ConnectionRepoFactory
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairos.kt
new file mode 100644
index 0000000..4580ad9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairos.kt
@@ -0,0 +1,380 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
+
+import android.content.Context
+import com.android.internal.telephony.flags.Flags
+import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.settingslib.graph.SignalDrawable
+import com.android.settingslib.mobile.MobileIconCarrierIdOverrides
+import com.android.settingslib.mobile.MobileIconCarrierIdOverridesImpl
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Connected
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel.DefaultIcon
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel.OverriddenIcon
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
+import com.android.systemui.statusbar.pipeline.satellite.ui.model.SatelliteIconModel
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+interface MobileIconInteractorKairos {
+ /** The table log created for this connection */
+ val tableLogBuffer: TableLogBuffer
+
+ /** The current mobile data activity */
+ val activity: Flow<DataActivityModel>
+
+ /** See [MobileConnectionsRepository.mobileIsDefault]. */
+ val mobileIsDefault: Flow<Boolean>
+
+ /**
+ * True when telephony tells us that the data state is CONNECTED. See
+ * [android.telephony.TelephonyCallback.DataConnectionStateListener] for more details. We
+ * consider this connection to be serving data, and thus want to show a network type icon, when
+ * data is connected. Other data connection states would typically cause us not to show the icon
+ */
+ val isDataConnected: StateFlow<Boolean>
+
+ /** True if we consider this connection to be in service, i.e. can make calls */
+ val isInService: StateFlow<Boolean>
+
+ /** True if this connection is emergency only */
+ val isEmergencyOnly: StateFlow<Boolean>
+
+ /** Observable for the data enabled state of this connection */
+ val isDataEnabled: StateFlow<Boolean>
+
+ /** True if the RAT icon should always be displayed and false otherwise. */
+ val alwaysShowDataRatIcon: StateFlow<Boolean>
+
+ /** Canonical representation of the current mobile signal strength as a triangle. */
+ val signalLevelIcon: StateFlow<SignalIconModel>
+
+ /** Observable for RAT type (network type) indicator */
+ val networkTypeIconGroup: StateFlow<NetworkTypeIconModel>
+
+ /** Whether or not to show the slice attribution */
+ val showSliceAttribution: StateFlow<Boolean>
+
+ /** True if this connection is satellite-based */
+ val isNonTerrestrial: StateFlow<Boolean>
+
+ /**
+ * Provider name for this network connection. The name can be one of 3 values:
+ * 1. The default network name, if one is configured
+ * 2. A derived name based off of the intent [ACTION_SERVICE_PROVIDERS_UPDATED]
+ * 3. Or, in the case where the repository sends us the default network name, we check for an
+ * override in [connectionInfo.operatorAlphaShort], a value that is derived from
+ * [ServiceState]
+ */
+ val networkName: StateFlow<NetworkNameModel>
+
+ /**
+ * Provider name for this network connection. The name can be one of 3 values:
+ * 1. The default network name, if one is configured
+ * 2. A name provided by the [SubscriptionModel] of this network connection
+ * 3. Or, in the case where the repository sends us the default network name, we check for an
+ * override in [connectionInfo.operatorAlphaShort], a value that is derived from
+ * [ServiceState]
+ *
+ * TODO(b/296600321): De-duplicate this field with [networkName] after determining the data
+ * provided is identical
+ */
+ val carrierName: StateFlow<String>
+
+ /** True if there is only one active subscription. */
+ val isSingleCarrier: StateFlow<Boolean>
+
+ /**
+ * True if this connection is considered roaming. The roaming bit can come from [ServiceState],
+ * or directly from the telephony manager's CDMA ERI number value. Note that we don't consider a
+ * connection to be roaming while carrier network change is active
+ */
+ val isRoaming: StateFlow<Boolean>
+
+ /** See [MobileIconsInteractor.isForceHidden]. */
+ val isForceHidden: Flow<Boolean>
+
+ /** See [MobileConnectionRepository.isAllowedDuringAirplaneMode]. */
+ val isAllowedDuringAirplaneMode: StateFlow<Boolean>
+
+ /** True when in carrier network change mode */
+ val carrierNetworkChangeActive: StateFlow<Boolean>
+}
+
+/** Interactor for a single mobile connection. This connection _should_ have one subscription ID */
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+class MobileIconInteractorKairosImpl(
+ @Background scope: CoroutineScope,
+ defaultSubscriptionHasDataEnabled: StateFlow<Boolean>,
+ override val alwaysShowDataRatIcon: StateFlow<Boolean>,
+ alwaysUseCdmaLevel: StateFlow<Boolean>,
+ override val isSingleCarrier: StateFlow<Boolean>,
+ override val mobileIsDefault: StateFlow<Boolean>,
+ defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>,
+ defaultMobileIconGroup: StateFlow<MobileIconGroup>,
+ isDefaultConnectionFailed: StateFlow<Boolean>,
+ override val isForceHidden: Flow<Boolean>,
+ connectionRepository: MobileConnectionRepository,
+ private val context: Context,
+ val carrierIdOverrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl(),
+) : MobileIconInteractor, MobileIconInteractorKairos {
+ override val tableLogBuffer: TableLogBuffer = connectionRepository.tableLogBuffer
+
+ override val activity = connectionRepository.dataActivityDirection
+
+ override val isDataEnabled: StateFlow<Boolean> = connectionRepository.dataEnabled
+
+ override val carrierNetworkChangeActive: StateFlow<Boolean> =
+ connectionRepository.carrierNetworkChangeActive
+
+ // True if there exists _any_ icon override for this carrierId. Note that overrides can include
+ // any or none of the icon groups defined in MobileMappings, so we still need to check on a
+ // per-network-type basis whether or not the given icon group is overridden
+ private val carrierIdIconOverrideExists =
+ connectionRepository.carrierId
+ .map { carrierIdOverrides.carrierIdEntryExists(it) }
+ .distinctUntilChanged()
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val networkName =
+ combine(connectionRepository.operatorAlphaShort, connectionRepository.networkName) {
+ operatorAlphaShort,
+ networkName ->
+ if (networkName is NetworkNameModel.Default && operatorAlphaShort != null) {
+ NetworkNameModel.IntentDerived(operatorAlphaShort)
+ } else {
+ networkName
+ }
+ }
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ connectionRepository.networkName.value,
+ )
+
+ override val carrierName =
+ combine(connectionRepository.operatorAlphaShort, connectionRepository.carrierName) {
+ operatorAlphaShort,
+ networkName ->
+ if (networkName is NetworkNameModel.Default && operatorAlphaShort != null) {
+ operatorAlphaShort
+ } else {
+ networkName.name
+ }
+ }
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ connectionRepository.carrierName.value.name,
+ )
+
+ /** What the mobile icon would be before carrierId overrides */
+ private val defaultNetworkType: StateFlow<MobileIconGroup> =
+ combine(
+ connectionRepository.resolvedNetworkType,
+ defaultMobileIconMapping,
+ defaultMobileIconGroup,
+ ) { resolvedNetworkType, mapping, defaultGroup ->
+ when (resolvedNetworkType) {
+ is ResolvedNetworkType.CarrierMergedNetworkType ->
+ resolvedNetworkType.iconGroupOverride
+ else -> {
+ mapping[resolvedNetworkType.lookupKey] ?: defaultGroup
+ }
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), defaultMobileIconGroup.value)
+
+ override val networkTypeIconGroup =
+ combine(defaultNetworkType, carrierIdIconOverrideExists) { networkType, overrideExists ->
+ // DefaultIcon comes out of the icongroup lookup, we check for overrides here
+ if (overrideExists) {
+ val iconOverride =
+ carrierIdOverrides.getOverrideFor(
+ connectionRepository.carrierId.value,
+ networkType.name,
+ context.resources,
+ )
+ if (iconOverride > 0) {
+ OverriddenIcon(networkType, iconOverride)
+ } else {
+ DefaultIcon(networkType)
+ }
+ } else {
+ DefaultIcon(networkType)
+ }
+ }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLogBuffer = tableLogBuffer,
+ initialValue = DefaultIcon(defaultMobileIconGroup.value),
+ )
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ DefaultIcon(defaultMobileIconGroup.value),
+ )
+
+ override val showSliceAttribution: StateFlow<Boolean> =
+ combine(
+ connectionRepository.allowNetworkSliceIndicator,
+ connectionRepository.hasPrioritizedNetworkCapabilities,
+ ) { allowed, hasPrioritizedNetworkCapabilities ->
+ allowed && hasPrioritizedNetworkCapabilities
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val isNonTerrestrial: StateFlow<Boolean> = connectionRepository.isNonTerrestrial
+
+ override val isRoaming: StateFlow<Boolean> =
+ combine(
+ connectionRepository.carrierNetworkChangeActive,
+ connectionRepository.isGsm,
+ connectionRepository.isRoaming,
+ connectionRepository.cdmaRoaming,
+ ) { carrierNetworkChangeActive, isGsm, isRoaming, cdmaRoaming ->
+ if (carrierNetworkChangeActive) {
+ false
+ } else if (isGsm) {
+ isRoaming
+ } else {
+ cdmaRoaming
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ private val level: StateFlow<Int> =
+ combine(
+ connectionRepository.isGsm,
+ connectionRepository.primaryLevel,
+ connectionRepository.cdmaLevel,
+ alwaysUseCdmaLevel,
+ ) { isGsm, primaryLevel, cdmaLevel, alwaysUseCdmaLevel ->
+ when {
+ // GSM connections should never use the CDMA level
+ isGsm -> primaryLevel
+ alwaysUseCdmaLevel -> cdmaLevel
+ else -> primaryLevel
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
+
+ private val numberOfLevels: StateFlow<Int> = connectionRepository.numberOfLevels
+
+ override val isDataConnected: StateFlow<Boolean> =
+ connectionRepository.dataConnectionState
+ .map { it == Connected }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val isInService = connectionRepository.isInService
+
+ override val isEmergencyOnly: StateFlow<Boolean> = connectionRepository.isEmergencyOnly
+
+ override val isAllowedDuringAirplaneMode = connectionRepository.isAllowedDuringAirplaneMode
+
+ /** Whether or not to show the error state of [SignalDrawable] */
+ private val showExclamationMark: StateFlow<Boolean> =
+ combine(defaultSubscriptionHasDataEnabled, isDefaultConnectionFailed, isInService) {
+ isDefaultDataEnabled,
+ isDefaultConnectionFailed,
+ isInService ->
+ !isDefaultDataEnabled || isDefaultConnectionFailed || !isInService
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), true)
+
+ private val cellularShownLevel: StateFlow<Int> =
+ combine(level, isInService, connectionRepository.inflateSignalStrength) {
+ level,
+ isInService,
+ inflate ->
+ if (isInService) {
+ if (inflate) level + 1 else level
+ } else 0
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
+
+ // Satellite level is unaffected by the inflateSignalStrength property
+ // See b/346904529 for details
+ private val satelliteShownLevel: StateFlow<Int> =
+ if (Flags.carrierRoamingNbIotNtn()) {
+ connectionRepository.satelliteLevel
+ } else {
+ combine(level, isInService) { level, isInService -> if (isInService) level else 0 }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
+
+ private val cellularIcon: Flow<SignalIconModel.Cellular> =
+ combine(
+ cellularShownLevel,
+ numberOfLevels,
+ showExclamationMark,
+ carrierNetworkChangeActive,
+ ) { cellularShownLevel, numberOfLevels, showExclamationMark, carrierNetworkChange ->
+ SignalIconModel.Cellular(
+ cellularShownLevel,
+ numberOfLevels,
+ showExclamationMark,
+ carrierNetworkChange,
+ )
+ }
+
+ private val satelliteIcon: Flow<SignalIconModel.Satellite> =
+ satelliteShownLevel.map {
+ SignalIconModel.Satellite(
+ level = it,
+ icon =
+ SatelliteIconModel.fromSignalStrength(it)
+ ?: SatelliteIconModel.fromSignalStrength(0)!!,
+ )
+ }
+
+ override val signalLevelIcon: StateFlow<SignalIconModel> = run {
+ val initial =
+ SignalIconModel.Cellular(
+ cellularShownLevel.value,
+ numberOfLevels.value,
+ showExclamationMark.value,
+ carrierNetworkChangeActive.value,
+ )
+ isNonTerrestrial
+ .flatMapLatest { ntn ->
+ if (ntn) {
+ satelliteIcon
+ } else {
+ cellularIcon
+ }
+ }
+ .distinctUntilChanged()
+ .logDiffsForTable(tableLogBuffer, columnPrefix = "icon", initialValue = initial)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), initial)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairos.kt
new file mode 100644
index 0000000..e8e0a83
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairos.kt
@@ -0,0 +1,452 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
+
+import android.content.Context
+import android.telephony.CarrierConfigManager
+import android.telephony.SubscriptionManager
+import android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING
+import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.statusbar.core.NewStatusBarIcons
+import com.android.systemui.statusbar.core.StatusBarRootModernization
+import com.android.systemui.statusbar.pipeline.dagger.MobileSummaryLog
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
+import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
+import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
+import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository
+import com.android.systemui.util.CarrierConfigTracker
+import java.lang.ref.WeakReference
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.transformLatest
+
+/**
+ * Business layer logic for the set of mobile subscription icons.
+ *
+ * This interactor represents known set of mobile subscriptions (represented by [SubscriptionInfo]).
+ * The list of subscriptions is filtered based on the opportunistic flags on the infos.
+ *
+ * It provides the default mapping between the telephony display info and the icon group that
+ * represents each RAT (LTE, 3G, etc.), as well as can produce an interactor for each individual
+ * icon
+ */
+interface MobileIconsInteractorKairos {
+ /** See [MobileConnectionsRepository.mobileIsDefault]. */
+ val mobileIsDefault: StateFlow<Boolean>
+
+ /** List of subscriptions, potentially filtered for CBRS */
+ val filteredSubscriptions: Flow<List<SubscriptionModel>>
+
+ /** Subscription ID of the current default data subscription */
+ val defaultDataSubId: Flow<Int?>
+
+ /**
+ * The current list of [MobileIconInteractor]s associated with the current list of
+ * [filteredSubscriptions]
+ */
+ val icons: StateFlow<List<MobileIconInteractor>>
+
+ /** Whether the mobile icons can be stacked vertically. */
+ val isStackable: StateFlow<Boolean>
+
+ /**
+ * Observable for the subscriptionId of the current mobile data connection. Null if we don't
+ * have a valid subscription id
+ */
+ val activeMobileDataSubscriptionId: StateFlow<Int?>
+
+ /** True if the active mobile data subscription has data enabled */
+ val activeDataConnectionHasDataEnabled: StateFlow<Boolean>
+
+ /**
+ * Flow providing a reference to the Interactor for the active data subId. This represents the
+ * [MobileIconInteractor] responsible for the active data connection, if any.
+ */
+ val activeDataIconInteractor: StateFlow<MobileIconInteractor?>
+
+ /** True if the RAT icon should always be displayed and false otherwise. */
+ val alwaysShowDataRatIcon: StateFlow<Boolean>
+
+ /** True if the CDMA level should be preferred over the primary level. */
+ val alwaysUseCdmaLevel: StateFlow<Boolean>
+
+ /** True if there is only one active subscription. */
+ val isSingleCarrier: StateFlow<Boolean>
+
+ /** The icon mapping from network type to [MobileIconGroup] for the default subscription */
+ val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>
+
+ /** Fallback [MobileIconGroup] in the case where there is no icon in the mapping */
+ val defaultMobileIconGroup: StateFlow<MobileIconGroup>
+
+ /** True only if the default network is mobile, and validation also failed */
+ val isDefaultConnectionFailed: StateFlow<Boolean>
+
+ /** True once the user has been set up */
+ val isUserSetUp: StateFlow<Boolean>
+
+ /** True if we're configured to force-hide the mobile icons and false otherwise. */
+ val isForceHidden: Flow<Boolean>
+
+ /**
+ * True if the device-level service state (with -1 subscription id) reports emergency calls
+ * only. This value is only useful when there are no other subscriptions OR all existing
+ * subscriptions report that they are not in service.
+ */
+ val isDeviceInEmergencyCallsOnlyMode: Flow<Boolean>
+
+ /**
+ * Vends out a [MobileIconInteractor] tracking the [MobileConnectionRepository] for the given
+ * subId.
+ */
+ fun getMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor
+}
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@SysUISingleton
+class MobileIconsInteractorKairosImpl
+@Inject
+constructor(
+ private val mobileConnectionsRepo: MobileConnectionsRepository,
+ private val carrierConfigTracker: CarrierConfigTracker,
+ @MobileSummaryLog private val tableLogger: TableLogBuffer,
+ connectivityRepository: ConnectivityRepository,
+ userSetupRepo: UserSetupRepository,
+ @Background private val scope: CoroutineScope,
+ private val context: Context,
+ private val featureFlagsClassic: FeatureFlagsClassic,
+) : MobileIconsInteractor, MobileIconsInteractorKairos {
+
+ // Weak reference lookup for created interactors
+ private val reuseCache = mutableMapOf<Int, WeakReference<MobileIconInteractor>>()
+
+ override val mobileIsDefault =
+ combine(
+ mobileConnectionsRepo.mobileIsDefault,
+ mobileConnectionsRepo.hasCarrierMergedConnection,
+ ) { mobileIsDefault, hasCarrierMergedConnection ->
+ // Because carrier merged networks are displayed as mobile networks, they're part of
+ // the `isDefault` calculation. See b/272586234.
+ mobileIsDefault || hasCarrierMergedConnection
+ }
+ .logDiffsForTable(
+ tableLogger,
+ LOGGING_PREFIX,
+ columnName = "mobileIsDefault",
+ initialValue = false,
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val activeMobileDataSubscriptionId: StateFlow<Int?> =
+ mobileConnectionsRepo.activeMobileDataSubscriptionId
+
+ override val activeDataConnectionHasDataEnabled: StateFlow<Boolean> =
+ mobileConnectionsRepo.activeMobileDataRepository
+ .flatMapLatest { it?.dataEnabled ?: flowOf(false) }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val activeDataIconInteractor: StateFlow<MobileIconInteractor?> =
+ mobileConnectionsRepo.activeMobileDataSubscriptionId
+ .mapLatest {
+ if (it != null) {
+ getMobileConnectionInteractorForSubId(it)
+ } else {
+ null
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
+ private val unfilteredSubscriptions: Flow<List<SubscriptionModel>> =
+ mobileConnectionsRepo.subscriptions
+
+ /** Any filtering that we can do based purely on the info of each subscription individually. */
+ private val subscriptionsBasedFilteredSubs =
+ unfilteredSubscriptions
+ .map { it.filterBasedOnProvisioning().filterBasedOnNtn() }
+ .distinctUntilChanged()
+
+ private fun List<SubscriptionModel>.filterBasedOnProvisioning(): List<SubscriptionModel> =
+ if (!featureFlagsClassic.isEnabled(FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS)) {
+ this
+ } else {
+ this.filter { it.profileClass != PROFILE_CLASS_PROVISIONING }
+ }
+
+ /**
+ * Subscriptions that exclusively support non-terrestrial networks should **never** directly
+ * show any iconography in the status bar. These subscriptions only exist to provide a backing
+ * for the device-based satellite connections, and the iconography for those connections are
+ * already being handled in
+ * [com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository]. We
+ * need to filter out those subscriptions here so we guarantee the subscription never turns into
+ * an icon. See b/336881301.
+ */
+ private fun List<SubscriptionModel>.filterBasedOnNtn(): List<SubscriptionModel> {
+ return this.filter { !it.isExclusivelyNonTerrestrial }
+ }
+
+ /**
+ * Generally, SystemUI wants to show iconography for each subscription that is listed by
+ * [SubscriptionManager]. However, in the case of opportunistic subscriptions, we want to only
+ * show a single representation of the pair of subscriptions. The docs define opportunistic as:
+ *
+ * "A subscription is opportunistic (if) the network it connects to has limited coverage"
+ * https://developer.android.com/reference/android/telephony/SubscriptionManager#setOpportunistic(boolean,%20int)
+ *
+ * In the case of opportunistic networks (typically CBRS), we will filter out one of the
+ * subscriptions based on
+ * [CarrierConfigManager.KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN],
+ * and by checking which subscription is opportunistic, or which one is active.
+ */
+ override val filteredSubscriptions: Flow<List<SubscriptionModel>> =
+ combine(
+ subscriptionsBasedFilteredSubs,
+ mobileConnectionsRepo.activeMobileDataSubscriptionId,
+ connectivityRepository.vcnSubId,
+ ) { preFilteredSubs, activeId, vcnSubId ->
+ filterSubsBasedOnOpportunistic(preFilteredSubs, activeId, vcnSubId)
+ }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLogger,
+ LOGGING_PREFIX,
+ columnName = "filteredSubscriptions",
+ initialValue = listOf(),
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
+
+ private fun filterSubsBasedOnOpportunistic(
+ subList: List<SubscriptionModel>,
+ activeId: Int?,
+ vcnSubId: Int?,
+ ): List<SubscriptionModel> {
+ // Based on the old logic,
+ if (subList.size != 2) {
+ return subList
+ }
+
+ val info1 = subList[0]
+ val info2 = subList[1]
+
+ // Filtering only applies to subscriptions in the same group
+ if (info1.groupUuid == null || info1.groupUuid != info2.groupUuid) {
+ return subList
+ }
+
+ // If both subscriptions are primary, show both
+ if (!info1.isOpportunistic && !info2.isOpportunistic) {
+ return subList
+ }
+
+ // NOTE: at this point, we are now returning a single SubscriptionInfo
+
+ // If carrier required, always show the icon of the primary subscription.
+ // Otherwise, show whichever subscription is currently active for internet.
+ if (carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) {
+ // return the non-opportunistic info
+ return if (info1.isOpportunistic) listOf(info2) else listOf(info1)
+ } else {
+ // It's possible for the subId of the VCN to disagree with the active subId in
+ // cases where the system has tried to switch but found no connection. In these
+ // scenarios, VCN will always have the subId that we want to use, so use that
+ // value instead of the activeId reported by telephony
+ val subIdToKeep = vcnSubId ?: activeId
+
+ return if (info1.subscriptionId == subIdToKeep) {
+ listOf(info1)
+ } else {
+ listOf(info2)
+ }
+ }
+ }
+
+ override val defaultDataSubId = mobileConnectionsRepo.defaultDataSubId
+
+ override val icons =
+ filteredSubscriptions
+ .mapLatest { subs ->
+ subs.map { getMobileConnectionInteractorForSubId(it.subscriptionId) }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), emptyList())
+
+ override val isStackable =
+ if (NewStatusBarIcons.isEnabled && StatusBarRootModernization.isEnabled) {
+ icons.flatMapLatest { icons ->
+ combine(icons.map { it.signalLevelIcon }) { signalLevelIcons ->
+ // These are only stackable if:
+ // - They are cellular
+ // - There's exactly two
+ // - They have the same number of levels
+ signalLevelIcons.filterIsInstance<SignalIconModel.Cellular>().let {
+ it.size == 2 && it[0].numberOfLevels == it[1].numberOfLevels
+ }
+ }
+ }
+ } else {
+ flowOf(false)
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ /**
+ * Copied from the old pipeline. We maintain a 2s period of time where we will keep the
+ * validated bit from the old active network (A) while data is changing to the new one (B).
+ *
+ * This condition only applies if
+ * 1. A and B are in the same subscription group (e.g. for CBRS data switching) and
+ * 2. A was validated before the switch
+ *
+ * The goal of this is to minimize the flickering in the UI of the cellular indicator
+ */
+ private val forcingCellularValidation =
+ mobileConnectionsRepo.activeSubChangedInGroupEvent
+ .filter { mobileConnectionsRepo.defaultConnectionIsValidated.value }
+ .transformLatest {
+ emit(true)
+ delay(2000)
+ emit(false)
+ }
+ .logDiffsForTable(
+ tableLogger,
+ LOGGING_PREFIX,
+ columnName = "forcingValidation",
+ initialValue = false,
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ /**
+ * Mapping from network type to [MobileIconGroup] using the config generated for the default
+ * subscription Id. This mapping is the same for every subscription.
+ */
+ override val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>> =
+ mobileConnectionsRepo.defaultMobileIconMapping.stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ initialValue = mapOf(),
+ )
+
+ override val alwaysShowDataRatIcon: StateFlow<Boolean> =
+ mobileConnectionsRepo.defaultDataSubRatConfig
+ .mapLatest { it.alwaysShowDataRatIcon }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val alwaysUseCdmaLevel: StateFlow<Boolean> =
+ mobileConnectionsRepo.defaultDataSubRatConfig
+ .mapLatest { it.alwaysShowCdmaRssi }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val isSingleCarrier: StateFlow<Boolean> =
+ mobileConnectionsRepo.subscriptions
+ .map { it.size == 1 }
+ .logDiffsForTable(
+ tableLogger,
+ columnPrefix = LOGGING_PREFIX,
+ columnName = "isSingleCarrier",
+ initialValue = false,
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ /** If there is no mapping in [defaultMobileIconMapping], then use this default icon group */
+ override val defaultMobileIconGroup: StateFlow<MobileIconGroup> =
+ mobileConnectionsRepo.defaultMobileIconGroup.stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ initialValue = TelephonyIcons.G,
+ )
+
+ /**
+ * We want to show an error state when cellular has actually failed to validate, but not if some
+ * other transport type is active, because then we expect there not to be validation.
+ */
+ override val isDefaultConnectionFailed: StateFlow<Boolean> =
+ combine(
+ mobileIsDefault,
+ mobileConnectionsRepo.defaultConnectionIsValidated,
+ forcingCellularValidation,
+ ) { mobileIsDefault, defaultConnectionIsValidated, forcingCellularValidation ->
+ when {
+ !mobileIsDefault -> false
+ forcingCellularValidation -> false
+ else -> !defaultConnectionIsValidated
+ }
+ }
+ .logDiffsForTable(
+ tableLogger,
+ LOGGING_PREFIX,
+ columnName = "isDefaultConnectionFailed",
+ initialValue = false,
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val isUserSetUp: StateFlow<Boolean> = userSetupRepo.isUserSetUp
+
+ override val isForceHidden: Flow<Boolean> =
+ connectivityRepository.forceHiddenSlots
+ .map { it.contains(ConnectivitySlot.MOBILE) }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val isDeviceInEmergencyCallsOnlyMode: Flow<Boolean> =
+ mobileConnectionsRepo.isDeviceEmergencyCallCapable
+
+ /** Vends out new [MobileIconInteractor] for a particular subId */
+ override fun getMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
+ reuseCache[subId]?.get() ?: createMobileConnectionInteractorForSubId(subId)
+
+ private fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
+ MobileIconInteractorImpl(
+ scope,
+ activeDataConnectionHasDataEnabled,
+ alwaysShowDataRatIcon,
+ alwaysUseCdmaLevel,
+ isSingleCarrier,
+ mobileIsDefault,
+ defaultMobileIconMapping,
+ defaultMobileIconGroup,
+ isDefaultConnectionFailed,
+ isForceHidden,
+ mobileConnectionsRepo.getRepoForSubId(subId),
+ context,
+ )
+ .also { reuseCache[subId] = WeakReference(it) }
+
+ companion object {
+ private const val LOGGING_PREFIX = "Intr"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
index d734889..cdd0286 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
@@ -47,7 +47,8 @@
import com.android.systemui.statusbar.pipeline.shared.ui.model.VisibilityModel
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel
import javax.inject.Inject
-import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
/**
@@ -153,39 +154,43 @@
OngoingActivityChipBinder.createBinding(primaryChipView)
launch {
- viewModel.primaryOngoingActivityChip.collect { primaryChipModel ->
- OngoingActivityChipBinder.bind(
- primaryChipModel,
- primaryChipViewBinding,
- iconViewStore,
+ combine(
+ viewModel.primaryOngoingActivityChip,
+ viewModel.canShowOngoingActivityChips,
+ ::Pair,
)
+ .distinctUntilChanged()
+ .collect { (primaryChipModel, areChipsAllowed) ->
+ OngoingActivityChipBinder.bind(
+ primaryChipModel,
+ primaryChipViewBinding,
+ iconViewStore,
+ )
- if (StatusBarRootModernization.isEnabled) {
- launch {
+ if (StatusBarRootModernization.isEnabled) {
bindLegacyPrimaryOngoingActivityChipWithVisibility(
- viewModel,
+ areChipsAllowed,
primaryChipModel,
primaryChipViewBinding,
)
- }
- } else {
- when (primaryChipModel) {
- is OngoingActivityChipModel.Active ->
- listener?.onOngoingActivityStatusChanged(
- hasPrimaryOngoingActivity = true,
- hasSecondaryOngoingActivity = false,
- shouldAnimate = true,
- )
+ } else {
+ when (primaryChipModel) {
+ is OngoingActivityChipModel.Active ->
+ listener?.onOngoingActivityStatusChanged(
+ hasPrimaryOngoingActivity = true,
+ hasSecondaryOngoingActivity = false,
+ shouldAnimate = true,
+ )
- is OngoingActivityChipModel.Inactive ->
- listener?.onOngoingActivityStatusChanged(
- hasPrimaryOngoingActivity = false,
- hasSecondaryOngoingActivity = false,
- shouldAnimate = primaryChipModel.shouldAnimate,
- )
+ is OngoingActivityChipModel.Inactive ->
+ listener?.onOngoingActivityStatusChanged(
+ hasPrimaryOngoingActivity = false,
+ hasSecondaryOngoingActivity = false,
+ shouldAnimate = primaryChipModel.shouldAnimate,
+ )
+ }
}
}
- }
}
}
@@ -199,49 +204,53 @@
view.requireViewById(R.id.ongoing_activity_chip_secondary)
)
launch {
- viewModel.ongoingActivityChipsLegacy.collectLatest { chips ->
- OngoingActivityChipBinder.bind(
- chips.primary,
- primaryChipViewBinding,
- iconViewStore,
+ combine(
+ viewModel.ongoingActivityChipsLegacy,
+ viewModel.canShowOngoingActivityChips,
+ ::Pair,
)
- OngoingActivityChipBinder.bind(
- chips.secondary,
- secondaryChipViewBinding,
- iconViewStore,
- )
-
- if (StatusBarRootModernization.isEnabled) {
- launch {
+ .distinctUntilChanged()
+ .collect { (chips, areChipsAllowed) ->
+ OngoingActivityChipBinder.bind(
+ chips.primary,
+ primaryChipViewBinding,
+ iconViewStore,
+ )
+ OngoingActivityChipBinder.bind(
+ chips.secondary,
+ secondaryChipViewBinding,
+ iconViewStore,
+ )
+ if (StatusBarRootModernization.isEnabled) {
bindOngoingActivityChipsWithVisibility(
- viewModel,
+ areChipsAllowed,
chips,
primaryChipViewBinding,
secondaryChipViewBinding,
)
+ } else {
+ listener?.onOngoingActivityStatusChanged(
+ hasPrimaryOngoingActivity =
+ chips.primary is OngoingActivityChipModel.Active,
+ hasSecondaryOngoingActivity =
+ chips.secondary is OngoingActivityChipModel.Active,
+ // TODO(b/364653005): Figure out the animation story here.
+ shouldAnimate = true,
+ )
}
- } else {
- listener?.onOngoingActivityStatusChanged(
- hasPrimaryOngoingActivity =
- chips.primary is OngoingActivityChipModel.Active,
- hasSecondaryOngoingActivity =
- chips.secondary is OngoingActivityChipModel.Active,
- // TODO(b/364653005): Figure out the animation story here.
- shouldAnimate = true,
- )
}
-
- viewModel.contentArea.collect { _ ->
- OngoingActivityChipBinder.resetPrimaryChipWidthRestrictions(
- primaryChipViewBinding,
- viewModel.ongoingActivityChipsLegacy.value.primary,
- )
- OngoingActivityChipBinder.resetSecondaryChipWidthRestrictions(
- secondaryChipViewBinding,
- viewModel.ongoingActivityChipsLegacy.value.secondary,
- )
- view.requestLayout()
- }
+ }
+ launch {
+ viewModel.contentArea.collect { _ ->
+ OngoingActivityChipBinder.resetPrimaryChipWidthRestrictions(
+ primaryChipViewBinding,
+ viewModel.ongoingActivityChipsLegacy.value.primary,
+ )
+ OngoingActivityChipBinder.resetSecondaryChipWidthRestrictions(
+ secondaryChipViewBinding,
+ viewModel.ongoingActivityChipsLegacy.value.secondary,
+ )
+ view.requestLayout()
}
}
}
@@ -314,48 +323,42 @@
}
/** Bind the (legacy) single primary ongoing activity chip with the status bar visibility */
- private suspend fun bindLegacyPrimaryOngoingActivityChipWithVisibility(
- viewModel: HomeStatusBarViewModel,
+ private fun bindLegacyPrimaryOngoingActivityChipWithVisibility(
+ areChipsAllowed: Boolean,
primaryChipModel: OngoingActivityChipModel,
primaryChipViewBinding: OngoingActivityChipViewBinding,
) {
- viewModel.canShowOngoingActivityChips.collectLatest { visible ->
- if (!visible) {
- primaryChipViewBinding.rootView.hide(shouldAnimateChange = false)
- } else {
- when (primaryChipModel) {
- is OngoingActivityChipModel.Active -> {
- primaryChipViewBinding.rootView.show(shouldAnimateChange = true)
- }
+ if (!areChipsAllowed) {
+ primaryChipViewBinding.rootView.hide(shouldAnimateChange = false)
+ } else {
+ when (primaryChipModel) {
+ is OngoingActivityChipModel.Active -> {
+ primaryChipViewBinding.rootView.show(shouldAnimateChange = true)
+ }
- is OngoingActivityChipModel.Inactive -> {
- primaryChipViewBinding.rootView.hide(
- state = View.GONE,
- shouldAnimateChange = primaryChipModel.shouldAnimate,
- )
- }
+ is OngoingActivityChipModel.Inactive -> {
+ primaryChipViewBinding.rootView.hide(
+ state = View.GONE,
+ shouldAnimateChange = primaryChipModel.shouldAnimate,
+ )
}
}
}
}
/** Bind the primary/secondary chips along with the home status bar's visibility */
- private suspend fun bindOngoingActivityChipsWithVisibility(
- viewModel: HomeStatusBarViewModel,
+ private fun bindOngoingActivityChipsWithVisibility(
+ areChipsAllowed: Boolean,
chips: MultipleOngoingActivityChipsModelLegacy,
primaryChipViewBinding: OngoingActivityChipViewBinding,
secondaryChipViewBinding: OngoingActivityChipViewBinding,
) {
- viewModel.canShowOngoingActivityChips.collectLatest { canShow ->
- if (!canShow) {
- primaryChipViewBinding.rootView.hide(shouldAnimateChange = false)
- secondaryChipViewBinding.rootView.hide(shouldAnimateChange = false)
- } else {
- primaryChipViewBinding.rootView.adjustVisibility(chips.primary.toVisibilityModel())
- secondaryChipViewBinding.rootView.adjustVisibility(
- chips.secondary.toVisibilityModel()
- )
- }
+ if (!areChipsAllowed) {
+ primaryChipViewBinding.rootView.hide(shouldAnimateChange = false)
+ secondaryChipViewBinding.rootView.hide(shouldAnimateChange = false)
+ } else {
+ primaryChipViewBinding.rootView.adjustVisibility(chips.primary.toVisibilityModel())
+ secondaryChipViewBinding.rootView.adjustVisibility(chips.secondary.toVisibilityModel())
}
}
@@ -428,10 +431,15 @@
// See CollapsedStatusBarFragment#hide.
private fun View.hide(state: Int = View.INVISIBLE, shouldAnimateChange: Boolean) {
animate().cancel()
- if (visibility == View.INVISIBLE || visibility == View.GONE) {
+
+ if (
+ (visibility == View.INVISIBLE && state == View.INVISIBLE) ||
+ (visibility == View.GONE && state == View.GONE)
+ ) {
return
}
- if (!shouldAnimateChange) {
+ val isAlreadyHidden = visibility == View.INVISIBLE || visibility == View.GONE
+ if (!shouldAnimateChange || isAlreadyHidden) {
alpha = 0f
visibility = state
return
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
index 4189221..c91ea9a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
@@ -196,13 +196,15 @@
setContent {
PlatformTheme {
- val chips by
+ val chipsVisibilityModel by
statusBarViewModel.ongoingActivityChips
.collectAsStateWithLifecycle()
- OngoingActivityChips(
- chips = chips,
- iconViewStore = iconViewStore,
- )
+ if (chipsVisibilityModel.areChipsAllowed) {
+ OngoingActivityChips(
+ chips = chipsVisibilityModel.chips,
+ iconViewStore = iconViewStore,
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/ChipsVisibilityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/ChipsVisibilityModel.kt
new file mode 100644
index 0000000..5cc432f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/ChipsVisibilityModel.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.pipeline.shared.ui.model
+
+import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel
+
+data class ChipsVisibilityModel(
+ val chips: MultipleOngoingActivityChipsModel,
+ /**
+ * True if the chips are allowed to be shown and false otherwise (e.g. if we're on lockscreen).
+ */
+ val areChipsAllowed: Boolean,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
index 807e905..9975aff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
@@ -67,6 +67,7 @@
import com.android.systemui.statusbar.pipeline.battery.ui.viewmodel.BatteryViewModel
import com.android.systemui.statusbar.pipeline.shared.domain.interactor.HomeStatusBarIconBlockListInteractor
import com.android.systemui.statusbar.pipeline.shared.domain.interactor.HomeStatusBarInteractor
+import com.android.systemui.statusbar.pipeline.shared.ui.model.ChipsVisibilityModel
import com.android.systemui.statusbar.pipeline.shared.ui.model.SystemInfoCombinedVisibilityModel
import com.android.systemui.statusbar.pipeline.shared.ui.model.VisibilityModel
import dagger.assisted.Assisted
@@ -125,7 +126,7 @@
val primaryOngoingActivityChip: StateFlow<OngoingActivityChipModel>
/** All supported activity chips, whether they are currently active or not. */
- val ongoingActivityChips: StateFlow<MultipleOngoingActivityChipsModel>
+ val ongoingActivityChips: StateFlow<ChipsVisibilityModel>
/**
* The multiple ongoing activity chips that should be shown on the left-hand side of the status
@@ -252,8 +253,6 @@
override val primaryOngoingActivityChip = ongoingActivityChipsViewModel.primaryChip
- override val ongoingActivityChips = ongoingActivityChipsViewModel.chips
-
override val ongoingActivityChipsLegacy = ongoingActivityChipsViewModel.chipsLegacy
override val popupChips
@@ -369,15 +368,6 @@
)
.flowOn(bgDispatcher)
- private val isAnyChipVisible =
- if (StatusBarChipsModernization.isEnabled) {
- ongoingActivityChips.map { it.active.any { chip -> !chip.isHidden } }
- } else if (StatusBarNotifChips.isEnabled) {
- ongoingActivityChipsLegacy.map { it.primary is OngoingActivityChipModel.Active }
- } else {
- primaryOngoingActivityChip.map { it is OngoingActivityChipModel.Active }
- }
-
/**
* True if we need to hide the usual start side content in order to show the heads up
* notification info.
@@ -419,9 +409,38 @@
combine(
isHomeStatusBarAllowed,
keyguardInteractor.isSecureCameraActive,
- headsUpNotificationInteractor.statusBarHeadsUpStatus,
- ) { isHomeStatusBarAllowed, isSecureCameraActive, headsUpState ->
- isHomeStatusBarAllowed && !isSecureCameraActive && !headsUpState.isPinned
+ hideStartSideContentForHeadsUp,
+ ) { isHomeStatusBarAllowed, isSecureCameraActive, hideStartSideContentForHeadsUp ->
+ isHomeStatusBarAllowed && !isSecureCameraActive && !hideStartSideContentForHeadsUp
+ }
+
+ override val ongoingActivityChips =
+ combine(ongoingActivityChipsViewModel.chips, canShowOngoingActivityChips) { chips, canShow
+ ->
+ ChipsVisibilityModel(chips, areChipsAllowed = canShow)
+ }
+ .stateIn(
+ bgScope,
+ SharingStarted.WhileSubscribed(),
+ initialValue =
+ ChipsVisibilityModel(
+ chips = MultipleOngoingActivityChipsModel(),
+ areChipsAllowed = false,
+ ),
+ )
+
+ private val hasOngoingActivityChips =
+ if (StatusBarChipsModernization.isEnabled) {
+ ongoingActivityChips.map { it.chips.active.any { chip -> !chip.isHidden } }
+ } else if (StatusBarNotifChips.isEnabled) {
+ ongoingActivityChipsLegacy.map { it.primary is OngoingActivityChipModel.Active }
+ } else {
+ primaryOngoingActivityChip.map { it is OngoingActivityChipModel.Active }
+ }
+
+ private val isAnyChipVisible =
+ combine(hasOngoingActivityChips, canShowOngoingActivityChips) { hasChips, canShowChips ->
+ hasChips && canShowChips
}
override val isClockVisible: Flow<VisibilityModel> =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index d41ef97..ab5b349 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -425,7 +425,7 @@
@Override
@Nullable
public Drawable getIcon() {
- DeprecateDpmSupervisionApis.assertInNewMode();
+ DeprecateDpmSupervisionApis.unsafeAssertInNewMode();
return isParentalControlsEnabled()
? mContext.getDrawable(R.drawable.ic_supervision)
: null;
@@ -439,7 +439,7 @@
@Override
@Nullable
public CharSequence getLabel() {
- DeprecateDpmSupervisionApis.assertInNewMode();
+ DeprecateDpmSupervisionApis.unsafeAssertInNewMode();
return isParentalControlsEnabled()
? mContext.getString(R.string.status_bar_supervision)
: null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
index a28d14f..e8347df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
@@ -137,7 +137,7 @@
)
else MutableStateFlow<ZenMode?>(null)
get() {
- ModesUi.assertInNewMode()
+ ModesUi.unsafeAssertInNewMode()
return field
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java
index 0596a80..25972ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java
@@ -185,7 +185,7 @@
@Override
public void stop() {
- StatusBarConnectedDisplays.assertInNewMode();
+ StatusBarConnectedDisplays.unsafeAssertInNewMode();
try {
mWindowManager.removeView(mStatusBarWindowView);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt
index f7688d2..39afc38 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt
@@ -24,11 +24,11 @@
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
import com.android.systemui.display.data.repository.PerDisplayStore
-import com.android.systemui.display.data.repository.PerDisplayStoreImpl
import com.android.systemui.display.data.repository.SingleDisplayStore
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.data.repository.StatusBarConfigurationControllerStore
import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore
+import com.android.systemui.statusbar.data.repository.StatusBarPerDisplayStoreImpl
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -48,10 +48,13 @@
displayRepository: DisplayRepository,
) :
StatusBarWindowControllerStore,
- PerDisplayStoreImpl<StatusBarWindowController>(backgroundApplicationScope, displayRepository) {
+ StatusBarPerDisplayStoreImpl<StatusBarWindowController>(
+ backgroundApplicationScope,
+ displayRepository,
+ ) {
init {
- StatusBarConnectedDisplays.assertInNewMode()
+ StatusBarConnectedDisplays.unsafeAssertInNewMode()
}
override fun createInstanceForDisplay(displayId: Int): StatusBarWindowController? {
diff --git a/packages/SystemUI/src/com/android/systemui/supervision/shared/DeprecateDpmSupervisionApis.kt b/packages/SystemUI/src/com/android/systemui/supervision/shared/DeprecateDpmSupervisionApis.kt
index cbd3dc7..59c05c2 100644
--- a/packages/SystemUI/src/com/android/systemui/supervision/shared/DeprecateDpmSupervisionApis.kt
+++ b/packages/SystemUI/src/com/android/systemui/supervision/shared/DeprecateDpmSupervisionApis.kt
@@ -50,7 +50,7 @@
* Caution!! Using this check incorrectly will cause crashes in nextfood builds!
*/
@JvmStatic
- inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+ inline fun unsafeAssertInNewMode() = RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME)
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Producer.kt
similarity index 77%
copy from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt
copy to packages/SystemUI/src/com/android/systemui/util/kotlin/Producer.kt
index 231fb2d..a6209fa 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Producer.kt
@@ -14,11 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.dagger.qualifiers
+package com.android.systemui.util.kotlin
-import javax.inject.Qualifier
-
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class NotifInflation
+/** Like a [javax.inject.Provider], but [get] is a `suspend fun`. */
+fun interface Producer<out T> {
+ suspend fun get(): T
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/shared/flag/VolumePanelFlag.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/flag/VolumePanelFlag.kt
index 485f4b5..4919139 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/shared/flag/VolumePanelFlag.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/flag/VolumePanelFlag.kt
@@ -29,6 +29,6 @@
}
fun assertNewVolumePanel() {
- RefactorFlagUtils.assertInNewMode(Flags.newVolumePanel(), Flags.FLAG_NEW_VOLUME_PANEL)
+ RefactorFlagUtils.unsafeAssertInNewMode(Flags.newVolumePanel(), Flags.FLAG_NEW_VOLUME_PANEL)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/GradientColorWallpaper.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/GradientColorWallpaper.kt
index 33e1929..952d40e 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/GradientColorWallpaper.kt
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/GradientColorWallpaper.kt
@@ -17,8 +17,13 @@
package com.android.systemui.wallpapers
import android.app.Flags
+import android.content.res.Configuration.UI_MODE_NIGHT_MASK
+import android.content.res.Configuration.UI_MODE_NIGHT_YES
import android.graphics.Canvas
+import android.graphics.Color
import android.graphics.Paint
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffXfermode
import android.graphics.RadialGradient
import android.graphics.Shader
import android.service.wallpaper.WallpaperService
@@ -74,9 +79,9 @@
.toFloat()
val totalHeight = destRectF.height() + (offsetPx * 2)
val leftCenterX = -offsetPx
- val leftCenterY = -offsetPx
+ val leftCenterY = totalHeight - offsetPx
val rightCenterX = offsetPx + destRectF.width()
- val rightCenterY = totalHeight - offsetPx
+ val rightCenterY = -offsetPx
val radius = (destRectF.width() / 2) + offsetPx
canvas.drawCircle(
@@ -112,6 +117,28 @@
)
},
)
+
+ val isDarkMode =
+ context.resources.configuration.uiMode and UI_MODE_NIGHT_MASK ==
+ UI_MODE_NIGHT_YES
+ val maskColor =
+ ColorUtils.setAlphaComponent(
+ if (isDarkMode) Color.BLACK else Color.WHITE,
+ /* alpha= */ 87, // 0.34f * 255
+ )
+ val maskPaint =
+ Paint().apply {
+ xfermode =
+ PorterDuffXfermode(
+ if (isDarkMode) {
+ PorterDuff.Mode.DARKEN
+ } else {
+ PorterDuff.Mode.LIGHTEN
+ }
+ )
+ color = maskColor
+ }
+ canvas.drawRect(destRectF, maskPaint)
} catch (exception: IllegalStateException) {
Log.d(TAG, "Fail to draw in the canvas", exception)
} finally {
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index feaf1a6..8e71349 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -302,7 +302,7 @@
.commitUpdate(mDisplayTracker.getDefaultDisplayId());
}
});
- mSysUiState.addCallback(sysUiStateFlag -> {
+ mSysUiState.addCallback((sysUiStateFlag, displayId) -> {
mIsSysUiStateValid = (sysUiStateFlag & INVALID_SYSUI_STATE_MASK) == 0;
pip.onSystemUiStateChanged(mIsSysUiStateValid, sysUiStateFlag);
});
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index f822ee9..f18d73d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -194,7 +194,7 @@
@Test
fun clockSet_validateInitialization() {
- verify(clock).initialize(any(), anyFloat(), anyFloat())
+ verify(clock).initialize(any(), anyFloat(), anyFloat(), any())
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index 7cf9327..5b32b92 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -191,7 +191,7 @@
mWindowManager = spy(new TestableWindowManager(wm));
mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
- mSysUiState = new SysUiState(mDisplayTracker, mKosmos.getSceneContainerPlugin());
+ mSysUiState = mKosmos.getSysuiState();
mSysUiState.addCallback(Mockito.mock(SysUiState.SysUiStateCallback.class));
when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).then(
returnsSecondArg());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/back/FlingOnBackAnimationCallbackTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/back/FlingOnBackAnimationCallbackTest.kt
index 75a5768..c81900d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/back/FlingOnBackAnimationCallbackTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/back/FlingOnBackAnimationCallbackTest.kt
@@ -69,9 +69,11 @@
callback.onBackProgressed(backEventOf(0.6f, 32))
assertTrue("Assert onBackProgressedCompat called", callback.backProgressedCalled)
assertEquals("Assert interpolated progress", 0.6f, callback.progressEvent?.progress)
- getInstrumentation().runOnMainSync { callback.onBackInvoked() }
- // Assert that onBackInvoked is not called immediately...
- assertFalse(callback.backInvokedCalled)
+ getInstrumentation().runOnMainSync {
+ callback.onBackInvoked()
+ // Assert that onBackInvoked is not called immediately.
+ assertFalse(callback.backInvokedCalled)
+ }
// Instead the fling animation is played and eventually onBackInvoked is called.
callback.backInvokedLatch.await(1000, TimeUnit.MILLISECONDS)
assertTrue(callback.backInvokedCalled)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index 5249620..a1d038a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -120,9 +120,7 @@
private lateinit var deviceEntryUdfpsTouchOverlayViewModel:
DeviceEntryUdfpsTouchOverlayViewModel
@Mock private lateinit var defaultUdfpsTouchOverlayViewModel: DefaultUdfpsTouchOverlayViewModel
- @Mock
- private lateinit var udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate
- private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
+ @Mock private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
@Mock private lateinit var shadeInteractor: ShadeInteractor
@Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
@@ -185,7 +183,6 @@
primaryBouncerInteractor,
alternateBouncerInteractor,
isDebuggable,
- udfpsKeyguardAccessibilityDelegate,
keyguardTransitionInteractor,
mSelectedUserInteractor,
{ deviceEntryUdfpsTouchOverlayViewModel },
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt
index 9ae5715..2fc81eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt
@@ -231,6 +231,11 @@
actionInteractorImpl.onActionIconClick(inAudioSharingMediaDeviceItem) {}
assertThat(bluetoothTileDialogAudioSharingRepository.audioSharingStarted)
.isEqualTo(false)
+ verify(bluetoothTileDialogLogger)
+ .logAudioSharingButtonClick(
+ AudioSharingButtonClick.CHECK_MARK,
+ inAudioSharingMediaDeviceItem,
+ )
}
}
}
@@ -243,6 +248,11 @@
actionInteractorImpl.onActionIconClick(connectedAudioSharingMediaDeviceItem) {}
assertThat(bluetoothTileDialogAudioSharingRepository.audioSharingStarted)
.isEqualTo(true)
+ verify(bluetoothTileDialogLogger)
+ .logAudioSharingButtonClick(
+ AudioSharingButtonClick.PLUS_BUTTON,
+ connectedAudioSharingMediaDeviceItem,
+ )
}
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
index 2001a3e..dad08e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
@@ -422,25 +422,6 @@
assertTrue(MediaPlayerData.playerKeys().elementAt(2).isSsMediaRec)
}
- @DisableSceneContainer
- @Test
- fun testOrderWithSmartspace_prioritized_updatingVisibleMediaPlayers() {
- verify(mediaDataManager).addListener(capture(listener))
-
- testPlayerOrdering()
-
- // If smartspace is prioritized
- listener.value.onSmartspaceMediaDataLoaded(
- SMARTSPACE_KEY,
- EMPTY_SMARTSPACE_MEDIA_DATA.copy(isActive = true),
- true,
- )
-
- // Then it should be shown immediately after any actively playing controls
- assertTrue(MediaPlayerData.playerKeys().elementAt(2).isSsMediaRec)
- assertTrue(MediaPlayerData.visiblePlayerKeys().elementAt(2).isSsMediaRec)
- }
-
@Test
fun testOrderWithSmartspace_notPrioritized() {
testPlayerOrdering()
@@ -571,146 +552,6 @@
verify(logger).logRecommendationRemoved(eq(packageName), eq(instanceId!!))
}
- @DisableSceneContainer
- @Test
- fun testMediaLoaded_ScrollToActivePlayer() {
- verify(mediaDataManager).addListener(capture(listener))
-
- listener.value.onMediaDataLoaded(
- PLAYING_LOCAL,
- null,
- DATA.copy(
- active = true,
- isPlaying = true,
- playbackLocation = MediaData.PLAYBACK_LOCAL,
- resumption = false,
- ),
- )
- listener.value.onMediaDataLoaded(
- PAUSED_LOCAL,
- null,
- DATA.copy(
- active = true,
- isPlaying = false,
- playbackLocation = MediaData.PLAYBACK_LOCAL,
- resumption = false,
- ),
- )
- runAllReady()
- // adding a media recommendation card.
- listener.value.onSmartspaceMediaDataLoaded(
- SMARTSPACE_KEY,
- EMPTY_SMARTSPACE_MEDIA_DATA,
- false,
- )
- mediaCarouselController.shouldScrollToKey = true
- // switching between media players.
- listener.value.onMediaDataLoaded(
- PLAYING_LOCAL,
- PLAYING_LOCAL,
- DATA.copy(
- active = true,
- isPlaying = false,
- playbackLocation = MediaData.PLAYBACK_LOCAL,
- resumption = true,
- ),
- )
- listener.value.onMediaDataLoaded(
- PAUSED_LOCAL,
- PAUSED_LOCAL,
- DATA.copy(
- active = true,
- isPlaying = true,
- playbackLocation = MediaData.PLAYBACK_LOCAL,
- resumption = false,
- ),
- )
- runAllReady()
-
- assertEquals(
- MediaPlayerData.getMediaPlayerIndex(PAUSED_LOCAL),
- mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex,
- )
- }
-
- @DisableSceneContainer
- @Test
- fun testMediaLoadedFromRecommendationCard_ScrollToActivePlayer() {
- verify(mediaDataManager).addListener(capture(listener))
-
- listener.value.onSmartspaceMediaDataLoaded(
- SMARTSPACE_KEY,
- EMPTY_SMARTSPACE_MEDIA_DATA.copy(packageName = "PACKAGE_NAME", isActive = true),
- false,
- )
- listener.value.onMediaDataLoaded(
- PLAYING_LOCAL,
- null,
- DATA.copy(
- active = true,
- isPlaying = true,
- playbackLocation = MediaData.PLAYBACK_LOCAL,
- resumption = false,
- ),
- )
- runAllReady()
-
- var playerIndex = MediaPlayerData.getMediaPlayerIndex(PLAYING_LOCAL)
- assertEquals(
- playerIndex,
- mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex,
- )
- assertEquals(playerIndex, 0)
-
- // Replaying the same media player one more time.
- // And check that the card stays in its position.
- mediaCarouselController.shouldScrollToKey = true
- listener.value.onMediaDataLoaded(
- PLAYING_LOCAL,
- null,
- DATA.copy(
- active = true,
- isPlaying = true,
- playbackLocation = MediaData.PLAYBACK_LOCAL,
- resumption = false,
- packageName = "PACKAGE_NAME",
- ),
- )
- runAllReady()
- playerIndex = MediaPlayerData.getMediaPlayerIndex(PLAYING_LOCAL)
- assertEquals(playerIndex, 0)
- }
-
- @DisableSceneContainer
- @Test
- fun testRecommendationRemovedWhileNotVisible_updateHostVisibility() {
- verify(mediaDataManager).addListener(capture(listener))
-
- var result = false
- mediaCarouselController.updateHostVisibility = { result = true }
-
- whenever(visualStabilityProvider.isReorderingAllowed).thenReturn(true)
- listener.value.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY, false)
-
- assertEquals(true, result)
- }
-
- @DisableSceneContainer
- @Test
- fun testRecommendationRemovedWhileVisible_thenReorders_updateHostVisibility() {
- verify(mediaDataManager).addListener(capture(listener))
-
- var result = false
- mediaCarouselController.updateHostVisibility = { result = true }
-
- whenever(visualStabilityProvider.isReorderingAllowed).thenReturn(false)
- listener.value.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY, false)
- assertEquals(false, result)
-
- visualStabilityCallback.value.onReorderingAllowed()
- assertEquals(true, result)
- }
-
@Test
fun testGetCurrentVisibleMediaContentIntent() {
val clickIntent1 = mock(PendingIntent::class.java)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
index 9543032..88fcc70 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
@@ -19,16 +19,12 @@
import android.animation.Animator
import android.animation.AnimatorSet
import android.app.PendingIntent
-import android.app.smartspace.SmartspaceAction
-import android.content.Context
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
-import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
-import android.graphics.Matrix
import android.graphics.drawable.Animatable2
import android.graphics.drawable.AnimatedVectorDrawable
import android.graphics.drawable.Drawable
@@ -47,7 +43,6 @@
import android.provider.Settings
import android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS
import android.testing.TestableLooper
-import android.util.TypedValue
import android.view.View
import android.view.ViewGroup
import android.view.animation.Interpolator
@@ -59,7 +54,6 @@
import androidx.constraintlayout.widget.Barrier
import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.LiveData
-import androidx.media.utils.MediaConstants
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
@@ -72,19 +66,15 @@
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.media.controls.MediaTestUtils
-import com.android.systemui.media.controls.domain.pipeline.EMPTY_SMARTSPACE_MEDIA_DATA
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import com.android.systemui.media.controls.shared.model.KEY_SMARTSPACE_APP_NAME
import com.android.systemui.media.controls.shared.model.MediaAction
import com.android.systemui.media.controls.shared.model.MediaButton
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.shared.model.MediaDeviceData
import com.android.systemui.media.controls.shared.model.MediaNotificationAction
-import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
import com.android.systemui.media.controls.ui.binder.SeekBarObserver
import com.android.systemui.media.controls.ui.view.GutsViewHolder
import com.android.systemui.media.controls.ui.view.MediaViewHolder
-import com.android.systemui.media.controls.ui.view.RecommendationViewHolder
import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.media.dialog.MediaOutputDialogManager
@@ -216,32 +206,9 @@
@Mock private lateinit var communalSceneInteractor: CommunalSceneInteractor
- @Mock private lateinit var recommendationViewHolder: RecommendationViewHolder
- @Mock private lateinit var smartspaceAction: SmartspaceAction
- private lateinit var smartspaceData: SmartspaceMediaData
- @Mock private lateinit var coverContainer1: ViewGroup
- @Mock private lateinit var coverContainer2: ViewGroup
- @Mock private lateinit var coverContainer3: ViewGroup
- @Mock private lateinit var recAppIconItem: CachingIconView
- @Mock private lateinit var recCardTitle: TextView
- @Mock private lateinit var coverItem: ImageView
- @Mock private lateinit var matrix: Matrix
- private lateinit var recTitle1: TextView
- private lateinit var recTitle2: TextView
- private lateinit var recTitle3: TextView
- private lateinit var recSubtitle1: TextView
- private lateinit var recSubtitle2: TextView
- private lateinit var recSubtitle3: TextView
- @Mock private lateinit var recProgressBar1: SeekBar
- @Mock private lateinit var recProgressBar2: SeekBar
- @Mock private lateinit var recProgressBar3: SeekBar
@Mock private lateinit var globalSettings: GlobalSettings
- private val intent =
- Intent().apply {
- putExtras(Bundle().also { it.putString(KEY_SMARTSPACE_APP_NAME, REC_APP_NAME) })
- setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- }
+ private val intent = Intent().apply { setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) }
private val pendingIntent =
PendingIntent.getActivity(
mContext,
@@ -282,7 +249,6 @@
mediaOutputDialogManager,
mediaCarouselController,
falsingManager,
- clock,
logger,
keyguardStateController,
activityIntentHelper,
@@ -304,27 +270,6 @@
initMediaViewHolderMocks()
initDeviceMediaData(false, DEVICE_NAME)
-
- // Set up recommendation view
- initRecommendationViewHolderMocks()
-
- // Set valid recommendation data
- val extras = Bundle()
- extras.putString(KEY_SMARTSPACE_APP_NAME, REC_APP_NAME)
- val intent =
- Intent().apply {
- putExtras(extras)
- setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- }
- whenever(smartspaceAction.intent).thenReturn(intent)
- whenever(smartspaceAction.extras).thenReturn(extras)
- smartspaceData =
- EMPTY_SMARTSPACE_MEDIA_DATA.copy(
- packageName = PACKAGE,
- instanceId = instanceId,
- recommendations = listOf(smartspaceAction, smartspaceAction, smartspaceAction),
- cardAction = smartspaceAction,
- )
}
private fun initGutsViewHolderMocks() {
@@ -471,49 +416,6 @@
whenever(viewHolder.loadingEffectView).thenReturn(loadingEffectView)
}
- /** Initialize elements for the recommendation view holder */
- private fun initRecommendationViewHolderMocks() {
- recTitle1 = TextView(context)
- recTitle2 = TextView(context)
- recTitle3 = TextView(context)
- recSubtitle1 = TextView(context)
- recSubtitle2 = TextView(context)
- recSubtitle3 = TextView(context)
-
- whenever(recommendationViewHolder.recommendations).thenReturn(view)
- whenever(recommendationViewHolder.mediaAppIcons)
- .thenReturn(listOf(recAppIconItem, recAppIconItem, recAppIconItem))
- whenever(recommendationViewHolder.cardTitle).thenReturn(recCardTitle)
- whenever(recommendationViewHolder.mediaCoverItems)
- .thenReturn(listOf(coverItem, coverItem, coverItem))
- whenever(recommendationViewHolder.mediaCoverContainers)
- .thenReturn(listOf(coverContainer1, coverContainer2, coverContainer3))
- whenever(recommendationViewHolder.mediaTitles)
- .thenReturn(listOf(recTitle1, recTitle2, recTitle3))
- whenever(recommendationViewHolder.mediaSubtitles)
- .thenReturn(listOf(recSubtitle1, recSubtitle2, recSubtitle3))
- whenever(recommendationViewHolder.mediaProgressBars)
- .thenReturn(listOf(recProgressBar1, recProgressBar2, recProgressBar3))
- whenever(coverItem.imageMatrix).thenReturn(matrix)
-
- // set ids for recommendation containers
- whenever(coverContainer1.id).thenReturn(1)
- whenever(coverContainer2.id).thenReturn(2)
- whenever(coverContainer3.id).thenReturn(3)
-
- whenever(recommendationViewHolder.gutsViewHolder).thenReturn(gutsViewHolder)
-
- val actionIcon = Icon.createWithResource(context, R.drawable.ic_android)
- whenever(smartspaceAction.icon).thenReturn(actionIcon)
-
- // Needed for card and item action click
- val mockContext = mock(Context::class.java)
- whenever(view.context).thenReturn(mockContext)
- whenever(coverContainer1.context).thenReturn(mockContext)
- whenever(coverContainer2.context).thenReturn(mockContext)
- whenever(coverContainer3.context).thenReturn(mockContext)
- }
-
@After
fun tearDown() {
session.release()
@@ -1488,169 +1390,6 @@
/* ***** END guts tests for the player ***** */
- /* ***** Guts tests for the recommendations ***** */
-
- @Test
- fun recommendations_longClick_isFalse() {
- whenever(falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)).thenReturn(true)
- player.attachRecommendation(recommendationViewHolder)
- player.bindRecommendation(smartspaceData)
-
- val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java)
- verify(viewHolder.player).onLongClickListener = captor.capture()
-
- captor.value.onLongClick(viewHolder.player)
- verify(mediaViewController, never()).openGuts()
- verify(mediaViewController, never()).closeGuts()
- }
-
- @Test
- fun recommendations_longClickWhenGutsClosed_gutsOpens() {
- player.attachRecommendation(recommendationViewHolder)
- player.bindRecommendation(smartspaceData)
- whenever(mediaViewController.isGutsVisible).thenReturn(false)
-
- val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java)
- verify(viewHolder.player).onLongClickListener = captor.capture()
-
- captor.value.onLongClick(viewHolder.player)
- verify(mediaViewController).openGuts()
- verify(logger).logLongPressOpen(anyInt(), eq(PACKAGE), eq(instanceId))
- }
-
- @Test
- fun recommendations_longClickWhenGutsOpen_gutsCloses() {
- player.attachRecommendation(recommendationViewHolder)
- player.bindRecommendation(smartspaceData)
- whenever(mediaViewController.isGutsVisible).thenReturn(true)
-
- val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java)
- verify(viewHolder.player).onLongClickListener = captor.capture()
-
- captor.value.onLongClick(viewHolder.player)
- verify(mediaViewController, never()).openGuts()
- verify(mediaViewController).closeGuts(false)
- }
-
- @Test
- fun recommendations_cancelButtonClick_animation() {
- player.attachRecommendation(recommendationViewHolder)
- player.bindRecommendation(smartspaceData)
-
- cancel.callOnClick()
-
- verify(mediaViewController).closeGuts(false)
- }
-
- @Test
- fun recommendations_settingsButtonClick() {
- player.attachRecommendation(recommendationViewHolder)
- player.bindRecommendation(smartspaceData)
-
- settings.callOnClick()
- verify(logger).logLongPressSettings(anyInt(), eq(PACKAGE), eq(instanceId))
-
- val captor = ArgumentCaptor.forClass(Intent::class.java)
- verify(activityStarter).startActivity(captor.capture(), eq(true))
-
- assertThat(captor.value.action).isEqualTo(ACTION_MEDIA_CONTROLS_SETTINGS)
- }
-
- @Test
- fun recommendations_dismissButtonClick() {
- val mediaKey = "key for dismissal"
- player.attachRecommendation(recommendationViewHolder)
- player.bindRecommendation(smartspaceData.copy(targetId = mediaKey))
-
- assertThat(dismiss.isEnabled).isEqualTo(true)
- dismiss.callOnClick()
- verify(logger).logLongPressDismiss(anyInt(), eq(PACKAGE), eq(instanceId))
- verify(mediaDataManager).dismissSmartspaceRecommendation(eq(mediaKey), anyLong())
- }
-
- @Test
- fun recommendation_gutsOpen_contentDescriptionIsForGuts() {
- whenever(mediaViewController.isGutsVisible).thenReturn(true)
- player.attachRecommendation(recommendationViewHolder)
-
- val gutsTextString = "gutsText"
- whenever(gutsText.text).thenReturn(gutsTextString)
- player.bindRecommendation(smartspaceData)
-
- val descriptionCaptor = ArgumentCaptor.forClass(CharSequence::class.java)
- verify(viewHolder.player).contentDescription = descriptionCaptor.capture()
- val description = descriptionCaptor.value.toString()
-
- assertThat(description).isEqualTo(gutsTextString)
- }
-
- @Test
- fun recommendation_gutsClosed_contentDescriptionIsForPlayer() {
- whenever(mediaViewController.isGutsVisible).thenReturn(false)
- player.attachRecommendation(recommendationViewHolder)
-
- player.bindRecommendation(smartspaceData)
-
- val descriptionCaptor = ArgumentCaptor.forClass(CharSequence::class.java)
- verify(viewHolder.player).contentDescription = descriptionCaptor.capture()
- val description = descriptionCaptor.value.toString()
-
- assertThat(description)
- .isEqualTo(context.getString(R.string.controls_media_smartspace_rec_header))
- }
-
- @Test
- fun recommendation_gutsChangesFromOpenToClosed_contentDescriptionUpdated() {
- // Start out open
- whenever(mediaViewController.isGutsVisible).thenReturn(true)
- whenever(gutsText.text).thenReturn("gutsText")
- player.attachRecommendation(recommendationViewHolder)
- player.bindRecommendation(smartspaceData)
-
- // Update to closed by long pressing
- val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java)
- verify(viewHolder.player).onLongClickListener = captor.capture()
- reset(viewHolder.player)
-
- whenever(mediaViewController.isGutsVisible).thenReturn(false)
- captor.value.onLongClick(viewHolder.player)
-
- // Then content description is now the player content description
- val descriptionCaptor = ArgumentCaptor.forClass(CharSequence::class.java)
- verify(viewHolder.player).contentDescription = descriptionCaptor.capture()
- val description = descriptionCaptor.value.toString()
-
- assertThat(description)
- .isEqualTo(context.getString(R.string.controls_media_smartspace_rec_header))
- }
-
- @Test
- fun recommendation_gutsChangesFromClosedToOpen_contentDescriptionUpdated() {
- // Start out closed
- whenever(mediaViewController.isGutsVisible).thenReturn(false)
- val gutsTextString = "gutsText"
- whenever(gutsText.text).thenReturn(gutsTextString)
- player.attachRecommendation(recommendationViewHolder)
- player.bindRecommendation(smartspaceData)
-
- // Update to open by long pressing
- val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java)
- verify(viewHolder.player).onLongClickListener = captor.capture()
- reset(viewHolder.player)
-
- whenever(mediaViewController.isGutsVisible).thenReturn(true)
- captor.value.onLongClick(viewHolder.player)
-
- // Then content description is now the guts content description
- val descriptionCaptor = ArgumentCaptor.forClass(CharSequence::class.java)
- verify(viewHolder.player).contentDescription = descriptionCaptor.capture()
- val description = descriptionCaptor.value.toString()
-
- assertThat(description).isEqualTo(gutsTextString)
- }
-
- /* ***** END guts tests for the recommendations ***** */
-
@Test
fun actionPlayPauseClick_isLogged() {
val semanticActions =
@@ -1887,578 +1626,6 @@
}
@Test
- fun recommendation_gutsClosed_longPressOpens() {
- player.attachRecommendation(recommendationViewHolder)
- player.bindRecommendation(smartspaceData)
- whenever(mediaViewController.isGutsVisible).thenReturn(false)
-
- val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java)
- verify(recommendationViewHolder.recommendations).setOnLongClickListener(captor.capture())
-
- captor.value.onLongClick(recommendationViewHolder.recommendations)
- verify(mediaViewController).openGuts()
- verify(logger).logLongPressOpen(anyInt(), eq(PACKAGE), eq(instanceId))
- }
-
- @Test
- fun recommendation_settingsButtonClick_isLogged() {
- player.attachRecommendation(recommendationViewHolder)
- player.bindRecommendation(smartspaceData)
-
- settings.callOnClick()
- verify(logger).logLongPressSettings(anyInt(), eq(PACKAGE), eq(instanceId))
-
- val captor = ArgumentCaptor.forClass(Intent::class.java)
- verify(activityStarter).startActivity(captor.capture(), eq(true))
-
- assertThat(captor.value.action).isEqualTo(ACTION_MEDIA_CONTROLS_SETTINGS)
- }
-
- @Test
- fun recommendation_dismissButton_isLogged() {
- player.attachRecommendation(recommendationViewHolder)
- player.bindRecommendation(smartspaceData)
-
- dismiss.callOnClick()
- verify(logger).logLongPressDismiss(anyInt(), eq(PACKAGE), eq(instanceId))
- }
-
- @Test
- fun recommendation_tapOnCard_isLogged() {
- val captor = ArgumentCaptor.forClass(View.OnClickListener::class.java)
- player.attachRecommendation(recommendationViewHolder)
- player.bindRecommendation(smartspaceData)
-
- verify(recommendationViewHolder.recommendations).setOnClickListener(captor.capture())
- captor.value.onClick(recommendationViewHolder.recommendations)
-
- verify(logger).logRecommendationCardTap(eq(PACKAGE), eq(instanceId))
- }
-
- @Test
- fun recommendation_tapOnItem_isLogged() {
- val captor = ArgumentCaptor.forClass(View.OnClickListener::class.java)
- player.attachRecommendation(recommendationViewHolder)
- player.bindRecommendation(smartspaceData)
-
- verify(coverContainer1).setOnClickListener(captor.capture())
- captor.value.onClick(recommendationViewHolder.recommendations)
-
- verify(logger).logRecommendationItemTap(eq(PACKAGE), eq(instanceId), eq(0))
- }
-
- @Test
- fun bindRecommendation_listHasTooFewRecs_notDisplayed() {
- player.attachRecommendation(recommendationViewHolder)
- val icon =
- Icon.createWithResource(context, com.android.settingslib.R.drawable.ic_1x_mobiledata)
- val data =
- smartspaceData.copy(
- recommendations =
- listOf(
- SmartspaceAction.Builder("id1", "title1")
- .setSubtitle("subtitle1")
- .setIcon(icon)
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id2", "title2")
- .setSubtitle("subtitle2")
- .setIcon(icon)
- .setExtras(Bundle.EMPTY)
- .build(),
- )
- )
-
- player.bindRecommendation(data)
-
- assertThat(recTitle1.text).isEqualTo("")
- verify(mediaViewController, never()).refreshState()
- }
-
- @Test
- fun bindRecommendation_listHasTooFewRecsWithIcons_notDisplayed() {
- player.attachRecommendation(recommendationViewHolder)
- val icon =
- Icon.createWithResource(context, com.android.settingslib.R.drawable.ic_1x_mobiledata)
- val data =
- smartspaceData.copy(
- recommendations =
- listOf(
- SmartspaceAction.Builder("id1", "title1")
- .setSubtitle("subtitle1")
- .setIcon(icon)
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id2", "title2")
- .setSubtitle("subtitle2")
- .setIcon(icon)
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id2", "empty icon 1")
- .setSubtitle("subtitle2")
- .setIcon(null)
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id2", "empty icon 2")
- .setSubtitle("subtitle2")
- .setIcon(null)
- .setExtras(Bundle.EMPTY)
- .build(),
- )
- )
-
- player.bindRecommendation(data)
-
- assertThat(recTitle1.text).isEqualTo("")
- verify(mediaViewController, never()).refreshState()
- }
-
- @Test
- fun bindRecommendation_hasTitlesAndSubtitles() {
- player.attachRecommendation(recommendationViewHolder)
-
- val title1 = "Title1"
- val title2 = "Title2"
- val title3 = "Title3"
- val subtitle1 = "Subtitle1"
- val subtitle2 = "Subtitle2"
- val subtitle3 = "Subtitle3"
- val icon =
- Icon.createWithResource(context, com.android.settingslib.R.drawable.ic_1x_mobiledata)
-
- val data =
- smartspaceData.copy(
- recommendations =
- listOf(
- SmartspaceAction.Builder("id1", title1)
- .setSubtitle(subtitle1)
- .setIcon(icon)
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id2", title2)
- .setSubtitle(subtitle2)
- .setIcon(icon)
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id3", title3)
- .setSubtitle(subtitle3)
- .setIcon(icon)
- .setExtras(Bundle.EMPTY)
- .build(),
- )
- )
- player.bindRecommendation(data)
-
- assertThat(recTitle1.text).isEqualTo(title1)
- assertThat(recTitle2.text).isEqualTo(title2)
- assertThat(recTitle3.text).isEqualTo(title3)
- assertThat(recSubtitle1.text).isEqualTo(subtitle1)
- assertThat(recSubtitle2.text).isEqualTo(subtitle2)
- assertThat(recSubtitle3.text).isEqualTo(subtitle3)
- }
-
- @Test
- fun bindRecommendation_noTitle_subtitleNotShown() {
- player.attachRecommendation(recommendationViewHolder)
-
- val data =
- smartspaceData.copy(
- recommendations =
- listOf(
- SmartspaceAction.Builder("id1", "")
- .setSubtitle("fake subtitle")
- .setIcon(
- Icon.createWithResource(
- context,
- com.android.settingslib.R.drawable.ic_1x_mobiledata,
- )
- )
- .setExtras(Bundle.EMPTY)
- .build()
- )
- )
- player.bindRecommendation(data)
-
- assertThat(recSubtitle1.text).isEqualTo("")
- }
-
- @Test
- fun bindRecommendation_someHaveTitles_allTitleViewsShown() {
- useRealConstraintSets()
- player.attachRecommendation(recommendationViewHolder)
-
- val icon =
- Icon.createWithResource(context, com.android.settingslib.R.drawable.ic_1x_mobiledata)
- val data =
- smartspaceData.copy(
- recommendations =
- listOf(
- SmartspaceAction.Builder("id1", "")
- .setSubtitle("fake subtitle")
- .setIcon(icon)
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id2", "title2")
- .setSubtitle("fake subtitle")
- .setIcon(icon)
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id3", "")
- .setSubtitle("fake subtitle")
- .setIcon(icon)
- .setExtras(Bundle.EMPTY)
- .build(),
- )
- )
- player.bindRecommendation(data)
-
- assertThat(expandedSet.getVisibility(recTitle1.id)).isEqualTo(ConstraintSet.VISIBLE)
- assertThat(expandedSet.getVisibility(recTitle2.id)).isEqualTo(ConstraintSet.VISIBLE)
- assertThat(expandedSet.getVisibility(recTitle3.id)).isEqualTo(ConstraintSet.VISIBLE)
- }
-
- @Test
- fun bindRecommendation_someHaveSubtitles_allSubtitleViewsShown() {
- useRealConstraintSets()
- player.attachRecommendation(recommendationViewHolder)
-
- val icon =
- Icon.createWithResource(context, com.android.settingslib.R.drawable.ic_1x_mobiledata)
- val data =
- smartspaceData.copy(
- recommendations =
- listOf(
- SmartspaceAction.Builder("id1", "")
- .setSubtitle("")
- .setIcon(icon)
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id2", "title2")
- .setSubtitle("subtitle2")
- .setIcon(icon)
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id3", "title3")
- .setSubtitle("")
- .setIcon(icon)
- .setExtras(Bundle.EMPTY)
- .build(),
- )
- )
- player.bindRecommendation(data)
-
- assertThat(expandedSet.getVisibility(recSubtitle1.id)).isEqualTo(ConstraintSet.VISIBLE)
- assertThat(expandedSet.getVisibility(recSubtitle2.id)).isEqualTo(ConstraintSet.VISIBLE)
- assertThat(expandedSet.getVisibility(recSubtitle3.id)).isEqualTo(ConstraintSet.VISIBLE)
- }
-
- @Test
- fun bindRecommendation_noneHaveSubtitles_subtitleViewsGone() {
- useRealConstraintSets()
- player.attachRecommendation(recommendationViewHolder)
- val data =
- smartspaceData.copy(
- recommendations =
- listOf(
- SmartspaceAction.Builder("id1", "title1")
- .setSubtitle("")
- .setIcon(
- Icon.createWithResource(
- context,
- com.android.settingslib.R.drawable.ic_1x_mobiledata,
- )
- )
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id2", "title2")
- .setSubtitle("")
- .setIcon(Icon.createWithResource(context, R.drawable.ic_alarm))
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id3", "title3")
- .setSubtitle("")
- .setIcon(
- Icon.createWithResource(
- context,
- com.android.settingslib.R.drawable.ic_3g_mobiledata,
- )
- )
- .setExtras(Bundle.EMPTY)
- .build(),
- )
- )
-
- player.bindRecommendation(data)
-
- assertThat(expandedSet.getVisibility(recSubtitle1.id)).isEqualTo(ConstraintSet.GONE)
- assertThat(expandedSet.getVisibility(recSubtitle2.id)).isEqualTo(ConstraintSet.GONE)
- assertThat(expandedSet.getVisibility(recSubtitle3.id)).isEqualTo(ConstraintSet.GONE)
- }
-
- @Test
- fun bindRecommendation_noneHaveTitles_titleAndSubtitleViewsGone() {
- useRealConstraintSets()
- player.attachRecommendation(recommendationViewHolder)
- val data =
- smartspaceData.copy(
- recommendations =
- listOf(
- SmartspaceAction.Builder("id1", "")
- .setSubtitle("subtitle1")
- .setIcon(
- Icon.createWithResource(
- context,
- com.android.settingslib.R.drawable.ic_1x_mobiledata,
- )
- )
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id2", "")
- .setSubtitle("subtitle2")
- .setIcon(Icon.createWithResource(context, R.drawable.ic_alarm))
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id3", "")
- .setSubtitle("subtitle3")
- .setIcon(
- Icon.createWithResource(
- context,
- com.android.settingslib.R.drawable.ic_3g_mobiledata,
- )
- )
- .setExtras(Bundle.EMPTY)
- .build(),
- )
- )
-
- player.bindRecommendation(data)
-
- assertThat(expandedSet.getVisibility(recTitle1.id)).isEqualTo(ConstraintSet.GONE)
- assertThat(expandedSet.getVisibility(recTitle2.id)).isEqualTo(ConstraintSet.GONE)
- assertThat(expandedSet.getVisibility(recTitle3.id)).isEqualTo(ConstraintSet.GONE)
- assertThat(expandedSet.getVisibility(recSubtitle1.id)).isEqualTo(ConstraintSet.GONE)
- assertThat(expandedSet.getVisibility(recSubtitle2.id)).isEqualTo(ConstraintSet.GONE)
- assertThat(expandedSet.getVisibility(recSubtitle3.id)).isEqualTo(ConstraintSet.GONE)
- assertThat(collapsedSet.getVisibility(recTitle1.id)).isEqualTo(ConstraintSet.GONE)
- assertThat(collapsedSet.getVisibility(recTitle2.id)).isEqualTo(ConstraintSet.GONE)
- assertThat(collapsedSet.getVisibility(recTitle3.id)).isEqualTo(ConstraintSet.GONE)
- assertThat(collapsedSet.getVisibility(recSubtitle1.id)).isEqualTo(ConstraintSet.GONE)
- assertThat(collapsedSet.getVisibility(recSubtitle2.id)).isEqualTo(ConstraintSet.GONE)
- assertThat(collapsedSet.getVisibility(recSubtitle3.id)).isEqualTo(ConstraintSet.GONE)
- }
-
- @Test
- fun bindRecommendation_setAfterExecutors() {
- val albumArt = getColorIcon(Color.RED)
- val data =
- smartspaceData.copy(
- recommendations =
- listOf(
- SmartspaceAction.Builder("id1", "title1")
- .setSubtitle("subtitle1")
- .setIcon(albumArt)
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id2", "title2")
- .setSubtitle("subtitle1")
- .setIcon(albumArt)
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id3", "title3")
- .setSubtitle("subtitle1")
- .setIcon(albumArt)
- .setExtras(Bundle.EMPTY)
- .build(),
- )
- )
-
- player.attachRecommendation(recommendationViewHolder)
- player.bindRecommendation(data)
- bgExecutor.runAllReady()
- mainExecutor.runAllReady()
-
- verify(recCardTitle).setTextColor(any<Int>())
- verify(recAppIconItem, times(3)).setImageDrawable(any<Drawable>())
- verify(coverItem, times(3)).setImageDrawable(any<Drawable>())
- verify(coverItem, times(3)).imageMatrix = any()
- }
-
- @Test
- fun bindRecommendationWithProgressBars() {
- useRealConstraintSets()
- val albumArt = getColorIcon(Color.RED)
- val bundle =
- Bundle().apply {
- putInt(
- MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
- MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED,
- )
- putDouble(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.5)
- }
- val data =
- smartspaceData.copy(
- recommendations =
- listOf(
- SmartspaceAction.Builder("id1", "title1")
- .setSubtitle("subtitle1")
- .setIcon(albumArt)
- .setExtras(bundle)
- .build(),
- SmartspaceAction.Builder("id2", "title2")
- .setSubtitle("subtitle1")
- .setIcon(albumArt)
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id3", "title3")
- .setSubtitle("subtitle1")
- .setIcon(albumArt)
- .setExtras(Bundle.EMPTY)
- .build(),
- )
- )
-
- player.attachRecommendation(recommendationViewHolder)
- player.bindRecommendation(data)
-
- verify(recProgressBar1).setProgress(50)
- verify(recProgressBar1).visibility = View.VISIBLE
- verify(recProgressBar2).visibility = View.GONE
- verify(recProgressBar3).visibility = View.GONE
- assertThat(recSubtitle1.visibility).isEqualTo(View.GONE)
- assertThat(recSubtitle2.visibility).isEqualTo(View.VISIBLE)
- assertThat(recSubtitle3.visibility).isEqualTo(View.VISIBLE)
- }
-
- @Test
- fun bindRecommendation_carouselNotFitThreeRecs_OrientationPortrait() {
- useRealConstraintSets()
- val albumArt = getColorIcon(Color.RED)
- val data =
- smartspaceData.copy(
- recommendations =
- listOf(
- SmartspaceAction.Builder("id1", "title1")
- .setSubtitle("subtitle1")
- .setIcon(albumArt)
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id2", "title2")
- .setSubtitle("subtitle1")
- .setIcon(albumArt)
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id3", "title3")
- .setSubtitle("subtitle1")
- .setIcon(albumArt)
- .setExtras(Bundle.EMPTY)
- .build(),
- )
- )
-
- // set the screen width less than the width of media controls.
- player.context.resources.configuration.screenWidthDp = 350
- player.context.resources.configuration.orientation = Configuration.ORIENTATION_PORTRAIT
- player.attachRecommendation(recommendationViewHolder)
- player.bindRecommendation(data)
-
- val res = player.context.resources
- val displayAvailableWidth =
- TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 350f, res.displayMetrics).toInt()
- val recCoverWidth: Int =
- (res.getDimensionPixelSize(R.dimen.qs_media_rec_album_width) +
- res.getDimensionPixelSize(R.dimen.qs_media_info_spacing) * 2)
- val numOfRecs = displayAvailableWidth / recCoverWidth
-
- assertThat(player.numberOfFittedRecommendations).isEqualTo(numOfRecs)
- recommendationViewHolder.mediaCoverContainers.forEachIndexed { index, container ->
- if (index < numOfRecs) {
- assertThat(expandedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.VISIBLE)
- assertThat(collapsedSet.getVisibility(container.id))
- .isEqualTo(ConstraintSet.VISIBLE)
- } else {
- assertThat(expandedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.GONE)
- assertThat(collapsedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.GONE)
- }
- }
- }
-
- @Test
- fun bindRecommendation_carouselNotFitThreeRecs_OrientationLandscape() {
- useRealConstraintSets()
- val albumArt = getColorIcon(Color.RED)
- val data =
- smartspaceData.copy(
- recommendations =
- listOf(
- SmartspaceAction.Builder("id1", "title1")
- .setSubtitle("subtitle1")
- .setIcon(albumArt)
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id2", "title2")
- .setSubtitle("subtitle1")
- .setIcon(albumArt)
- .setExtras(Bundle.EMPTY)
- .build(),
- SmartspaceAction.Builder("id3", "title3")
- .setSubtitle("subtitle1")
- .setIcon(albumArt)
- .setExtras(Bundle.EMPTY)
- .build(),
- )
- )
-
- // set the screen width less than the width of media controls.
- // We should have dp width less than 378 to test. In landscape we should have 2x.
- player.context.resources.configuration.screenWidthDp = 700
- player.context.resources.configuration.orientation = Configuration.ORIENTATION_LANDSCAPE
- player.attachRecommendation(recommendationViewHolder)
- player.bindRecommendation(data)
-
- val res = player.context.resources
- val displayAvailableWidth =
- TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 350f, res.displayMetrics).toInt()
- val recCoverWidth: Int =
- (res.getDimensionPixelSize(R.dimen.qs_media_rec_album_width) +
- res.getDimensionPixelSize(R.dimen.qs_media_info_spacing) * 2)
- val numOfRecs = displayAvailableWidth / recCoverWidth
-
- assertThat(player.numberOfFittedRecommendations).isEqualTo(numOfRecs)
- recommendationViewHolder.mediaCoverContainers.forEachIndexed { index, container ->
- if (index < numOfRecs) {
- assertThat(expandedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.VISIBLE)
- assertThat(collapsedSet.getVisibility(container.id))
- .isEqualTo(ConstraintSet.VISIBLE)
- } else {
- assertThat(expandedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.GONE)
- assertThat(collapsedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.GONE)
- }
- }
- }
-
- @Test
- fun addTwoRecommendationGradients_differentStates() {
- // Setup redArtwork and its color scheme.
- val redArt = getColorIcon(Color.RED)
- val redWallpaperColor = player.getWallpaperColor(redArt)
- val redColorScheme = ColorScheme(redWallpaperColor, true, Style.CONTENT)
-
- // Setup greenArt and its color scheme.
- val greenArt = getColorIcon(Color.GREEN)
- val greenWallpaperColor = player.getWallpaperColor(greenArt)
- val greenColorScheme = ColorScheme(greenWallpaperColor, true, Style.CONTENT)
-
- // Add gradient to both icons.
- val redArtwork = player.addGradientToRecommendationAlbum(redArt, redColorScheme, 10, 10)
- val greenArtwork =
- player.addGradientToRecommendationAlbum(greenArt, greenColorScheme, 10, 10)
-
- // They should have different constant states as they have different gradient color.
- assertThat(redArtwork.getDrawable(1).constantState)
- .isNotEqualTo(greenArtwork.getDrawable(1).constantState)
- }
-
- @Test
fun onButtonClick_playsTouchRipple() {
val semanticActions =
MediaButton(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt
index e765b6f..760f73c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt
@@ -42,7 +42,6 @@
import com.android.systemui.media.controls.ui.view.GutsViewHolder
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.media.controls.ui.view.MediaViewHolder
-import com.android.systemui.media.controls.ui.view.RecommendationViewHolder
import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel
import com.android.systemui.res.R
import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffectView
@@ -79,7 +78,6 @@
private val configurationController =
com.android.systemui.statusbar.phone.ConfigurationControllerImpl(context)
private var player = TransitionLayout(context, /* attrs */ null, /* defStyleAttr */ 0)
- private var recommendation = TransitionLayout(context, /* attrs */ null, /* defStyleAttr */ 0)
private val clock = FakeSystemClock()
private lateinit var mainExecutor: FakeExecutor
private lateinit var seekBar: SeekBar
@@ -110,9 +108,6 @@
@Mock private lateinit var mockCopiedState: TransitionViewState
@Mock private lateinit var detailWidgetState: WidgetState
@Mock private lateinit var controlWidgetState: WidgetState
- @Mock private lateinit var mediaTitleWidgetState: WidgetState
- @Mock private lateinit var mediaSubTitleWidgetState: WidgetState
- @Mock private lateinit var mediaContainerWidgetState: WidgetState
@Mock private lateinit var seekBarViewModel: SeekBarViewModel
@Mock private lateinit var seekBarData: LiveData<SeekBarViewModel.Progress>
@Mock private lateinit var globalSettings: GlobalSettings
@@ -145,7 +140,7 @@
context: Context,
animId: Int,
motionInterpolator: Interpolator?,
- vararg targets: View?
+ vararg targets: View?,
): AnimatorSet {
return mockAnimator
}
@@ -158,7 +153,7 @@
fun testOrientationChanged_heightOfPlayerIsUpdated() {
val newConfig = Configuration()
- mediaViewController.attach(player, MediaViewController.TYPE.PLAYER)
+ mediaViewController.attach(player)
// Change the height to see the effect of orientation change.
MediaViewHolder.backgroundIds.forEach { id ->
mediaViewController.expandedLayout.getConstraint(id).layout.mHeight = 10
@@ -177,30 +172,8 @@
}
@Test
- fun testOrientationChanged_heightOfRecCardIsUpdated() {
- val newConfig = Configuration()
-
- mediaViewController.attach(recommendation, MediaViewController.TYPE.RECOMMENDATION)
- // Change the height to see the effect of orientation change.
- mediaViewController.expandedLayout
- .getConstraint(RecommendationViewHolder.backgroundId)
- .layout
- .mHeight = 10
- newConfig.orientation = ORIENTATION_LANDSCAPE
- configurationController.onConfigurationChanged(newConfig)
-
- assertTrue(
- mediaViewController.expandedLayout
- .getConstraint(RecommendationViewHolder.backgroundId)
- .layout
- .mHeight ==
- context.resources.getDimensionPixelSize(R.dimen.qs_media_session_height_expanded)
- )
- }
-
- @Test
fun testObtainViewState_applySquishFraction_toPlayerTransitionViewState_height() {
- mediaViewController.attach(player, MediaViewController.TYPE.PLAYER)
+ mediaViewController.attach(player)
player.measureState =
TransitionViewState().apply {
this.height = 100
@@ -224,29 +197,8 @@
}
@Test
- fun testObtainViewState_applySquishFraction_toRecommendationTransitionViewState_height() {
- mediaViewController.attach(recommendation, MediaViewController.TYPE.RECOMMENDATION)
- recommendation.measureState = TransitionViewState().apply { this.height = 100 }
- mediaHostStateHolder.expansion = 1f
- val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
- val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
- mediaHostStateHolder.measurementInput =
- MeasurementInput(widthMeasureSpec, heightMeasureSpec)
-
- // Test no squish
- mediaHostStateHolder.squishFraction = 1f
- assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.height == 100)
- assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.measureHeight == 100)
-
- // Test half squish
- mediaHostStateHolder.squishFraction = 0.5f
- assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.height == 50)
- assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.measureHeight == 100)
- }
-
- @Test
fun testObtainViewState_expandedMatchesParentHeight() {
- mediaViewController.attach(player, MediaViewController.TYPE.PLAYER)
+ mediaViewController.attach(player)
player.measureState =
TransitionViewState().apply {
this.height = 100
@@ -283,7 +235,7 @@
.thenReturn(
mutableMapOf(
R.id.media_progress_bar to controlWidgetState,
- R.id.header_artist to detailWidgetState
+ R.id.header_artist to detailWidgetState,
)
)
whenever(mockCopiedState.measureHeight).thenReturn(200)
@@ -311,7 +263,7 @@
.thenReturn(
mutableMapOf(
R.id.media_progress_bar to controlWidgetState,
- R.id.header_artist to detailWidgetState
+ R.id.header_artist to detailWidgetState,
)
)
whenever(mockCopiedState.measureHeight).thenReturn(200)
@@ -332,46 +284,6 @@
verify(detailWidgetState, never()).alpha = floatThat { it > 0 }
}
- @Test
- fun testSquishViewState_applySquishFraction_toTransitionViewState_alpha_forRecommendation() {
- whenever(mockViewState.copy()).thenReturn(mockCopiedState)
- whenever(mockCopiedState.widgetStates)
- .thenReturn(
- mutableMapOf(
- R.id.media_title to mediaTitleWidgetState,
- R.id.media_subtitle to mediaSubTitleWidgetState,
- R.id.media_cover1_container to mediaContainerWidgetState
- )
- )
- whenever(mockCopiedState.measureHeight).thenReturn(360)
- // media container widgets occupy [20, 300]
- whenever(mediaContainerWidgetState.y).thenReturn(20F)
- whenever(mediaContainerWidgetState.height).thenReturn(280)
- whenever(mediaContainerWidgetState.alpha).thenReturn(1F)
- // media title widgets occupy [320, 330]
- whenever(mediaTitleWidgetState.y).thenReturn(320F)
- whenever(mediaTitleWidgetState.height).thenReturn(10)
- whenever(mediaTitleWidgetState.alpha).thenReturn(1F)
- // media subtitle widgets occupy [340, 350]
- whenever(mediaSubTitleWidgetState.y).thenReturn(340F)
- whenever(mediaSubTitleWidgetState.height).thenReturn(10)
- whenever(mediaSubTitleWidgetState.alpha).thenReturn(1F)
-
- // in current beizer, when the progress reach 0.38, the result will be 0.5
- mediaViewController.squishViewState(mockViewState, 307.6F / 360F)
- verify(mediaContainerWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta }
- mediaViewController.squishViewState(mockViewState, 320F / 360F)
- verify(mediaContainerWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
- // media title and media subtitle are in same widget group, should be calculate together and
- // have same alpha
- mediaViewController.squishViewState(mockViewState, 353.8F / 360F)
- verify(mediaTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta }
- verify(mediaSubTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta }
- mediaViewController.squishViewState(mockViewState, 360F / 360F)
- verify(mediaTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
- verify(mediaSubTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
- }
-
@EnableSceneContainer
@Test
fun attachPlayer_seekBarDisabled_seekBarVisibilityIsSetToInvisible() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
index 5c26dac..88c2697 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
@@ -1573,25 +1573,6 @@
assertThat(items.get(1).isFirstDeviceInGroup()).isFalse();
}
- @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_DEVICE_GROUPING)
- @Test
- public void deviceListUpdateWithDifferentDevices_firstSelectedDeviceIsFirstDeviceInGroup() {
- when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(true);
- doReturn(mMediaDevices)
- .when(mLocalMediaManager)
- .getSelectedMediaDevice();
- mMediaSwitchingController.start(mCb);
- reset(mCb);
- mMediaSwitchingController.getMediaItemList().clear();
- mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
- mMediaDevices.clear();
- mMediaDevices.add(mMediaDevice2);
- mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
-
- List<MediaItem> items = mMediaSwitchingController.getMediaItemList();
- assertThat(items.get(0).isFirstDeviceInGroup()).isTrue();
- }
-
private int getNumberOfConnectDeviceButtons() {
int numberOfConnectDeviceButtons = 0;
for (MediaItem item : mMediaSwitchingController.getMediaItemList()) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/EditModeTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/EditModeTest.kt
index 8c09b81..e76be82c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/EditModeTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/EditModeTest.kt
@@ -25,6 +25,8 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onAllNodesWithText
+import androidx.compose.ui.test.onFirst
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
@@ -80,7 +82,9 @@
composeRule.assertCurrentTilesGridContainsExactly(
listOf("tileA", "tileB", "tileC", "tileD_large", "tileE", "tileF")
)
- composeRule.assertAvailableTilesGridContainsExactly(listOf("tileG_large"))
+ composeRule.assertAvailableTilesGridContainsExactly(
+ TestEditTiles.map { it.tile.tileSpec.spec }
+ )
}
@Test
@@ -88,7 +92,8 @@
composeRule.setContent { EditTileGridUnderTest() }
composeRule.waitForIdle()
- composeRule.onNodeWithContentDescription("tileA").performClick() // Selects
+ // Selects first "tileA", i.e. the one in the current grid
+ composeRule.onAllNodesWithText("tileA").onFirst().performClick()
composeRule.onNodeWithText("Remove").performClick() // Removes
composeRule.waitForIdle()
@@ -96,7 +101,9 @@
composeRule.assertCurrentTilesGridContainsExactly(
listOf("tileB", "tileC", "tileD_large", "tileE")
)
- composeRule.assertAvailableTilesGridContainsExactly(listOf("tileA", "tileF", "tileG_large"))
+ composeRule.assertAvailableTilesGridContainsExactly(
+ TestEditTiles.map { it.tile.tileSpec.spec }
+ )
}
private fun ComposeContentTestRule.assertCurrentTilesGridContainsExactly(specs: List<String>) =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt
index bd4c5f5..021be32 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt
@@ -25,7 +25,8 @@
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.click
import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithContentDescription
+import androidx.compose.ui.test.onAllNodesWithText
+import androidx.compose.ui.test.onFirst
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performCustomAccessibilityActionWithLabel
import androidx.compose.ui.test.performTouchInput
@@ -85,7 +86,8 @@
composeRule.waitForIdle()
composeRule
- .onNodeWithContentDescription("tileA")
+ .onAllNodesWithText("tileA")
+ .onFirst()
.performCustomAccessibilityActionWithLabel(
context.getString(R.string.accessibility_qs_edit_toggle_tile_size_action)
)
@@ -103,7 +105,8 @@
composeRule.waitForIdle()
composeRule
- .onNodeWithContentDescription("tileD_large")
+ .onAllNodesWithText("tileD_large")
+ .onFirst()
.performCustomAccessibilityActionWithLabel(
context.getString(R.string.accessibility_qs_edit_toggle_tile_size_action)
)
@@ -121,7 +124,8 @@
composeRule.waitForIdle()
composeRule
- .onNodeWithContentDescription("tileA")
+ .onAllNodesWithText("tileA")
+ .onFirst()
.performClick() // Select
.performTouchInput { // Tap on resizing handle
click(centerRight)
@@ -141,7 +145,8 @@
composeRule.waitForIdle()
composeRule
- .onNodeWithContentDescription("tileD_large")
+ .onAllNodesWithText("tileD_large")
+ .onFirst()
.performClick() // Select
.performTouchInput { // Tap on resizing handle
click(centerRight)
@@ -161,7 +166,8 @@
composeRule.waitForIdle()
composeRule
- .onNodeWithContentDescription("tileA")
+ .onAllNodesWithText("tileA")
+ .onFirst()
.performClick() // Select
.performTouchInput { // Resize up
swipeRight(startX = right, endX = right * 2)
@@ -181,7 +187,8 @@
composeRule.waitForIdle()
composeRule
- .onNodeWithContentDescription("tileD_large")
+ .onAllNodesWithText("tileD_large")
+ .onFirst()
.performClick() // Select
.performTouchInput { // Resize down
swipeLeft()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/LauncherProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/LauncherProxyServiceTest.kt
index 40547c2..0a5efb7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recents/LauncherProxyServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/LauncherProxyServiceTest.kt
@@ -24,6 +24,7 @@
import android.os.UserManager
import android.testing.TestableContext
import android.testing.TestableLooper
+import android.view.Display
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.app.AssistUtils
@@ -35,8 +36,7 @@
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.ui.view.InWindowLauncherUnlockAnimationManager
-import com.android.systemui.model.SysUiState
-import com.android.systemui.model.sceneContainerPlugin
+import com.android.systemui.model.sysUiState
import com.android.systemui.navigationbar.NavigationBarController
import com.android.systemui.navigationbar.NavigationModeController
import com.android.systemui.process.ProcessWrapper
@@ -67,6 +67,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.Mockito.any
import org.mockito.Mockito.anyInt
@@ -93,7 +94,7 @@
@Mock private lateinit var processWrapper: ProcessWrapper
private val displayTracker = FakeDisplayTracker(mContext)
private val fakeSystemClock = FakeSystemClock()
- private val sysUiState = SysUiState(displayTracker, kosmos.sceneContainerPlugin)
+ private val sysUiState = kosmos.sysUiState
private val wakefulnessLifecycle =
WakefulnessLifecycle(mContext, null, fakeSystemClock, dumpManager)
@@ -161,11 +162,13 @@
wakefulnessLifecycle.dispatchFinishedGoingToSleep()
clearInvocations(launcherProxy)
- wakefulnessLifecycle.dispatchFinishedWakingUp()
+ wakefulnessLifecycle
+ .dispatchFinishedWakingUp()
verify(launcherProxy)
.onSystemUiStateChanged(
- longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_AWAKE }
+ longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_AWAKE },
+ eq(Display.DEFAULT_DISPLAY),
)
}
@@ -175,7 +178,8 @@
verify(launcherProxy)
.onSystemUiStateChanged(
- longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_WAKING }
+ longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_WAKING },
+ eq(Display.DEFAULT_DISPLAY),
)
}
@@ -185,7 +189,8 @@
verify(launcherProxy)
.onSystemUiStateChanged(
- longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_ASLEEP }
+ longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_ASLEEP },
+ eq(Display.DEFAULT_DISPLAY),
)
}
@@ -197,7 +202,8 @@
verify(launcherProxy)
.onSystemUiStateChanged(
- longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_GOING_TO_SLEEP }
+ longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_GOING_TO_SLEEP },
+ eq(Display.DEFAULT_DISPLAY),
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt
index 6005725..1f189a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt
@@ -35,6 +35,10 @@
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
@@ -43,9 +47,13 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class ActionIntentCreatorTest : SysuiTestCase() {
+ private val scheduler = TestCoroutineScheduler()
+ private val mainDispatcher = UnconfinedTestDispatcher(scheduler)
+ private val testScope = TestScope(mainDispatcher)
val context = mock<Context>()
val packageManager = mock<PackageManager>()
- private val actionIntentCreator = ActionIntentCreator(context, packageManager)
+ private val actionIntentCreator =
+ ActionIntentCreator(context, packageManager, testScope.backgroundScope, mainDispatcher)
@Test
fun testCreateShare() {
@@ -132,7 +140,7 @@
@Test
@DisableFlags(Flags.FLAG_USE_PREFERRED_IMAGE_EDITOR)
- fun testCreateEditLegacy() {
+ fun testCreateEditLegacy() = runTest {
val uri = Uri.parse("content://fake")
whenever(context.getString(eq(R.string.config_screenshotEditor))).thenReturn("")
@@ -155,7 +163,7 @@
@Test
@DisableFlags(Flags.FLAG_USE_PREFERRED_IMAGE_EDITOR)
- fun testCreateEditLegacy_embeddedUserIdRemoved() {
+ fun testCreateEditLegacy_embeddedUserIdRemoved() = runTest {
val uri = Uri.parse("content://555@fake")
whenever(context.getString(eq(R.string.config_screenshotEditor))).thenReturn("")
@@ -166,7 +174,7 @@
@Test
@DisableFlags(Flags.FLAG_USE_PREFERRED_IMAGE_EDITOR)
- fun testCreateEditLegacy_withEditor() {
+ fun testCreateEditLegacy_withEditor() = runTest {
val uri = Uri.parse("content://fake")
val component = ComponentName("com.android.foo", "com.android.foo.Something")
@@ -180,7 +188,7 @@
@Test
@EnableFlags(Flags.FLAG_USE_PREFERRED_IMAGE_EDITOR)
- fun testCreateEdit() {
+ fun testCreateEdit() = runTest {
val uri = Uri.parse("content://fake")
whenever(context.getString(eq(R.string.config_screenshotEditor))).thenReturn("")
@@ -203,7 +211,7 @@
@Test
@EnableFlags(Flags.FLAG_USE_PREFERRED_IMAGE_EDITOR)
- fun testCreateEdit_embeddedUserIdRemoved() {
+ fun testCreateEdit_embeddedUserIdRemoved() = runTest {
val uri = Uri.parse("content://555@fake")
whenever(context.getString(eq(R.string.config_screenshotEditor))).thenReturn("")
@@ -214,7 +222,7 @@
@Test
@EnableFlags(Flags.FLAG_USE_PREFERRED_IMAGE_EDITOR)
- fun testCreateEdit_withPreferredEditorEnabled() {
+ fun testCreateEdit_withPreferredEditorEnabled() = runTest {
val uri = Uri.parse("content://fake")
val fallbackComponent = ComponentName("com.android.foo", "com.android.foo.Something")
val preferredComponent = ComponentName("com.android.bar", "com.android.bar.Something")
@@ -243,7 +251,7 @@
@Test
@EnableFlags(Flags.FLAG_USE_PREFERRED_IMAGE_EDITOR)
- fun testCreateEdit_withPreferredEditorDisabled() {
+ fun testCreateEdit_withPreferredEditorDisabled() = runTest {
val uri = Uri.parse("content://fake")
val fallbackComponent = ComponentName("com.android.foo", "com.android.foo.Something")
val preferredComponent = ComponentName("com.android.bar", "com.android.bar.Something")
@@ -266,7 +274,7 @@
@Test
@EnableFlags(Flags.FLAG_USE_PREFERRED_IMAGE_EDITOR)
- fun testCreateEdit_withFallbackEditor() {
+ fun testCreateEdit_withFallbackEditor() = runTest {
val uri = Uri.parse("content://fake")
val component = ComponentName("com.android.foo", "com.android.foo.Something")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index e42bf19..51bb38f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -23,7 +23,6 @@
import android.testing.TestableLooper
import android.testing.TestableLooper.RunWithLooper
import android.view.Choreographer
-import android.view.KeyEvent
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
@@ -54,6 +53,7 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.flags.QSComposeFragment
import com.android.systemui.res.R
+import com.android.systemui.scene.ui.view.WindowRootViewKeyEventHandler
import com.android.systemui.settings.brightness.data.repository.BrightnessMirrorShowingRepository
import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractorPassThrough
import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
@@ -96,7 +96,6 @@
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
-import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -245,7 +244,7 @@
notificationLaunchAnimationInteractor,
featureFlagsClassic,
fakeClock,
- sysUIKeyEventHandler,
+ WindowRootViewKeyEventHandler({ sysUIKeyEventHandler }, falsingCollector),
quickSettingsController,
primaryBouncerInteractor,
alternateBouncerInteractor,
@@ -591,34 +590,6 @@
}
@Test
- fun forwardsDispatchKeyEvent() {
- val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_B)
- interactionEventHandler.dispatchKeyEvent(keyEvent)
- verify(sysUIKeyEventHandler).dispatchKeyEvent(keyEvent)
- }
-
- @Test
- fun forwardsDispatchKeyEventPreIme() {
- val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_B)
- interactionEventHandler.dispatchKeyEventPreIme(keyEvent)
- verify(sysUIKeyEventHandler).dispatchKeyEventPreIme(keyEvent)
- }
-
- @Test
- fun forwardsInterceptMediaKey() {
- val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_UP)
- interactionEventHandler.interceptMediaKey(keyEvent)
- verify(sysUIKeyEventHandler).interceptMediaKey(keyEvent)
- }
-
- @Test
- fun forwardsCollectKeyEvent() {
- val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_A)
- interactionEventHandler.collectKeyEvent(keyEvent)
- assertEquals(keyEvent, falsingCollector.lastKeyEvent)
- }
-
- @Test
fun cancelCurrentTouch_callsDragDownHelper() {
underTest.cancelCurrentTouch()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
index f3af794f..1269b23 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
@@ -390,30 +390,12 @@
fun testControllersCreatedAndInitialized() {
verify(variableDateViewController).init()
- verify(batteryMeterViewController).init()
- verify(batteryMeterViewController).ignoreTunerUpdates()
-
val inOrder = Mockito.inOrder(mShadeCarrierGroupControllerBuilder)
inOrder.verify(mShadeCarrierGroupControllerBuilder).setShadeCarrierGroup(carrierGroup)
inOrder.verify(mShadeCarrierGroupControllerBuilder).build()
}
@Test
- fun batteryModeControllerCalledWhenQsExpandedFractionChanges() {
- whenever(qsBatteryModeController.getBatteryMode(Mockito.same(null), eq(0f)))
- .thenReturn(BatteryMeterView.MODE_ON)
- whenever(qsBatteryModeController.getBatteryMode(Mockito.same(null), eq(1f)))
- .thenReturn(BatteryMeterView.MODE_ESTIMATE)
- shadeHeaderController.qsVisible = true
-
- val times = 10
- repeat(times) { shadeHeaderController.qsExpandedFraction = it / (times - 1).toFloat() }
-
- verify(batteryMeterView).setPercentShowMode(BatteryMeterView.MODE_ON)
- verify(batteryMeterView).setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
- }
-
- @Test
fun testClockPivotLtr() {
val width = 200
whenever(clock.width).thenReturn(width)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
index 3c4aecc..f8b35a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
@@ -29,6 +29,7 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.privacy.OngoingPrivacyChip
import com.android.systemui.statusbar.BatteryStatusChip
+import com.android.systemui.statusbar.core.NewStatusBarIcons
import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState
import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.AnimatingIn
import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.AnimatingOut
@@ -118,11 +119,11 @@
}
@Test
- fun testBatteryStatusEvent_standardAnimationLifecycle() = runTest {
+ fun testStatusEvent_standardAnimationLifecycle() = runTest {
// Instantiate class under test with TestScope from runTest
- initializeSystemStatusAnimationScheduler(testScope = this)
+ initializeSystemStatusAnimationScheduler(this)
- val batteryChip = createAndScheduleFakeBatteryEvent()
+ val eventChip = createAndScheduleFakeEvent()
// assert that animation is queued
assertEquals(AnimationQueued, systemStatusAnimationScheduler.animationState.value)
@@ -131,8 +132,7 @@
advanceTimeBy(DEBOUNCE_DELAY + 1)
// status chip starts animating in after debounce delay
assertEquals(AnimatingIn, systemStatusAnimationScheduler.animationState.value)
- assertEquals(0f, batteryChip.contentView.alpha)
- assertEquals(0f, batteryChip.view.alpha)
+ assertEquals(0f, eventChip.view.alpha)
verify(listener, times(1)).onSystemEventAnimationBegin()
// skip appear animation
@@ -140,23 +140,20 @@
advanceTimeBy(APPEAR_ANIMATION_DURATION)
// assert that status chip is visible
assertEquals(RunningChipAnim, systemStatusAnimationScheduler.animationState.value)
- assertEquals(1f, batteryChip.contentView.alpha)
- assertEquals(1f, batteryChip.view.alpha)
+ assertEquals(1f, eventChip.view.alpha)
// skip status chip display time
advanceTimeBy(DISPLAY_LENGTH + 1)
// assert that it is still visible but switched to the AnimatingOut state
assertEquals(AnimatingOut, systemStatusAnimationScheduler.animationState.value)
- assertEquals(1f, batteryChip.contentView.alpha)
- assertEquals(1f, batteryChip.view.alpha)
+ assertEquals(1f, eventChip.view.alpha)
verify(listener, times(1)).onSystemEventAnimationFinish(false)
// skip disappear animation
animatorTestRule.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION)
// assert that it is not visible anymore
assertEquals(Idle, systemStatusAnimationScheduler.animationState.value)
- assertEquals(0f, batteryChip.contentView.alpha)
- assertEquals(0f, batteryChip.view.alpha)
+ assertEquals(0f, eventChip.view.alpha)
}
/** Regression test for b/294104969. */
@@ -226,8 +223,8 @@
initializeSystemStatusAnimationScheduler(testScope = this)
// create and schedule low priority event
- val batteryChip = createAndScheduleFakeBatteryEvent()
- batteryChip.view.alpha = 0f
+ val eventChip = createAndScheduleFakeEvent()
+ eventChip.view.alpha = 0f
// assert that animation is queued
assertEquals(AnimationQueued, systemStatusAnimationScheduler.animationState.value)
@@ -244,7 +241,7 @@
// high priority status chip is visible while low priority status chip is not visible
assertEquals(RunningChipAnim, systemStatusAnimationScheduler.animationState.value)
assertEquals(1f, privacyChip.view.alpha)
- assertEquals(0f, batteryChip.view.alpha)
+ assertEquals(0f, eventChip.view.alpha)
}
@Test
@@ -253,14 +250,14 @@
initializeSystemStatusAnimationScheduler(testScope = this)
// create and schedule low priority event
- val batteryChip = createAndScheduleFakeBatteryEvent()
+ val eventChip = createAndScheduleFakeEvent()
// fast forward to RunningChipAnim state
fastForwardAnimationToState(RunningChipAnim)
// assert that chip is displayed
assertEquals(RunningChipAnim, systemStatusAnimationScheduler.animationState.value)
- assertEquals(1f, batteryChip.view.alpha)
+ assertEquals(1f, eventChip.view.alpha)
// create and schedule high priority event
val privacyChip = createAndScheduleFakePrivacyEvent()
@@ -284,7 +281,7 @@
// high priority status chip is visible while low priority status chip is not visible
assertEquals(RunningChipAnim, systemStatusAnimationScheduler.animationState.value)
assertEquals(1f, privacyChip.view.alpha)
- assertEquals(0f, batteryChip.view.alpha)
+ assertEquals(0f, eventChip.view.alpha)
}
@Test
@@ -293,7 +290,7 @@
initializeSystemStatusAnimationScheduler(testScope = this)
// create and schedule low priority event
- val batteryChip = createAndScheduleFakeBatteryEvent()
+ val eventChip = createAndScheduleFakeEvent()
// skip debounce delay
advanceTimeBy(DEBOUNCE_DELAY + 1)
@@ -331,7 +328,7 @@
// high priority status chip is visible while low priority status chip is not visible
assertEquals(RunningChipAnim, systemStatusAnimationScheduler.animationState.value)
assertEquals(1f, privacyChip.view.alpha)
- assertEquals(0f, batteryChip.view.alpha)
+ assertEquals(0f, eventChip.view.alpha)
}
@Test
@@ -343,8 +340,8 @@
val privacyChip = createAndScheduleFakePrivacyEvent()
// create and schedule low priority event
- val batteryChip = createAndScheduleFakeBatteryEvent()
- batteryChip.view.alpha = 0f
+ val eventChip = createAndScheduleFakeEvent()
+ eventChip.view.alpha = 0f
// skip debounce delay and appear animation
advanceTimeBy(DEBOUNCE_DELAY + APPEAR_ANIMATION_DURATION + 1)
@@ -353,7 +350,7 @@
// high priority status chip is visible while low priority status chip is not visible
assertEquals(RunningChipAnim, systemStatusAnimationScheduler.animationState.value)
assertEquals(1f, privacyChip.view.alpha)
- assertEquals(0f, batteryChip.view.alpha)
+ assertEquals(0f, eventChip.view.alpha)
}
@Test
@@ -649,12 +646,17 @@
systemStatusAnimationScheduler.onStatusEvent(fakeEvent)
}
- private fun createAndScheduleFakeBatteryEvent(): BatteryStatusChip {
- val batteryChip = BatteryStatusChip(mContext)
- val fakeBatteryEvent =
- FakeStatusEvent(viewCreator = { batteryChip }, priority = 50, forceVisible = false)
- systemStatusAnimationScheduler.onStatusEvent(fakeBatteryEvent)
- return batteryChip
+ private fun createAndScheduleFakeEvent(): BackgroundAnimatableView {
+ val eventChip =
+ if (NewStatusBarIcons.isEnabled) {
+ BGImageView(mContext)
+ } else {
+ BatteryStatusChip(mContext)
+ }
+ val fakeStatusEvent =
+ FakeStatusEvent(viewCreator = { eventChip }, priority = 50, forceVisible = false)
+ systemStatusAnimationScheduler.onStatusEvent(fakeStatusEvent)
+ return eventChip
}
private fun initializeSystemStatusAnimationScheduler(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index acfa94a..cd2ea7d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -67,6 +67,7 @@
import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips;
import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
import com.android.systemui.statusbar.notification.FeedbackIcon;
import com.android.systemui.statusbar.notification.SourceType;
@@ -1128,6 +1129,30 @@
assertThat(row.mustStayOnScreen()).isFalse();
}
+ @Test
+ @DisableFlags(StatusBarNotifChips.FLAG_NAME)
+ public void hasStatusBarChipDuringHeadsUpAnimation_flagOff_false() throws Exception {
+ final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+
+ row.setHasStatusBarChipDuringHeadsUpAnimation(true);
+
+ assertThat(row.hasStatusBarChipDuringHeadsUpAnimation()).isFalse();
+ }
+
+ @Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ public void hasStatusBarChipDuringHeadsUpAnimation_flagOn_returnsValue() throws Exception {
+ final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+
+ assertThat(row.hasStatusBarChipDuringHeadsUpAnimation()).isFalse();
+
+ row.setHasStatusBarChipDuringHeadsUpAnimation(true);
+ assertThat(row.hasStatusBarChipDuringHeadsUpAnimation()).isTrue();
+
+ row.setHasStatusBarChipDuringHeadsUpAnimation(false);
+ assertThat(row.hasStatusBarChipDuringHeadsUpAnimation()).isFalse();
+ }
+
private void setDrawableIconsInImageView(CachingIconView icon, Drawable iconDrawable,
Drawable rightIconDrawable) {
ImageView iconView = mock(ImageView.class);
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 2a58890..8fb2a24 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.chips.notification.shared.StatusBarNotifChips;
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;
@@ -92,6 +93,7 @@
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
import com.android.systemui.statusbar.notification.headsup.AvalancheController;
import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
+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.shared.NotificationThrottleHun;
@@ -1260,7 +1262,8 @@
prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener);
// WHEN we generate a disappear event
- mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ false);
+ mStackScroller.generateHeadsUpAnimation(
+ row, /* isHeadsUp = */ false, /* hasStatusBarChip= */ false);
// THEN headsUpAnimatingAway is true
verify(headsUpAnimatingAwayListener).accept(true);
@@ -1269,6 +1272,51 @@
@Test
@EnableSceneContainer
+ @DisableFlags(StatusBarNotifChips.FLAG_NAME)
+ public void testGenerateHeadsUpDisappearEvent_notifChipsFlagOff_statusBarChipNotSet() {
+ // GIVEN NSSL is ready for HUN animations
+ Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
+ ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+ prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener);
+
+ mStackScroller.generateHeadsUpAnimation(
+ row, /* isHeadsUp = */ false, /* hasStatusBarChip= */ true);
+
+ verify(row, never()).setHasStatusBarChipDuringHeadsUpAnimation(anyBoolean());
+ }
+
+ @Test
+ @EnableSceneContainer
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ public void testGenerateHeadsUpDisappearEvent_notifChipsFlagOn_statusBarChipSetToFalse() {
+ // GIVEN NSSL is ready for HUN animations
+ Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
+ ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+ prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener);
+
+ mStackScroller.generateHeadsUpAnimation(
+ row, /* isHeadsUp = */ false, /* hasStatusBarChip= */ false);
+
+ verify(row).setHasStatusBarChipDuringHeadsUpAnimation(false);
+ }
+
+ @Test
+ @EnableSceneContainer
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ public void testGenerateHeadsUpDisappearEvent_notifChipsFlagOn_statusBarChipSetToTrue() {
+ // GIVEN NSSL is ready for HUN animations
+ Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
+ ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+ prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener);
+
+ mStackScroller.generateHeadsUpAnimation(
+ row, /* isHeadsUp = */ false, /* hasStatusBarChip= */ true);
+
+ verify(row).setHasStatusBarChipDuringHeadsUpAnimation(true);
+ }
+
+ @Test
+ @EnableSceneContainer
public void testGenerateHeadsUpDisappearEvent_stackExpanded_headsUpAnimatingAwayNotSet() {
// GIVEN NSSL would be ready for HUN animations, BUT it is expanded
Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
@@ -1279,7 +1327,8 @@
mStackScroller.setHeadsUpGoingAwayAnimationsAllowed(true);
// WHEN we generate a disappear event
- mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ false);
+ mStackScroller.generateHeadsUpAnimation(
+ row, /* isHeadsUp = */ false, /* hasStatusBarChip= */ false);
// THEN nothing happens
verify(headsUpAnimatingAwayListener, never()).accept(anyBoolean());
@@ -1294,10 +1343,12 @@
ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener);
// BUT there is a pending appear event
- mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ true);
+ mStackScroller.generateHeadsUpAnimation(
+ row, /* isHeadsUp = */ true, /* hasStatusBarChip= */ false);
// WHEN we generate a disappear event
- mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ false);
+ mStackScroller.generateHeadsUpAnimation(
+ row, /* isHeadsUp = */ false, /* hasStatusBarChip= */ false);
// THEN nothing happens
verify(headsUpAnimatingAwayListener, never()).accept(anyBoolean());
@@ -1313,7 +1364,8 @@
prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener);
// WHEN we generate a disappear event
- mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ true);
+ mStackScroller.generateHeadsUpAnimation(
+ row, /* isHeadsUp = */ true, /* hasStatusBarChip= */ false);
// THEN headsUpAnimatingWay is not set
verify(headsUpAnimatingAwayListener, never()).accept(anyBoolean());
@@ -1335,7 +1387,8 @@
when(row.getEntry()).thenReturn(entry);
// WHEN we generate an add event
- mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ true);
+ mStackScroller.generateHeadsUpAnimation(
+ row, /* isHeadsUp = */ true, /* hasStatusBarChip= */ false);
// THEN nothing happens
assertThat(mStackScroller.isAddOrRemoveAnimationPending()).isFalse();
@@ -1350,7 +1403,8 @@
prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener);
// AND there is a HUN animating away
- mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ false);
+ mStackScroller.generateHeadsUpAnimation(
+ row, /* isHeadsUp = */ false, /* hasStatusBarChip= */ false);
assertTrue("a HUN should be animating away", mStackScroller.mHeadsUpAnimatingAway);
// WHEN the child animations are finished
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/battery/domain/interactor/BatteryInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/battery/domain/interactor/BatteryInteractorTest.kt
index df45e2e..7946a68 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/battery/domain/interactor/BatteryInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/battery/domain/interactor/BatteryInteractorTest.kt
@@ -22,6 +22,7 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.statusbar.policy.batteryController
import com.android.systemui.statusbar.policy.fake
import com.android.systemui.testKosmos
@@ -32,7 +33,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class BatteryInteractorTest : SysuiTestCase() {
- val kosmos = testKosmos()
+ val kosmos = testKosmos().useUnconfinedTestDispatcher()
val Kosmos.underTest by Kosmos.Fixture { batteryInteractor }
@Test
@@ -50,12 +51,62 @@
assertThat(latest).isEqualTo(BatteryAttributionModel.Defend)
+ batteryController.fake._isDefender = false
batteryController.fake._isPowerSave = true
assertThat(latest).isEqualTo(BatteryAttributionModel.PowerSave)
+ batteryController.fake._isPowerSave = false
batteryController.fake._isPluggedIn = true
assertThat(latest).isEqualTo(BatteryAttributionModel.Charging)
}
+
+ @Test
+ fun attributionType_prioritizesPowerSaveOverCharging() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.batteryAttributionType)
+
+ batteryController.fake._isPluggedIn = true
+ batteryController.fake._isDefender = true
+ batteryController.fake._isPowerSave = true
+
+ assertThat(latest).isEqualTo(BatteryAttributionModel.PowerSave)
+ }
+
+ @Test
+ fun attributionType_prioritizesPowerSaveOverDefender() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.batteryAttributionType)
+
+ batteryController.fake._isPluggedIn = true
+ batteryController.fake._isPowerSave = true
+ batteryController.fake._isDefender = false
+
+ assertThat(latest).isEqualTo(BatteryAttributionModel.PowerSave)
+ }
+
+ @Test
+ fun attributionType_prioritizesDefenderOverCharging() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.batteryAttributionType)
+
+ batteryController.fake._isPluggedIn = true
+ batteryController.fake._isPowerSave = false
+ batteryController.fake._isDefender = true
+
+ assertThat(latest).isEqualTo(BatteryAttributionModel.Defend)
+ }
+
+ @Test
+ fun attributionType_prioritizesChargingOnly() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.batteryAttributionType)
+
+ batteryController.fake._isPluggedIn = true
+ batteryController.fake._isDefender = false
+ batteryController.fake._isPowerSave = false
+
+ assertThat(latest).isEqualTo(BatteryAttributionModel.Charging)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/battery/ui/viewmodel/BatteryViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/battery/ui/viewmodel/BatteryViewModelTest.kt
index 6f4c745..d817348 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/battery/ui/viewmodel/BatteryViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/battery/ui/viewmodel/BatteryViewModelTest.kt
@@ -97,4 +97,39 @@
assertThat(underTest.glyphList).isEqualTo(listOf(BatteryGlyph.BoltLarge))
}
+
+ @Test
+ fun glyphList_attributionOrdering_prioritizesDefendOverCharging() =
+ kosmos.runTest {
+ fakeSystemSettingsRepository.setInt(Settings.System.SHOW_BATTERY_PERCENT, 0)
+ batteryController.fake._level = 39
+ batteryController.fake._isPluggedIn = true
+ batteryController.fake._isDefender = true
+
+ assertThat(underTest.glyphList).isEqualTo(listOf(BatteryGlyph.DefendLarge))
+ }
+
+ @Test
+ fun glyphList_attributionOrdering_prioritizesPowerSaveOverDefend() =
+ kosmos.runTest {
+ fakeSystemSettingsRepository.setInt(Settings.System.SHOW_BATTERY_PERCENT, 0)
+ batteryController.fake._level = 39
+ batteryController.fake._isPluggedIn = true
+ batteryController.fake._isDefender = true
+ batteryController.fake._isPowerSave = true
+
+ assertThat(underTest.glyphList).isEqualTo(listOf(BatteryGlyph.PlusLarge))
+ }
+
+ @Test
+ fun glyphList_attributionOrdering_prioritizesPowerSaveOverCharging() =
+ kosmos.runTest {
+ fakeSystemSettingsRepository.setInt(Settings.System.SHOW_BATTERY_PERCENT, 0)
+ batteryController.fake._level = 39
+ batteryController.fake._isPluggedIn = true
+ batteryController.fake._isDefender = false
+ batteryController.fake._isPowerSave = true
+
+ assertThat(underTest.glyphList).isEqualTo(listOf(BatteryGlyph.PlusLarge))
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt
index df74404..db948e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.pipeline.mobile.data.model
import android.os.PersistableBundle
-import android.telephony.CarrierConfigManager
import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL
import android.telephony.CarrierConfigManager.KEY_SHOW_5G_SLICE_ICON_BOOL
import android.telephony.CarrierConfigManager.KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL
@@ -37,7 +36,7 @@
@Before
fun setUp() {
- underTest = SystemUiCarrierConfig(SUB_1_ID, createTestConfig())
+ underTest = SystemUiCarrierConfig(SUB_1_ID, testCarrierConfig())
}
@Test
@@ -46,7 +45,7 @@
assertThat(underTest.isUsingDefault).isTrue()
// ANY new config means we're no longer tracking defaults
- underTest.processNewCarrierConfig(createTestConfig())
+ underTest.processNewCarrierConfig(testCarrierConfig())
assertThat(underTest.isUsingDefault).isFalse()
}
@@ -58,7 +57,7 @@
assertThat(underTest.allowNetworkSliceIndicator.value).isTrue()
underTest.processNewCarrierConfig(
- configWithOverrides(
+ testCarrierConfigWithOverrides(
KEY_INFLATE_SIGNAL_STRENGTH_BOOL to true,
KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL to true,
KEY_SHOW_5G_SLICE_ICON_BOOL to false,
@@ -81,11 +80,11 @@
underTest =
SystemUiCarrierConfig(
SUB_1_ID,
- configWithOverrides(
+ testCarrierConfigWithOverrides(
KEY_INFLATE_SIGNAL_STRENGTH_BOOL to true,
KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL to true,
KEY_SHOW_5G_SLICE_ICON_BOOL to true,
- )
+ ),
)
assertThat(underTest.isUsingDefault).isTrue()
@@ -104,26 +103,5 @@
companion object {
private const val SUB_1_ID = 1
-
- /**
- * In order to keep us from having to update every place that might want to create a config,
- * make sure to add new keys here
- */
- fun createTestConfig() =
- PersistableBundle().also {
- it.putBoolean(CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL, false)
- it.putBoolean(CarrierConfigManager.KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL, false)
- it.putBoolean(CarrierConfigManager.KEY_SHOW_5G_SLICE_ICON_BOOL, true)
- }
-
- /** Override the default config with the given (key, value) pair */
- fun configWithOverride(key: String, override: Boolean): PersistableBundle =
- createTestConfig().also { it.putBoolean(key, override) }
-
- /** Override any number of configs from the default */
- fun configWithOverrides(vararg overrides: Pair<String, Boolean>) =
- createTestConfig().also { config ->
- overrides.forEach { (key, value) -> config.putBoolean(key, value) }
- }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryImplTest.kt
index d074fc2..e1cc259 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryImplTest.kt
@@ -26,7 +26,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
-import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfigTest.Companion.createTestConfig
+import com.android.systemui.statusbar.pipeline.mobile.data.model.testCarrierConfig
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
@@ -163,8 +163,8 @@
private const val SUB_ID_1 = 1
private const val SUB_ID_2 = 2
- private val DEFAULT_CONFIG = createTestConfig()
- private val CONFIG_1 = createTestConfig()
- private val CONFIG_2 = createTestConfig()
+ private val DEFAULT_CONFIG = testCarrierConfig()
+ private val CONFIG_1 = testCarrierConfig()
+ private val CONFIG_2 = testCarrierConfig()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index a51e919..ed8be9b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -86,8 +86,8 @@
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.UnknownNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig
-import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfigTest.Companion.configWithOverride
-import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfigTest.Companion.createTestConfig
+import com.android.systemui.statusbar.pipeline.mobile.data.model.testCarrierConfig
+import com.android.systemui.statusbar.pipeline.mobile.data.model.testCarrierConfigWithOverride
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.signalStrength
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.telephonyDisplayInfo
@@ -129,7 +129,7 @@
@Mock private lateinit var context: Context
private val mobileMappings = FakeMobileMappingsProxy()
- private val systemUiCarrierConfig = SystemUiCarrierConfig(SUB_1_ID, createTestConfig())
+ private val systemUiCarrierConfig = SystemUiCarrierConfig(SUB_1_ID, testCarrierConfig())
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
@@ -1314,13 +1314,13 @@
assertThat(latest).isEqualTo(DEFAULT_NUM_LEVELS)
systemUiCarrierConfig.processNewCarrierConfig(
- configWithOverride(KEY_INFLATE_SIGNAL_STRENGTH_BOOL, true)
+ testCarrierConfigWithOverride(KEY_INFLATE_SIGNAL_STRENGTH_BOOL, true)
)
assertThat(latest).isEqualTo(DEFAULT_NUM_LEVELS + 1)
systemUiCarrierConfig.processNewCarrierConfig(
- configWithOverride(KEY_INFLATE_SIGNAL_STRENGTH_BOOL, false)
+ testCarrierConfigWithOverride(KEY_INFLATE_SIGNAL_STRENGTH_BOOL, false)
)
assertThat(latest).isEqualTo(DEFAULT_NUM_LEVELS)
@@ -1336,13 +1336,13 @@
assertThat(latest).isEqualTo(false)
systemUiCarrierConfig.processNewCarrierConfig(
- configWithOverride(KEY_INFLATE_SIGNAL_STRENGTH_BOOL, true)
+ testCarrierConfigWithOverride(KEY_INFLATE_SIGNAL_STRENGTH_BOOL, true)
)
assertThat(latest).isEqualTo(true)
systemUiCarrierConfig.processNewCarrierConfig(
- configWithOverride(KEY_INFLATE_SIGNAL_STRENGTH_BOOL, false)
+ testCarrierConfigWithOverride(KEY_INFLATE_SIGNAL_STRENGTH_BOOL, false)
)
assertThat(latest).isEqualTo(false)
@@ -1354,13 +1354,13 @@
val latest by collectLastValue(underTest.allowNetworkSliceIndicator)
systemUiCarrierConfig.processNewCarrierConfig(
- configWithOverride(KEY_SHOW_5G_SLICE_ICON_BOOL, true)
+ testCarrierConfigWithOverride(KEY_SHOW_5G_SLICE_ICON_BOOL, true)
)
assertThat(latest).isTrue()
systemUiCarrierConfig.processNewCarrierConfig(
- configWithOverride(KEY_SHOW_5G_SLICE_ICON_BOOL, false)
+ testCarrierConfigWithOverride(KEY_SHOW_5G_SLICE_ICON_BOOL, false)
)
assertThat(latest).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
index ec260fc..6f21e79 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
@@ -41,7 +41,7 @@
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig
-import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfigTest
+import com.android.systemui.statusbar.pipeline.mobile.data.model.testCarrierConfig
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.getTelephonyCallbackForType
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.signalStrength
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
@@ -105,11 +105,7 @@
@Mock private lateinit var subscriptionModel: StateFlow<SubscriptionModel?>
private val mobileMappings = FakeMobileMappingsProxy()
- private val systemUiCarrierConfig =
- SystemUiCarrierConfig(
- SUB_1_ID,
- SystemUiCarrierConfigTest.createTestConfig(),
- )
+ private val systemUiCarrierConfig = SystemUiCarrierConfig(SUB_1_ID, testCarrierConfig())
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
@@ -185,12 +181,7 @@
val job = underTest.dataActivityDirection.onEach { latest = it }.launchIn(this)
assertThat(latest)
- .isEqualTo(
- DataActivityModel(
- hasActivityIn = true,
- hasActivityOut = true,
- )
- )
+ .isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = true))
displayInfoJob.cancel()
job.cancel()
@@ -209,7 +200,7 @@
connectionCallback.onDataConnectionStateChanged(
TelephonyManager.DATA_CONNECTED,
- 200 /* unused */
+ 200, /* unused */
)
flipActivity(100, activityCallback)
@@ -320,10 +311,7 @@
job.cancel()
}
- private fun flipActivity(
- times: Int,
- callback: DataActivityListener,
- ) {
+ private fun flipActivity(times: Int, callback: DataActivityListener) {
repeat(times) { index -> callback.onDataActivity(index % 4) }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt
index b4fbaad..5f34420 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt
@@ -18,7 +18,6 @@
import android.app.Flags
import android.content.Context
-import android.content.res.Resources
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Rect
@@ -43,6 +42,7 @@
import org.mockito.Mockito.spy
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyZeroInteractions
@@ -61,23 +61,20 @@
@Mock private lateinit var mockContext: Context
- @Mock private lateinit var mockResources: Resources
-
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ val spyResources = spy(context.resources)
+
whenever(surfaceHolder.surface).thenReturn(surface)
whenever(surfaceHolder.surfaceFrame).thenReturn(surfaceFrame)
whenever(surface.lockHardwareCanvas()).thenReturn(canvas)
whenever(mockContext.getColor(anyInt())).thenReturn(1)
- whenever(mockContext.resources).thenReturn(mockResources)
- whenever(
- mockResources.getDimensionPixelOffset(
- eq(R.dimen.gradient_color_wallpaper_center_offset)
- )
- )
- .thenReturn(OFFSET_PX)
+ whenever(mockContext.resources).thenReturn(spyResources)
+ doReturn(OFFSET_PX)
+ .`when`(spyResources)
+ .getDimensionPixelOffset(eq(R.dimen.gradient_color_wallpaper_center_offset))
}
private fun createGradientColorWallpaperEngine(): Engine {
@@ -106,7 +103,8 @@
engine.onSurfaceRedrawNeeded(surfaceHolder)
- verify(canvas).drawRect(any<RectF>(), any<Paint>())
+ // One rect for the background, one rect for the foreground mask.
+ verify(canvas, times(2)).drawRect(any<RectF>(), any<Paint>())
verify(canvas, times(2)).drawCircle(anyFloat(), anyFloat(), anyFloat(), any<Paint>())
}
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 8281132..341bd3a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -33,8 +33,6 @@
import static com.google.common.truth.Truth.assertThat;
-import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -57,6 +55,8 @@
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
+import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
+
import android.app.ActivityManager;
import android.app.IActivityManager;
import android.app.INotificationManager;
@@ -464,8 +464,8 @@
mZenModeConfig.suppressedVisualEffects = 0;
when(mZenModeController.getConfig()).thenReturn(mZenModeConfig);
- mSysUiState = new SysUiState(mDisplayTracker, mKosmos.getSceneContainerPlugin());
- mSysUiState.addCallback(sysUiFlags -> {
+ mSysUiState = mKosmos.getSysuiState();
+ mSysUiState.addCallback((sysUiFlags, displayId) -> {
mSysUiStateBubblesManageMenuExpanded =
(sysUiFlags
& QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED) != 0;
@@ -624,7 +624,8 @@
TAG,
String.format("waiting for animations to complete. attempt %d", retryCount));
// post a message to the looper and wait for it to be processed
- mTestableLooper.runWithLooper(() -> {});
+ mTestableLooper.runWithLooper(() -> {
+ });
retryCount++;
}
mTestableLooper.processAllMessages();
@@ -2996,9 +2997,11 @@
}
@Override
- public void onDragItemOverBubbleBarDragZone(@NonNull BubbleBarLocation location) {}
+ public void onDragItemOverBubbleBarDragZone(@NonNull BubbleBarLocation location) {
+ }
@Override
- public void onItemDraggedOutsideBubbleBarDropZone() {}
+ public void onItemDraggedOutsideBubbleBarDropZone() {
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt b/packages/SystemUI/tests/utils/src/android/net/ConnectivityManagerKosmos.kt
similarity index 76%
copy from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt
copy to packages/SystemUI/tests/utils/src/android/net/ConnectivityManagerKosmos.kt
index 231fb2d..516053d 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt
+++ b/packages/SystemUI/tests/utils/src/android/net/ConnectivityManagerKosmos.kt
@@ -14,11 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.dagger.qualifiers
+package android.net
-import javax.inject.Qualifier
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mockFixture
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class NotifInflation
+var Kosmos.connectivityManager: ConnectivityManager by mockFixture()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorKosmos.kt
index e0c0fbd..bc8e62c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorKosmos.kt
@@ -26,6 +26,7 @@
applicationContext,
localBluetoothManager,
bluetoothTileDialogAudioSharingRepository,
+ bluetoothTileDialogLogger,
testDispatcher,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/FakeAudioSharingRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/FakeAudioSharingRepository.kt
index c744eac..0f6f191 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/FakeAudioSharingRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/FakeAudioSharingRepository.kt
@@ -28,7 +28,7 @@
private val mutableInAudioSharing: MutableStateFlow<Boolean> = MutableStateFlow(false)
- private val mutableAudioSourceStateUpdate = MutableSharedFlow<Unit>()
+ private val mutableAudioSourceStateUpdate = MutableSharedFlow<Unit>(replay = 1)
var sourceAdded: Boolean = false
private set
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt
index d27ecce..94d27f7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt
@@ -28,6 +28,7 @@
import com.android.systemui.log.sessionTracker
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.domain.interactor.sceneBackInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
val Kosmos.bouncerInteractor by Fixture {
BouncerInteractor(
@@ -39,6 +40,7 @@
powerInteractor = powerInteractor,
uiEventLogger = uiEventLogger,
sessionTracker = sessionTracker,
+ sceneInteractor = sceneInteractor,
sceneBackInteractor = sceneBackInteractor,
configurationInteractor = configurationInteractor,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/development/domain/interactor/BuildNumberInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/development/domain/interactor/BuildNumberInteractorKosmos.kt
index aa4dd18..74d53b0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/development/domain/interactor/BuildNumberInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/development/domain/interactor/BuildNumberInteractorKosmos.kt
@@ -20,6 +20,7 @@
import android.content.res.mainResources
import com.android.systemui.development.data.repository.developmentSettingRepository
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.user.data.repository.userRepository
@@ -31,5 +32,6 @@
userRepository,
{ clipboardManager },
testDispatcher,
+ applicationCoroutineScope,
)
}
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 d6f0e06..663a853 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
@@ -53,6 +53,7 @@
MutableSharedFlow<DisplayRepository.PendingDisplay?>(replay = 1)
private val displayAdditionEventFlow = MutableSharedFlow<Display?>(replay = 0)
private val displayRemovalEventFlow = MutableSharedFlow<Int>(replay = 0)
+ private val displayIdsWithSystemDecorationsFlow = MutableStateFlow<Set<Int>>(emptySet())
suspend fun addDisplay(displayId: Int, type: Int = Display.TYPE_EXTERNAL) {
addDisplay(display(type, id = displayId))
@@ -62,10 +63,19 @@
displays.forEach { addDisplay(it) }
}
+ suspend operator fun plusAssign(display: Display) {
+ addDisplay(display)
+ }
+
+ suspend operator fun minusAssign(displayId: Int) {
+ removeDisplay(displayId)
+ }
+
suspend fun addDisplay(display: Display) {
flow.value += display
displayIdFlow.value += display.displayId
displayAdditionEventFlow.emit(display)
+ displayIdsWithSystemDecorationsFlow.value += display.displayId
}
suspend fun removeDisplay(displayId: Int) {
@@ -74,6 +84,16 @@
displayRemovalEventFlow.emit(displayId)
}
+ suspend fun triggerAddDisplaySystemDecorationEvent(displayId: Int) {
+ displayIdsWithSystemDecorationsFlow.value += displayId
+ displayIdsWithSystemDecorationsFlow.emit(displayIdsWithSystemDecorationsFlow.value)
+ }
+
+ suspend fun triggerRemoveSystemDecorationEvent(displayId: Int) {
+ displayIdsWithSystemDecorationsFlow.value -= displayId
+ displayIdsWithSystemDecorationsFlow.emit(displayIdsWithSystemDecorationsFlow.value)
+ }
+
/** Emits [value] as [displayAdditionEvent] flow value. */
suspend fun emit(value: Display?) = displayAdditionEventFlow.emit(value)
@@ -104,7 +124,8 @@
private val _displayChangeEvent = MutableSharedFlow<Int>(replay = 1)
override val displayChangeEvent: Flow<Int> = _displayChangeEvent
- override val displayIdsWithSystemDecorations: StateFlow<Set<Int>> = MutableStateFlow(emptySet())
+ override val displayIdsWithSystemDecorations: StateFlow<Set<Int>> =
+ displayIdsWithSystemDecorationsFlow
suspend fun emitDisplayChangeEvent(displayId: Int) = _displayChangeEvent.emit(displayId)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt
index e379726..aa23aa3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt
@@ -16,8 +16,10 @@
package com.android.systemui.display.data.repository
+import com.android.systemui.dump.dumpManager
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
import kotlinx.coroutines.CoroutineScope
class FakePerDisplayStore(
@@ -47,3 +49,30 @@
displayRepository = displayRepository,
)
}
+
+class FakePerDisplayInstanceProviderWithTeardown :
+ PerDisplayInstanceProviderWithTeardown<TestPerDisplayInstance> {
+ val destroyed = mutableListOf<TestPerDisplayInstance>()
+
+ override fun destroyInstance(instance: TestPerDisplayInstance) {
+ destroyed += instance
+ }
+
+ override fun createInstance(displayId: Int): TestPerDisplayInstance? {
+ return TestPerDisplayInstance(displayId)
+ }
+}
+
+val Kosmos.fakePerDisplayInstanceProviderWithTeardown by
+ Kosmos.Fixture { FakePerDisplayInstanceProviderWithTeardown() }
+
+val Kosmos.fakePerDisplayInstanceRepository by
+ Kosmos.Fixture {
+ PerDisplayInstanceRepositoryImpl(
+ debugName = "fakePerDisplayInstanceRepository",
+ instanceProvider = fakePerDisplayInstanceProviderWithTeardown,
+ testScope.backgroundScope,
+ displayRepository,
+ dumpManager,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kairos/CollectLastValue.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kairos/CollectLastValue.kt
new file mode 100644
index 0000000..927209f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kairos/CollectLastValue.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.kairos
+
+import com.android.systemui.coroutines.collectLastValue
+import kotlin.properties.ReadOnlyProperty
+import kotlin.reflect.KProperty
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+
+/**
+ * Collect [state] in a new [Job] and return a getter for the collection of values collected.
+ *
+ * ```
+ * fun myTest() = runTest {
+ * // ...
+ * val values by collectValues(underTest.flow)
+ * assertThat(values).isEqualTo(listOf(expected1, expected2, ...))
+ * }
+ * ```
+ */
+@ExperimentalKairosApi
+fun <T> TestScope.collectLastValue(state: State<T>, kairosNetwork: KairosNetwork): KairosValue<T?> {
+ var value: T? = null
+ backgroundScope.launch { kairosNetwork.activateSpec { state.observe { value = it } } }
+ return KairosValueImpl {
+ runCurrent()
+ value
+ }
+}
+
+/**
+ * Collect [flow] in a new [Job] and return a getter for the collection of values collected.
+ *
+ * ```
+ * fun myTest() = runTest {
+ * // ...
+ * val values by collectValues(underTest.flow)
+ * assertThat(values).isEqualTo(listOf(expected1, expected2, ...))
+ * }
+ * ```
+ */
+@ExperimentalKairosApi
+fun <T> TestScope.collectLastValue(flow: Events<T>, kairosNetwork: KairosNetwork): KairosValue<T?> {
+ var value: T? = null
+ backgroundScope.launch { kairosNetwork.activateSpec { flow.observe { value = it } } }
+ return KairosValueImpl {
+ runCurrent()
+ value
+ }
+}
+
+/**
+ * Collect [flow] in a new [Job] and return a getter for the collection of values collected.
+ *
+ * ```
+ * fun myTest() = runTest {
+ * // ...
+ * val values by collectValues(underTest.flow)
+ * assertThat(values).isEqualTo(listOf(expected1, expected2, ...))
+ * }
+ * ```
+ */
+@ExperimentalKairosApi
+fun <T> TestScope.collectValues(
+ flow: Events<T>,
+ kairosNetwork: KairosNetwork,
+): KairosValue<List<T>> {
+ val values = mutableListOf<T>()
+ backgroundScope.launch { kairosNetwork.activateSpec { flow.observe { values.add(it) } } }
+ return KairosValueImpl {
+ runCurrent()
+ values.toList()
+ }
+}
+
+/** @see collectLastValue */
+interface KairosValue<T> : ReadOnlyProperty<Any?, T> {
+ val value: T
+}
+
+private class KairosValueImpl<T>(private val block: () -> T) : KairosValue<T> {
+ override val value: T
+ get() = block()
+
+ override fun getValue(thisRef: Any?, property: KProperty<*>): T = value
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kairos/KairosKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kairos/KairosKosmos.kt
new file mode 100644
index 0000000..d7f2041
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kairos/KairosKosmos.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2024 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.kairos
+
+import com.android.systemui.KairosActivatable
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+
+@ExperimentalKairosApi
+val Kosmos.kairos: KairosNetwork by Fixture { applicationCoroutineScope.launchKairosNetwork() }
+
+@ExperimentalKairosApi
+fun Kosmos.activateKairosActivatable(activatable: KairosActivatable) {
+ applicationCoroutineScope.launch { kairos.activateSpec { activatable.run { activate() } } }
+}
+
+@ExperimentalKairosApi
+fun <T : KairosActivatable> ActivatedKairosFixture(block: Kosmos.() -> T) = Fixture {
+ block().also { activateKairosActivatable(it) }
+}
+
+@ExperimentalKairosApi
+fun Kosmos.runKairosTest(timeout: Duration = 5.seconds, block: suspend KairosTestScope.() -> Unit) =
+ testScope.runTest(timeout) { KairosTestScopeImpl(this@runKairosTest, this, kairos).block() }
+
+@ExperimentalKairosApi
+interface KairosTestScope : Kosmos {
+ fun <T> State<T>.collectLastValue(): KairosValue<T?>
+
+ suspend fun <T> State<T>.sample(): T
+
+ fun <T : KairosActivatable> T.activated(): T
+}
+
+@ExperimentalKairosApi
+private class KairosTestScopeImpl(
+ kosmos: Kosmos,
+ val testScope: TestScope,
+ val kairos: KairosNetwork,
+) : KairosTestScope, Kosmos by kosmos {
+ override fun <T> State<T>.collectLastValue(): KairosValue<T?> =
+ testScope.collectLastValue(this@collectLastValue, kairos)
+
+ override suspend fun <T> State<T>.sample(): T = kairos.transact { sample() }
+
+ override fun <T : KairosActivatable> T.activated(): T =
+ this.also { activateKairosActivatable(it) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyevent/domain/interactor/SysUIKeyEventHandlerKosmos.kt
similarity index 61%
copy from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/keyevent/domain/interactor/SysUIKeyEventHandlerKosmos.kt
index 231fb2d..6fd7ef31 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyevent/domain/interactor/SysUIKeyEventHandlerKosmos.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2024 The Android Open Source Project
+ * Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,11 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.dagger.qualifiers
+package com.android.systemui.keyevent.domain.interactor
-import javax.inject.Qualifier
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class NotifInflation
+var Kosmos.mockSysUIKeyEventHandler by Kosmos.Fixture { mock<SysUIKeyEventHandler>() }
+var Kosmos.sysUIKeyEventHandler by Kosmos.Fixture { mockSysUIKeyEventHandler }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt
index 697e7b9..3f3c3c0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt
@@ -19,6 +19,7 @@
import android.content.applicationContext
import android.view.mockedLayoutInflater
import android.view.windowManager
+import com.android.systemui.accessibility.domain.interactor.accessibilityInteractor
import com.android.systemui.biometrics.domain.interactor.fingerprintPropertyInteractor
import com.android.systemui.biometrics.domain.interactor.udfpsOverlayInteractor
import com.android.systemui.common.ui.domain.interactor.configurationInteractor
@@ -37,6 +38,7 @@
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.statusbar.gesture.TapGestureDetector
+import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager
import com.android.systemui.util.mockito.mock
val Kosmos.alternateBouncerViewBinder by
@@ -76,5 +78,7 @@
fingerprintPropertyInteractor = fingerprintPropertyInteractor,
udfpsOverlayInteractor = udfpsOverlayInteractor,
alternateBouncerViewModel = alternateBouncerViewModel,
+ statusBarKeyguardViewManager = statusBarKeyguardViewManager,
+ accessibilityInteractor = accessibilityInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelKosmos.kt
index b233d3f..c6f55f0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelKosmos.kt
@@ -16,16 +16,20 @@
package com.android.systemui.keyguard.ui.viewmodel
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.blurConfig
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.statusbar.policy.keyguardStateController
val Kosmos.glanceableHubToPrimaryBouncerTransitionViewModel by Fixture {
GlanceableHubToPrimaryBouncerTransitionViewModel(
animationFlow = keyguardTransitionAnimationFlow,
blurConfig = blurConfig,
communalSettingsInteractor = communalSettingsInteractor,
+ communalSceneInteractor = communalSceneInteractor,
+ keyguardStateController = keyguardStateController,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 02cf1f5..af89403 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -29,6 +29,7 @@
import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.communal.ui.viewmodel.communalTransitionViewModel
import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
@@ -52,6 +53,8 @@
import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.lockscreenToGlanceableHubTransitionViewModel
import com.android.systemui.model.sceneContainerPlugin
+import com.android.systemui.model.sysUIStateDispatcher
+import com.android.systemui.model.sysUiState
import com.android.systemui.plugins.statusbar.statusBarStateController
import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.power.domain.interactor.powerInteractor
@@ -63,6 +66,7 @@
import com.android.systemui.scene.shared.model.sceneDataSource
import com.android.systemui.scene.ui.view.mockWindowRootViewProvider
import com.android.systemui.settings.brightness.data.repository.brightnessMirrorShowingRepository
+import com.android.systemui.settings.displayTracker
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.domain.interactor.shadeLayoutParams
@@ -87,6 +91,7 @@
import com.android.systemui.statusbar.policy.configurationController
import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
+import com.android.systemui.statusbar.policy.keyguardStateController
import com.android.systemui.statusbar.ui.viewmodel.keyguardStatusBarViewModel
import com.android.systemui.util.time.systemClock
import com.android.systemui.volume.domain.interactor.volumeDialogInteractor
@@ -126,6 +131,7 @@
val keyguardInteractor by lazy { kosmos.keyguardInteractor }
val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
val keyguardTransitionInteractor by lazy { kosmos.keyguardTransitionInteractor }
+ val keyguardStateController by lazy { kosmos.keyguardStateController }
val keyguardStatusBarViewModel by lazy { kosmos.keyguardStatusBarViewModel }
val powerRepository by lazy { kosmos.fakePowerRepository }
val clock by lazy { kosmos.systemClock }
@@ -147,6 +153,7 @@
val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor }
val communalInteractor by lazy { kosmos.communalInteractor }
val communalSceneInteractor by lazy { kosmos.communalSceneInteractor }
+ val communalSettingsInteractor by lazy { kosmos.communalSettingsInteractor }
val sceneContainerPlugin by lazy { kosmos.sceneContainerPlugin }
val deviceProvisioningInteractor by lazy { kosmos.deviceProvisioningInteractor }
val fakeDeviceProvisioningRepository by lazy { kosmos.fakeDeviceProvisioningRepository }
@@ -194,4 +201,7 @@
val fakeDisableFlagsRepository by lazy { kosmos.fakeDisableFlagsRepository }
val mockWindowRootViewProvider by lazy { kosmos.mockWindowRootViewProvider }
val windowRootViewBlurInteractor by lazy { kosmos.windowRootViewBlurInteractor }
+ val sysuiState by lazy { kosmos.sysUiState }
+ val displayTracker by lazy { kosmos.displayTracker }
+ val sysUIStateDispatcher by lazy { kosmos.sysUIStateDispatcher }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractorKosmos.kt
deleted file mode 100644
index 1edd405..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractorKosmos.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2024 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.media.controls.domain.pipeline.interactor
-
-import android.content.applicationContext
-import com.android.systemui.broadcast.broadcastSender
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
-import com.android.systemui.media.controls.data.repository.mediaFilterRepository
-import com.android.systemui.media.controls.domain.pipeline.mediaDataProcessor
-import com.android.systemui.plugins.activityStarter
-
-val Kosmos.mediaRecommendationsInteractor by
- Kosmos.Fixture {
- MediaRecommendationsInteractor(
- applicationScope = applicationCoroutineScope,
- applicationContext = applicationContext,
- repository = mediaFilterRepository,
- mediaDataProcessor = mediaDataProcessor,
- broadcastSender = broadcastSender,
- activityStarter = activityStarter,
- )
- }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelKosmos.kt
index 5e6434d..976b404 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelKosmos.kt
@@ -37,7 +37,6 @@
visualStabilityProvider = visualStabilityProvider,
interactor = mediaCarouselInteractor,
controlInteractorFactory = mediaControlInteractorFactory,
- recommendationsViewModel = mediaRecommendationsViewModel,
logger = mediaUiEventLogger,
mediaLogger = mediaLogger,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModelKosmos.kt
deleted file mode 100644
index 34a5277..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModelKosmos.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2024 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.media.controls.ui.viewmodel
-
-import android.content.applicationContext
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testDispatcher
-import com.android.systemui.media.controls.domain.pipeline.interactor.mediaRecommendationsInteractor
-import com.android.systemui.media.controls.util.mediaUiEventLogger
-
-val Kosmos.mediaRecommendationsViewModel by
- Kosmos.Fixture {
- MediaRecommendationsViewModel(
- applicationContext = applicationContext,
- backgroundDispatcher = testDispatcher,
- interactor = mediaRecommendationsInteractor,
- logger = mediaUiEventLogger,
- )
- }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/model/SceneContainerPluginKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/model/SceneContainerPluginKosmos.kt
index d19dfe8..79506f9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/model/SceneContainerPluginKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/model/SceneContainerPluginKosmos.kt
@@ -20,10 +20,12 @@
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.shade.data.repository.fakeShadeDisplaysRepository
val Kosmos.sceneContainerPlugin by Fixture {
SceneContainerPlugin(
sceneInteractor = { sceneInteractor },
occlusionInteractor = { sceneContainerOcclusionInteractor },
+ shadeDisplaysRepository = { fakeShadeDisplaysRepository },
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/model/SysUiStateKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/model/SysUiStateKosmos.kt
index 6ddf633..dbd1c9c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/model/SysUiStateKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/model/SysUiStateKosmos.kt
@@ -16,16 +16,35 @@
package com.android.systemui.model
+import android.view.Display
+import com.android.systemui.common.domain.interactor.SysUIStateDisplaysInteractor
+import com.android.systemui.display.data.repository.FakePerDisplayRepository
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.dump.dumpManager
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.settings.displayTracker
import org.mockito.Mockito.spy
-val Kosmos.sysUiState by Fixture {
- spy(
- SysUiState(
- displayTracker,
- sceneContainerPlugin,
- )
- )
+val Kosmos.sysUiState by Fixture { sysUiStateFactory.create(Display.DEFAULT_DISPLAY) }
+val Kosmos.sysUIStateDispatcher by Fixture { SysUIStateDispatcher() }
+
+val Kosmos.sysUiStateFactory by Fixture {
+ object : SysUiStateImpl.Factory {
+ override fun create(displayId: Int): SysUiStateImpl {
+ return spy(
+ SysUiStateImpl(
+ displayId,
+ sceneContainerPlugin,
+ dumpManager,
+ sysUIStateDispatcher,
+ )
+ )
+ }
+ }
+}
+
+val Kosmos.fakeSysUIStatePerDisplayRepository by Fixture { FakePerDisplayRepository<SysUiState>() }
+
+val Kosmos.sysuiStateInteractor by Fixture {
+ SysUIStateDisplaysInteractor(fakeSysUIStatePerDisplayRepository, displayRepository)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
index 4330770..4618dc7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
@@ -58,7 +58,7 @@
import com.android.systemui.util.settings.FakeGlobalSettings
import com.android.systemui.util.settings.GlobalSettings
import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestCoroutineScheduler
@@ -94,7 +94,7 @@
return createFooterActionsViewModel(
context,
footerActionsInteractor,
- flowOf(shadeMode),
+ MutableStateFlow(shadeMode),
falsingManager,
globalActionsDialogLite,
mockActivityStarter,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/view/WindowRootViewKeyEventHandlerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/view/WindowRootViewKeyEventHandlerKosmos.kt
new file mode 100644
index 0000000..9369057
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/view/WindowRootViewKeyEventHandlerKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.scene.ui.view
+
+import com.android.systemui.classifier.falsingCollector
+import com.android.systemui.keyevent.domain.interactor.sysUIKeyEventHandler
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.windowRootViewKeyEventHandler by
+ Kosmos.Fixture {
+ WindowRootViewKeyEventHandler(
+ sysUIKeyEventHandlerLazy = { sysUIKeyEventHandler },
+ falsingCollector = falsingCollector,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
index 9776fd9..66a1751 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.core
import android.content.testableContext
-import android.view.mockIWindowManager
import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.display.data.repository.displayScopeRepository
@@ -94,6 +93,5 @@
statusBarInitializerStore,
privacyDotWindowControllerStore,
lightBarControllerStore,
- mockIWindowManager,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayStoreKosmos.kt
new file mode 100644
index 0000000..0db8ee8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarPerDisplayStoreKosmos.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.data.repository
+
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import kotlinx.coroutines.CoroutineScope
+
+class FakeStatusBarPerDisplayStore(
+ backgroundApplicationScope: CoroutineScope,
+ displayRepository: DisplayRepository,
+) :
+ StatusBarPerDisplayStoreImpl<TestPerDisplayInstance>(
+ backgroundApplicationScope,
+ displayRepository,
+ ) {
+
+ val removalActions = mutableListOf<TestPerDisplayInstance>()
+
+ override fun createInstanceForDisplay(displayId: Int): TestPerDisplayInstance {
+ return TestPerDisplayInstance(displayId)
+ }
+
+ override val instanceClass = TestPerDisplayInstance::class.java
+
+ override suspend fun onDisplayRemovalAction(instance: TestPerDisplayInstance) {
+ removalActions += instance
+ }
+}
+
+data class TestPerDisplayInstance(val displayId: Int)
+
+val Kosmos.fakeStatusBarPerDisplayStore by
+ Kosmos.Fixture {
+ FakeStatusBarPerDisplayStore(
+ backgroundApplicationScope = applicationCoroutineScope,
+ displayRepository = displayRepository,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModelKosmos.kt
index 7145907..39391d0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModelKosmos.kt
@@ -18,14 +18,19 @@
import android.content.testableContext
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.statusbar.featurepods.media.domain.interactor.mediaControlChipInteractor
-val Kosmos.mediaControlChipViewModel: MediaControlChipViewModel by
+private val Kosmos.mediaControlChipViewModel: MediaControlChipViewModel by
Kosmos.Fixture {
MediaControlChipViewModel(
- backgroundScope = applicationCoroutineScope,
applicationContext = testableContext,
mediaControlChipInteractor = mediaControlChipInteractor,
)
}
+
+val Kosmos.mediaControlChipViewModelFactory by
+ Kosmos.Fixture {
+ object : MediaControlChipViewModel.Factory {
+ override fun create(): MediaControlChipViewModel = mediaControlChipViewModel
+ }
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelKosmos.kt
index b876095..2a3167cb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelKosmos.kt
@@ -17,10 +17,12 @@
package com.android.systemui.statusbar.featurepods.popups.ui.viewmodel
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.statusbar.featurepods.media.ui.viewmodel.mediaControlChipViewModel
+import com.android.systemui.statusbar.featurepods.media.ui.viewmodel.mediaControlChipViewModelFactory
private val Kosmos.statusBarPopupChipsViewModel: StatusBarPopupChipsViewModel by
- Kosmos.Fixture { StatusBarPopupChipsViewModel(mediaControlChip = mediaControlChipViewModel) }
+ Kosmos.Fixture {
+ StatusBarPopupChipsViewModel(mediaControlChipFactory = mediaControlChipViewModelFactory)
+ }
val Kosmos.statusBarPopupChipsViewModelFactory by
Kosmos.Fixture {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
index fbc2a21..219ecbf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
@@ -21,6 +21,7 @@
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel
import com.android.systemui.statusbar.domain.interactor.remoteInputInteractor
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.notification.emptyshade.ui.viewmodel.emptyShadeViewModelFactory
@@ -35,6 +36,7 @@
NotificationListViewModel(
notificationShelfViewModel,
hideListViewModel,
+ ongoingActivityChipsViewModel,
footerViewModelFactory,
emptyShadeViewModelFactory,
Optional.of(notificationListLoggerViewModel),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt
index 167b11d..87ce501 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
import com.android.systemui.dump.dumpManager
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.Kosmos
@@ -32,6 +33,7 @@
stackAppearanceInteractor = notificationStackAppearanceInteractor,
shadeInteractor = shadeInteractor,
shadeModeInteractor = shadeModeInteractor,
+ bouncerInteractor = bouncerInteractor,
remoteInputInteractor = remoteInputInteractor,
sceneInteractor = sceneInteractor,
keyguardInteractor = { keyguardInteractor },
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
index 17ef208..85fe3d9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
import android.content.applicationContext
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.dump.dumpManager
@@ -74,6 +75,7 @@
keyguardInteractor = keyguardInteractor,
keyguardTransitionInteractor = keyguardTransitionInteractor,
shadeInteractor = shadeInteractor,
+ bouncerInteractor = bouncerInteractor,
shadeModeInteractor = shadeModeInteractor,
notificationStackAppearanceInteractor = notificationStackAppearanceInteractor,
alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinderKosmos.kt
index 6a995c0..2c5aed4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinderKosmos.kt
@@ -17,7 +17,13 @@
package com.android.systemui.statusbar.notification.ui.viewbinder
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationListViewModel
val Kosmos.headsUpNotificationViewBinder by
- Kosmos.Fixture { HeadsUpNotificationViewBinder(viewModel = notificationListViewModel) }
+ Kosmos.Fixture {
+ HeadsUpNotificationViewBinder(
+ viewModel = notificationListViewModel,
+ ongoingActivityChipsViewModel = ongoingActivityChipsViewModel,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigFakes.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigFakes.kt
new file mode 100644
index 0000000..13b01634
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigFakes.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.pipeline.mobile.data.model
+
+import android.os.PersistableBundle
+import android.telephony.CarrierConfigManager
+
+/**
+ * In order to keep us from having to update every place that might want to create a config, make
+ * sure to add new keys here
+ */
+fun testCarrierConfig() =
+ PersistableBundle().also {
+ it.putBoolean(CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL, false)
+ it.putBoolean(CarrierConfigManager.KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL, false)
+ it.putBoolean(CarrierConfigManager.KEY_SHOW_5G_SLICE_ICON_BOOL, true)
+ }
+
+/** Override the default config with the given (key, value) pair */
+fun testCarrierConfigWithOverride(key: String, override: Boolean): PersistableBundle =
+ testCarrierConfig().also { it.putBoolean(key, override) }
+
+/** Override any number of configs from the default */
+fun testCarrierConfigWithOverrides(vararg overrides: Pair<String, Boolean>) =
+ testCarrierConfig().also { config ->
+ overrides.forEach { (key, value) -> config.putBoolean(key, value) }
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepositoryKairos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepositoryKairos.kt
new file mode 100644
index 0000000..8cf3ee8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepositoryKairos.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2024 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.pipeline.mobile.data.repository
+
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.KairosNetwork
+import com.android.systemui.kairos.MutableState
+import com.android.systemui.kairos.State
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository.Companion.DEFAULT_NETWORK_NAME
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+
+@ExperimentalKairosApi
+class FakeMobileConnectionRepositoryKairos(
+ override val subId: Int,
+ kairos: KairosNetwork,
+ override val tableLogBuffer: TableLogBuffer,
+) : MobileConnectionRepositoryKairos {
+ override val carrierId = MutableState(kairos, 0)
+ override val inflateSignalStrength = MutableState(kairos, false)
+ override val allowNetworkSliceIndicator = MutableState(kairos, true)
+ override val isEmergencyOnly = MutableState(kairos, false)
+ override val isRoaming = MutableState(kairos, false)
+ override val operatorAlphaShort = MutableState<String?>(kairos, null)
+ override val isInService = MutableState(kairos, false)
+ override val isNonTerrestrial = MutableState(kairos, false)
+ override val isGsm = MutableState(kairos, false)
+ override val cdmaLevel = MutableState(kairos, 0)
+ override val primaryLevel = MutableState(kairos, 0)
+ override val satelliteLevel = MutableState(kairos, 0)
+ override val dataConnectionState = MutableState(kairos, DataConnectionState.Disconnected)
+ override val dataActivityDirection =
+ MutableState(kairos, DataActivityModel(hasActivityIn = false, hasActivityOut = false))
+ override val carrierNetworkChangeActive = MutableState(kairos, false)
+ override val resolvedNetworkType =
+ MutableState<ResolvedNetworkType>(kairos, ResolvedNetworkType.UnknownNetworkType)
+ override val numberOfLevels = MutableState(kairos, DEFAULT_NUM_LEVELS)
+ override val dataEnabled = MutableState(kairos, true)
+ override val cdmaRoaming = MutableState(kairos, false)
+ override val networkName =
+ MutableState<NetworkNameModel>(kairos, NetworkNameModel.Default(DEFAULT_NETWORK_NAME))
+ override val carrierName =
+ MutableState<NetworkNameModel>(kairos, NetworkNameModel.Default(DEFAULT_NETWORK_NAME))
+ override val isAllowedDuringAirplaneMode = MutableState(kairos, false)
+ override val hasPrioritizedNetworkCapabilities = MutableState(kairos, false)
+ override val isInEcmMode: State<Boolean> = MutableState(kairos, false)
+
+ /**
+ * Set [primaryLevel] and [cdmaLevel]. Convenient when you don't care about the connection type
+ */
+ fun setAllLevels(level: Int) {
+ cdmaLevel.setValue(level)
+ primaryLevel.setValue(level)
+ }
+
+ /** Set the correct [resolvedNetworkType] for the given group via its lookup key */
+ fun setNetworkTypeKey(key: String) {
+ resolvedNetworkType.setValue(ResolvedNetworkType.DefaultNetworkType(key))
+ }
+
+ /**
+ * Set both [isRoaming] and [cdmaRoaming] properties, in the event that you don't care about the
+ * connection type
+ */
+ fun setAllRoaming(roaming: Boolean) {
+ isRoaming.setValue(roaming)
+ cdmaRoaming.setValue(roaming)
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepositoryKairos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepositoryKairos.kt
new file mode 100644
index 0000000..624b2cc
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepositoryKairos.kt
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2024 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.pipeline.mobile.data.repository
+
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
+import android.telephony.TelephonyDisplayInfo
+import android.telephony.TelephonyManager
+import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.MobileMappings
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.KairosBuilder
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.KairosNetwork
+import com.android.systemui.kairos.MutableEvents
+import com.android.systemui.kairos.MutableState
+import com.android.systemui.kairos.State
+import com.android.systemui.kairos.asIncremental
+import com.android.systemui.kairos.buildSpec
+import com.android.systemui.kairos.combine
+import com.android.systemui.kairos.map
+import com.android.systemui.kairos.mapValues
+import com.android.systemui.kairosBuilder
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
+
+// TODO(b/261632894): remove this in favor of the real impl or DemoMobileConnectionsRepositoryKairos
+@ExperimentalKairosApi
+class FakeMobileConnectionsRepositoryKairos(
+ kairos: KairosNetwork,
+ val tableLogBuffer: TableLogBuffer,
+ mobileMappings: MobileMappingsProxy = FakeMobileMappingsProxy(),
+) : MobileConnectionsRepositoryKairos, KairosBuilder by kairosBuilder() {
+
+ val GSM_KEY = mobileMappings.toIconKey(GSM)
+ val LTE_KEY = mobileMappings.toIconKey(LTE)
+ val UMTS_KEY = mobileMappings.toIconKey(UMTS)
+ val LTE_ADVANCED_KEY = mobileMappings.toIconKeyOverride(LTE_ADVANCED_PRO)
+
+ /**
+ * To avoid a reliance on [MobileMappings], we'll build a simpler map from network type to
+ * mobile icon. See TelephonyManager.NETWORK_TYPES for a list of types and [TelephonyIcons] for
+ * the exhaustive set of icons
+ */
+ val TEST_MAPPING: Map<String, SignalIcon.MobileIconGroup> =
+ mapOf(
+ GSM_KEY to TelephonyIcons.THREE_G,
+ LTE_KEY to TelephonyIcons.LTE,
+ UMTS_KEY to TelephonyIcons.FOUR_G,
+ LTE_ADVANCED_KEY to TelephonyIcons.NR_5G,
+ )
+
+ override val subscriptions = MutableState(kairos, emptyList<SubscriptionModel>())
+
+ override val mobileConnectionsBySubId = buildIncremental {
+ subscriptions
+ .map { it.associate { sub -> sub.subscriptionId to Unit } }
+ .asIncremental()
+ .mapValues { (subId, _) ->
+ buildSpec {
+ FakeMobileConnectionRepositoryKairos(subId, kairosNetwork, tableLogBuffer)
+ }
+ }
+ .applyLatestSpecForKey()
+ }
+
+ private val _activeMobileDataSubscriptionId = MutableState<Int?>(kairos, null)
+ override val activeMobileDataSubscriptionId: State<Int?> = _activeMobileDataSubscriptionId
+
+ override val activeMobileDataRepository: State<MobileConnectionRepositoryKairos?> =
+ combine(mobileConnectionsBySubId, activeMobileDataSubscriptionId) { conns, activeSub ->
+ conns[activeSub]
+ }
+
+ override val activeSubChangedInGroupEvent = MutableEvents<Unit>(kairos)
+
+ override val defaultDataSubId = MutableState(kairos, INVALID_SUBSCRIPTION_ID)
+
+ override val mobileIsDefault = MutableState(kairos, false)
+
+ override val hasCarrierMergedConnection = MutableState(kairos, false)
+
+ override val defaultConnectionIsValidated = MutableState(kairos, false)
+
+ override val defaultDataSubRatConfig = MutableState(kairos, MobileMappings.Config())
+
+ override val defaultMobileIconMapping = MutableState(kairos, TEST_MAPPING)
+
+ override val defaultMobileIconGroup = MutableState(kairos, DEFAULT_ICON)
+
+ override val isDeviceEmergencyCallCapable = MutableState(kairos, false)
+
+ override val isAnySimSecure = MutableState(kairos, false)
+
+ override val isInEcmMode: State<Boolean> = MutableState(kairos, false)
+
+ fun setActiveMobileDataSubscriptionId(subId: Int) {
+ // Simulate the filtering that the repo does
+ if (subId == INVALID_SUBSCRIPTION_ID) {
+ _activeMobileDataSubscriptionId.setValue(null)
+ } else {
+ _activeMobileDataSubscriptionId.setValue(subId)
+ }
+ }
+
+ companion object {
+ val DEFAULT_ICON = TelephonyIcons.G
+
+ // Use [MobileMappings] to define some simple definitions
+ const val GSM = TelephonyManager.NETWORK_TYPE_GSM
+ const val LTE = TelephonyManager.NETWORK_TYPE_LTE
+ const val UMTS = TelephonyManager.NETWORK_TYPE_UMTS
+ const val LTE_ADVANCED_PRO = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO
+ }
+}
+
+@ExperimentalKairosApi
+val MobileConnectionsRepositoryKairos.fake
+ get() = this as FakeMobileConnectionsRepositoryKairos
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileDataRepositoryKairosKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileDataRepositoryKairosKosmos.kt
new file mode 100644
index 0000000..f57cf99
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileDataRepositoryKairosKosmos.kt
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2024 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.pipeline.mobile.data.repository
+
+import android.content.applicationContext
+import android.telephony.SubscriptionManager
+import android.telephony.telephonyManager
+import com.android.keyguard.keyguardUpdateMonitor
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.demoModeController
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.kairos.ActivatedKairosFixture
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.KairosNetwork
+import com.android.systemui.kairos.MutableEvents
+import com.android.systemui.kairos.buildSpec
+import com.android.systemui.kairos.kairos
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logcatTableLogBuffer
+import com.android.systemui.log.table.tableLogBufferFactory
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.airplaneModeRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoModeMobileConnectionDataSourceKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileConnectionsRepositoryKairosImpl
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeSubscriptionManagerProxy
+import com.android.systemui.statusbar.pipeline.mobile.util.SubscriptionManagerProxy
+import com.android.systemui.statusbar.pipeline.shared.data.repository.connectivityRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.wifiRepository
+import com.android.systemui.util.mockito.mockFixture
+import org.mockito.kotlin.mock
+
+@ExperimentalKairosApi
+var Kosmos.mobileConnectionsRepositoryKairos: MobileConnectionsRepositoryKairos by Fixture {
+ mobileRepositorySwitcherKairos
+}
+
+@ExperimentalKairosApi
+val Kosmos.fakeMobileConnectionsRepositoryKairos by ActivatedKairosFixture {
+ FakeMobileConnectionsRepositoryKairos(kairos, logcatTableLogBuffer(this), mobileMappingsProxy)
+}
+
+@ExperimentalKairosApi
+val Kosmos.demoMobileConnectionsRepositoryKairos by ActivatedKairosFixture {
+ DemoMobileConnectionsRepositoryKairos(
+ mobileDataSource = demoModeMobileConnectionDataSourceKairos,
+ wifiDataSource = wifiDataSource,
+ context = applicationContext,
+ logFactory = tableLogBufferFactory,
+ )
+}
+
+@ExperimentalKairosApi
+val Kosmos.demoModeMobileConnectionDataSourceKairos:
+ DemoModeMobileConnectionDataSourceKairos by Fixture {
+ FakeDemoModeMobileConnectionDataSourceKairos(kairos)
+}
+
+val Kosmos.wifiDataSource: DemoModeWifiDataSource by mockFixture()
+
+@ExperimentalKairosApi
+class FakeDemoModeMobileConnectionDataSourceKairos(kairos: KairosNetwork) :
+ DemoModeMobileConnectionDataSourceKairos {
+ override val mobileEvents = MutableEvents<FakeNetworkEventModel?>(kairos)
+}
+
+@ExperimentalKairosApi
+val DemoModeMobileConnectionDataSourceKairos.fake
+ get() = this as FakeDemoModeMobileConnectionDataSourceKairos
+
+@ExperimentalKairosApi
+val Kosmos.mobileRepositorySwitcherKairos:
+ MobileRepositorySwitcherKairos by ActivatedKairosFixture {
+ MobileRepositorySwitcherKairos(
+ realRepository = mobileConnectionsRepositoryKairosImpl,
+ demoRepositoryFactory = demoMobileConnectionsRepositoryKairosFactory,
+ demoModeController = demoModeController,
+ )
+}
+
+@ExperimentalKairosApi
+val Kosmos.demoMobileConnectionsRepositoryKairosFactory:
+ DemoMobileConnectionsRepositoryKairos.Factory by Fixture {
+ DemoMobileConnectionsRepositoryKairos.Factory {
+ DemoMobileConnectionsRepositoryKairos(
+ mobileDataSource = demoModeMobileConnectionDataSourceKairos,
+ wifiDataSource = wifiDataSource,
+ context = applicationContext,
+ logFactory = tableLogBufferFactory,
+ )
+ }
+}
+
+@ExperimentalKairosApi
+val Kosmos.mobileConnectionsRepositoryKairosImpl:
+ MobileConnectionsRepositoryKairosImpl by ActivatedKairosFixture {
+ MobileConnectionsRepositoryKairosImpl(
+ connectivityRepository = connectivityRepository,
+ subscriptionManager = subscriptionManager,
+ subscriptionManagerProxy = subscriptionManagerProxy,
+ telephonyManager = telephonyManager,
+ logger = mobileInputLogger,
+ tableLogger = summaryLogger,
+ mobileMappingsProxy = mobileMappingsProxy,
+ broadcastDispatcher = broadcastDispatcher,
+ context = applicationContext,
+ bgDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
+ airplaneModeRepository = airplaneModeRepository,
+ wifiRepository = wifiRepository,
+ keyguardUpdateMonitor = keyguardUpdateMonitor,
+ dumpManager = dumpManager,
+ mobileRepoFactory = { mobileConnectionRepositoryKairosFactory },
+ )
+}
+
+val Kosmos.subscriptionManager: SubscriptionManager by mockFixture()
+val Kosmos.mobileInputLogger: MobileInputLogger by mockFixture()
+val Kosmos.summaryLogger: TableLogBuffer by Fixture { logcatTableLogBuffer(this, "summaryLogger") }
+
+@ExperimentalKairosApi
+val Kosmos.mobileConnectionRepositoryKairosFactory by Fixture {
+ MobileConnectionsRepositoryKairosImpl.ConnectionRepoFactory { subId ->
+ buildSpec { FakeMobileConnectionRepositoryKairos(subId, kairos, mock()) }
+ }
+}
+
+val Kosmos.subscriptionManagerProxy: SubscriptionManagerProxy by Fixture {
+ FakeSubscriptionManagerProxy()
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeSubscriptionManagerProxy.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeSubscriptionManagerProxy.kt
similarity index 88%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeSubscriptionManagerProxy.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeSubscriptionManagerProxy.kt
index a802381..a391c44 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeSubscriptionManagerProxy.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeSubscriptionManagerProxy.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,7 +23,7 @@
class FakeSubscriptionManagerProxy(
/** Set the default data subId to be returned in [getDefaultDataSubscriptionId] */
var defaultDataSubId: Int = INVALID_SUBSCRIPTION_ID,
- var activeSubscriptionInfo: SubscriptionInfo? = null
+ var activeSubscriptionInfo: SubscriptionInfo? = null,
) : SubscriptionManagerProxy {
override fun getDefaultDataSubscriptionId(): Int = defaultDataSubId
@@ -41,3 +41,6 @@
SubscriptionInfo.Builder().setId(subId).setEmbedded(isEmbedded).build()
}
}
+
+val SubscriptionManagerProxy.fake
+ get() = this as FakeSubscriptionManagerProxy
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryKosmos.kt
index 00bfa99..bb254a1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryKosmos.kt
@@ -18,5 +18,5 @@
import com.android.systemui.kosmos.Kosmos
-val Kosmos.connectivityRepository: ConnectivityRepository by
+var Kosmos.connectivityRepository: ConnectivityRepository by
Kosmos.Fixture { FakeConnectivityRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryKosmos.kt
index e44061a..f560c50 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryKosmos.kt
@@ -19,4 +19,4 @@
import com.android.systemui.kosmos.Kosmos
val Kosmos.fakeWifiRepository: FakeWifiRepository by Kosmos.Fixture { FakeWifiRepository() }
-val Kosmos.wifiRepository: WifiRepository by Kosmos.Fixture { fakeWifiRepository }
+var Kosmos.wifiRepository: WifiRepository by Kosmos.Fixture { fakeWifiRepository }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/MockitoKosmos.kt
similarity index 75%
copy from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/MockitoKosmos.kt
index 231fb2d..1638cb7 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/MockitoKosmos.kt
@@ -14,11 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.dagger.qualifiers
+package com.android.systemui.util.mockito
-import javax.inject.Qualifier
+import com.android.systemui.kosmos.Kosmos.Fixture
+import org.mockito.kotlin.mock
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class NotifInflation
+inline fun <reified T> mockFixture(): Fixture<T> = Fixture { mock() }
diff --git a/packages/Vcn/service-b/Android.bp b/packages/Vcn/service-b/Android.bp
index 97574e6..aad534c3 100644
--- a/packages/Vcn/service-b/Android.bp
+++ b/packages/Vcn/service-b/Android.bp
@@ -29,7 +29,10 @@
"vcn-location-flag/platform/com/android/server/vcn/VcnLocation.java",
],
}),
- visibility: ["//frameworks/base/services/core"],
+ visibility: [
+ "//frameworks/base/services/core",
+ "//packages/modules/Connectivity/service-t",
+ ],
}
// TODO: b/374174952 This library is only used in "service-connectivity-b-platform"
diff --git a/packages/Vcn/service-b/src/com/android/server/ConnectivityServiceInitializerB.java b/packages/Vcn/service-b/src/com/android/server/ConnectivityServiceInitializerB.java
index b9dcc61..aac217b 100644
--- a/packages/Vcn/service-b/src/com/android/server/ConnectivityServiceInitializerB.java
+++ b/packages/Vcn/service-b/src/com/android/server/ConnectivityServiceInitializerB.java
@@ -37,9 +37,27 @@
private static final String TAG = ConnectivityServiceInitializerB.class.getSimpleName();
private final VcnManagementService mVcnManagementService;
+ // STOPSHIP: b/385203616 This static flag is for handling a temporary case when the mainline
+ // module prebuilt has updated to register the VCN but the platform change to remove
+ // registration is not merged. After mainline prebuilt is updated, we should merge the platform
+ // ASAP and remove this static check. This check is safe because both mainline and platform
+ // registration are triggered from the same method on the same thread.
+ private static boolean sIsRegistered = false;
+
public ConnectivityServiceInitializerB(Context context) {
super(context);
- mVcnManagementService = VcnManagementService.create(context);
+
+ if (!sIsRegistered) {
+ mVcnManagementService = VcnManagementService.create(context);
+ sIsRegistered = true;
+ } else {
+ mVcnManagementService = null;
+ Log.e(
+ TAG,
+ "Ignore this registration since VCN is already registered. It will happen when"
+ + " the mainline module prebuilt has updated to register the VCN but the"
+ + " platform change to remove registration is not merged.");
+ }
}
@Override
diff --git a/proto/src/metrics_constants/OWNERS b/proto/src/metrics_constants/OWNERS
index b032841..169f887 100644
--- a/proto/src/metrics_constants/OWNERS
+++ b/proto/src/metrics_constants/OWNERS
@@ -1,3 +1,2 @@
cwren@android.com
yaochen@google.com
-yro@google.com
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 42834ce..c49151d 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -287,7 +287,8 @@
public static final int INVALID_SERVICE_ID = -1;
- // Each service has an ID. Also provide one for magnification gesture handling
+ // Each service has an ID. Also provide one for magnification gesture handling.
+ // This ID is also used for mouse event handling.
public static final int MAGNIFICATION_GESTURE_HANDLER_ID = 0;
private static int sIdCounter = MAGNIFICATION_GESTURE_HANDLER_ID + 1;
diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
index bb3c710..0f6f86b 100644
--- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
+++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
@@ -103,12 +103,16 @@
@Override
public void toggleAutoclickPause(boolean paused) {
if (paused) {
- if (mClickScheduler != null) {
- mClickScheduler.cancel();
- }
- if (mAutoclickIndicatorScheduler != null) {
- mAutoclickIndicatorScheduler.cancel();
- }
+ cancelPendingClick();
+ }
+ }
+
+ @Override
+ public void onHoverChange(boolean hovered) {
+ // Cancel all pending clicks when the mouse moves outside the panel while
+ // autoclick is still paused.
+ if (!hovered && isPaused()) {
+ cancelPendingClick();
}
}
};
@@ -226,8 +230,17 @@
}
private boolean isPaused() {
- // TODO (b/397460424): Unpause when hovering over panel.
- return Flags.enableAutoclickIndicator() && mAutoclickTypePanel.isPaused();
+ return Flags.enableAutoclickIndicator() && mAutoclickTypePanel.isPaused()
+ && !mAutoclickTypePanel.isHovered();
+ }
+
+ private void cancelPendingClick() {
+ if (mClickScheduler != null) {
+ mClickScheduler.cancel();
+ }
+ if (mAutoclickIndicatorScheduler != null) {
+ mAutoclickIndicatorScheduler.cancel();
+ }
}
@VisibleForTesting
diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickLinearLayout.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickLinearLayout.java
new file mode 100644
index 0000000..fe8adf7
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickLinearLayout.java
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+
+package com.android.server.accessibility.autoclick;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.widget.LinearLayout;
+
+/**
+ * A custom LinearLayout that provides enhanced hover event handling.
+ * This class overrides hover methods to track hover events for the entire panel ViewGroup,
+ * including the descendant buttons. This allows for consistent hover behavior and feedback
+ * across the entire layout.
+ */
+public class AutoclickLinearLayout extends LinearLayout {
+ public interface OnHoverChangedListener {
+ /**
+ * Called when the hover state of the AutoclickLinearLayout changes.
+ *
+ * @param hovered {@code true} if the view is now hovered, {@code false} otherwise.
+ */
+ void onHoverChanged(boolean hovered);
+ }
+
+ private OnHoverChangedListener mListener;
+
+ public AutoclickLinearLayout(Context context) {
+ super(context);
+ }
+
+ public AutoclickLinearLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public AutoclickLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public AutoclickLinearLayout(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ public void setOnHoverChangedListener(OnHoverChangedListener listener) {
+ mListener = listener;
+ }
+
+ @Override
+ public boolean onInterceptHoverEvent(MotionEvent event) {
+ int action = event.getActionMasked();
+ setHovered(action == MotionEvent.ACTION_HOVER_ENTER
+ || action == MotionEvent.ACTION_HOVER_MOVE);
+
+ return false;
+ }
+
+ @Override
+ public void onHoverChanged(boolean hovered) {
+ super.onHoverChanged(hovered);
+
+ if (mListener != null) {
+ mListener.onHoverChanged(hovered);
+ }
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java
index ab4b3b1..90ddc43 100644
--- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java
+++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java
@@ -86,13 +86,6 @@
})
public @interface Corner {}
- private static final @Corner int[] CORNER_ROTATION_ORDER = {
- CORNER_BOTTOM_RIGHT,
- CORNER_BOTTOM_LEFT,
- CORNER_TOP_LEFT,
- CORNER_TOP_RIGHT
- };
-
// An interface exposed to {@link AutoclickController) to handle different actions on the panel,
// including changing autoclick type, pausing/resuming autoclick.
public interface ClickPanelControllerInterface {
@@ -110,11 +103,18 @@
* @param paused {@code true} to pause autoclick, {@code false} to resume.
*/
void toggleAutoclickPause(boolean paused);
+
+ /**
+ * Called when the hovered state of the panel changes.
+ *
+ * @param hovered {@code true} if the panel is now hovered, {@code false} otherwise.
+ */
+ void onHoverChange(boolean hovered);
}
private final Context mContext;
- private final View mContentView;
+ private final AutoclickLinearLayout mContentView;
private final WindowManager mWindowManager;
@@ -129,10 +129,9 @@
// Whether autoclick is paused.
private boolean mPaused = false;
- // Tracks the current corner position of the panel using an index into CORNER_ROTATION_ORDER
- // array. This allows the panel to cycle through screen corners in a defined sequence when
- // repositioned.
- private int mCurrentCornerIndex = 0;
+
+ // The current corner position of the panel, default to bottom right.
+ private @Corner int mCurrentCorner = CORNER_BOTTOM_RIGHT;
private final LinearLayout mLeftClickButton;
private final LinearLayout mRightClickButton;
@@ -164,8 +163,9 @@
R.drawable.accessibility_autoclick_resume);
mContentView =
- LayoutInflater.from(context)
+ (AutoclickLinearLayout) LayoutInflater.from(context)
.inflate(R.layout.accessibility_autoclick_type_panel, null);
+ mContentView.setOnHoverChangedListener(mClickPanelController::onHoverChange);
mLeftClickButton =
mContentView.findViewById(R.id.accessibility_autoclick_left_click_layout);
mRightClickButton =
@@ -249,13 +249,13 @@
params.gravity = Gravity.START | Gravity.TOP;
// Set the current corner to be bottom-left to ensure that the subsequent reposition
// action rotates the panel clockwise from bottom-left towards top-left.
- mCurrentCornerIndex = 1;
+ mCurrentCorner = CORNER_BOTTOM_LEFT;
} else {
// Snap to right edge. Set params.gravity to make sure x, y offsets from correct anchor.
params.gravity = Gravity.END | Gravity.TOP;
// Set the current corner to be top-right to ensure that the subsequent reposition
// action rotates the panel clockwise from top-right towards bottom-right.
- mCurrentCornerIndex = 3;
+ mCurrentCorner = CORNER_TOP_RIGHT;
}
// Apply final position: set params.x to be edge margin, params.y to maintain vertical
@@ -339,6 +339,10 @@
return mPaused;
}
+ public boolean isHovered() {
+ return mContentView.isHovered();
+ }
+
/** Toggles the panel expanded or collapsed state. */
private void togglePanelExpansion(@AutoclickType int clickType) {
final LinearLayout button = getButtonFromClickType(clickType);
@@ -403,10 +407,10 @@
/** Moves the panel to the next corner in clockwise direction. */
private void moveToNextCorner() {
- @Corner int nextCornerIndex = (mCurrentCornerIndex + 1) % CORNER_ROTATION_ORDER.length;
- mCurrentCornerIndex = nextCornerIndex;
+ @Corner int nextCorner = (mCurrentCorner + 1) % 4;
+ mCurrentCorner = nextCorner;
- setPanelPositionForCorner(mParams, mCurrentCornerIndex);
+ setPanelPositionForCorner(mParams, mCurrentCorner);
mWindowManager.updateViewLayout(mContentView, mParams);
}
@@ -445,7 +449,7 @@
String.valueOf(mParams.gravity),
String.valueOf(mParams.x),
String.valueOf(mParams.y),
- String.valueOf(mCurrentCornerIndex)
+ String.valueOf(mCurrentCorner)
});
Settings.Secure.putStringForUser(mContext.getContentResolver(),
ACCESSIBILITY_AUTOCLICK_PANEL_POSITION, positionString, mUserId);
@@ -461,7 +465,7 @@
ACCESSIBILITY_AUTOCLICK_PANEL_POSITION, mUserId);
if (savedPosition == null) {
setPanelPositionForCorner(mParams, CORNER_BOTTOM_RIGHT);
- mCurrentCornerIndex = 0;
+ mCurrentCorner = CORNER_BOTTOM_RIGHT;
return;
}
@@ -469,7 +473,7 @@
String[] parts = TextUtils.split(savedPosition, POSITION_DELIMITER);
if (!isValidPositionParts(parts)) {
setPanelPositionForCorner(mParams, CORNER_BOTTOM_RIGHT);
- mCurrentCornerIndex = 0;
+ mCurrentCorner = CORNER_BOTTOM_RIGHT;
return;
}
@@ -477,7 +481,7 @@
mParams.gravity = Integer.parseInt(parts[0]);
mParams.x = Integer.parseInt(parts[1]);
mParams.y = Integer.parseInt(parts[2]);
- mCurrentCornerIndex = Integer.parseInt(parts[3]);
+ mCurrentCorner = Integer.parseInt(parts[3]);
}
private boolean isValidPositionParts(String[] parts) {
@@ -520,14 +524,14 @@
@VisibleForTesting
@NonNull
- View getContentViewForTesting() {
+ AutoclickLinearLayout getContentViewForTesting() {
return mContentView;
}
@VisibleForTesting
@Corner
- int getCurrentCornerIndexForTesting() {
- return mCurrentCornerIndex;
+ int getCurrentCornerForTesting() {
+ return mCurrentCorner;
}
@VisibleForTesting
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index fd230f6..fb32943 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -887,6 +887,9 @@
mSendHoverEnterAndMoveDelayed.cancel();
mSendHoverExitDelayed.cancel();
}
+ if (pointerIndex < 0) {
+ return;
+ }
// If the user is touch exploring the second pointer may be
// performing a double tap to activate an item without need
// for the user to lift their exploring finger.
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index 11b8ccb..004b3ff 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -121,6 +121,8 @@
@NonNull private final Supplier<MagnificationThumbnail> mThumbnailSupplier;
@NonNull private final Supplier<Boolean> mMagnificationConnectionStateSupplier;
+ private boolean mIsPointerMotionFilterInstalled = false;
+
/**
* This class implements {@link WindowManagerInternal.MagnificationCallbacks} and holds
* magnification information per display.
@@ -830,9 +832,17 @@
return;
}
- final float nonNormOffsetX = mCurrentMagnificationSpec.offsetX - offsetX;
- final float nonNormOffsetY = mCurrentMagnificationSpec.offsetY - offsetY;
- if (updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY)) {
+ setOffset(mCurrentMagnificationSpec.offsetX - offsetX,
+ mCurrentMagnificationSpec.offsetY - offsetY, id);
+ }
+
+ @GuardedBy("mLock")
+ void setOffset(float offsetX, float offsetY, int id) {
+ if (!mRegistered) {
+ return;
+ }
+
+ if (updateCurrentSpecWithOffsetsLocked(offsetX, offsetY)) {
onMagnificationChangedLocked(/* isScaleTransient= */ false);
}
if (id != INVALID_SERVICE_ID) {
@@ -1065,6 +1075,7 @@
if (display.register()) {
mDisplays.put(displayId, display);
mScreenStateObserver.registerIfNecessary();
+ configurePointerMotionFilter(true);
}
}
}
@@ -1613,6 +1624,28 @@
}
/**
+ * Sets the offset of the magnified region.
+ *
+ * @param displayId The logical display id.
+ * @param offsetX the offset of the magnified region in the X coordinate, in current
+ * screen pixels.
+ * @param offsetY the offset of the magnified region in the Y coordinate, in current
+ * screen pixels.
+ * @param id the ID of the service requesting the change
+ */
+ @SuppressWarnings("GuardedBy")
+ // errorprone cannot recognize an inner class guarded by an outer class member.
+ public void setOffset(int displayId, float offsetX, float offsetY, int id) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return;
+ }
+ display.setOffset(offsetX, offsetY, id);
+ }
+ }
+
+ /**
* Offsets the magnified region. Note that the offsetX and offsetY values actually move in the
* opposite direction as the offsets passed in here.
*
@@ -1885,6 +1918,7 @@
}
if (!hasRegister) {
mScreenStateObserver.unregister();
+ configurePointerMotionFilter(false);
}
}
@@ -1900,6 +1934,22 @@
}
}
+ private void configurePointerMotionFilter(boolean enabled) {
+ if (!Flags.enableMagnificationFollowsMouseWithPointerMotionFilter()) {
+ return;
+ }
+ if (enabled == mIsPointerMotionFilterInstalled) {
+ return;
+ }
+ if (!enabled) {
+ mControllerCtx.getInputManager().registerAccessibilityPointerMotionFilter(null);
+ } else {
+ mControllerCtx.getInputManager().registerAccessibilityPointerMotionFilter(
+ new FullScreenMagnificationPointerMotionEventFilter(this));
+ }
+ mIsPointerMotionFilterInstalled = enabled;
+ }
+
private boolean traceEnabled() {
return mControllerCtx.getTraceManager().isA11yTracingEnabledForTypes(
FLAGS_WINDOW_MANAGER_INTERNAL);
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index e0dd8b6..59b4a16 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -182,6 +182,7 @@
private final int mMinimumVelocity;
private final int mMaximumVelocity;
+ @Nullable
private final MouseEventHandler mMouseEventHandler;
public FullScreenMagnificationGestureHandler(
@@ -313,7 +314,9 @@
mOverscrollEdgeSlop = context.getResources().getDimensionPixelSize(
R.dimen.accessibility_fullscreen_magnification_gesture_edge_slop);
mIsWatch = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
- mMouseEventHandler = new MouseEventHandler(mFullScreenMagnificationController);
+ mMouseEventHandler =
+ Flags.enableMagnificationFollowsMouseWithPointerMotionFilter()
+ ? null : new MouseEventHandler(mFullScreenMagnificationController);
if (mDetectShortcutTrigger) {
mScreenStateReceiver = new ScreenStateReceiver(context, this);
@@ -337,9 +340,11 @@
@Override
void handleMouseOrStylusEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
- if (!mFullScreenMagnificationController.isActivated(mDisplayId)) {
+ if (mMouseEventHandler == null
+ || !mFullScreenMagnificationController.isActivated(mDisplayId)) {
return;
}
+
// TODO(b/354696546): Allow mouse/stylus to activate whichever display they are
// over, rather than only interacting with the current display.
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationPointerMotionEventFilter.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationPointerMotionEventFilter.java
new file mode 100644
index 0000000..f1ba83e
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationPointerMotionEventFilter.java
@@ -0,0 +1,66 @@
+/*
+ * 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.server.accessibility.magnification;
+
+import static com.android.server.accessibility.AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID;
+
+import android.annotation.NonNull;
+
+import com.android.server.input.InputManagerInternal;
+
+/**
+ * Handles pointer motion event for full screen magnification.
+ * Responsible for controlling magnification's cursor following feature.
+ */
+public class FullScreenMagnificationPointerMotionEventFilter implements
+ InputManagerInternal.AccessibilityPointerMotionFilter {
+
+ private final FullScreenMagnificationController mController;
+
+ public FullScreenMagnificationPointerMotionEventFilter(
+ FullScreenMagnificationController controller) {
+ mController = controller;
+ }
+
+ /**
+ * This call happens on the input hot path and it is extremely performance sensitive. It
+ * also must not call back into native code.
+ */
+ @Override
+ @NonNull
+ public float[] filterPointerMotionEvent(float dx, float dy, float currentX, float currentY,
+ int displayId) {
+ if (!mController.isActivated(displayId)) {
+ // unrelated display.
+ return new float[]{dx, dy};
+ }
+
+ // TODO(361817142): implement centered and edge following types.
+
+ // Continuous cursor following.
+ float scale = mController.getScale(displayId);
+ final float newCursorX = currentX + dx;
+ final float newCursorY = currentY + dy;
+ mController.setOffset(displayId,
+ newCursorX - newCursorX * scale, newCursorY - newCursorY * scale,
+ MAGNIFICATION_GESTURE_HANDLER_ID);
+
+ // In the continuous mode, the cursor speed in physical display is kept.
+ // Thus, we don't consume any motion delta.
+ return new float[]{dx, dy};
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
index ce7dcd0..0310756 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
@@ -299,14 +299,24 @@
public void writeLastRemovedAssociation(AssociationInfo association, String reason) {
Slog.i(TAG, "Writing last removed association=" + association.getId() + " to disk...");
+ // Remove indirect identifier i.e. Mac Address
+ AssociationInfo.Builder builder = new AssociationInfo.Builder(association)
+ .setDeviceMacAddress(null);
+ // Set a placeholder display name if it's null because Mac Address and display name can't be
+ // both null.
+ if (association.getDisplayName() == null) {
+ builder.setDisplayName("");
+ }
+ AssociationInfo redactedAssociation = builder.build();
+
final AtomicFile file = createStorageFileForUser(
- association.getUserId(), FILE_NAME_LAST_REMOVED_ASSOCIATION);
+ redactedAssociation.getUserId(), FILE_NAME_LAST_REMOVED_ASSOCIATION);
writeToFileSafely(file, out -> {
out.write(String.valueOf(System.currentTimeMillis()).getBytes());
out.write(' ');
out.write(reason.getBytes());
out.write(' ');
- out.write(association.toString().getBytes());
+ out.write(redactedAssociation.toString().getBytes());
});
}
diff --git a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
index 6c7c9b3..4c62c0d 100644
--- a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
+++ b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
@@ -73,6 +73,8 @@
private int mVerificationResult = FLAG_FAILURE_UNKNOWN;
private boolean mPskVerified;
+ private final Object mHandshakeLock = new Object();
+
/**
* Create a new secure channel object. This secure channel allows secure messages to be
@@ -342,20 +344,22 @@
}
private void initiateHandshake() throws IOException, BadHandleException , HandshakeException {
- if (mConnectionContext != null) {
- Slog.d(TAG, "Ukey2 handshake is already completed.");
- return;
- }
+ synchronized (mHandshakeLock) {
+ if (mConnectionContext != null) {
+ Slog.d(TAG, "Ukey2 handshake is already completed.");
+ return;
+ }
- mRole = Role.INITIATOR;
- mHandshakeContext = D2DHandshakeContext.forInitiator();
- mClientInit = mHandshakeContext.getNextHandshakeMessage();
+ mRole = Role.INITIATOR;
+ mHandshakeContext = D2DHandshakeContext.forInitiator();
+ mClientInit = mHandshakeContext.getNextHandshakeMessage();
- // Send Client Init
- if (DEBUG) {
- Slog.d(TAG, "Sending Ukey2 Client Init message");
+ // Send Client Init
+ if (DEBUG) {
+ Slog.d(TAG, "Sending Ukey2 Client Init message");
+ }
+ sendMessage(MessageType.HANDSHAKE_INIT, constructHandshakeInitMessage(mClientInit));
}
- sendMessage(MessageType.HANDSHAKE_INIT, constructHandshakeInitMessage(mClientInit));
}
// In an occasion where both participants try to initiate a handshake, resolve the conflict
@@ -414,8 +418,17 @@
// Mark "in-progress" upon receiving the first message
mInProgress = true;
+ // Complete a series of handshake exchange and processing
+ synchronized (mHandshakeLock) {
+ completeHandshake(handshakeInitMessage);
+ }
+ }
+
+ private void completeHandshake(byte[] initMessage) throws IOException, HandshakeException,
+ BadHandleException, CryptoException, AlertException {
+
// Handle a potential collision where both devices tried to initiate a connection
- byte[] handshakeMessage = handleHandshakeCollision(handshakeInitMessage);
+ byte[] handshakeMessage = handleHandshakeCollision(initMessage);
// Proceed with the rest of Ukey2 handshake
if (mHandshakeContext == null) { // Server-side logic
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 2aaf6a9..14d9d3f 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -121,6 +121,13 @@
out: ["com/android/server/location/contexthub/ContextHubStatsLog.java"],
}
+genrule {
+ name: "statslog-mediarouter-java-gen",
+ tools: ["stats-log-api-gen"],
+ cmd: "$(location stats-log-api-gen) --java $(out) --module mediarouter --javaPackage com.android.server.media --javaClass MediaRouterStatsLog",
+ out: ["com/android/server/media/MediaRouterStatsLog.java"],
+}
+
java_library_static {
name: "services.core.unboosted",
defaults: [
@@ -136,6 +143,7 @@
":android.hardware.tv.mediaquality-V1-java-source",
":statslog-art-java-gen",
":statslog-contexthub-java-gen",
+ ":statslog-mediarouter-java-gen",
":services.core-aidl-sources",
":services.core-sources",
":services.core.protologsrc",
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 600b124..79fdcca 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -190,6 +190,7 @@
}
final Context mContext;
+ final PackageMonitor mPackageMonitor;
private static final int[] INTERESTING_APP_OPS = new int[] {
AppOpsManager.OP_GET_ACCOUNTS,
@@ -373,7 +374,7 @@
}, UserHandle.ALL, userFilter, null, null);
// Need to cancel account request notifications if the update/install can access the account
- new PackageMonitor() {
+ mPackageMonitor = new PackageMonitor() {
@Override
public void onPackageAdded(String packageName, int uid) {
// Called on a handler, and running as the system
@@ -397,7 +398,8 @@
return;
}
}
- }.register(mContext, mHandler.getLooper(), UserHandle.ALL, true);
+ };
+ mPackageMonitor.register(mContext, mHandler.getLooper(), UserHandle.ALL, true);
// Cancel account request notification if an app op was preventing the account access
for (int i = 0; i < INTERESTING_APP_OPS.length; ++i) {
diff --git a/services/core/java/com/android/server/am/BroadcastController.java b/services/core/java/com/android/server/am/BroadcastController.java
index bec5db7..afd639d 100644
--- a/services/core/java/com/android/server/am/BroadcastController.java
+++ b/services/core/java/com/android/server/am/BroadcastController.java
@@ -1961,7 +1961,9 @@
private void sendPackageBroadcastLocked(int cmd, String[] packages, int userId) {
mService.mProcessList.sendPackageBroadcastLocked(cmd, packages, userId);
- }private List<ResolveInfo> collectReceiverComponents(
+ }
+
+ private List<ResolveInfo> collectReceiverComponents(
Intent intent, String resolvedType, int callingUid, int callingPid,
int[] users, int[] broadcastAllowList) {
// TODO: come back and remove this assumption to triage all broadcasts
diff --git a/services/core/java/com/android/server/am/BroadcastProcessedEventRecord.java b/services/core/java/com/android/server/am/BroadcastProcessedEventRecord.java
new file mode 100644
index 0000000..1bcedf6
--- /dev/null
+++ b/services/core/java/com/android/server/am/BroadcastProcessedEventRecord.java
@@ -0,0 +1,142 @@
+/*
+ * 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.server.am;
+
+import static com.android.internal.util.FrameworkStatsLog.BROADCAST_PROCESSED;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+final class BroadcastProcessedEventRecord {
+
+ /**
+ * Minimum threshold for logging the broadcast processed event.
+ */
+ private static final int MIN_THRESHOLD_FOR_LOGGING_TIME_MILLIS = 10;
+
+ @Nullable
+ private String mIntentAction;
+
+ private int mSenderUid;
+
+ private int mReceiverUid;
+
+ private int mNumberOfReceivers;
+
+ @NonNull
+ private String mReceiverProcessName;
+
+ private long mTotalBroadcastFinishTimeMillis;
+
+ private long mMaxReceiverFinishTimeMillis = Long.MIN_VALUE;
+
+ @NonNull
+ private int[] mBroadcastTypes;
+
+ @NonNull
+ public BroadcastProcessedEventRecord setBroadcastTypes(@NonNull int[] broadcastTypes) {
+ this.mBroadcastTypes = broadcastTypes;
+ return this;
+ }
+
+ @NonNull
+ public BroadcastProcessedEventRecord setReceiverProcessName(
+ @NonNull String receiverProcessName) {
+ mReceiverProcessName = receiverProcessName;
+ return this;
+ }
+
+ @NonNull
+ public BroadcastProcessedEventRecord setIntentAction(@Nullable String intentAction) {
+ mIntentAction = intentAction;
+ return this;
+ }
+
+ @NonNull
+ public BroadcastProcessedEventRecord setSenderUid(int uid) {
+ mSenderUid = uid;
+ return this;
+ }
+
+ @NonNull
+ public BroadcastProcessedEventRecord setReceiverUid(int uid) {
+ mReceiverUid = uid;
+ return this;
+ }
+
+ public void addReceiverFinishTime(long timeMillis) {
+ mTotalBroadcastFinishTimeMillis += timeMillis;
+ mMaxReceiverFinishTimeMillis = Math.max(mMaxReceiverFinishTimeMillis, timeMillis);
+ mNumberOfReceivers++;
+ }
+
+ @Nullable
+ String getIntentActionForTest() {
+ return mIntentAction;
+ }
+
+ int getSenderUidForTest() {
+ return mSenderUid;
+ }
+
+ int getReceiverUidForTest() {
+ return mReceiverUid;
+ }
+
+ int getNumberOfReceiversForTest() {
+ return mNumberOfReceivers;
+ }
+
+ @NonNull
+ String getReceiverProcessNameForTest() {
+ return mReceiverProcessName;
+ }
+
+ long getTotalBroadcastFinishTimeMillisForTest() {
+ return mTotalBroadcastFinishTimeMillis;
+ }
+
+ long getMaxReceiverFinishTimeMillisForTest() {
+ return mMaxReceiverFinishTimeMillis;
+ }
+
+ @NonNull
+ int[] getBroadcastTypesForTest() {
+ return mBroadcastTypes;
+ }
+
+ public void logToStatsD() {
+ // We do not care about the processes where total time to process the
+ // broadcast is less than 10ms/ are quick to process the broadcast.
+ if (mTotalBroadcastFinishTimeMillis <= MIN_THRESHOLD_FOR_LOGGING_TIME_MILLIS) {
+ return;
+ }
+
+ FrameworkStatsLog.write(
+ BROADCAST_PROCESSED,
+ mIntentAction,
+ mSenderUid,
+ mReceiverUid,
+ mNumberOfReceivers,
+ mReceiverProcessName,
+ mTotalBroadcastFinishTimeMillis,
+ mMaxReceiverFinishTimeMillis,
+ mBroadcastTypes);
+ }
+}
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index 78beb18..c76a0d0 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -2189,6 +2189,11 @@
logBroadcastDeliveryEventReported(queue, app, r, index, receiver);
}
+ if (!r.isAssumedDelivered(index) && r.wasDelivered(index)) {
+ r.updateBroadcastProcessedEventRecord(receiver,
+ r.terminalTime[index] - r.scheduledTime[index]);
+ }
+
final boolean recordFinished = (r.terminalCount == r.receivers.size());
if (recordFinished) {
notifyFinishBroadcast(r);
@@ -2254,6 +2259,7 @@
mHistory.onBroadcastFinishedLocked(r);
logBootCompletedBroadcastCompletionLatencyIfPossible(r);
+ r.logBroadcastProcessedEventRecord();
if (r.intent.getComponent() == null && r.intent.getPackage() == null
&& (r.intent.getFlags() & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index c1b0a76..45c7d59 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -167,6 +167,12 @@
@Nullable
private ArrayMap<BroadcastRecord, Boolean> mMatchingRecordsCache;
+ // Stores the {@link BroadcastProcessedEventRecord} for each process associated with this
+ // record.
+ @NonNull
+ private ArrayMap<String, BroadcastProcessedEventRecord> mBroadcastProcessedRecords =
+ new ArrayMap<>();
+
private @Nullable String mCachedToString;
private @Nullable String mCachedToShortString;
@@ -654,6 +660,17 @@
}
}
+ boolean wasDelivered(int index) {
+ final int deliveryState = getDeliveryState(index);
+ switch (deliveryState) {
+ case DELIVERY_DELIVERED:
+ case DELIVERY_TIMEOUT:
+ return true;
+ default:
+ return false;
+ }
+ }
+
void copyEnqueueTimeFrom(@NonNull BroadcastRecord replacedBroadcast) {
originalEnqueueClockTime = enqueueClockTime;
enqueueTime = replacedBroadcast.enqueueTime;
@@ -1327,4 +1344,48 @@
proto.write(BroadcastRecordProto.INTENT_ACTION, intent.getAction());
proto.end(token);
}
+
+ /**
+ * Uses the {@link BroadcastProcessedEventRecord} pojo to store the logging information related
+ * to {@param receiver} object.
+ */
+ public void updateBroadcastProcessedEventRecord(@NonNull Object receiver, long timeMillis) {
+ if (!Flags.logBroadcastProcessedEvent()) {
+ return;
+ }
+
+ final String receiverProcessName = getReceiverProcessName(receiver);
+ BroadcastProcessedEventRecord broadcastProcessedEventRecord =
+ mBroadcastProcessedRecords.get(receiverProcessName);
+ if (broadcastProcessedEventRecord == null) {
+ broadcastProcessedEventRecord = new BroadcastProcessedEventRecord()
+ .setBroadcastTypes(calculateTypesForLogging())
+ .setIntentAction(intent.getAction())
+ .setReceiverProcessName(receiverProcessName)
+ .setReceiverUid(getReceiverUid(receiver))
+ .setSenderUid(callingUid);
+
+ mBroadcastProcessedRecords.put(receiverProcessName, broadcastProcessedEventRecord);
+ }
+
+ broadcastProcessedEventRecord.addReceiverFinishTime(timeMillis);
+ }
+
+ public void logBroadcastProcessedEventRecord() {
+ if (!Flags.logBroadcastProcessedEvent()) {
+ return;
+ }
+
+ int size = mBroadcastProcessedRecords.size();
+ for (int i = 0; i < size; i++) {
+ mBroadcastProcessedRecords.valueAt(i).logToStatsD();
+ }
+ mBroadcastProcessedRecords.clear();
+ }
+
+ @VisibleForTesting
+ @NonNull
+ ArrayMap<String, BroadcastProcessedEventRecord> getBroadcastProcessedRecordsForTest() {
+ return mBroadcastProcessedRecords;
+ }
}
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 13d367a..336a35e 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -3433,8 +3433,12 @@
// Process has user visible activities.
return PROCESS_CAPABILITY_CPU_TIME;
}
- if (app.mServices.hasUndemotedShortForegroundService(nowUptime)) {
- // It running a short fgs, just give it cpu time.
+ if (Flags.prototypeAggressiveFreezing()) {
+ if (app.mServices.hasUndemotedShortForegroundService(nowUptime)) {
+ // Grant cpu time for short FGS even when aggressively freezing.
+ return PROCESS_CAPABILITY_CPU_TIME;
+ }
+ } else if (app.mServices.hasForegroundServices()) {
return PROCESS_CAPABILITY_CPU_TIME;
}
if (app.mReceivers.numberOfCurReceivers() > 0) {
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 2c0366e..ac30be9 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -31,7 +31,6 @@
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.window.flags.Flags.balClearAllowlistDuration;
import android.annotation.IntDef;
import android.annotation.Nullable;
@@ -330,7 +329,7 @@
mAllowBgActivityStartsForActivitySender.remove(token);
mAllowBgActivityStartsForBroadcastSender.remove(token);
mAllowBgActivityStartsForServiceSender.remove(token);
- if (mAllowlistDuration != null && balClearAllowlistDuration()) {
+ if (mAllowlistDuration != null) {
TempAllowListDuration duration = mAllowlistDuration.get(token);
if (duration != null
&& duration.type == TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED) {
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 0954c49..3a041fd 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -228,6 +228,7 @@
"media_reliability",
"media_solutions",
"media_tv",
+ "microxr",
"nearby",
"nfc",
"pdf_viewer",
diff --git a/services/core/java/com/android/server/am/broadcasts_flags.aconfig b/services/core/java/com/android/server/am/broadcasts_flags.aconfig
index 68e21a3..3867f77 100644
--- a/services/core/java/com/android/server/am/broadcasts_flags.aconfig
+++ b/services/core/java/com/android/server/am/broadcasts_flags.aconfig
@@ -26,4 +26,15 @@
metadata {
purpose: PURPOSE_BUGFIX
}
-}
\ No newline at end of file
+}
+
+flag {
+ name: "log_broadcast_processed_event"
+ namespace: "backstage_power"
+ description: "Log the broadcast processed event to Statsd"
+ bug: "387576580"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
index ca9a25b..6f8c241 100644
--- a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
@@ -341,17 +341,15 @@
}
private void commitUidPendingState(int uid) {
- int pendingUidState = mPendingUidStates.get(uid,
- mUidStates.get(uid, MIN_PRIORITY_UID_STATE));
- int pendingCapability = mPendingCapability.get(uid,
- mCapability.get(uid, PROCESS_CAPABILITY_NONE));
- boolean pendingAppWidgetVisible = mPendingAppWidgetVisible.get(uid,
- mAppWidgetVisible.get(uid, false));
int uidState = mUidStates.get(uid, MIN_PRIORITY_UID_STATE);
int capability = mCapability.get(uid, PROCESS_CAPABILITY_NONE);
boolean appWidgetVisible = mAppWidgetVisible.get(uid, false);
+ int pendingUidState = mPendingUidStates.get(uid, uidState);
+ int pendingCapability = mPendingCapability.get(uid, capability);
+ boolean pendingAppWidgetVisible = mPendingAppWidgetVisible.get(uid, appWidgetVisible);
+
boolean foregroundChange = uidState <= UID_STATE_MAX_LAST_NON_RESTRICTED
!= pendingUidState <= UID_STATE_MAX_LAST_NON_RESTRICTED
|| capability != pendingCapability
diff --git a/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java b/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java
index 84402c8..12c35ae 100644
--- a/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java
+++ b/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java
@@ -177,6 +177,8 @@
*/
abstract void writeAndClearOldAccessHistory();
+ void shutdown() {}
+
/** Remove all discrete op events. */
abstract void clearHistory();
diff --git a/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java b/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java
index 604cb30..dc11be9 100644
--- a/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java
+++ b/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java
@@ -57,13 +57,18 @@
public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry {
private static final String TAG = "DiscreteOpsSqlRegistry";
+ private static final long DB_WRITE_INTERVAL = Duration.ofMinutes(10).toMillis();
+ private static final long EXPIRED_ENTRY_DELETION_INTERVAL = Duration.ofHours(6).toMillis();
+
+ // Event type handled by SqliteWriteHandler
+ private static final int WRITE_DATABASE_RECURRING = 1;
+ private static final int DELETE_EXPIRED_ENTRIES = 2;
+ private static final int WRITE_DATABASE_CACHE_FULL = 3;
+
private final Context mContext;
private final DiscreteOpsDbHelper mDiscreteOpsDbHelper;
private final SqliteWriteHandler mSqliteWriteHandler;
private final DiscreteOpCache mDiscreteOpCache = new DiscreteOpCache(512);
- private static final long THREE_HOURS = Duration.ofHours(3).toMillis();
- private static final int WRITE_CACHE_EVICTED_OP_EVENTS = 1;
- private static final int DELETE_OLD_OP_EVENTS = 2;
// Attribution chain id is used to identify an attribution source chain, This is
// set for startOp only. PermissionManagerService resets this ID on device restart, so
// we use previously persisted chain id as offset, and add it to chain id received from
@@ -83,6 +88,9 @@
mSqliteWriteHandler = new SqliteWriteHandler(thread.getLooper());
mDiscreteOpsDbHelper = new DiscreteOpsDbHelper(context, databaseFile);
mChainIdOffset = mDiscreteOpsDbHelper.getLargestAttributionChainId();
+ mSqliteWriteHandler.sendEmptyMessageDelayed(WRITE_DATABASE_RECURRING, DB_WRITE_INTERVAL);
+ mSqliteWriteHandler.sendEmptyMessageDelayed(DELETE_EXPIRED_ENTRIES,
+ EXPIRED_ENTRY_DELETION_INTERVAL);
}
@Override
@@ -117,15 +125,14 @@
}
@Override
- void writeAndClearOldAccessHistory() {
- // Let the sql impl also follow the same disk write frequencies as xml,
- // controlled by AppOpsService.
+ void shutdown() {
+ mSqliteWriteHandler.removeAllPendingMessages();
mDiscreteOpsDbHelper.insertDiscreteOps(mDiscreteOpCache.getAllEventsAndClear());
- if (!mSqliteWriteHandler.hasMessages(DELETE_OLD_OP_EVENTS)) {
- if (mSqliteWriteHandler.sendEmptyMessageDelayed(DELETE_OLD_OP_EVENTS, THREE_HOURS)) {
- Slog.w(TAG, "DELETE_OLD_OP_EVENTS is not queued");
- }
- }
+ }
+
+ @Override
+ void writeAndClearOldAccessHistory() {
+ // no-op
}
@Override
@@ -175,7 +182,7 @@
@Nullable String attributionTagFilter, int opFlagsFilter,
Set<String> attributionExemptPkgs) {
// flush the cache into database before read.
- writeAndClearOldAccessHistory();
+ mDiscreteOpsDbHelper.insertDiscreteOps(mDiscreteOpCache.getAllEventsAndClear());
boolean assembleChains = attributionExemptPkgs != null;
IntArray opCodes = getAppOpCodes(filter, opNamesFilter);
beginTimeMillis = Math.max(beginTimeMillis, Instant.now().minus(sDiscreteHistoryCutoff,
@@ -363,20 +370,59 @@
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case WRITE_CACHE_EVICTED_OP_EVENTS:
- List<DiscreteOp> opEvents = (List<DiscreteOp>) msg.obj;
- mDiscreteOpsDbHelper.insertDiscreteOps(opEvents);
- break;
- case DELETE_OLD_OP_EVENTS:
+ case WRITE_DATABASE_RECURRING -> {
+ try {
+ List<DiscreteOp> evictedEvents;
+ synchronized (mDiscreteOpCache) {
+ evictedEvents = mDiscreteOpCache.evict();
+ }
+ mDiscreteOpsDbHelper.insertDiscreteOps(evictedEvents);
+ } finally {
+ mSqliteWriteHandler.sendEmptyMessageDelayed(WRITE_DATABASE_RECURRING,
+ DB_WRITE_INTERVAL);
+ // Schedule a cleanup to truncate older (before cutoff time) entries.
+ if (!mSqliteWriteHandler.hasMessages(DELETE_EXPIRED_ENTRIES)) {
+ mSqliteWriteHandler.sendEmptyMessageDelayed(DELETE_EXPIRED_ENTRIES,
+ EXPIRED_ENTRY_DELETION_INTERVAL);
+ }
+ }
+ }
+ case DELETE_EXPIRED_ENTRIES -> {
long cutOffTimeStamp = System.currentTimeMillis() - sDiscreteHistoryCutoff;
mDiscreteOpsDbHelper.execSQL(
DiscreteOpsTable.DELETE_TABLE_DATA_BEFORE_ACCESS_TIME,
new Object[]{cutOffTimeStamp});
- break;
- default:
- throw new IllegalStateException("Unexpected value: " + msg.what);
+ }
+ case WRITE_DATABASE_CACHE_FULL -> {
+ try {
+ List<DiscreteOp> evictedEvents;
+ synchronized (mDiscreteOpCache) {
+ evictedEvents = mDiscreteOpCache.evict();
+ // if nothing to evict, just write the whole cache to database.
+ if (evictedEvents.isEmpty()
+ && mDiscreteOpCache.size() >= mDiscreteOpCache.capacity()) {
+ evictedEvents.addAll(mDiscreteOpCache.mCache);
+ mDiscreteOpCache.clear();
+ }
+ }
+ mDiscreteOpsDbHelper.insertDiscreteOps(evictedEvents);
+ } finally {
+ // Just in case initial message is not scheduled.
+ if (!mSqliteWriteHandler.hasMessages(WRITE_DATABASE_RECURRING)) {
+ mSqliteWriteHandler.sendEmptyMessageDelayed(WRITE_DATABASE_RECURRING,
+ DB_WRITE_INTERVAL);
+ }
+ }
+ }
+ default -> throw new IllegalStateException("Unexpected value: " + msg.what);
}
}
+
+ void removeAllPendingMessages() {
+ removeMessages(WRITE_DATABASE_RECURRING);
+ removeMessages(DELETE_EXPIRED_ENTRIES);
+ removeMessages(WRITE_DATABASE_CACHE_FULL);
+ }
}
/**
@@ -390,6 +436,7 @@
* 4) During shutdown.
*/
class DiscreteOpCache {
+ private static final String TAG = "DiscreteOpCache";
private final int mCapacity;
private final ArraySet<DiscreteOp> mCache;
@@ -404,23 +451,9 @@
return;
}
mCache.add(opEvent);
+
if (mCache.size() >= mCapacity) {
- if (DEBUG_LOG) {
- Slog.i(TAG, "Current discrete ops cache size: " + mCache.size());
- }
- List<DiscreteOp> evictedEvents = evict();
- if (DEBUG_LOG) {
- Slog.i(TAG, "Evicted discrete ops size: " + evictedEvents.size());
- }
- // if nothing to evict, just write the whole cache to disk
- if (evictedEvents.isEmpty()) {
- Slog.w(TAG, "No discrete ops event is evicted, write cache to db.");
- evictedEvents.addAll(mCache);
- mCache.clear();
- }
- Message msg = mSqliteWriteHandler.obtainMessage(
- WRITE_CACHE_EVICTED_OP_EVENTS, evictedEvents);
- mSqliteWriteHandler.sendMessage(msg);
+ mSqliteWriteHandler.sendEmptyMessage(WRITE_DATABASE_CACHE_FULL);
}
}
}
@@ -461,6 +494,14 @@
}
}
+ int size() {
+ return mCache.size();
+ }
+
+ int capacity() {
+ return mCapacity;
+ }
+
/**
* Remove all entries from the cache.
*/
diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java
index 928a4b2..d267e0d 100644
--- a/services/core/java/com/android/server/appop/HistoricalRegistry.java
+++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java
@@ -750,6 +750,7 @@
}
// Do not call persistPendingHistory inside the memory lock, due to possible deadlock
persistPendingHistory();
+ mDiscreteRegistry.shutdown();
}
void persistPendingHistory() {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index ef80d59..8ef79a91 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -16,6 +16,22 @@
package com.android.server.audio;
import static android.media.audio.Flags.scoManagedByAudio;
+import static android.media.AudioSystem.DEVICE_IN_ALL_SCO_SET;
+import static android.media.AudioSystem.DEVICE_IN_BLE_HEADSET;
+import static android.media.AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET;
+import static android.media.AudioSystem.DEVICE_IN_USB_HEADSET;
+import static android.media.AudioSystem.DEVICE_IN_WIRED_HEADSET;
+import static android.media.AudioSystem.DEVICE_OUT_ALL_SCO_SET;
+import static android.media.AudioSystem.DEVICE_OUT_BLE_HEADSET;
+import static android.media.AudioSystem.DEVICE_OUT_BLUETOOTH_SCO;
+import static android.media.AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT;
+import static android.media.AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET;
+import static android.media.AudioSystem.DEVICE_OUT_BUS;
+import static android.media.AudioSystem.DEVICE_OUT_EARPIECE;
+import static android.media.AudioSystem.DEVICE_OUT_SPEAKER;
+import static android.media.AudioSystem.DEVICE_OUT_USB_HEADSET;
+import static android.media.AudioSystem.DEVICE_OUT_WIRED_HEADSET;
+import static android.media.AudioSystem.isBluetoothScoOutDevice;
import static com.android.media.audio.Flags.equalScoLeaVcIndexRange;
import static com.android.media.audio.Flags.optimizeBtDeviceSwitch;
@@ -78,9 +94,11 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
@@ -514,7 +532,7 @@
@GuardedBy("mDeviceStateLock")
private CommunicationRouteClient topCommunicationRouteClient() {
for (CommunicationRouteClient crc : mCommunicationRouteClients) {
- if (crc.getUid() == mAudioModeOwner.mUid) {
+ if (crc.getUid() == mAudioModeOwner.mUid && !crc.isDisabled()) {
return crc;
}
}
@@ -574,36 +592,6 @@
return false;
}
- /*package */
- void postCheckCommunicationDeviceRemoval(@NonNull AudioDeviceAttributes device) {
- if (!isValidCommunicationDeviceType(
- AudioDeviceInfo.convertInternalDeviceToDeviceType(device.getInternalType()))) {
- return;
- }
- sendLMsgNoDelay(MSG_L_CHECK_COMMUNICATION_DEVICE_REMOVAL, SENDMSG_QUEUE, device);
- }
-
- @GuardedBy("mDeviceStateLock")
- void onCheckCommunicationDeviceRemoval(@NonNull AudioDeviceAttributes device) {
- if (AudioService.DEBUG_COMM_RTE) {
- Log.v(TAG, "onCheckCommunicationDeviceRemoval device: " + device.toString());
- }
- for (CommunicationRouteClient crc : mCommunicationRouteClients) {
- if (device.equals(crc.getDevice())) {
- if (AudioService.DEBUG_COMM_RTE) {
- Log.v(TAG, "onCheckCommunicationDeviceRemoval removing client: "
- + crc.toString());
- }
- // Cancelling the route for this client will remove it from the stack and update
- // the communication route.
- CommunicationDeviceInfo deviceInfo = new CommunicationDeviceInfo(
- crc.getBinder(), crc.getAttributionSource(), device, false,
- BtHelper.SCO_MODE_UNDEFINED, "onCheckCommunicationDeviceRemoval",
- crc.isPrivileged());
- postSetCommunicationDeviceForClient(deviceInfo);
- }
- }
- }
// check playback or record activity after 6 seconds for UIDs
private static final int CHECK_CLIENT_STATE_DELAY_MS = 6000;
@@ -1693,8 +1681,18 @@
boolean connect, @Nullable BluetoothDevice btDevice,
boolean deviceSwitch) {
synchronized (mDeviceStateLock) {
- return mDeviceInventory.handleDeviceConnection(
+ boolean status = mDeviceInventory.handleDeviceConnection(
attributes, connect, false /*for test*/, btDevice, deviceSwitch);
+ if (isValidCommunicationDeviceType(attributes.getType())
+ || mDuplexCommunicationDevices.containsValue(attributes.getInternalType())) {
+ checkCommunicationRouteClientsDevices();
+ if (connect || !deviceSwitch) {
+ onUpdateCommunicationRouteClient(
+ bluetoothScoRequestOwnerAttributionSource(),
+ "handleDeviceConnection");
+ }
+ }
+ return status;
}
}
@@ -1953,15 +1951,17 @@
|| btInfo.mIsLeOutput)
? mAudioService.getBluetoothContextualVolumeStream()
: AudioSystem.STREAM_DEFAULT);
- if ((btInfo.mProfile == BluetoothProfile.LE_AUDIO
+ if (btInfo.mProfile == BluetoothProfile.LE_AUDIO
|| btInfo.mProfile == BluetoothProfile.HEARING_AID
|| (mScoManagedByAudio
- && btInfo.mProfile == BluetoothProfile.HEADSET))
- && (btInfo.mState == BluetoothProfile.STATE_CONNECTED
- || !btInfo.mIsDeviceSwitch)) {
- onUpdateCommunicationRouteClient(
+ && btInfo.mProfile == BluetoothProfile.HEADSET)) {
+ checkCommunicationRouteClientsDevices();
+ if (btInfo.mState == BluetoothProfile.STATE_CONNECTED
+ || !btInfo.mIsDeviceSwitch) {
+ onUpdateCommunicationRouteClient(
bluetoothScoRequestOwnerAttributionSource(),
"setBluetoothActiveDevice");
+ }
}
}
}
@@ -2123,13 +2123,6 @@
final BluetoothDevice btDevice = (BluetoothDevice) msg.obj;
BtHelper.onNotifyPreferredAudioProfileApplied(btDevice);
} break;
- case MSG_L_CHECK_COMMUNICATION_DEVICE_REMOVAL: {
- synchronized (mSetModeLock) {
- synchronized (mDeviceStateLock) {
- onCheckCommunicationDeviceRemoval((AudioDeviceAttributes) msg.obj);
- }
- }
- } break;
case MSG_PERSIST_AUDIO_DEVICE_SETTINGS:
onPersistAudioDeviceSettings();
break;
@@ -2227,7 +2220,6 @@
private static final int MSG_IIL_BTLEAUDIO_TIMEOUT = 49;
private static final int MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED = 52;
- private static final int MSG_L_CHECK_COMMUNICATION_DEVICE_REMOVAL = 53;
private static final int MSG_PERSIST_AUDIO_DEVICE_SETTINGS = 54;
@@ -2431,18 +2423,21 @@
private final IBinder mCb;
@NonNull private final AttributionSource mAttributionSource;
private final boolean mIsPrivileged;
- private AudioDeviceAttributes mDevice;
+ @NonNull private AudioDeviceAttributes mDevice;
private boolean mPlaybackActive;
private boolean mRecordingActive;
+ private boolean mDisabled;
+
CommunicationRouteClient(IBinder cb, @NonNull AttributionSource attributionSource,
- AudioDeviceAttributes device, boolean isPrivileged) {
+ @NonNull AudioDeviceAttributes device, boolean isPrivileged) {
mCb = cb;
mAttributionSource = attributionSource;
mDevice = device;
mIsPrivileged = isPrivileged;
mPlaybackActive = mAudioService.isPlaybackActiveForUid(attributionSource.getUid());
mRecordingActive = mAudioService.isRecordingActiveForUid(attributionSource.getUid());
+ mDisabled = false;
}
public boolean registerDeathRecipient() {
@@ -2485,7 +2480,10 @@
return mIsPrivileged;
}
- AudioDeviceAttributes getDevice() {
+ void setDevice(@NonNull AudioDeviceAttributes device) {
+ mDevice = device;
+ }
+ @NonNull AudioDeviceAttributes getDevice() {
return mDevice;
}
@@ -2498,7 +2496,14 @@
}
public boolean isActive() {
- return mIsPrivileged || mRecordingActive || mPlaybackActive;
+ return !mDisabled && (mIsPrivileged || mRecordingActive || mPlaybackActive);
+ }
+
+ public void setDisabled(boolean disabled) {
+ mDisabled = disabled;
+ }
+ public boolean isDisabled() {
+ return mDisabled;
}
@Override
@@ -2507,7 +2512,8 @@
+ " mDevice: " + mDevice.toString()
+ " mIsPrivileged: " + mIsPrivileged
+ " mPlaybackActive: " + mPlaybackActive
- + " mRecordingActive: " + mRecordingActive + "]";
+ + " mRecordingActive: " + mRecordingActive
+ + " mDisabled: " + mDisabled + "]";
}
}
@@ -2593,6 +2599,76 @@
onUpdatePhoneStrategyDevice(preferredCommunicationDevice);
}
+ // Pairs of input and output devices for duplex communication devices (headsets)
+ private final HashMap<Integer, Integer> mDuplexCommunicationDevices = new HashMap<>(
+ Map.of(DEVICE_OUT_BLE_HEADSET, DEVICE_IN_BLE_HEADSET,
+ DEVICE_OUT_WIRED_HEADSET, DEVICE_IN_WIRED_HEADSET,
+ DEVICE_OUT_USB_HEADSET, DEVICE_IN_USB_HEADSET,
+ DEVICE_OUT_BLUETOOTH_SCO, DEVICE_IN_BLUETOOTH_SCO_HEADSET,
+ DEVICE_OUT_BLUETOOTH_SCO_HEADSET, DEVICE_IN_BLUETOOTH_SCO_HEADSET,
+ DEVICE_OUT_BLUETOOTH_SCO_CARKIT, DEVICE_IN_BLUETOOTH_SCO_HEADSET
+ ));
+ /**
+ * Scan communication route clients and disable them if their selected device is not connected
+ * or re-enable them if a device of the same type as their connected device is connected
+ */
+ // @GuardedBy("mSetModeLock")
+ @GuardedBy("mDeviceStateLock")
+ private void checkCommunicationRouteClientsDevices() {
+ for (CommunicationRouteClient crc : mCommunicationRouteClients) {
+ int deviceType = crc.getDevice().getInternalType();
+ // Skip non detachable devices
+ if (deviceType == DEVICE_OUT_EARPIECE || deviceType == DEVICE_OUT_SPEAKER
+ || deviceType == DEVICE_OUT_BUS) {
+ continue;
+ }
+
+ // outDeviceSet is the expected connected output device types for the requested device
+ Set<Integer> outDeviceSet = null;
+ // inDeviceSet is the expected input device for outDeviceSet. Null for non
+ // duplex devices
+ Set<Integer> inDeviceSet = null;
+ // Special case for SCO because several device types are equivalent
+ if (isBluetoothScoOutDevice(deviceType)) {
+ outDeviceSet = DEVICE_OUT_ALL_SCO_SET;
+ inDeviceSet = DEVICE_IN_ALL_SCO_SET;
+ } else {
+ outDeviceSet = new HashSet<>();
+ outDeviceSet.add(deviceType);
+ if (mDuplexCommunicationDevices.containsKey(deviceType)) {
+ inDeviceSet = new HashSet<>();
+ inDeviceSet.add(mDuplexCommunicationDevices.get(deviceType));
+ }
+ }
+
+ AudioDeviceAttributes outAda =
+ mDeviceInventory.getFirstConnectedDeviceAttributesOfTypes(outDeviceSet);
+ AudioDeviceAttributes inAda = (inDeviceSet == null) ? null
+ : mDeviceInventory.getFirstConnectedDeviceAttributesOfTypes(inDeviceSet);
+
+ // A device is fully connected if the output device is connect and if not duplex
+ // or an input device with the same address is connected
+ boolean fullyConnected = outAda != null && (inDeviceSet == null
+ || (inAda != null && inAda.getAddress().equals(outAda.getAddress())));
+
+ if (fullyConnected) {
+ crc.setDevice(outAda);
+ if (crc.isDisabled()) {
+ crc.setDisabled(false);
+ if (AudioService.DEBUG_COMM_RTE) {
+ Log.v(TAG,
+ "checkCommunicationRouteClientsDevices, enabling client: " + crc);
+ }
+ }
+ } else if (!crc.isDisabled()) {
+ crc.setDisabled(true);
+ if (AudioService.DEBUG_COMM_RTE) {
+ Log.v(TAG, "checkCommunicationRouteClientsDevices, disabling client: " + crc);
+ }
+ }
+ }
+ }
+
/**
* Select new communication device from communication route client at the top of the stack
* and restore communication route including restarting SCO audio if needed.
@@ -2601,6 +2677,7 @@
@GuardedBy("mDeviceStateLock")
private void onUpdateCommunicationRouteClient(
@Nullable AttributionSource previousBtScoRequesterAS, String eventSource) {
+
CommunicationRouteClient crc = topCommunicationRouteClient();
if (AudioService.DEBUG_COMM_RTE) {
Log.v(TAG, "onUpdateCommunicationRouteClient, crc: " + crc
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index ae91934..829d9ea 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -1867,7 +1867,6 @@
deviceSwitch);
// always remove even if disconnection failed
mConnectedDevices.remove(deviceKey);
- mDeviceBroker.postCheckCommunicationDeviceRemoval(attributes);
status = true;
}
if (status) {
@@ -2413,7 +2412,7 @@
} else {
AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
"A2DP device addr=" + Utils.anonymizeBluetoothAddress(address)
- + " made unavailable, deviceSwitch" + deviceSwitch))
+ + " made unavailable, deviceSwitch: " + deviceSwitch))
.printSlog(EventLogger.Event.ALOGI, TAG));
}
mApmConnectedDevices.remove(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
@@ -2423,7 +2422,6 @@
mmi.record();
updateBluetoothPreferredModes_l(null /*connectedDevice*/);
purgeDevicesRoles_l();
- mDeviceBroker.postCheckCommunicationDeviceRemoval(ada);
}
@GuardedBy("mDevicesLock")
@@ -2479,7 +2477,6 @@
// always remove regardless of the result
mConnectedDevices.remove(
DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address));
- mDeviceBroker.postCheckCommunicationDeviceRemoval(ada);
}
@GuardedBy("mDevicesLock")
@@ -2541,7 +2538,6 @@
.set(MediaMetrics.Property.DEVICE,
AudioSystem.getDeviceName(DEVICE_OUT_HEARING_AID))
.record();
- mDeviceBroker.postCheckCommunicationDeviceRemoval(ada);
}
@GuardedBy("mDevicesLock")
@@ -2572,6 +2568,15 @@
}
/**
+ * Returns a DeviceInfo for the first connected device matching one of the supplied types
+ */
+ AudioDeviceAttributes getFirstConnectedDeviceAttributesOfTypes(Set<Integer> internalTypes) {
+ DeviceInfo di = getFirstConnectedDeviceOfTypes(internalTypes);
+ return di == null ? null : new AudioDeviceAttributes(
+ di.mDeviceType, di.mDeviceAddress, di.mDeviceName);
+ }
+
+ /**
* Returns a list of connected devices matching one of the supplied types
*/
private List<DeviceInfo> getConnectedDevicesOfTypes(Set<Integer> internalTypes) {
@@ -2689,13 +2694,16 @@
if (res != AudioSystem.AUDIO_STATUS_OK) {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
- "APM failed to make unavailable LE Audio device addr=" + address
- + " error=" + res).printSlog(EventLogger.Event.ALOGE, TAG));
+ "APM failed to make unavailable LE Audio "
+ + (AudioSystem.isInputDevice(device) ? "source" : "sink")
+ + " device addr=" + address
+ + " error=" + res).printSlog(EventLogger.Event.ALOGE, TAG));
// not taking further action: proceeding as if disconnection from APM worked
} else {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
- "LE Audio device addr=" + Utils.anonymizeBluetoothAddress(address)
- + " made unavailable, deviceSwitch" + deviceSwitch)
+ "LE Audio " + (AudioSystem.isInputDevice(device) ? "source" : "sink")
+ + "device addr=" + Utils.anonymizeBluetoothAddress(address)
+ + " made unavailable, deviceSwitch: " + deviceSwitch)
.printSlog(EventLogger.Event.ALOGI, TAG));
}
mConnectedDevices.remove(DeviceInfo.makeDeviceListKey(device, address));
@@ -2704,9 +2712,6 @@
setCurrentAudioRouteNameIfPossible(null, false /*fromA2dp*/);
updateBluetoothPreferredModes_l(null /*connectedDevice*/);
purgeDevicesRoles_l();
- if (ada != null) {
- mDeviceBroker.postCheckCommunicationDeviceRemoval(ada);
- }
}
@GuardedBy("mDevicesLock")
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0f1228f..ada1cd7 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -7806,7 +7806,8 @@
return AudioSystem.STREAM_RING;
}
default:
- if (isInCommunication()) {
+ if (isInCommunication()
+ || mAudioSystem.isStreamActive(AudioManager.STREAM_VOICE_CALL, 0)) {
if (!replaceStreamBtSco()
&& mBtCommDeviceActive.get() == BT_COMM_DEVICE_ACTIVE_SCO) {
if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_BLUETOOTH_SCO");
diff --git a/services/core/java/com/android/server/clipboard/OWNERS b/services/core/java/com/android/server/clipboard/OWNERS
index 0d5dbf9..4ca4b80 100644
--- a/services/core/java/com/android/server/clipboard/OWNERS
+++ b/services/core/java/com/android/server/clipboard/OWNERS
@@ -1,3 +1,3 @@
-per-file EmulatorClipboardMonitor.java = bohu@google.com,lfy@google.com,rkir@google.com
+per-file EmulatorClipboardMonitor.java = bohu@google.com,rkir@google.com
olilan@google.com
diff --git a/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java b/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java
index 8637d2d..47b1c544 100644
--- a/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java
+++ b/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java
@@ -103,12 +103,6 @@
}
}
- @Override
- public void finalize() {
- unregisterDeviceConfigListeners();
- unregisterPackageReceiver();
- }
-
@VisibleForTesting
void registerDeviceConfigListeners() {
for (DeviceConfigListener listener : mDeviceConfigListeners) {
@@ -116,21 +110,11 @@
}
}
- private void unregisterDeviceConfigListeners() {
- for (DeviceConfigListener listener : mDeviceConfigListeners) {
- listener.unregister();
- }
- }
-
@VisibleForTesting
void registerPackageReceiver() {
mPackageReceiver.register();
}
- private void unregisterPackageReceiver() {
- mPackageReceiver.unregister();
- }
-
/**
* Same as {@link #applyOverrides(Properties, Set, Map)} except all properties of the given
* {@code namespace} are fetched via {@link DeviceConfig#getProperties}.
@@ -374,10 +358,6 @@
this);
}
- private void unregister() {
- DeviceConfig.removeOnPropertiesChangedListener(this);
- }
-
@Override
public void onPropertiesChanged(Properties properties) {
boolean removeOverridesFlagChanged = properties.getKeyset().contains(
@@ -426,10 +406,6 @@
null, /* scheduler= */ null);
}
- private void unregister() {
- mContext.unregisterReceiver(this);
- }
-
@Override
public void onReceive(@NonNull final Context context, @NonNull final Intent intent) {
Uri data = intent.getData();
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 969a684..2d387ea 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -261,6 +261,7 @@
private final SyncLogger mLogger;
private final AppCloningDeviceConfigHelper mAppCloningDeviceConfigHelper;
+ private final PackageMonitorImpl mPackageMonitor;
private boolean isJobIdInUseLockedH(int jobId, List<JobInfo> pendingJobs) {
for (int i = 0, size = pendingJobs.size(); i < size; i++) {
@@ -725,8 +726,8 @@
mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null);
- final PackageMonitor packageMonitor = new PackageMonitorImpl();
- packageMonitor.register(mContext, null /* thread */, UserHandle.ALL,
+ mPackageMonitor = new PackageMonitorImpl();
+ mPackageMonitor.register(mContext, null /* thread */, UserHandle.ALL,
false /* externalStorage */);
intentFilter = new IntentFilter(Intent.ACTION_TIME_CHANGED);
diff --git a/services/core/java/com/android/server/display/DisplayAdapter.java b/services/core/java/com/android/server/display/DisplayAdapter.java
index 69bc66f..155f82a 100644
--- a/services/core/java/com/android/server/display/DisplayAdapter.java
+++ b/services/core/java/com/android/server/display/DisplayAdapter.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.os.Handler;
import android.view.Display;
+import android.view.SurfaceControl;
import com.android.server.display.feature.DisplayManagerFlags;
@@ -138,6 +139,21 @@
vsyncRate, /* isSynthetic= */ false, alternativeRefreshRates, supportedHdrTypes);
}
+ static int getPowerModeForState(int state) {
+ switch (state) {
+ case Display.STATE_OFF:
+ return SurfaceControl.POWER_MODE_OFF;
+ case Display.STATE_DOZE:
+ return SurfaceControl.POWER_MODE_DOZE;
+ case Display.STATE_DOZE_SUSPEND:
+ return SurfaceControl.POWER_MODE_DOZE_SUSPEND;
+ case Display.STATE_ON_SUSPEND:
+ return SurfaceControl.POWER_MODE_ON_SUSPEND;
+ default:
+ return SurfaceControl.POWER_MODE_NORMAL;
+ }
+ }
+
public interface Listener {
void onDisplayDeviceEvent(DisplayDevice device, int event);
void onTraversalRequested();
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 258c955..d402f01 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -2618,8 +2618,7 @@
// Blank or unblank the display immediately to match the state requested
// by the display power controller (if known).
DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
- if ((info.flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0
- || android.companion.virtualdevice.flags.Flags.correctVirtualDisplayPowerState()) {
+ if ((info.flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0) {
final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device);
if (display == null) {
return null;
@@ -5580,9 +5579,7 @@
final DisplayDevice displayDevice = mLogicalDisplayMapper.getDisplayLocked(
id).getPrimaryDisplayDeviceLocked();
final int flags = displayDevice.getDisplayDeviceInfoLocked().flags;
- if ((flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0
- || android.companion.virtualdevice.flags.Flags
- .correctVirtualDisplayPowerState()) {
+ if ((flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0) {
final DisplayPowerController displayPowerController =
mDisplayPowerControllers.get(id);
if (displayPowerController != null) {
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 551202c..7b714ad 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -208,21 +208,6 @@
}
}
- static int getPowerModeForState(int state) {
- switch (state) {
- case Display.STATE_OFF:
- return SurfaceControl.POWER_MODE_OFF;
- case Display.STATE_DOZE:
- return SurfaceControl.POWER_MODE_DOZE;
- case Display.STATE_DOZE_SUSPEND:
- return SurfaceControl.POWER_MODE_DOZE_SUSPEND;
- case Display.STATE_ON_SUSPEND:
- return SurfaceControl.POWER_MODE_ON_SUSPEND;
- default:
- return SurfaceControl.POWER_MODE_NORMAL;
- }
- }
-
private final class LocalDisplayDevice extends DisplayDevice {
private final long mPhysicalDisplayId;
private final SparseArray<DisplayModeRecord> mSupportedModes = new SparseArray<>();
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index e7939bb..ac03a93 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -113,6 +113,11 @@
public void destroyDisplay(IBinder displayToken) {
DisplayControl.destroyVirtualDisplay(displayToken);
}
+
+ @Override
+ public void setDisplayPowerMode(IBinder displayToken, int mode) {
+ SurfaceControl.setDisplayPowerMode(displayToken, mode);
+ }
}, featureFlags);
}
@@ -340,6 +345,7 @@
private Display.Mode mMode;
private int mDisplayIdToMirror;
private boolean mIsWindowManagerMirroring;
+ private final boolean mNeverBlank;
private final DisplayCutout mDisplayCutout;
private final float mDefaultBrightness;
private final float mDimBrightness;
@@ -371,7 +377,11 @@
mCallback = callback;
mProjection = projection;
mMediaProjectionCallback = mediaProjectionCallback;
- if (android.companion.virtualdevice.flags.Flags.correctVirtualDisplayPowerState()) {
+ // Private non-mirror displays are never blank and always on.
+ mNeverBlank = (flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) == 0
+ && (flags & VIRTUAL_DISPLAY_FLAG_PUBLIC) == 0;
+ if (android.companion.virtualdevice.flags.Flags.correctVirtualDisplayPowerState()
+ && !mNeverBlank) {
// The display's power state depends on the power state of the state of its
// display / power group, which we don't know here. Initializing to UNKNOWN allows
// the first call to requestDisplayStateLocked() to set the correct state.
@@ -471,7 +481,15 @@
@Override
public Runnable requestDisplayStateLocked(int state, float brightnessState,
float sdrBrightnessState, DisplayOffloadSessionImpl displayOffloadSession) {
+ Runnable runnable = null;
if (state != mDisplayState) {
+ Slog.d(TAG, "Changing state of virtual display " + mName + " from "
+ + Display.stateToString(mDisplayState) + " to "
+ + Display.stateToString(state));
+ if (state != Display.STATE_ON && state != Display.STATE_OFF) {
+ Slog.wtf(TAG, "Unexpected display state for Virtual Display: "
+ + Display.stateToString(state));
+ }
mDisplayState = state;
mInfo = null;
sendDisplayDeviceEventLocked(this, DISPLAY_DEVICE_EVENT_CHANGED);
@@ -480,6 +498,15 @@
} else {
mCallback.dispatchDisplayResumed();
}
+
+ if (android.companion.virtualdevice.flags.Flags.correctVirtualDisplayPowerState()) {
+ final IBinder token = getDisplayTokenLocked();
+ runnable = () -> {
+ final int mode = getPowerModeForState(state);
+ Slog.d(TAG, "Requesting power mode for display " + mName + " to " + mode);
+ mSurfaceControlDisplayFactory.setDisplayPowerMode(token, mode);
+ };
+ }
}
if (android.companion.virtualdevice.flags.Flags.deviceAwareDisplayPower()
&& mBrightnessListener != null
@@ -488,7 +515,7 @@
mCurrentBrightness = brightnessState;
mCallback.dispatchRequestedBrightnessChanged(mCurrentBrightness);
}
- return null;
+ return runnable;
}
@Override
@@ -572,23 +599,14 @@
mInfo.yDpi = mDensityDpi;
mInfo.presentationDeadlineNanos = 1000000000L / (int) getRefreshRate(); // 1 frame
mInfo.flags = 0;
- if (android.companion.virtualdevice.flags.Flags.correctVirtualDisplayPowerState()) {
- if ((mFlags & VIRTUAL_DISPLAY_FLAG_PUBLIC) == 0) {
- mInfo.flags |= DisplayDeviceInfo.FLAG_PRIVATE;
- }
- if ((mFlags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) == 0) {
- mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY;
- }
- } else {
- if ((mFlags & VIRTUAL_DISPLAY_FLAG_PUBLIC) == 0) {
- mInfo.flags |= DisplayDeviceInfo.FLAG_PRIVATE
- | DisplayDeviceInfo.FLAG_NEVER_BLANK;
- }
- if ((mFlags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) {
- mInfo.flags &= ~DisplayDeviceInfo.FLAG_NEVER_BLANK;
- } else {
- mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY;
- }
+ if ((mFlags & VIRTUAL_DISPLAY_FLAG_PUBLIC) == 0) {
+ mInfo.flags |= DisplayDeviceInfo.FLAG_PRIVATE;
+ }
+ if ((mFlags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) == 0) {
+ mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY;
+ }
+ if (mNeverBlank) {
+ mInfo.flags |= DisplayDeviceInfo.FLAG_NEVER_BLANK;
}
if ((mFlags & VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP) != 0) {
mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP;
@@ -782,5 +800,13 @@
* @param displayToken The display token for the display to be destroyed.
*/
void destroyDisplay(IBinder displayToken);
+
+ /**
+ * Set the display power mode in SurfaceFlinger.
+ *
+ * @param displayToken The display token for the display.
+ * @param mode the SurfaceControl power mode, e.g. {@link SurfaceControl#POWER_MODE_OFF}.
+ */
+ void setDisplayPowerMode(IBinder displayToken, int mode);
}
}
diff --git a/services/core/java/com/android/server/display/brightness/BrightnessReason.java b/services/core/java/com/android/server/display/brightness/BrightnessReason.java
index a4804e1..d4b9a6c 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessReason.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessReason.java
@@ -49,11 +49,9 @@
public static final int MODIFIER_HDR = 0x4;
public static final int MODIFIER_THROTTLED = 0x8;
public static final int MODIFIER_MIN_LUX = 0x10;
- public static final int MODIFIER_MIN_USER_SET_LOWER_BOUND = 0x20;
- public static final int MODIFIER_STYLUS_UNDER_USE = 0x40;
+ public static final int MODIFIER_STYLUS_UNDER_USE = 0x20;
public static final int MODIFIER_MASK = MODIFIER_DIMMED | MODIFIER_LOW_POWER | MODIFIER_HDR
- | MODIFIER_THROTTLED | MODIFIER_MIN_LUX | MODIFIER_MIN_USER_SET_LOWER_BOUND
- | MODIFIER_STYLUS_UNDER_USE;
+ | MODIFIER_THROTTLED | MODIFIER_MIN_LUX | MODIFIER_STYLUS_UNDER_USE;
// ADJUSTMENT_*
// These things can happen at any point, even if the main brightness reason doesn't
@@ -157,9 +155,6 @@
if ((mModifier & MODIFIER_MIN_LUX) != 0) {
sb.append(" lux_lower_bound");
}
- if ((mModifier & MODIFIER_MIN_USER_SET_LOWER_BOUND) != 0) {
- sb.append(" user_min_pref");
- }
if ((mModifier & MODIFIER_STYLUS_UNDER_USE) != 0) {
sb.append(" stylus_under_use");
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java
index c3596c3..72cb31d 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java
@@ -19,12 +19,8 @@
import android.content.ContentResolver;
import android.content.Context;
-import android.database.ContentObserver;
import android.hardware.display.DisplayManagerInternal;
-import android.net.Uri;
import android.os.Handler;
-import android.os.UserHandle;
-import android.provider.Settings;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
@@ -49,7 +45,6 @@
private static final String TAG = "BrightnessLowLuxModifier";
private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
private static final float MIN_NITS_DEFAULT = 0.2f;
- private final SettingsObserver mSettingsObserver;
private final ContentResolver mContentResolver;
private final Handler mHandler;
private final BrightnessClamperController.ClamperChangeListener mChangeListener;
@@ -69,7 +64,6 @@
mChangeListener = listener;
mHandler = handler;
mContentResolver = context.getContentResolver();
- mSettingsObserver = new SettingsObserver(mHandler);
mDisplayDeviceConfig = displayDeviceConfig;
mHandler.post(() -> {
start();
@@ -82,12 +76,7 @@
*/
@VisibleForTesting
public void recalculateLowerBound() {
- float settingNitsLowerBound = Settings.Secure.getFloatForUser(
- mContentResolver, Settings.Secure.EVEN_DIMMER_MIN_NITS,
- /* def= */ MIN_NITS_DEFAULT, UserHandle.USER_CURRENT);
-
- boolean isActive = isSettingEnabled()
- && mAmbientLux != BrightnessMappingStrategy.INVALID_LUX;
+ boolean isActive = mAmbientLux != BrightnessMappingStrategy.INVALID_LUX;
final int reason;
float minNitsAllowed = -1f; // undefined, if setting is off.
@@ -95,12 +84,9 @@
if (isActive) {
float luxBasedNitsLowerBound = mDisplayDeviceConfig.getMinNitsFromLux(mAmbientLux);
- minNitsAllowed = Math.max(settingNitsLowerBound,
- luxBasedNitsLowerBound);
+ minNitsAllowed = Math.max(MIN_NITS_DEFAULT, luxBasedNitsLowerBound);
minBrightnessAllowed = getBrightnessFromNits(minNitsAllowed);
- reason = settingNitsLowerBound > luxBasedNitsLowerBound
- ? BrightnessReason.MODIFIER_MIN_USER_SET_LOWER_BOUND
- : BrightnessReason.MODIFIER_MIN_LUX;
+ reason = BrightnessReason.MODIFIER_MIN_LUX;
} else {
minBrightnessAllowed = mDisplayDeviceConfig.getEvenDimmerTransitionPoint();
reason = 0;
@@ -169,7 +155,6 @@
@Override
public void apply(DisplayManagerInternal.DisplayPowerRequest request,
DisplayBrightnessState.Builder stateBuilder) {
-
stateBuilder.setMinBrightness(mBrightnessLowerBound);
float boundedBrightness = Math.max(mBrightnessLowerBound, stateBuilder.getBrightness());
stateBuilder.setBrightness(boundedBrightness);
@@ -181,12 +166,11 @@
@Override
public void stop() {
- mContentResolver.unregisterContentObserver(mSettingsObserver);
}
@Override
public boolean shouldListenToLightSensor() {
- return isSettingEnabled();
+ return true;
}
@Override
@@ -204,37 +188,8 @@
pw.println(" mMinNitsAllowed=" + mMinNitsAllowed);
}
- /**
- * Defaults to true, on devices where setting is unset.
- *
- * @return if setting indicates feature is enabled
- */
- private boolean isSettingEnabled() {
- return Settings.Secure.getFloatForUser(mContentResolver,
- Settings.Secure.EVEN_DIMMER_ACTIVATED,
- /* def= */ 1.0f, UserHandle.USER_CURRENT) == 1.0f;
- }
-
private float getBrightnessFromNits(float nits) {
return mDisplayDeviceConfig.getBrightnessFromBacklight(
mDisplayDeviceConfig.getBacklightFromNits(nits));
}
-
- private final class SettingsObserver extends ContentObserver {
-
- SettingsObserver(Handler handler) {
- super(handler);
- mContentResolver.registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.EVEN_DIMMER_MIN_NITS),
- false, this, UserHandle.USER_ALL);
- mContentResolver.registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.EVEN_DIMMER_ACTIVATED),
- false, this, UserHandle.USER_ALL);
- }
-
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- recalculateLowerBound();
- }
- }
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 3cb21c3..97f9a7c 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -793,7 +793,7 @@
@Override
public void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos) {
for (HdmiDeviceInfo info : deviceInfos) {
- if (!isInputReady(info.getDeviceId())) {
+ if (!isInputReady(info.getId())) {
mService.getHdmiCecNetwork().removeCecDevice(
HdmiCecLocalDeviceTv.this, info.getLogicalAddress());
}
diff --git a/services/core/java/com/android/server/input/InputGestureManager.java b/services/core/java/com/android/server/input/InputGestureManager.java
index 7f85384..67e1ccc 100644
--- a/services/core/java/com/android/server/input/InputGestureManager.java
+++ b/services/core/java/com/android/server/input/InputGestureManager.java
@@ -138,11 +138,6 @@
KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT
),
createKeyGesture(
- KeyEvent.KEYCODE_DEL,
- KeyEvent.META_META_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_BACK
- ),
- createKeyGesture(
KeyEvent.KEYCODE_ESCAPE,
KeyEvent.META_META_ON,
KeyGestureEvent.KEY_GESTURE_TYPE_BACK
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
index f40d0dd..2d937bd 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
@@ -16,6 +16,8 @@
package com.android.server.location.contexthub;
+import static com.android.server.location.contexthub.ContextHubTransactionManager.RELIABLE_MESSAGE_DUPLICATE_DETECTION_TIMEOUT;
+
import android.annotation.NonNull;
import android.app.AppOpsManager;
import android.content.Context;
@@ -44,6 +46,9 @@
import java.util.Collection;
import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
@@ -100,7 +105,7 @@
private final Object mOpenSessionLock = new Object();
- static class SessionInfo {
+ static class Session {
enum SessionState {
/* The session is pending acceptance from the remote endpoint. */
PENDING,
@@ -119,7 +124,15 @@
*/
private final Set<Integer> mPendingSequenceNumbers = new HashSet<>();
- SessionInfo(HubEndpointInfo remoteEndpointInfo, boolean remoteInitiated) {
+ /**
+ * Stores the history of received messages that are timestamped. We use a LinkedHashMap to
+ * guarantee insertion ordering for easier manipulation of removing expired entries.
+ *
+ * <p>The key is the sequence number, and the value is the timestamp in milliseconds.
+ */
+ private final LinkedHashMap<Integer, Long> mRxMessageHistoryMap = new LinkedHashMap<>();
+
+ Session(HubEndpointInfo remoteEndpointInfo, boolean remoteInitiated) {
mRemoteEndpointInfo = remoteEndpointInfo;
mRemoteInitiated = remoteInitiated;
}
@@ -157,11 +170,43 @@
consumer.accept(sequenceNumber);
}
}
+
+ public boolean isInMessageHistory(HubMessage message) {
+ // Clean up the history
+ Iterator<Map.Entry<Integer, Long>> iterator =
+ mRxMessageHistoryMap.entrySet().iterator();
+ long nowMillis = System.currentTimeMillis();
+ while (iterator.hasNext()) {
+ Map.Entry<Integer, Long> nextEntry = iterator.next();
+ long expiryMillis = RELIABLE_MESSAGE_DUPLICATE_DETECTION_TIMEOUT.toMillis();
+ if (nowMillis >= nextEntry.getValue() + expiryMillis) {
+ iterator.remove();
+ }
+ break;
+ }
+
+ return mRxMessageHistoryMap.containsKey(message.getMessageSequenceNumber());
+ }
+
+ public void addMessageToHistory(HubMessage message) {
+ if (mRxMessageHistoryMap.containsKey(message.getMessageSequenceNumber())) {
+ long value = mRxMessageHistoryMap.get(message.getMessageSequenceNumber());
+ Log.w(
+ TAG,
+ "Message already exists in history (inserted @ "
+ + value
+ + " ms): "
+ + message);
+ return;
+ }
+ mRxMessageHistoryMap.put(
+ message.getMessageSequenceNumber(), System.currentTimeMillis());
+ }
}
/** A map between a session ID which maps to its current state. */
@GuardedBy("mOpenSessionLock")
- private final SparseArray<SessionInfo> mSessionInfoMap = new SparseArray<>();
+ private final SparseArray<Session> mSessionMap = new SparseArray<>();
/** The package name of the app that created the endpoint */
private final String mPackageName;
@@ -232,7 +277,7 @@
synchronized (mOpenSessionLock) {
try {
- mSessionInfoMap.put(sessionId, new SessionInfo(destination, false));
+ mSessionMap.put(sessionId, new Session(destination, false));
mHubInterface.openEndpointSession(
sessionId, halEndpointInfo.id, mHalEndpointInfo.id, serviceDescriptor);
} catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) {
@@ -263,8 +308,8 @@
super.unregister_enforcePermission();
synchronized (mOpenSessionLock) {
// Iterate in reverse since cleanupSessionResources will remove the entry
- for (int i = mSessionInfoMap.size() - 1; i >= 0; i--) {
- int id = mSessionInfoMap.keyAt(i);
+ for (int i = mSessionMap.size() - 1; i >= 0; i--) {
+ int id = mSessionMap.keyAt(i);
halCloseEndpointSessionNoThrow(id, Reason.ENDPOINT_GONE);
cleanupSessionResources(id);
}
@@ -290,14 +335,14 @@
public void openSessionRequestComplete(int sessionId) {
super.openSessionRequestComplete_enforcePermission();
synchronized (mOpenSessionLock) {
- SessionInfo info = mSessionInfoMap.get(sessionId);
+ Session info = mSessionMap.get(sessionId);
if (info == null) {
throw new IllegalArgumentException(
"openSessionRequestComplete for invalid session id=" + sessionId);
}
try {
mHubInterface.endpointSessionOpenComplete(sessionId);
- info.setSessionState(SessionInfo.SessionState.ACTIVE);
+ info.setSessionState(Session.SessionState.ACTIVE);
} catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) {
Log.e(TAG, "Exception while calling endpointSessionOpenComplete", e);
}
@@ -310,7 +355,7 @@
int sessionId, HubMessage message, IContextHubTransactionCallback callback) {
super.sendMessage_enforcePermission();
synchronized (mOpenSessionLock) {
- SessionInfo info = mSessionInfoMap.get(sessionId);
+ Session info = mSessionMap.get(sessionId);
if (info == null) {
throw new IllegalArgumentException(
"sendMessage for invalid session id=" + sessionId);
@@ -393,9 +438,9 @@
} else {
synchronized (mOpenSessionLock) {
// Iterate in reverse since cleanupSessionResources will remove the entry
- for (int i = mSessionInfoMap.size() - 1; i >= 0; i--) {
- int id = mSessionInfoMap.keyAt(i);
- HubEndpointInfo target = mSessionInfoMap.get(id).getRemoteEndpointInfo();
+ for (int i = mSessionMap.size() - 1; i >= 0; i--) {
+ int id = mSessionMap.keyAt(i);
+ HubEndpointInfo target = mSessionMap.get(id).getRemoteEndpointInfo();
if (!hasEndpointPermissions(target)) {
halCloseEndpointSessionNoThrow(id, Reason.PERMISSION_DENIED);
onCloseEndpointSession(id, Reason.PERMISSION_DENIED);
@@ -415,13 +460,13 @@
sb.append("wakelock: ").append(mWakeLock);
}
synchronized (mOpenSessionLock) {
- if (mSessionInfoMap.size() != 0) {
+ if (mSessionMap.size() != 0) {
sb.append(System.lineSeparator());
sb.append(" sessions: ");
sb.append(System.lineSeparator());
}
- for (int i = 0; i < mSessionInfoMap.size(); i++) {
- int id = mSessionInfoMap.keyAt(i);
+ for (int i = 0; i < mSessionMap.size(); i++) {
+ int id = mSessionMap.keyAt(i);
int count = i + 1;
sb.append(
" "
@@ -429,7 +474,7 @@
+ ". id="
+ id
+ ", remote:"
- + mSessionInfoMap.get(id).getRemoteEndpointInfo());
+ + mSessionMap.get(id).getRemoteEndpointInfo());
sb.append(System.lineSeparator());
}
}
@@ -485,23 +530,23 @@
Log.w(TAG, "Unknown session ID in onEndpointSessionOpenComplete: id=" + sessionId);
return;
}
- mSessionInfoMap.get(sessionId).setSessionState(SessionInfo.SessionState.ACTIVE);
+ mSessionMap.get(sessionId).setSessionState(Session.SessionState.ACTIVE);
}
invokeCallback((consumer) -> consumer.onSessionOpenComplete(sessionId));
}
/* package */ void onMessageReceived(int sessionId, HubMessage message) {
- byte code = onMessageReceivedInternal(sessionId, message);
- if (code != ErrorCode.OK && message.isResponseRequired()) {
- sendMessageDeliveryStatus(sessionId, message.getMessageSequenceNumber(), code);
+ byte errorCode = onMessageReceivedInternal(sessionId, message);
+ if (errorCode != ErrorCode.OK && message.isResponseRequired()) {
+ sendMessageDeliveryStatus(sessionId, message.getMessageSequenceNumber(), errorCode);
}
}
/* package */ void onMessageDeliveryStatusReceived(
int sessionId, int sequenceNumber, byte errorCode) {
synchronized (mOpenSessionLock) {
- SessionInfo info = mSessionInfoMap.get(sessionId);
+ Session info = mSessionMap.get(sessionId);
if (info == null || !info.isActive()) {
Log.w(TAG, "Received delivery status for invalid session: id=" + sessionId);
return;
@@ -517,7 +562,7 @@
/* package */ boolean hasSessionId(int sessionId) {
synchronized (mOpenSessionLock) {
- return mSessionInfoMap.contains(sessionId);
+ return mSessionMap.contains(sessionId);
}
}
@@ -531,8 +576,8 @@
}
}
synchronized (mOpenSessionLock) {
- for (int i = mSessionInfoMap.size() - 1; i >= 0; i--) {
- int id = mSessionInfoMap.keyAt(i);
+ for (int i = mSessionMap.size() - 1; i >= 0; i--) {
+ int id = mSessionMap.keyAt(i);
onCloseEndpointSession(id, Reason.HUB_RESET);
}
}
@@ -555,7 +600,7 @@
Log.e(TAG, "Existing session in onEndpointSessionOpenRequest: id=" + sessionId);
return Optional.of(Reason.UNSPECIFIED);
}
- mSessionInfoMap.put(sessionId, new SessionInfo(initiator, true));
+ mSessionMap.put(sessionId, new Session(initiator, true));
}
boolean success =
@@ -567,7 +612,6 @@
}
private byte onMessageReceivedInternal(int sessionId, HubMessage message) {
- HubEndpointInfo remote;
synchronized (mOpenSessionLock) {
if (!isSessionActive(sessionId)) {
Log.e(
@@ -578,29 +622,36 @@
+ message);
return ErrorCode.PERMANENT_ERROR;
}
- remote = mSessionInfoMap.get(sessionId).getRemoteEndpointInfo();
- }
+ HubEndpointInfo remote = mSessionMap.get(sessionId).getRemoteEndpointInfo();
+ if (mSessionMap.get(sessionId).isInMessageHistory(message)) {
+ Log.e(TAG, "Dropping duplicate message: " + message);
+ return ErrorCode.TRANSIENT_ERROR;
+ }
- try {
- Binder.withCleanCallingIdentity(
- () -> {
- if (!notePermissions(remote)) {
- throw new RuntimeException(
- "Dropping message from "
- + remote
- + ". "
- + mPackageName
- + " doesn't have permission");
- }
- });
- } catch (RuntimeException e) {
- Log.e(TAG, e.getMessage());
- return ErrorCode.PERMISSION_DENIED;
- }
+ try {
+ Binder.withCleanCallingIdentity(
+ () -> {
+ if (!notePermissions(remote)) {
+ throw new RuntimeException(
+ "Dropping message from "
+ + remote
+ + ". "
+ + mPackageName
+ + " doesn't have permission");
+ }
+ });
+ } catch (RuntimeException e) {
+ Log.e(TAG, e.getMessage());
+ return ErrorCode.PERMISSION_DENIED;
+ }
- boolean success =
- invokeCallback((consumer) -> consumer.onMessageReceived(sessionId, message));
- return success ? ErrorCode.OK : ErrorCode.TRANSIENT_ERROR;
+ boolean success =
+ invokeCallback((consumer) -> consumer.onMessageReceived(sessionId, message));
+ if (success) {
+ mSessionMap.get(sessionId).addMessageToHistory(message);
+ }
+ return success ? ErrorCode.OK : ErrorCode.TRANSIENT_ERROR;
+ }
}
/**
@@ -634,7 +685,7 @@
*/
private boolean cleanupSessionResources(int sessionId) {
synchronized (mOpenSessionLock) {
- SessionInfo info = mSessionInfoMap.get(sessionId);
+ Session info = mSessionMap.get(sessionId);
if (info != null) {
if (!info.isRemoteInitiated()) {
mEndpointManager.returnSessionId(sessionId);
@@ -644,7 +695,7 @@
mTransactionManager.onMessageDeliveryResponse(
sequenceNumber, /* success= */ false);
});
- mSessionInfoMap.remove(sessionId);
+ mSessionMap.remove(sessionId);
}
return info != null;
}
@@ -656,7 +707,7 @@
*/
private boolean isSessionActive(int sessionId) {
synchronized (mOpenSessionLock) {
- return hasSessionId(sessionId) && mSessionInfoMap.get(sessionId).isActive();
+ return hasSessionId(sessionId) && mSessionMap.get(sessionId).isActive();
}
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/OWNERS b/services/core/java/com/android/server/locksettings/recoverablekeystore/OWNERS
index ebf7e6b..e540616 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/OWNERS
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/OWNERS
@@ -1,3 +1,2 @@
aseemk@google.com
-bozhu@google.com
dementyev@google.com
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index 23e9ac5..96e4539 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
@@ -983,6 +983,14 @@
Objects.requireNonNull(providerInfo, "providerInfo must not be null");
for (MediaRoute2Info route : providerInfo.getRoutes()) {
+ if (Flags.enableMirroringInMediaRouter2()
+ && route.supportsRemoteRouting()
+ && route.supportsSystemMediaRouting()
+ && route.getDeduplicationIds().isEmpty()) {
+ // This code is not accessible if the app is using the public API.
+ throw new SecurityException("Route is missing deduplication id: " + route);
+ }
+
if (route.isSystemRoute()) {
throw new SecurityException(
"Only the system is allowed to publish system routes. "
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index debac94..f137de1 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -25,8 +25,20 @@
import static android.media.MediaRouter2.SCANNING_STATE_WHILE_INTERACTIVE;
import static android.media.MediaRouter2Utils.getOriginalId;
import static android.media.MediaRouter2Utils.getProviderId;
-
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_CREATE_SESSION;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_DESELECT_ROUTE;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_RELEASE_SESSION;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_SELECT_ROUTE;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_TRANSFER_TO_ROUTE;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_INVALID_COMMAND;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_INVALID_ROUTE_ID;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_INVALID_SESSION_ID;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_MANAGER_RECORD_NOT_FOUND;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_PERMISSION_DENIED;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_ROUTER_RECORD_NOT_FOUND;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_SUCCESS;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNSPECIFIED;
import android.Manifest;
import android.annotation.NonNull;
@@ -64,14 +76,12 @@
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
-
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.media.flags.Flags;
import com.android.server.LocalServices;
import com.android.server.pm.UserManagerInternal;
import com.android.server.statusbar.StatusBarManagerInternal;
-
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -130,9 +140,14 @@
private final ArrayMap<IBinder, RouterRecord> mAllRouterRecords = new ArrayMap<>();
@GuardedBy("mLock")
private final ArrayMap<IBinder, ManagerRecord> mAllManagerRecords = new ArrayMap<>();
+
@GuardedBy("mLock")
private int mCurrentActiveUserId = -1;
+ @GuardedBy("mLock")
+ private static final MediaRouterMetricLogger mMediaRouterMetricLogger =
+ new MediaRouterMetricLogger();
+
private final ActivityManager.OnUidImportanceListener mOnUidImportanceListener =
(uid, importance) -> {
synchronized (mLock) {
@@ -350,8 +365,8 @@
}
}
- public void setDiscoveryRequestWithRouter2(@NonNull IMediaRouter2 router,
- @NonNull RouteDiscoveryPreference preference) {
+ public void setDiscoveryRequestWithRouter2(
+ @NonNull IMediaRouter2 router, @NonNull RouteDiscoveryPreference preference) {
Objects.requireNonNull(router, "router must not be null");
Objects.requireNonNull(preference, "preference must not be null");
@@ -409,8 +424,8 @@
}
}
- public void setRouteVolumeWithRouter2(@NonNull IMediaRouter2 router,
- @NonNull MediaRoute2Info route, int volume) {
+ public void setRouteVolumeWithRouter2(
+ @NonNull IMediaRouter2 router, @NonNull MediaRoute2Info route, int volume) {
Objects.requireNonNull(router, "router must not be null");
Objects.requireNonNull(route, "route must not be null");
@@ -439,12 +454,7 @@
try {
synchronized (mLock) {
requestCreateSessionWithRouter2Locked(
- requestId,
- managerRequestId,
- router,
- oldSession,
- route,
- sessionHints);
+ requestId, managerRequestId, router, oldSession, route, sessionHints);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -1326,6 +1336,9 @@
final RouterRecord routerRecord = mAllRouterRecords.get(binder);
if (routerRecord == null) {
+ mMediaRouterMetricLogger.logOperationFailure(
+ MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_CREATE_SESSION,
+ MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_ROUTER_RECORD_NOT_FOUND);
return;
}
@@ -1344,15 +1357,22 @@
if (managerRequestId != MediaRoute2ProviderService.REQUEST_ID_NONE) {
ManagerRecord manager = userHandler.findManagerWithId(toRequesterId(managerRequestId));
if (manager == null || manager.mLastSessionCreationRequest == null) {
- Slog.w(TAG, "requestCreateSessionWithRouter2Locked: "
- + "Ignoring unknown request.");
+ Slog.w(TAG, "requestCreateSessionWithRouter2Locked: Ignoring unknown request.");
+ mMediaRouterMetricLogger.logOperationFailure(
+ MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_CREATE_SESSION,
+ MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_MANAGER_RECORD_NOT_FOUND);
routerRecord.notifySessionCreationFailed(requestId);
return;
}
- if (!TextUtils.equals(manager.mLastSessionCreationRequest.mOldSession.getId(),
- oldSession.getId())) {
- Slog.w(TAG, "requestCreateSessionWithRouter2Locked: "
- + "Ignoring unmatched routing session.");
+ if (!TextUtils.equals(
+ manager.mLastSessionCreationRequest.mOldSession.getId(), oldSession.getId())) {
+ Slog.w(
+ TAG,
+ "requestCreateSessionWithRouter2Locked: "
+ + "Ignoring unmatched routing session.");
+ mMediaRouterMetricLogger.logOperationFailure(
+ MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_CREATE_SESSION,
+ MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_INVALID_SESSION_ID);
routerRecord.notifySessionCreationFailed(requestId);
return;
}
@@ -1364,8 +1384,13 @@
&& route.isSystemRoute()) {
route = manager.mLastSessionCreationRequest.mRoute;
} else {
- Slog.w(TAG, "requestCreateSessionWithRouter2Locked: "
- + "Ignoring unmatched route.");
+ Slog.w(
+ TAG,
+ "requestCreateSessionWithRouter2Locked: "
+ + "Ignoring unmatched route.");
+ mMediaRouterMetricLogger.logOperationFailure(
+ MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_CREATE_SESSION,
+ MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_INVALID_ROUTE_ID);
routerRecord.notifySessionCreationFailed(requestId);
return;
}
@@ -1376,14 +1401,19 @@
if (route.isSystemRoute()
&& !routerRecord.hasSystemRoutingPermission()
&& !TextUtils.equals(route.getId(), defaultRouteId)) {
- Slog.w(TAG, "MODIFY_AUDIO_ROUTING permission is required to transfer to"
- + route);
+ Slog.w(TAG, "MODIFY_AUDIO_ROUTING permission is required to transfer to" + route);
+ mMediaRouterMetricLogger.logOperationFailure(
+ MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_CREATE_SESSION,
+ MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_PERMISSION_DENIED);
routerRecord.notifySessionCreationFailed(requestId);
return;
}
}
long uniqueRequestId = toUniqueRequestId(routerRecord.mRouterId, requestId);
+ mMediaRouterMetricLogger.addRequestInfo(
+ uniqueRequestId,
+ MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_CREATE_SESSION);
userHandler.sendMessage(
obtainMessage(
UserHandler::requestCreateSessionWithRouter2OnHandler,
@@ -1403,6 +1433,9 @@
final RouterRecord routerRecord = mAllRouterRecords.get(binder);
if (routerRecord == null) {
+ mMediaRouterMetricLogger.logOperationFailure(
+ MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_SELECT_ROUTE,
+ MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_ROUTER_RECORD_NOT_FOUND);
return;
}
@@ -1411,6 +1444,9 @@
TextUtils.formatSimple(
"selectRouteWithRouter2 | router: %s(id: %d), route: %s",
routerRecord.mPackageName, routerRecord.mRouterId, route.getId()));
+ mMediaRouterMetricLogger.logOperationTriggered(
+ MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_SELECT_ROUTE,
+ MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNSPECIFIED);
routerRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(UserHandler::selectRouteOnHandler,
@@ -1425,6 +1461,9 @@
final RouterRecord routerRecord = mAllRouterRecords.get(binder);
if (routerRecord == null) {
+ mMediaRouterMetricLogger.logOperationFailure(
+ MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_DESELECT_ROUTE,
+ MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_ROUTER_RECORD_NOT_FOUND);
return;
}
@@ -1433,6 +1472,9 @@
TextUtils.formatSimple(
"deselectRouteWithRouter2 | router: %s(id: %d), route: %s",
routerRecord.mPackageName, routerRecord.mRouterId, route.getId()));
+ mMediaRouterMetricLogger.logOperationTriggered(
+ MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_DESELECT_ROUTE,
+ MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNSPECIFIED);
routerRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(UserHandler::deselectRouteOnHandler,
@@ -1450,6 +1492,9 @@
final RouterRecord routerRecord = mAllRouterRecords.get(binder);
if (routerRecord == null) {
+ mMediaRouterMetricLogger.logOperationFailure(
+ MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_TRANSFER_TO_ROUTE,
+ MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_ROUTER_RECORD_NOT_FOUND);
return;
}
@@ -1458,6 +1503,9 @@
TextUtils.formatSimple(
"transferToRouteWithRouter2 | router: %s(id: %d), route: %s",
routerRecord.mPackageName, routerRecord.mRouterId, route.getId()));
+ mMediaRouterMetricLogger.logOperationTriggered(
+ MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_TRANSFER_TO_ROUTE,
+ MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNSPECIFIED);
UserHandler userHandler = routerRecord.mUserRecord.mHandler;
String defaultRouteId = userHandler.getSystemProvider().getDefaultRoute().getId();
@@ -1516,6 +1564,9 @@
final RouterRecord routerRecord = mAllRouterRecords.get(binder);
if (routerRecord == null) {
+ mMediaRouterMetricLogger.logOperationFailure(
+ MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_RELEASE_SESSION,
+ MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_ROUTER_RECORD_NOT_FOUND);
return;
}
@@ -1794,6 +1845,9 @@
.findRouterWithSessionLocked(uniqueSessionId);
long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
+ mMediaRouterMetricLogger.addRequestInfo(
+ uniqueRequestId, MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_SELECT_ROUTE);
+
managerRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(UserHandler::selectRouteOnHandler,
managerRecord.mUserRecord.mHandler,
@@ -1820,6 +1874,10 @@
.findRouterWithSessionLocked(uniqueSessionId);
long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
+ mMediaRouterMetricLogger.addRequestInfo(
+ uniqueRequestId,
+ MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_DESELECT_ROUTE);
+
managerRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(UserHandler::deselectRouteOnHandler,
managerRecord.mUserRecord.mHandler,
@@ -1851,6 +1909,10 @@
.findRouterWithSessionLocked(uniqueSessionId);
long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
+ mMediaRouterMetricLogger.addRequestInfo(
+ uniqueRequestId,
+ MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_TRANSFER_TO_ROUTE);
+
managerRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(
UserHandler::transferToRouteOnHandler,
@@ -2792,7 +2854,8 @@
if (!addedRoutes.isEmpty()) {
// If routes were added, newInfo cannot be null.
- Slog.i(TAG,
+ Slog.i(
+ TAG,
toLoggingMessage(
/* source= */ "addProviderRoutes",
newInfo.getUniqueId(),
@@ -2954,7 +3017,7 @@
private void selectRouteOnHandler(long uniqueRequestId, @Nullable RouterRecord routerRecord,
@NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
if (!checkArgumentsForSessionControl(routerRecord, uniqueSessionId, route,
- "selecting")) {
+ "selecting", uniqueRequestId)) {
return;
}
@@ -2963,8 +3026,12 @@
if (provider == null) {
return;
}
- provider.selectRoute(uniqueRequestId, getOriginalId(uniqueSessionId),
- route.getOriginalId());
+ provider.selectRoute(
+ uniqueRequestId, getOriginalId(uniqueSessionId), route.getOriginalId());
+
+ // Log the success result.
+ mMediaRouterMetricLogger.logRequestResult(
+ uniqueRequestId, MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_SUCCESS);
}
// routerRecord can be null if the session is system's or RCN.
@@ -2972,7 +3039,7 @@
@Nullable RouterRecord routerRecord,
@NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
if (!checkArgumentsForSessionControl(routerRecord, uniqueSessionId, route,
- "deselecting")) {
+ "deselecting", uniqueRequestId)) {
return;
}
@@ -2982,8 +3049,12 @@
return;
}
- provider.deselectRoute(uniqueRequestId, getOriginalId(uniqueSessionId),
- route.getOriginalId());
+ provider.deselectRoute(
+ uniqueRequestId, getOriginalId(uniqueSessionId), route.getOriginalId());
+
+ // Log the success result.
+ mMediaRouterMetricLogger.logRequestResult(
+ uniqueRequestId, MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_SUCCESS);
}
// routerRecord can be null if the session is system's or RCN.
@@ -2996,7 +3067,7 @@
@NonNull MediaRoute2Info route,
@RoutingSessionInfo.TransferReason int transferReason) {
if (!checkArgumentsForSessionControl(routerRecord, uniqueSessionId, route,
- "transferring to")) {
+ "transferring to", uniqueRequestId)) {
return;
}
@@ -3016,18 +3087,25 @@
getOriginalId(uniqueSessionId),
route.getOriginalId(),
transferReason);
+
+ // Log the success result.
+ mMediaRouterMetricLogger.logRequestResult(
+ uniqueRequestId, MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_SUCCESS);
}
// routerRecord is null if and only if the session is created without the request, which
// includes the system's session and RCN cases.
private boolean checkArgumentsForSessionControl(@Nullable RouterRecord routerRecord,
@NonNull String uniqueSessionId, @NonNull MediaRoute2Info route,
- @NonNull String description) {
+ @NonNull String description, long uniqueRequestId) {
final String providerId = route.getProviderId();
final MediaRoute2Provider provider = findProvider(providerId);
if (provider == null) {
Slog.w(TAG, "Ignoring " + description + " route since no provider found for "
+ "given route=" + route);
+ mMediaRouterMetricLogger.logRequestResult(
+ uniqueRequestId,
+ MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_INVALID_COMMAND);
return false;
}
@@ -3050,6 +3128,9 @@
+ getPackageNameFromNullableRecord(matchingRecord)
+ " route="
+ route);
+ mMediaRouterMetricLogger.logRequestResult(
+ uniqueRequestId,
+ MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_ROUTER_RECORD_NOT_FOUND);
return false;
}
@@ -3057,6 +3138,9 @@
if (sessionId == null) {
Slog.w(TAG, "Failed to get original session id from unique session id. "
+ "uniqueSessionId=" + uniqueSessionId);
+ mMediaRouterMetricLogger.logRequestResult(
+ uniqueRequestId,
+ MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_INVALID_SESSION_ID);
return false;
}
@@ -3168,6 +3252,10 @@
}
matchingRequest.mRouterRecord.notifySessionCreated(
toOriginalRequestId(uniqueRequestId), sessionInfo);
+
+ // Log the success result.
+ mMediaRouterMetricLogger.logRequestResult(
+ uniqueRequestId, MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_SUCCESS);
}
/**
@@ -3255,10 +3343,14 @@
// Currently, only manager records can get notified of failures.
// TODO(b/282936553): Notify regular routers of request failures.
+
+ // Log the request result.
+ mMediaRouterMetricLogger.logRequestResult(
+ uniqueRequestId, MediaRouterMetricLogger.convertResultFromReason(reason));
}
- private boolean handleSessionCreationRequestFailed(@NonNull MediaRoute2Provider provider,
- long uniqueRequestId, int reason) {
+ private boolean handleSessionCreationRequestFailed(
+ @NonNull MediaRoute2Provider provider, long uniqueRequestId, int reason) {
// Check whether the failure is about creating a session
SessionCreationRequest matchingRequest = null;
for (SessionCreationRequest request : mSessionCreationRequests) {
@@ -3385,8 +3477,8 @@
}
}
- private void notifySessionCreatedToManagers(long managerRequestId,
- @NonNull RoutingSessionInfo session) {
+ private void notifySessionCreatedToManagers(
+ long managerRequestId, @NonNull RoutingSessionInfo session) {
int requesterId = toRequesterId(managerRequestId);
int originalRequestId = toOriginalRequestId(managerRequestId);
diff --git a/services/core/java/com/android/server/media/MediaRouterMetricLogger.java b/services/core/java/com/android/server/media/MediaRouterMetricLogger.java
new file mode 100644
index 0000000..56d2a1b
--- /dev/null
+++ b/services/core/java/com/android/server/media/MediaRouterMetricLogger.java
@@ -0,0 +1,219 @@
+/*
+ * 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.
+ */
+
+package com.android.server.media;
+
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_FAILED_TO_REROUTE_SYSTEM_MEDIA;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_INVALID_COMMAND;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_NETWORK_ERROR;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_REJECTED;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_ROUTE_NOT_AVAILABLE;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNIMPLEMENTED;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNKNOWN_ERROR;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNSPECIFIED;
+
+import android.annotation.NonNull;
+import android.media.MediaRoute2ProviderService;
+import android.util.Log;
+import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
+import java.io.PrintWriter;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Logs metrics for MediaRouter2.
+ *
+ * @hide
+ */
+final class MediaRouterMetricLogger {
+ private static final String TAG = "MediaRouterMetricLogger";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final int REQUEST_INFO_CACHE_CAPACITY = 100;
+
+ /** LRU cache to store request info. */
+ private final RequestInfoCache mRequestInfoCache;
+
+ /** Constructor for {@link MediaRouterMetricLogger}. */
+ public MediaRouterMetricLogger() {
+ mRequestInfoCache = new RequestInfoCache(REQUEST_INFO_CACHE_CAPACITY);
+ }
+
+ /**
+ * Adds a new request info to the cache.
+ *
+ * @param uniqueRequestId The unique request id.
+ * @param eventType The event type.
+ */
+ public void addRequestInfo(long uniqueRequestId, int eventType) {
+ RequestInfo requestInfo = new RequestInfo(uniqueRequestId, eventType);
+ mRequestInfoCache.put(requestInfo.mUniqueRequestId, requestInfo);
+ }
+
+ /**
+ * Removes a request info from the cache.
+ *
+ * @param uniqueRequestId The unique request id.
+ */
+ public void removeRequestInfo(long uniqueRequestId) {
+ mRequestInfoCache.remove(uniqueRequestId);
+ }
+
+ /**
+ * Logs an operation failure.
+ *
+ * @param eventType The event type.
+ * @param result The result of the operation.
+ */
+ public void logOperationFailure(int eventType, int result) {
+ logMediaRouterEvent(eventType, result);
+ }
+
+ /**
+ * Logs an operation triggered.
+ *
+ * @param eventType The event type.
+ */
+ public void logOperationTriggered(int eventType, int result) {
+ logMediaRouterEvent(eventType, result);
+ }
+
+ /**
+ * Logs the result of a request.
+ *
+ * @param uniqueRequestId The unique request id.
+ * @param result The result of the request.
+ */
+ public void logRequestResult(long uniqueRequestId, int result) {
+ RequestInfo requestInfo = mRequestInfoCache.get(uniqueRequestId);
+ if (requestInfo == null) {
+ Slog.w(
+ TAG,
+ "logRequestResult: No RequestInfo found for uniqueRequestId="
+ + uniqueRequestId);
+ return;
+ }
+
+ int eventType = requestInfo.mEventType;
+ logMediaRouterEvent(eventType, result);
+
+ removeRequestInfo(uniqueRequestId);
+ }
+
+ /**
+ * Converts a reason code from {@link MediaRoute2ProviderService} to a result code for logging.
+ *
+ * @param reason The reason code from {@link MediaRoute2ProviderService}.
+ * @return The result code for logging.
+ */
+ public static int convertResultFromReason(int reason) {
+ switch (reason) {
+ case MediaRoute2ProviderService.REASON_UNKNOWN_ERROR:
+ return MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNKNOWN_ERROR;
+ case MediaRoute2ProviderService.REASON_REJECTED:
+ return MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_REJECTED;
+ case MediaRoute2ProviderService.REASON_NETWORK_ERROR:
+ return MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_NETWORK_ERROR;
+ case MediaRoute2ProviderService.REASON_ROUTE_NOT_AVAILABLE:
+ return MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_ROUTE_NOT_AVAILABLE;
+ case MediaRoute2ProviderService.REASON_INVALID_COMMAND:
+ return MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_INVALID_COMMAND;
+ case MediaRoute2ProviderService.REASON_UNIMPLEMENTED:
+ return MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNIMPLEMENTED;
+ case MediaRoute2ProviderService.REASON_FAILED_TO_REROUTE_SYSTEM_MEDIA:
+ return MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_FAILED_TO_REROUTE_SYSTEM_MEDIA;
+ default:
+ return MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNSPECIFIED;
+ }
+ }
+
+ /**
+ * Gets the size of the request info cache.
+ *
+ * @return The size of the request info cache.
+ */
+ @VisibleForTesting
+ public int getRequestCacheSize() {
+ return mRequestInfoCache.size();
+ }
+
+ private void logMediaRouterEvent(int eventType, int result) {
+ MediaRouterStatsLog.write(
+ MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED, eventType, result);
+
+ if (DEBUG) {
+ Slog.d(TAG, "logMediaRouterEvent: " + eventType + " " + result);
+ }
+ }
+
+ /** A cache for storing request info that evicts entries when it reaches its capacity. */
+ class RequestInfoCache extends LinkedHashMap<Long, RequestInfo> {
+
+ public final int capacity;
+
+ /**
+ * Constructor for {@link RequestInfoCache}.
+ *
+ * @param capacity The maximum capacity of the cache.
+ */
+ public RequestInfoCache(int capacity) {
+ super(capacity, 1.0f, true);
+ this.capacity = capacity;
+ }
+
+ @Override
+ protected boolean removeEldestEntry(Map.Entry<Long, RequestInfo> eldest) {
+ boolean shouldRemove = size() > capacity;
+ if (shouldRemove) {
+ Slog.d(TAG, "Evicted request info: " + eldest.getValue());
+ logOperationTriggered(
+ eldest.getValue().mEventType,
+ MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNSPECIFIED);
+ }
+ return shouldRemove;
+ }
+ }
+
+ /** Class to store request info. */
+ static class RequestInfo {
+ public final long mUniqueRequestId;
+ public final int mEventType;
+
+ /**
+ * Constructor for {@link RequestInfo}.
+ *
+ * @param uniqueRequestId The unique request id.
+ * @param eventType The event type.
+ */
+ RequestInfo(long uniqueRequestId, int eventType) {
+ mUniqueRequestId = uniqueRequestId;
+ mEventType = eventType;
+ }
+
+ /**
+ * Dumps the request info.
+ *
+ * @param pw The print writer.
+ * @param prefix The prefix for the output.
+ */
+ public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+ pw.println(prefix + "RequestInfo");
+ String indent = prefix + " ";
+ pw.println(indent + "mUniqueRequestId=" + mUniqueRequestId);
+ pw.println(indent + "mEventType=" + mEventType);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java
index 02f817e..6e5308e 100644
--- a/services/core/java/com/android/server/notification/GroupHelper.java
+++ b/services/core/java/com/android/server/notification/GroupHelper.java
@@ -35,6 +35,7 @@
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -130,8 +131,10 @@
mUngroupedAbuseNotifications = new ArrayMap<>();
// Contains the list of group summaries that were canceled when "singleton groups" were
- // force grouped. Used to remove the original group's children when an app cancels the
- // already removed summary. Key is userId|packageName|g:OriginalGroupName
+ // force grouped. Key is userId|packageName|g:OriginalGroupName. Used to:
+ // 1) remove the original group's children when an app cancels the already removed summary.
+ // 2) perform the same side effects that would happen if the group is removed because
+ // all its force-regrouped children are removed (e.g. firing its deleteIntent).
@GuardedBy("mAggregatedNotifications")
private final ArrayMap<FullyQualifiedGroupKey, CachedSummary>
mCanceledSummaries = new ArrayMap<>();
@@ -278,7 +281,11 @@
public void onNotificationRemoved(NotificationRecord record) {
try {
if (notificationForceGrouping()) {
- onNotificationRemoved(record, new ArrayList<>());
+ Slog.wtf(TAG,
+ "This overload of onNotificationRemoved() should not be called if "
+ + "notification_force_grouping is enabled!",
+ new Exception("call stack"));
+ onNotificationRemoved(record, new ArrayList<>(), false);
} else {
final StatusBarNotification sbn = record.getSbn();
maybeUngroup(sbn, true, sbn.getUserId());
@@ -926,10 +933,12 @@
*
* @param record the removed notification
* @param notificationList the full notification list from NotificationManagerService
+ * @param sendingDelete whether the removed notification is being removed in a way that sends
+ * its {@code deleteIntent}
*/
@FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUPING)
protected void onNotificationRemoved(final NotificationRecord record,
- final List<NotificationRecord> notificationList) {
+ final List<NotificationRecord> notificationList, boolean sendingDelete) {
final StatusBarNotification sbn = record.getSbn();
final String pkgName = sbn.getPackageName();
final int userId = record.getUserId();
@@ -973,9 +982,11 @@
}
// Try to cleanup cached summaries if notification was canceled (not snoozed)
+ // If the notification was cancelled by an action that fires its delete intent,
+ // also fire it for the cached summary.
if (record.isCanceled) {
maybeClearCanceledSummariesCache(pkgName, userId,
- record.getNotification().getGroup(), notificationList);
+ record.getNotification().getGroup(), notificationList, sendingDelete);
}
}
}
@@ -1759,13 +1770,18 @@
private void cacheCanceledSummary(NotificationRecord record) {
final FullyQualifiedGroupKey groupKey = new FullyQualifiedGroupKey(record.getUserId(),
record.getSbn().getPackageName(), record.getNotification().getGroup());
- mCanceledSummaries.put(groupKey, new CachedSummary(record.getSbn().getId(),
- record.getSbn().getTag(), record.getNotification().getGroup(), record.getKey()));
+ mCanceledSummaries.put(groupKey, new CachedSummary(
+ record.getSbn().getId(),
+ record.getSbn().getTag(),
+ record.getNotification().getGroup(),
+ record.getKey(),
+ record.getNotification().deleteIntent));
}
@GuardedBy("mAggregatedNotifications")
private void maybeClearCanceledSummariesCache(String pkgName, int userId,
- String groupName, List<NotificationRecord> notificationList) {
+ String groupName, List<NotificationRecord> notificationList,
+ boolean sendSummaryDelete) {
final FullyQualifiedGroupKey findKey = new FullyQualifiedGroupKey(userId, pkgName,
groupName);
CachedSummary summary = mCanceledSummaries.get(findKey);
@@ -1786,6 +1802,9 @@
}
if (!stillHasChildren) {
removeCachedSummary(pkgName, userId, summary);
+ if (sendSummaryDelete && summary.deleteIntent != null) {
+ mCallback.sendAppProvidedSummaryDeleteIntent(pkgName, summary.deleteIntent);
+ }
}
}
}
@@ -1965,7 +1984,8 @@
}
}
- record CachedSummary(int id, String tag, String originalGroupKey, String key) {}
+ record CachedSummary(int id, String tag, String originalGroupKey, String key,
+ @Nullable PendingIntent deleteIntent) { }
protected static class NotificationAttributes {
public final int flags;
@@ -2035,6 +2055,15 @@
// New callbacks for API abuse grouping
void removeAppProvidedSummary(String key);
+ /**
+ * Send a cached summary's deleteIntent, when the last of its original children is removed.
+ *
+ * <p>While technically the group summary was "canceled" much earlier (because it was the
+ * summary of a sparse group and its children got reparented), the posting package expected
+ * the summary's deleteIntent to fire when the summary is auto-dismissed.
+ */
+ void sendAppProvidedSummaryDeleteIntent(String pkg, PendingIntent deleteIntent);
+
void removeNotificationFromCanceledGroup(int userId, String pkg, String groupKey,
int cancelReason);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 6fddfb5..0f1d28d 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2809,7 +2809,6 @@
mNotificationChannelLogger,
mAppOps,
mUserProfiles,
- mUgmInternal,
mShowReviewPermissionsNotification,
Clock.systemUTC());
mRankingHelper = new RankingHelper(getContext(), mRankingHandler, mPreferencesHelper,
@@ -3211,6 +3210,11 @@
}
@Override
+ public void sendAppProvidedSummaryDeleteIntent(String pkg, PendingIntent deleteIntent) {
+ sendDeleteIntent(deleteIntent, pkg);
+ }
+
+ @Override
public void removeNotificationFromCanceledGroup(int userId, String pkg,
String groupKey, int cancelReason) {
synchronized (mNotificationLock) {
@@ -7205,7 +7209,13 @@
final Uri originalSoundUri =
(originalChannel != null) ? originalChannel.getSound() : null;
if (soundUri != null && !Objects.equals(originalSoundUri, soundUri)) {
- PermissionHelper.grantUriPermission(mUgmInternal, soundUri, sourceUid);
+ Binder.withCleanCallingIdentity(() -> {
+ mUgmInternal.checkGrantUriPermission(sourceUid, null,
+ ContentProvider.getUriWithoutUserId(soundUri),
+ Intent.FLAG_GRANT_READ_URI_PERMISSION,
+ ContentProvider.getUserIdFromUri(soundUri,
+ UserHandle.getUserId(sourceUid)));
+ });
}
}
@@ -9898,7 +9908,8 @@
if (notificationForceGrouping()) {
mHandler.post(() -> {
synchronized (mNotificationLock) {
- mGroupHelper.onNotificationRemoved(r, mNotificationList);
+ mGroupHelper.onNotificationRemoved(r, mNotificationList,
+ /* sendingDelete= */ false);
}
});
} else {
@@ -10826,20 +10837,7 @@
// tell the app
if (sendDelete) {
- final PendingIntent deleteIntent = r.getNotification().deleteIntent;
- if (deleteIntent != null) {
- try {
- // make sure deleteIntent cannot be used to start activities from background
- LocalServices.getService(ActivityManagerInternal.class)
- .clearPendingIntentAllowBgActivityStarts(deleteIntent.getTarget(),
- ALLOWLIST_TOKEN);
- deleteIntent.send();
- } catch (PendingIntent.CanceledException ex) {
- // do nothing - there's no relevant way to recover, and
- // no reason to let this propagate
- Slog.w(TAG, "canceled PendingIntent for " + r.getSbn().getPackageName(), ex);
- }
- }
+ sendDeleteIntent(r.getNotification().deleteIntent, r.getSbn().getPackageName());
}
// Only cancel these if this notification actually got to be posted.
@@ -10854,7 +10852,7 @@
mHandler.removeCallbacksAndEqualMessages(r.getKey());
mHandler.post(() -> {
synchronized (NotificationManagerService.this.mNotificationLock) {
- mGroupHelper.onNotificationRemoved(r, mNotificationList);
+ mGroupHelper.onNotificationRemoved(r, mNotificationList, sendDelete);
}
});
@@ -10952,6 +10950,21 @@
}
}
+ private static void sendDeleteIntent(@Nullable PendingIntent deleteIntent, String fromPkg) {
+ if (deleteIntent != null) {
+ try {
+ // make sure deleteIntent cannot be used to start activities from background
+ LocalServices.getService(ActivityManagerInternal.class)
+ .clearPendingIntentAllowBgActivityStarts(deleteIntent.getTarget(),
+ ALLOWLIST_TOKEN);
+ deleteIntent.send();
+ } catch (PendingIntent.CanceledException ex) {
+ // There's no relevant way to recover, and no reason to let this propagate
+ Slog.w(TAG, "canceled PendingIntent for " + fromPkg, ex);
+ }
+ }
+ }
+
@VisibleForTesting
void updateUriPermissions(@Nullable NotificationRecord newRecord,
@Nullable NotificationRecord oldRecord, String targetPkg, int targetUserId) {
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 5a58f45..cec5a93 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -37,7 +37,10 @@
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.Person;
+import android.content.ContentProvider;
+import android.content.ContentResolver;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ShortcutInfo;
@@ -46,6 +49,7 @@
import android.media.AudioSystem;
import android.metrics.LogMaker;
import android.net.Uri;
+import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
@@ -813,7 +817,13 @@
}
if ((android.app.Flags.nmSummarizationUi() || android.app.Flags.nmSummarization())
&& signals.containsKey(KEY_SUMMARIZATION)) {
- mSummarization = signals.getString(KEY_SUMMARIZATION);
+ CharSequence summary = signals.getCharSequence(KEY_SUMMARIZATION,
+ signals.getString(KEY_SUMMARIZATION));
+ if (summary != null) {
+ mSummarization = summary.toString();
+ } else {
+ mSummarization = null;
+ }
EventLogTags.writeNotificationAdjusted(getKey(),
KEY_SUMMARIZATION, Boolean.toString(mSummarization != null));
}
@@ -1532,15 +1542,21 @@
* {@link #mGrantableUris}. Otherwise, this will either log or throw
* {@link SecurityException} depending on target SDK of enqueuing app.
*/
- private void visitGrantableUri(Uri uri, boolean userOverriddenUri,
- boolean isSound) {
+ private void visitGrantableUri(Uri uri, boolean userOverriddenUri, boolean isSound) {
+ if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return;
+
if (mGrantableUris != null && mGrantableUris.contains(uri)) {
return; // already verified this URI
}
final int sourceUid = getSbn().getUid();
+ final long ident = Binder.clearCallingIdentity();
try {
- PermissionHelper.grantUriPermission(mUgmInternal, uri, sourceUid);
+ // This will throw a SecurityException if the caller can't grant.
+ mUgmInternal.checkGrantUriPermission(sourceUid, null,
+ ContentProvider.getUriWithoutUserId(uri),
+ Intent.FLAG_GRANT_READ_URI_PERMISSION,
+ ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(sourceUid)));
if (mGrantableUris == null) {
mGrantableUris = new ArraySet<>();
@@ -1560,6 +1576,8 @@
}
}
}
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
}
diff --git a/services/core/java/com/android/server/notification/PermissionHelper.java b/services/core/java/com/android/server/notification/PermissionHelper.java
index 1464d48..b6f4889 100644
--- a/services/core/java/com/android/server/notification/PermissionHelper.java
+++ b/services/core/java/com/android/server/notification/PermissionHelper.java
@@ -25,25 +25,19 @@
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.companion.virtual.VirtualDeviceManager;
-import android.content.ContentProvider;
-import android.content.ContentResolver;
import android.content.Context;
-import android.content.Intent;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
-import android.net.Uri;
import android.os.Binder;
import android.os.RemoteException;
-import android.os.UserHandle;
import android.permission.IPermissionManager;
import android.util.ArrayMap;
import android.util.Pair;
import android.util.Slog;
import com.android.internal.util.ArrayUtils;
-import com.android.server.uri.UriGrantsManagerInternal;
import java.util.Collections;
import java.util.HashSet;
@@ -64,7 +58,7 @@
private final IPermissionManager mPermManager;
public PermissionHelper(Context context, IPackageManager packageManager,
- IPermissionManager permManager) {
+ IPermissionManager permManager) {
mContext = context;
mPackageManager = packageManager;
mPermManager = permManager;
@@ -304,19 +298,6 @@
return false;
}
- static void grantUriPermission(final UriGrantsManagerInternal ugmInternal, Uri uri,
- int sourceUid) {
- if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return;
-
- Binder.withCleanCallingIdentity(() -> {
- // This will throw a SecurityException if the caller can't grant.
- ugmInternal.checkGrantUriPermission(sourceUid, null,
- ContentProvider.getUriWithoutUserId(uri),
- Intent.FLAG_GRANT_READ_URI_PERMISSION,
- ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(sourceUid)));
- });
- }
-
public static class PackagePermission {
public final String packageName;
public final @UserIdInt int userId;
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 0fc182f..b26b457 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -42,6 +42,7 @@
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED;
+import static com.android.server.notification.PreferencesHelper.LockableAppFields.USER_LOCKED_BUBBLE;
import static com.android.server.notification.PreferencesHelper.LockableAppFields.USER_LOCKED_PROMOTABLE;
import android.annotation.FlaggedApi;
@@ -99,7 +100,6 @@
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.notification.PermissionHelper.PackagePermission;
-import com.android.server.uri.UriGrantsManagerInternal;
import org.json.JSONArray;
import org.json.JSONException;
@@ -155,6 +155,7 @@
private static final String ATT_VERSION = "version";
private static final String ATT_NAME = "name";
private static final String ATT_UID = "uid";
+ private static final String ATT_LAST_BUBBLES_VERSION_UPGRADE = "last_bubbles_version_upgrade";
private static final String ATT_USERID = "userid";
private static final String ATT_ID = "id";
@@ -226,7 +227,6 @@
private final NotificationChannelLogger mNotificationChannelLogger;
private final AppOpsManager mAppOps;
private final ManagedServices.UserProfiles mUserProfiles;
- private final UriGrantsManagerInternal mUgmInternal;
private SparseBooleanArray mBadgingEnabled;
private SparseBooleanArray mBubblesEnabled;
@@ -245,7 +245,6 @@
ZenModeHelper zenHelper, PermissionHelper permHelper, PermissionManager permManager,
NotificationChannelLogger notificationChannelLogger,
AppOpsManager appOpsManager, ManagedServices.UserProfiles userProfiles,
- UriGrantsManagerInternal ugmInternal,
boolean showReviewPermissionsNotification, Clock clock) {
mContext = context;
mZenModeHelper = zenHelper;
@@ -256,7 +255,6 @@
mNotificationChannelLogger = notificationChannelLogger;
mAppOps = appOpsManager;
mUserProfiles = userProfiles;
- mUgmInternal = ugmInternal;
mShowReviewPermissionsNotification = showReviewPermissionsNotification;
mIsMediaNotificationFilteringEnabled = context.getResources()
.getBoolean(R.bool.config_quickSettingsShowMediaPlayer);
@@ -286,7 +284,8 @@
if (!TAG_RANKING.equals(tag)) return;
final int xmlVersion = parser.getAttributeInt(null, ATT_VERSION, -1);
- boolean upgradeForBubbles = xmlVersion == XML_VERSION_BUBBLES_UPGRADE;
+ boolean upgradeForBubbles = parser.getAttributeInt(null,
+ ATT_LAST_BUBBLES_VERSION_UPGRADE, -1) < Build.VERSION.SDK_INT;
boolean migrateToPermission = (xmlVersion < XML_VERSION_NOTIF_PERMISSION);
if (mShowReviewPermissionsNotification
&& (xmlVersion < XML_VERSION_REVIEW_PERMISSIONS_NOTIFICATION)) {
@@ -337,15 +336,19 @@
}
boolean skipWarningLogged = false;
boolean skipGroupWarningLogged = false;
- boolean hasSAWPermission = false;
- if (upgradeForBubbles && uid != UNKNOWN_UID) {
- hasSAWPermission = mAppOps.noteOpNoThrow(
- OP_SYSTEM_ALERT_WINDOW, uid, name, null,
- "check-notif-bubble") == AppOpsManager.MODE_ALLOWED;
+ int bubblePref = parser.getAttributeInt(null, ATT_ALLOW_BUBBLE,
+ DEFAULT_BUBBLE_PREFERENCE);
+ boolean bubbleLocked = (parser.getAttributeInt(null,
+ ATT_APP_USER_LOCKED_FIELDS, DEFAULT_LOCKED_APP_FIELDS) & USER_LOCKED_BUBBLE)
+ != 0;
+ if (!bubbleLocked
+ && upgradeForBubbles
+ && uid != UNKNOWN_UID
+ && mAppOps.noteOpNoThrow(OP_SYSTEM_ALERT_WINDOW, uid, name, null,
+ "check-notif-bubble") == AppOpsManager.MODE_ALLOWED) {
+ // User hasn't changed bubble pref & the app has SAW, so allow all bubbles.
+ bubblePref = BUBBLE_PREFERENCE_ALL;
}
- int bubblePref = hasSAWPermission
- ? BUBBLE_PREFERENCE_ALL
- : parser.getAttributeInt(null, ATT_ALLOW_BUBBLE, DEFAULT_BUBBLE_PREFERENCE);
int appImportance = parser.getAttributeInt(null, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
// when data is loaded from disk it's loaded as USER_ALL, but restored data that
@@ -684,6 +687,7 @@
public void writeXml(TypedXmlSerializer out, boolean forBackup, int userId) throws IOException {
out.startTag(null, TAG_RANKING);
out.attributeInt(null, ATT_VERSION, XML_VERSION);
+ out.attributeInt(null, ATT_LAST_BUBBLES_VERSION_UPGRADE, Build.VERSION.SDK_INT);
if (mHideSilentStatusBarIcons != DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS) {
out.startTag(null, TAG_STATUS_ICONS);
out.attributeBoolean(null, ATT_HIDE_SILENT, mHideSilentStatusBarIcons);
@@ -1187,11 +1191,6 @@
}
clearLockedFieldsLocked(channel);
- // Verify that the app has permission to read the sound Uri
- // Only check for new channels, as regular apps can only set sound
- // before creating. See: {@link NotificationChannel#setSound}
- PermissionHelper.grantUriPermission(mUgmInternal, channel.getSound(), uid);
-
channel.setImportanceLockedByCriticalDeviceFunction(
r.defaultAppLockedImportance || r.fixedImportance);
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
index 60d028b..f379761 100644
--- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
@@ -392,7 +392,7 @@
private boolean installedByAdb(String initiatingPackageName) {
// GTS tests needs to adopt shell identity to install apps.
- if(!SystemProperties.get("gts.transparency.bg-install-apps").isEmpty()) {
+ if(!SystemProperties.get("debug.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");
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 635ef06..af788ea 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -21,6 +21,7 @@
import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_UPDATED_BY_DO;
import static android.content.pm.DataLoaderType.INCREMENTAL;
import static android.content.pm.DataLoaderType.STREAMING;
+import static android.content.pm.Flags.cloudCompilationVerification;
import static android.content.pm.PackageInstaller.LOCATION_DATA_APP;
import static android.content.pm.PackageInstaller.UNARCHIVAL_OK;
import static android.content.pm.PackageInstaller.UNARCHIVAL_STATUS_UNSET;
@@ -3687,6 +3688,10 @@
CollectionUtils.addAll(stagedSplitTypes, apk.getSplitTypes());
}
+ if (cloudCompilationVerification()) {
+ verifySdmSignatures(artManagedFilePaths, mSigningDetails);
+ }
+
if (removeSplitList.size() > 0) {
if (pkgInfo == null) {
throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
@@ -4028,6 +4033,14 @@
File targetArtManagedFile = new File(
ArtManagedInstallFileHelper.getTargetPathForApk(path, targetFile.getPath()));
stageFileLocked(artManagedFile, targetArtManagedFile);
+ if (!artManagedFile.equals(targetArtManagedFile)) {
+ // The file has been renamed. Update the list to reflect the change.
+ for (int i = 0; i < artManagedFilePaths.size(); ++i) {
+ if (artManagedFilePaths.get(i).equals(path)) {
+ artManagedFilePaths.set(i, targetArtManagedFile.getAbsolutePath());
+ }
+ }
+ }
}
}
@@ -4309,6 +4322,37 @@
}
/**
+ * Verifies the signatures of SDM files.
+ *
+ * SDM is a file format that contains the cloud compilation artifacts. As a requirement, the SDM
+ * file should be signed with the same key as the APK.
+ *
+ * TODO(b/377474232): Move this logic to ART Service.
+ */
+ private static void verifySdmSignatures(List<String> artManagedFilePaths,
+ SigningDetails expectedSigningDetails) throws PackageManagerException {
+ ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+ for (String path : artManagedFilePaths) {
+ if (!path.endsWith(".sdm")) {
+ continue;
+ }
+ // SDM is a format introduced in Android 16, so we don't need to support older
+ // signature schemes.
+ int minSignatureScheme = SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3;
+ ParseResult<SigningDetails> verified =
+ ApkSignatureVerifier.verify(input, path, minSignatureScheme);
+ if (verified.isError()) {
+ throw new PackageManagerException(
+ INSTALL_FAILED_INVALID_APK, "Failed to verify SDM signatures");
+ }
+ if (!expectedSigningDetails.signaturesMatchExactly(verified.getResult())) {
+ throw new PackageManagerException(
+ INSTALL_FAILED_INVALID_APK, "SDM signatures are inconsistent with APK");
+ }
+ }
+ }
+
+ /**
* @return the uid of the owner this session
*/
public int getInstallerUid() {
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index d351305..66e9e77 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -1792,7 +1792,7 @@
void injectPostToHandlerDebounced(@NonNull final Object token, @NonNull final Runnable r) {
Objects.requireNonNull(token);
Objects.requireNonNull(r);
- synchronized (mServiceLock) {
+ synchronized (mHandler) {
mHandler.removeCallbacksAndMessages(token);
mHandler.postDelayed(r, token, CALLBACK_DELAY);
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index f27194a..46dc758 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3801,7 +3801,7 @@
return true;
}
}
- // fall through
+ break;
case KeyEvent.KEYCODE_ESCAPE:
if (firstDown && event.isMetaPressed()) {
notifyKeyGestureCompleted(event,
@@ -6706,12 +6706,6 @@
return mKeyguardDelegate.isInputRestricted();
}
- /** {@inheritDoc} */
- @Override
- public boolean isKeyguardUnoccluding() {
- return keyguardOn() && !mWindowManagerFuncs.isAppTransitionStateIdle();
- }
-
@Override
public void dismissKeyguardLw(IKeyguardDismissCallback callback, CharSequence message) {
if (mKeyguardDelegate != null && mKeyguardDelegate.isShowing()) {
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index cc31bb1..d7de22e 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -337,12 +337,6 @@
void moveDisplayToTopIfAllowed(int displayId);
/**
- * Return whether the app transition state is idle.
- * @return {@code true} if app transition state is idle on the default display.
- */
- boolean isAppTransitionStateIdle();
-
- /**
* Enables the screen if all conditions are met.
*/
void enableScreenIfNeeded();
@@ -989,14 +983,6 @@
public boolean isKeyguardOccluded();
/**
- * Return whether the keyguard is unoccluding.
- * @return {@code true} if the keyguard is unoccluding.
- */
- default boolean isKeyguardUnoccluding() {
- return false;
- }
-
- /**
* @return true if in keyguard is on.
*/
boolean isKeyguardShowing();
diff --git a/services/core/java/com/android/server/security/advancedprotection/OWNERS b/services/core/java/com/android/server/security/advancedprotection/OWNERS
index 9bf5e58..8336ee8 100644
--- a/services/core/java/com/android/server/security/advancedprotection/OWNERS
+++ b/services/core/java/com/android/server/security/advancedprotection/OWNERS
@@ -1 +1,2 @@
file:platform/frameworks/base:main:/core/java/android/security/advancedprotection/OWNERS
+per-file features/UsbDataAdvancedProtectionHook.java = georgechan@google.com, maunik@google.com
diff --git a/services/core/java/com/android/server/updates/CertPinInstallReceiver.java b/services/core/java/com/android/server/updates/CertPinInstallReceiver.java
index 250e99b..c8e7a8d 100644
--- a/services/core/java/com/android/server/updates/CertPinInstallReceiver.java
+++ b/services/core/java/com/android/server/updates/CertPinInstallReceiver.java
@@ -19,7 +19,10 @@
import android.content.Context;
import android.content.Intent;
+import java.io.File;
+
public class CertPinInstallReceiver extends ConfigUpdateInstallReceiver {
+ private static final String KEYCHAIN_DIR = "/data/misc/keychain/";
public CertPinInstallReceiver() {
super("/data/misc/keychain/", "pins", "metadata/", "version");
@@ -27,7 +30,22 @@
@Override
public void onReceive(final Context context, final Intent intent) {
- if (!com.android.server.flags.Flags.certpininstallerRemoval()) {
+ if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
+ if (com.android.server.flags.Flags.certpininstallerRemoval()) {
+ File pins = new File(KEYCHAIN_DIR + "pins");
+ if (pins.exists()) {
+ pins.delete();
+ }
+ File version = new File(KEYCHAIN_DIR + "metadata/version");
+ if (version.exists()) {
+ version.delete();
+ }
+ File metadata = new File(KEYCHAIN_DIR + "metadata");
+ if (metadata.exists()) {
+ metadata.delete();
+ }
+ }
+ } else if (!com.android.server.flags.Flags.certpininstallerRemoval()) {
super.onReceive(context, intent);
}
}
diff --git a/services/core/java/com/android/server/vibrator/VendorVibrationSession.java b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
index 621a128..94e8ca5 100644
--- a/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
+++ b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
@@ -51,7 +51,9 @@
final class VendorVibrationSession extends IVibrationSession.Stub
implements VibrationSession, CancellationSignal.OnCancelListener, IBinder.DeathRecipient {
private static final String TAG = "VendorVibrationSession";
- private static final boolean DEBUG = false;
+ // To enable these logs, run:
+ // 'adb shell setprop persist.log.tag.VendorVibrationSession DEBUG && adb reboot'
+ private static final boolean DEBUG = VibratorDebugUtils.isDebuggable(TAG);
/** Calls into VibratorManager functionality needed for playing an {@link ExternalVibration}. */
interface VibratorManagerHooks {
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index cb9988f..ab30cdc 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -36,7 +36,9 @@
/** Plays a {@link HalVibration} in dedicated thread. */
final class VibrationThread extends Thread {
static final String TAG = "VibrationThread";
- static final boolean DEBUG = false;
+ // To enable these logs, run:
+ // 'adb shell setprop persist.log.tag.VibrationThread DEBUG && adb reboot'
+ static final boolean DEBUG = VibratorDebugUtils.isDebuggable(TAG);
/** Calls into VibratorManager functionality needed for playing a {@link HalVibration}. */
interface VibratorManagerHooks {
diff --git a/services/core/java/com/android/server/vibrator/VibratorDebugUtils.java b/services/core/java/com/android/server/vibrator/VibratorDebugUtils.java
new file mode 100644
index 0000000..9f37e76
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/VibratorDebugUtils.java
@@ -0,0 +1,37 @@
+/*
+ * 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.server.vibrator;
+
+import android.util.Log;
+
+class VibratorDebugUtils {
+
+ /**
+ * Checks if debugging is enabled for the specified tag or globally.
+ *
+ * <p>To enable debugging:<br>
+ * {@code adb shell setprop persist.log.tag.Vibrator_All DEBUG}<br>
+ * To disable debugging:<br>
+ * {@code adb shell setprop persist.log.tag.Vibrator_All \"\" }
+ *
+ * @param tag The tag to check for debugging. Use the tag name from the calling class.
+ * @return True if debugging is enabled for the tag or globally (Vibrator_All), false otherwise.
+ */
+ public static boolean isDebuggable(String tag) {
+ return Log.isLoggable(tag, Log.DEBUG) || Log.isLoggable("Vibrator_All", Log.DEBUG);
+ }
+}
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index ce91e63..b953097 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -108,7 +108,9 @@
private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service";
private static final String VIBRATOR_CONTROL_SERVICE =
"android.frameworks.vibrator.IVibratorControlService/default";
- private static final boolean DEBUG = false;
+ // To enable these logs, run:
+ // 'adb shell setprop persist.log.tag.VibratorManagerService DEBUG && adb reboot'
+ private static final boolean DEBUG = VibratorDebugUtils.isDebuggable(TAG);
private static final VibrationAttributes DEFAULT_ATTRIBUTES =
new VibrationAttributes.Builder().build();
private static final int ATTRIBUTES_ALL_BYPASS_FLAGS =
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 58534b9..1299a4d 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -294,19 +294,6 @@
}
}
- void onAppWindowTransition(int displayId, int transition) {
- if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
- mAccessibilityTracing.logTrace(TAG + ".onAppWindowTransition",
- FLAGS_MAGNIFICATION_CALLBACK,
- "displayId=" + displayId + "; transition=" + transition);
- }
- final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
- if (displayMagnifier != null) {
- displayMagnifier.onAppWindowTransition(displayId, transition);
- }
- // Not relevant for the window observer.
- }
-
void onWMTransition(int displayId, @TransitionType int type, @TransitionFlags int flags) {
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
mAccessibilityTracing.logTrace(TAG + ".onWMTransition",
@@ -670,34 +657,6 @@
mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_DISPLAY_SIZE_CHANGED);
}
- void onAppWindowTransition(int displayId, int transition) {
- if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
- mAccessibilityTracing.logTrace(LOG_TAG + ".onAppWindowTransition",
- FLAGS_MAGNIFICATION_CALLBACK,
- "displayId=" + displayId + "; transition=" + transition);
- }
- if (DEBUG_WINDOW_TRANSITIONS) {
- Slog.i(LOG_TAG, "Window transition: "
- + AppTransition.appTransitionOldToString(transition)
- + " displayId: " + displayId);
- }
- final boolean isMagnifierActivated = isFullscreenMagnificationActivated();
- if (!isMagnifierActivated) {
- return;
- }
- switch (transition) {
- case WindowManager.TRANSIT_OLD_ACTIVITY_OPEN:
- case WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN:
- case WindowManager.TRANSIT_OLD_TASK_OPEN:
- case WindowManager.TRANSIT_OLD_TASK_TO_FRONT:
- case WindowManager.TRANSIT_OLD_WALLPAPER_OPEN:
- case WindowManager.TRANSIT_OLD_WALLPAPER_CLOSE:
- case WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_OPEN: {
- mUserContextChangedNotifier.onAppWindowTransition(transition);
- }
- }
- }
-
void onWMTransition(int displayId, @TransitionType int type, @TransitionFlags int flags) {
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
mAccessibilityTracing.logTrace(LOG_TAG + ".onWMTransition",
@@ -734,7 +693,7 @@
}
if (DEBUG_WINDOW_TRANSITIONS) {
Slog.i(LOG_TAG, "Window transition: "
- + AppTransition.appTransitionOldToString(transition)
+ + WindowManager.transitTypeToString(transition)
+ " displayId: " + windowState.getDisplayId());
}
final boolean isMagnifierActivated = isFullscreenMagnificationActivated();
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 7ce52b1..7da4beb 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -1113,11 +1113,11 @@
false /* fromClient */);
}
+ final EnterPipRequestedItem item = new EnterPipRequestedItem(r.token);
try {
- final EnterPipRequestedItem item = new EnterPipRequestedItem(r.token);
- mService.getLifecycleManager().scheduleTransactionItem(r.app.getThread(), item);
- return true;
- } catch (Exception e) {
+ return mService.getLifecycleManager().scheduleTransactionItem(r.app.getThread(), item);
+ } catch (RemoteException e) {
+ // TODO(b/323801078): remove Exception when cleanup
Slog.w(TAG, "Failed to send enter pip requested item: "
+ r.intent.getComponent(), e);
return false;
@@ -1129,10 +1129,11 @@
*/
void onPictureInPictureUiStateChanged(@NonNull ActivityRecord r,
PictureInPictureUiState pipState) {
+ final PipStateTransactionItem item = new PipStateTransactionItem(r.token, pipState);
try {
- final PipStateTransactionItem item = new PipStateTransactionItem(r.token, pipState);
mService.getLifecycleManager().scheduleTransactionItem(r.app.getThread(), item);
- } catch (Exception e) {
+ } catch (RemoteException e) {
+ // TODO(b/323801078): remove Exception when cleanup
Slog.w(TAG, "Failed to send pip state transaction item: "
+ r.intent.getComponent(), e);
}
@@ -1510,9 +1511,6 @@
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
if (r != null && r.isState(RESUMED, PAUSING)) {
- r.mDisplayContent.mAppTransition.overridePendingAppTransition(
- packageName, enterAnim, exitAnim, backgroundColor, null, null,
- r.mOverrideTaskTransition);
r.mTransitionController.setOverrideAnimation(
TransitionInfo.AnimationOptions.makeCustomAnimOptions(packageName,
enterAnim, 0 /* changeResId */, exitAnim,
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index d452d76..3cd4db7 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -253,6 +253,7 @@
import android.app.Activity;
import android.app.ActivityManager.TaskDescription;
import android.app.ActivityOptions;
+import android.app.IApplicationThread;
import android.app.IScreenCaptureObserver;
import android.app.PendingIntent;
import android.app.PictureInPictureParams;
@@ -672,9 +673,6 @@
// TODO(b/317000737): Replace it with visibility states lookup.
int mTransitionChangeFlags;
- /** Whether we need to setup the animation to animate only within the letterbox. */
- private boolean mNeedsLetterboxedAnimation;
-
/**
* @see #currentLaunchCanTurnScreenOn()
*/
@@ -1351,15 +1349,16 @@
this, displayId);
return;
}
- try {
- ProtoLog.v(WM_DEBUG_SWITCH, "Reporting activity moved to "
- + "display, activityRecord=%s, displayId=%d, config=%s", this, displayId,
- config);
+ ProtoLog.v(WM_DEBUG_SWITCH, "Reporting activity moved to "
+ + "display, activityRecord=%s, displayId=%d, config=%s", this, displayId,
+ config);
- final MoveToDisplayItem item =
- new MoveToDisplayItem(token, displayId, config, activityWindowInfo);
+ final MoveToDisplayItem item =
+ new MoveToDisplayItem(token, displayId, config, activityWindowInfo);
+ try {
mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), item);
} catch (RemoteException e) {
+ // TODO(b/323801078): remove Exception when cleanup
// If process died, whatever.
}
}
@@ -1371,14 +1370,15 @@
+ "update - client not running, activityRecord=%s", this);
return;
}
- try {
- ProtoLog.v(WM_DEBUG_CONFIGURATION, "Sending new config to %s, "
- + "config: %s", this, config);
+ ProtoLog.v(WM_DEBUG_CONFIGURATION, "Sending new config to %s, "
+ + "config: %s", this, config);
- final ActivityConfigurationChangeItem item =
- new ActivityConfigurationChangeItem(token, config, activityWindowInfo);
+ final ActivityConfigurationChangeItem item =
+ new ActivityConfigurationChangeItem(token, config, activityWindowInfo);
+ try {
mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), item);
} catch (RemoteException e) {
+ // TODO(b/323801078): remove Exception when cleanup
// If process died, whatever.
}
}
@@ -1393,19 +1393,18 @@
if (onTop) {
app.addToPendingTop();
}
- try {
- ProtoLog.v(WM_DEBUG_STATES, "Sending position change to %s, onTop: %b",
- this, onTop);
+ ProtoLog.v(WM_DEBUG_STATES, "Sending position change to %s, onTop: %b",
+ this, onTop);
- final TopResumedActivityChangeItem item =
- new TopResumedActivityChangeItem(token, onTop);
- mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), item);
+ final TopResumedActivityChangeItem item = new TopResumedActivityChangeItem(token, onTop);
+ try {
+ return mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), item);
} catch (RemoteException e) {
+ // TODO(b/323801078): remove Exception when cleanup
// If process died, whatever.
Slog.w(TAG, "Failed to send top-resumed=" + onTop + " to " + this, e);
return false;
}
- return true;
}
void updateMultiWindowMode() {
@@ -2604,14 +2603,21 @@
removeStartingWindow();
return;
}
+ mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT;
+ final TransferSplashScreenViewStateItem item =
+ new TransferSplashScreenViewStateItem(token, parcelable, windowAnimationLeash);
+ boolean isSuccessful;
try {
- mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT;
- final TransferSplashScreenViewStateItem item =
- new TransferSplashScreenViewStateItem(token, parcelable, windowAnimationLeash);
- mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), item);
- scheduleTransferSplashScreenTimeout();
- } catch (Exception e) {
+ isSuccessful = mAtmService.getLifecycleManager().scheduleTransactionItem(
+ app.getThread(), item);
+ } catch (RemoteException e) {
+ // TODO(b/323801078): remove Exception when cleanup
Slog.w(TAG, "onCopySplashScreenComplete fail: " + this);
+ isSuccessful = false;
+ }
+ if (isSuccessful) {
+ scheduleTransferSplashScreenTimeout();
+ } else {
mStartingWindow.cancelAnimation();
parcelable.clearIfNeeded();
mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH;
@@ -3957,11 +3963,23 @@
boolean skipDestroy = false;
- try {
- if (DEBUG_SWITCH) Slog.i(TAG_SWITCH, "Destroying: " + this);
+ if (DEBUG_SWITCH) Slog.i(TAG_SWITCH, "Destroying: " + this);
+ boolean isSuccessful;
+ final IApplicationThread client = app.getThread();
+ if (client == null) {
+ Slog.w(TAG_WM, "Failed to schedule DestroyActivityItem because client is inactive");
+ isSuccessful = false;
+ } else {
final DestroyActivityItem item = new DestroyActivityItem(token, finishing);
- mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), item);
- } catch (Exception e) {
+ try {
+ isSuccessful = mAtmService.getLifecycleManager().scheduleTransactionItem(
+ client, item);
+ } catch (RemoteException e) {
+ // TODO(b/323801078): remove Exception when cleanup
+ isSuccessful = false;
+ }
+ }
+ if (!isSuccessful) {
// We can just ignore exceptions here... if the process has crashed, our death
// notification will clean things up.
if (finishing) {
@@ -4884,13 +4902,17 @@
}
if (isState(RESUMED) && attachedToProcess()) {
+ final ArrayList<ResultInfo> list = new ArrayList<>();
+ list.add(new ResultInfo(resultWho, requestCode, resultCode, data, callerToken));
+ final ActivityResultItem item = new ActivityResultItem(token, list);
try {
- final ArrayList<ResultInfo> list = new ArrayList<>();
- list.add(new ResultInfo(resultWho, requestCode, resultCode, data, callerToken));
- final ActivityResultItem item = new ActivityResultItem(token, list);
- mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), item);
- return;
- } catch (Exception e) {
+ final boolean isSuccessful = mAtmService.getLifecycleManager()
+ .scheduleTransactionItem(app.getThread(), item);
+ if (isSuccessful) {
+ return;
+ }
+ } catch (RemoteException e) {
+ // TODO(b/323801078): remove Exception when cleanup
Slog.w(TAG, "Exception thrown sending result to " + this, e);
}
}
@@ -4917,6 +4939,7 @@
app.getThread(), activityResultItem);
}
} catch (RemoteException e) {
+ // TODO(b/323801078): remove Exception when cleanup
Slog.w(TAG, "Exception thrown sending result to " + this, e);
}
// We return here to ensure that result for media projection setup is not stored as a
@@ -4989,7 +5012,6 @@
}
final ReferrerIntent rintent = new ReferrerIntent(intent, getFilteredReferrer(referrer),
callerToken);
- boolean unsent = true;
final boolean isTopActivityWhileSleeping = isSleeping() && isTopRunningActivity();
// We want to immediately deliver the intent to the activity if:
@@ -4998,25 +5020,26 @@
// - The device is sleeping and it is the top activity behind the lock screen (b/6700897).
if ((mState == RESUMED || mState == PAUSED || isTopActivityWhileSleeping)
&& attachedToProcess()) {
+ final ArrayList<ReferrerIntent> ar = new ArrayList<>(1);
+ ar.add(rintent);
+ // Making sure the client state is RESUMED after transaction completed and doing
+ // so only if activity is currently RESUMED. Otherwise, client may have extra
+ // life-cycle calls to RESUMED (and PAUSED later).
+ final NewIntentItem item = new NewIntentItem(token, ar, mState == RESUMED /* resume */);
try {
- ArrayList<ReferrerIntent> ar = new ArrayList<>(1);
- ar.add(rintent);
- // Making sure the client state is RESUMED after transaction completed and doing
- // so only if activity is currently RESUMED. Otherwise, client may have extra
- // life-cycle calls to RESUMED (and PAUSED later).
- final NewIntentItem item =
- new NewIntentItem(token, ar, mState == RESUMED /* resume */);
- mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), item);
- unsent = false;
+ final boolean isSuccessful = mAtmService.getLifecycleManager()
+ .scheduleTransactionItem(app.getThread(), item);
+ if (isSuccessful) {
+ return;
+ }
} catch (RemoteException e) {
- Slog.w(TAG, "Exception thrown sending new intent to " + this, e);
- } catch (NullPointerException e) {
+ // TODO(b/323801078): remove Exception when cleanup
Slog.w(TAG, "Exception thrown sending new intent to " + this, e);
}
}
- if (unsent) {
- addNewIntentLocked(rintent);
- }
+
+ // Didn't send.
+ addNewIntentLocked(rintent);
}
void updateOptionsLocked(ActivityOptions options) {
@@ -5580,18 +5603,6 @@
commitVisibility(visible, performLayout, false /* fromTransition */);
}
- void setNeedsLetterboxedAnimation(boolean needsLetterboxedAnimation) {
- mNeedsLetterboxedAnimation = needsLetterboxedAnimation;
- }
-
- boolean isNeedsLetterboxedAnimation() {
- return mNeedsLetterboxedAnimation;
- }
-
- boolean isInLetterboxAnimation() {
- return mNeedsLetterboxedAnimation && isAnimating();
- }
-
/** Updates draw state and shows drawn windows. */
void commitFinishDrawing(SurfaceControl.Transaction t) {
boolean committed = false;
@@ -6044,11 +6055,12 @@
setState(PAUSING, "makeActiveIfNeeded");
EventLogTags.writeWmPauseActivity(mUserId, System.identityHashCode(this),
shortComponentName, "userLeaving=false", "make-active");
+ final PauseActivityItem item = new PauseActivityItem(token, finishing,
+ false /* userLeaving */, false /* dontReport */, mAutoEnteringPip);
try {
- final PauseActivityItem item = new PauseActivityItem(token, finishing,
- false /* userLeaving */, false /* dontReport */, mAutoEnteringPip);
mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), item);
- } catch (Exception e) {
+ } catch (RemoteException e) {
+ // TODO(b/323801078): remove Exception when cleanup
Slog.w(TAG, "Exception thrown sending pause: " + intent.getComponent(), e);
}
} else if (shouldStartActivity()) {
@@ -6057,10 +6069,11 @@
}
setState(STARTED, "makeActiveIfNeeded");
+ final StartActivityItem item = new StartActivityItem(token, takeSceneTransitionInfo());
try {
- mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
- new StartActivityItem(token, takeSceneTransitionInfo()));
- } catch (Exception e) {
+ mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), item);
+ } catch (RemoteException e) {
+ // TODO(b/323801078): remove Exception when cleanup
Slog.w(TAG, "Exception thrown sending start: " + intent.getComponent(), e);
}
// The activity may be waiting for stop, but that is no longer appropriate if we are
@@ -6343,23 +6356,29 @@
return;
}
resumeKeyDispatchingLocked();
+ ProtoLog.v(WM_DEBUG_STATES, "Moving to STOPPING: %s (stop requested)", this);
+
+ setState(STOPPING, "stopIfPossible");
+ if (DEBUG_VISIBILITY) {
+ Slog.v(TAG_VISIBILITY, "Stopping:" + this);
+ }
+ EventLogTags.writeWmStopActivity(
+ mUserId, System.identityHashCode(this), shortComponentName);
+ final StopActivityItem item = new StopActivityItem(token);
+ boolean isSuccessful;
try {
- ProtoLog.v(WM_DEBUG_STATES, "Moving to STOPPING: %s (stop requested)", this);
-
- setState(STOPPING, "stopIfPossible");
- if (DEBUG_VISIBILITY) {
- Slog.v(TAG_VISIBILITY, "Stopping:" + this);
- }
- EventLogTags.writeWmStopActivity(
- mUserId, System.identityHashCode(this), shortComponentName);
- mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
- new StopActivityItem(token));
-
- mAtmService.mH.postDelayed(mStopTimeoutRunnable, STOP_TIMEOUT);
- } catch (Exception e) {
+ isSuccessful = mAtmService.getLifecycleManager().scheduleTransactionItem(
+ app.getThread(), item);
+ } catch (RemoteException e) {
+ // TODO(b/323801078): remove Exception when cleanup
// Maybe just ignore exceptions here... if the process has crashed, our death
// notification will clean things up.
Slog.w(TAG, "Exception thrown during pause", e);
+ isSuccessful = false;
+ }
+ if (isSuccessful) {
+ mAtmService.mH.postDelayed(mStopTimeoutRunnable, STOP_TIMEOUT);
+ } else {
// Just in case, assume it to be stopped.
mAppStopped = true;
mStoppedTime = SystemClock.uptimeMillis();
@@ -7253,10 +7272,6 @@
.setParent(getAnimationLeashParent())
.setName(getSurfaceControl() + " - animation-bounds")
.setCallsite("ActivityRecord.createAnimationBoundsLayer");
- if (mNeedsLetterboxedAnimation) {
- // Needs to be an effect layer to support rounded corners
- builder.setEffectLayer();
- }
final SurfaceControl boundsLayer = builder.build();
t.show(boundsLayer);
return boundsLayer;
@@ -7274,11 +7289,6 @@
@Override
public void onLeashAnimationStarting(Transaction t, SurfaceControl leash) {
- if (mNeedsLetterboxedAnimation) {
- updateLetterboxSurfaceIfNeeded(findMainWindow(), t);
- mNeedsAnimationBoundsLayer = true;
- }
-
// If the animation needs to be cropped then an animation bounds layer is created as a
// child of the root pinned task or animation layer. The leash is then reparented to this
// new layer.
@@ -7291,17 +7301,6 @@
t.setLayer(leash, 0);
t.setLayer(mAnimationBoundsLayer, getLastLayer());
- if (mNeedsLetterboxedAnimation) {
- final int cornerRadius = mAppCompatController.getLetterboxPolicy()
- .getRoundedCornersRadius(findMainWindow());
-
- final Rect letterboxInnerBounds = new Rect();
- getLetterboxInnerBounds(letterboxInnerBounds);
-
- t.setCornerRadius(mAnimationBoundsLayer, cornerRadius)
- .setCrop(mAnimationBoundsLayer, letterboxInnerBounds);
- }
-
// Reparent leash to animation bounds layer.
t.reparent(leash, mAnimationBoundsLayer);
}
@@ -7355,10 +7354,6 @@
}
mNeedsAnimationBoundsLayer = false;
- if (mNeedsLetterboxedAnimation) {
- mNeedsLetterboxedAnimation = false;
- updateLetterboxSurfaceIfNeeded(findMainWindow(), t);
- }
}
@Override
@@ -8711,9 +8706,11 @@
}
// Figure out how to handle the changes between the configurations.
- ProtoLog.v(WM_DEBUG_CONFIGURATION, "Checking to restart %s: changed=0x%s, "
- + "handles=0x%s, mLastReportedConfiguration=%s", info.name,
- Integer.toHexString(changes), Integer.toHexString(info.getRealConfigChanged()),
+ ProtoLog.v(WM_DEBUG_CONFIGURATION, "Checking to restart %s: changed=%s, "
+ + "handles=%s, not-handles=%s, mLastReportedConfiguration=%s", info.name,
+ Configuration.configurationDiffToString(changes),
+ Configuration.configurationDiffToString(info.getRealConfigChanged()),
+ Configuration.configurationDiffToString(changes & ~(info.getRealConfigChanged())),
mLastReportedConfiguration);
if (shouldRelaunchLocked(changes, mTmpConfig)) {
@@ -8926,29 +8923,34 @@
task.mTaskId, shortComponentName, Integer.toHexString(configChangeFlags));
}
+ ProtoLog.i(WM_DEBUG_STATES, "Moving to %s Relaunching %s callers=%s" ,
+ (andResume ? "RESUMED" : "PAUSED"), this, Debug.getCallers(6));
+ final ClientTransactionItem callbackItem = new ActivityRelaunchItem(token,
+ pendingResults, pendingNewIntents, configChangeFlags,
+ new MergedConfiguration(getProcessGlobalConfiguration(),
+ getMergedOverrideConfiguration()),
+ preserveWindow, getActivityWindowInfo());
+ final ActivityLifecycleItem lifecycleItem;
+ if (andResume) {
+ lifecycleItem = new ResumeActivityItem(token, isTransitionForward(),
+ shouldSendCompatFakeFocus());
+ } else {
+ lifecycleItem = new PauseActivityItem(token);
+ }
+ boolean isSuccessful;
try {
- ProtoLog.i(WM_DEBUG_STATES, "Moving to %s Relaunching %s callers=%s" ,
- (andResume ? "RESUMED" : "PAUSED"), this, Debug.getCallers(6));
- final ClientTransactionItem callbackItem = new ActivityRelaunchItem(token,
- pendingResults, pendingNewIntents, configChangeFlags,
- new MergedConfiguration(getProcessGlobalConfiguration(),
- getMergedOverrideConfiguration()),
- preserveWindow, getActivityWindowInfo());
- final ActivityLifecycleItem lifecycleItem;
- if (andResume) {
- lifecycleItem = new ResumeActivityItem(token, isTransitionForward(),
- shouldSendCompatFakeFocus());
- } else {
- lifecycleItem = new PauseActivityItem(token);
- }
- mAtmService.getLifecycleManager().scheduleTransactionItems(
+ isSuccessful = mAtmService.getLifecycleManager().scheduleTransactionItems(
app.getThread(), callbackItem, lifecycleItem);
+ } catch (RemoteException e) {
+ // TODO(b/323801078): remove Exception when cleanup
+ Slog.w(TAG, "Failed to relaunch " + this + ": " + e);
+ isSuccessful = false;
+ }
+ if (isSuccessful) {
startRelaunching();
// Note: don't need to call pauseIfSleepingLocked() here, because the caller will only
// request resume if this activity is currently resumed, which implies we aren't
// sleeping.
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to relaunch " + this + ": " + e);
}
if (andResume) {
@@ -9028,10 +9030,11 @@
private void scheduleStopForRestartProcess() {
// The process will be killed until the activity reports stopped with saved state (see
// {@link ActivityTaskManagerService.activityStopped}).
+ final StopActivityItem item = new StopActivityItem(token);
try {
- mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
- new StopActivityItem(token));
+ mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(), item);
} catch (RemoteException e) {
+ // TODO(b/323801078): remove Exception when cleanup
Slog.w(TAG, "Exception thrown during restart " + this, e);
}
mTaskSupervisor.scheduleRestartTimeout(this);
diff --git a/services/core/java/com/android/server/wm/ActivityRefresher.java b/services/core/java/com/android/server/wm/ActivityRefresher.java
index 25e38b3..8fe603c 100644
--- a/services/core/java/com/android/server/wm/ActivityRefresher.java
+++ b/services/core/java/com/android/server/wm/ActivityRefresher.java
@@ -88,17 +88,22 @@
new RefreshCallbackItem(activity.token, cycleThroughStop ? ON_STOP : ON_PAUSE);
final ResumeActivityItem resumeActivityItem = new ResumeActivityItem(
activity.token, /* isForward */ false, /* shouldSendCompatFakeFocus */ false);
+ boolean isSuccessful;
try {
- activity.mAtmService.getLifecycleManager().scheduleTransactionItems(
+ isSuccessful = activity.mAtmService.getLifecycleManager().scheduleTransactionItems(
activity.app.getThread(), refreshCallbackItem, resumeActivityItem);
+ } catch (RemoteException e) {
+ isSuccessful = false;
+ }
+ if (isSuccessful) {
mHandler.postDelayed(() -> {
synchronized (mWmService.mGlobalLock) {
onActivityRefreshed(activity);
}
}, REFRESH_CALLBACK_TIMEOUT_MS);
- } catch (RemoteException e) {
- activity.mAppCompatController.getCameraOverrides()
- .setIsRefreshRequested(false);
+ } else {
+ activity.mAppCompatController.getCameraOverrides().setIsRefreshRequested(false);
+
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index bdde5fe..233f913 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -59,9 +59,7 @@
import static android.security.Flags.preventIntentRedirectShowToast;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.TRANSIT_FLAG_AVOID_MOVE_TO_FRONT;
-import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
-import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_CONFIGURATION;
@@ -1992,6 +1990,11 @@
}
}
+ if (com.android.window.flags.Flags.earlyLaunchHint()) {
+ mRootWindowContainer.startPowerModeLaunchIfNeeded(
+ false /* forceSend */, mStartActivity);
+ }
+
if (mTargetRootTask == null) {
mTargetRootTask = getOrCreateRootTask(mStartActivity, mLaunchFlags, targetTask,
mOptions);
@@ -2064,8 +2067,10 @@
mStartActivity.getTaskFragment().clearLastPausedActivity();
- mRootWindowContainer.startPowerModeLaunchIfNeeded(
- false /* forceSend */, mStartActivity);
+ if (!com.android.window.flags.Flags.earlyLaunchHint()) {
+ mRootWindowContainer.startPowerModeLaunchIfNeeded(
+ false /* forceSend */, mStartActivity);
+ }
final boolean isTaskSwitch = startedTask != prevTopTask;
mTargetRootTask.startActivityLocked(mStartActivity, topRootTask, newTask, isTaskSwitch,
@@ -2551,11 +2556,6 @@
if (actuallyMoved) {
// Only record if the activity actually moved.
mMovedToTopActivity = act;
- if (mNoAnimation) {
- act.mDisplayContent.prepareAppTransition(TRANSIT_NONE);
- } else {
- act.mDisplayContent.prepareAppTransition(TRANSIT_TO_FRONT);
- }
}
act.updateOptionsLocked(mOptions);
deliverNewIntent(act, intentGrants);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index c45f7e8..a7f2153 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -125,6 +125,7 @@
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
+import android.os.DeadObjectException;
import android.os.Debug;
import android.os.Handler;
import android.os.IBinder;
@@ -1045,16 +1046,23 @@
// transaction.
mService.getLifecycleManager().dispatchPendingTransaction(proc.getThread());
}
+ final boolean isSuccessful;
try {
- mService.getLifecycleManager().scheduleTransactionItems(
+ isSuccessful = mService.getLifecycleManager().scheduleTransactionItems(
proc.getThread(),
// Immediately dispatch the transaction, so that if it fails, the server can
// restart the process and retry now.
true /* shouldDispatchImmediately */,
launchActivityItem, lifecycleItem);
} catch (RemoteException e) {
+ // TODO(b/323801078): remove Exception when cleanup
return e;
}
+ if (com.android.window.flags.Flags.cleanupDispatchPendingTransactionsRemoteException()
+ && !isSuccessful) {
+ return new DeadObjectException("Failed to dispatch the ClientTransaction to dead"
+ + " process. See earlier log for more details.");
+ }
if (procConfig.seq > mRootWindowContainer.getConfiguration().seq) {
// If the seq is increased, there should be something changed (e.g. registered
diff --git a/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java b/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java
index 6873270..27511b2 100644
--- a/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java
@@ -217,7 +217,7 @@
}
final boolean shouldShowLetterboxUi =
- (mActivityRecord.isInLetterboxAnimation() || mActivityRecord.isVisible()
+ (mActivityRecord.isVisible()
|| mActivityRecord.isVisibleRequested())
&& mainWindow.areAppWindowBoundsLetterboxed()
// Check for FLAG_SHOW_WALLPAPER explicitly instead of using
@@ -360,8 +360,7 @@
.mAppCompatController.getReachabilityPolicy();
mLetterbox = new Letterbox(() -> mActivityRecord.makeChildSurface(null),
mActivityRecord.mWmService.mTransactionFactory,
- reachabilityPolicy, letterboxOverrides,
- this::getLetterboxParentSurface);
+ reachabilityPolicy, letterboxOverrides);
mActivityRecord.mAppCompatController.getReachabilityPolicy()
.setLetterboxInnerBoundsSupplier(mLetterbox::getInnerFrame);
}
@@ -469,15 +468,6 @@
public boolean isFullyTransparentBarAllowed(@NonNull Rect rect) {
return !isRunning() || mLetterbox.notIntersectsOrFullyContains(rect);
}
-
- @Nullable
- private SurfaceControl getLetterboxParentSurface() {
- if (mActivityRecord.isInLetterboxAnimation()) {
- return mActivityRecord.getTask().getSurfaceControl();
- }
- return mActivityRecord.getSurfaceControl();
- }
-
}
/**
diff --git a/services/core/java/com/android/server/wm/AppCompatLetterboxUtils.java b/services/core/java/com/android/server/wm/AppCompatLetterboxUtils.java
index 83d7cb7..e5b61db 100644
--- a/services/core/java/com/android/server/wm/AppCompatLetterboxUtils.java
+++ b/services/core/java/com/android/server/wm/AppCompatLetterboxUtils.java
@@ -36,12 +36,7 @@
outLetterboxPosition.set(0, 0);
return;
}
- if (activity.isInLetterboxAnimation()) {
- // In this case we attach the letterbox to the task instead of the activity.
- activity.getTask().getPosition(outLetterboxPosition);
- } else {
- activity.getPosition(outLetterboxPosition);
- }
+ activity.getPosition(outLetterboxPosition);
}
/**
diff --git a/services/core/java/com/android/server/wm/AppCompatRoundedCorners.java b/services/core/java/com/android/server/wm/AppCompatRoundedCorners.java
index 8165638..92b91464 100644
--- a/services/core/java/com/android/server/wm/AppCompatRoundedCorners.java
+++ b/services/core/java/com/android/server/wm/AppCompatRoundedCorners.java
@@ -58,7 +58,7 @@
@VisibleForTesting
@Nullable
Rect getCropBoundsIfNeeded(@NonNull final WindowState mainWindow) {
- if (!requiresRoundedCorners(mainWindow) || mActivityRecord.isInLetterboxAnimation()) {
+ if (!requiresRoundedCorners(mainWindow)) {
// We don't want corner radius on the window.
// In the case the ActivityRecord requires a letterboxed animation we never want
// rounded corners on the window because rounded corners are applied at the
diff --git a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
index 2cfa242..b03aa52 100644
--- a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
@@ -23,6 +23,7 @@
import static android.content.pm.ActivityInfo.SIZE_CHANGES_UNSUPPORTED_OVERRIDE;
import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
+import static com.android.window.flags.Flags.enableSizeCompatModeImprovementsForConnectedDisplays;
import static com.android.server.wm.AppCompatUtils.isInDesktopMode;
import android.annotation.NonNull;
@@ -357,6 +358,11 @@
// relatively fixed.
overrideConfig.colorMode = fullConfig.colorMode;
overrideConfig.densityDpi = fullConfig.densityDpi;
+ if (enableSizeCompatModeImprovementsForConnectedDisplays()) {
+ overrideConfig.touchscreen = fullConfig.touchscreen;
+ overrideConfig.navigation = fullConfig.navigation;
+ overrideConfig.fontScale = fullConfig.fontScale;
+ }
// The smallest screen width is the short side of screen bounds. Because the bounds
// and density won't be changed, smallestScreenWidthDp is also fixed.
overrideConfig.smallestScreenWidthDp = fullConfig.smallestScreenWidthDp;
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
deleted file mode 100644
index 12d4a21..0000000
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ /dev/null
@@ -1,1587 +0,0 @@
-/*
- * Copyright (C) 2011 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.server.wm;
-
-import static android.view.WindowManager.LayoutParams;
-import static android.view.WindowManager.TRANSIT_CHANGE;
-import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_LAUNCHER_CLEAR_SNAPSHOT;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
-import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
-import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
-import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
-import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
-import static android.view.WindowManager.TRANSIT_NONE;
-import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_RELAUNCH;
-import static android.view.WindowManager.TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY;
-import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
-import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_OCCLUDE;
-import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_OCCLUDE_BY_DREAM;
-import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_UNOCCLUDE;
-import static android.view.WindowManager.TRANSIT_OLD_NONE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN_BEHIND;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_TO_BACK;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_TO_FRONT;
-import static android.view.WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_UNSET;
-import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_OPEN;
-import static android.view.WindowManager.TRANSIT_OPEN;
-import static android.view.WindowManager.TRANSIT_RELAUNCH;
-import static android.view.WindowManager.TRANSIT_TO_BACK;
-import static android.view.WindowManager.TRANSIT_TO_FRONT;
-
-import static com.android.internal.R.styleable.WindowAnimation_activityCloseEnterAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_activityCloseExitAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_activityOpenEnterAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_activityOpenExitAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_dreamActivityCloseExitAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_dreamActivityOpenEnterAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_dreamActivityOpenExitAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_launchTaskBehindSourceAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_launchTaskBehindTargetAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_taskCloseEnterAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_taskCloseExitAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_taskOpenEnterAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_taskOpenExitAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_taskToBackEnterAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_taskToBackExitAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_taskToFrontEnterAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_taskToFrontExitAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_wallpaperCloseEnterAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_wallpaperCloseExitAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraCloseEnterAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraCloseExitAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenEnterAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenEnterAnimation;
-import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenExitAnimation;
-import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ANIM;
-import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_APP_TRANSITIONS_ANIM;
-import static com.android.server.wm.AppTransitionProto.APP_TRANSITION_STATE;
-import static com.android.server.wm.AppTransitionProto.LAST_USED_APP_TRANSITION;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerInternal.AppTransitionListener;
-import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_AFTER_ANIM;
-import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_NONE;
-
-import android.annotation.ColorInt;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.hardware.HardwareBuffer;
-import android.os.Binder;
-import android.os.Debug;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.IRemoteCallback;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.util.Pair;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.util.proto.ProtoOutputStream;
-import android.view.AppTransitionAnimationSpec;
-import android.view.IAppTransitionAnimationSpecsFuture;
-import android.view.RemoteAnimationAdapter;
-import android.view.WindowManager.TransitionFlags;
-import android.view.WindowManager.TransitionOldType;
-import android.view.WindowManager.TransitionType;
-import android.view.animation.AlphaAnimation;
-import android.view.animation.Animation;
-import android.view.animation.AnimationSet;
-import android.view.animation.ScaleAnimation;
-import android.view.animation.TranslateAnimation;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.policy.TransitionAnimation;
-import com.android.internal.protolog.ProtoLog;
-import com.android.internal.protolog.common.LogLevel;
-import com.android.internal.util.DumpUtils.Dump;
-import com.android.internal.util.function.pooled.PooledLambda;
-import com.android.internal.util.function.pooled.PooledPredicate;
-import com.android.server.wm.ActivityRecord.CustomAppTransition;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-// State management of app transitions. When we are preparing for a
-// transition, mNextAppTransition will be the kind of transition to
-// perform or TRANSIT_NONE if we are not waiting. If we are waiting,
-// mOpeningApps and mClosingApps are the lists of tokens that will be
-// made visible or hidden at the next transition.
-public class AppTransition implements Dump {
- private static final String TAG = TAG_WITH_CLASS_NAME ? "AppTransition" : TAG_WM;
-
- static final int DEFAULT_APP_TRANSITION_DURATION = 336;
-
- /**
- * Maximum duration for the clip reveal animation. This is used when there is a lot of movement
- * involved, to make it more understandable.
- */
- private static final long APP_TRANSITION_TIMEOUT_MS = 5000;
- static final int MAX_APP_TRANSITION_DURATION = 3 * 1000; // 3 secs.
-
- private final Context mContext;
- private final WindowManagerService mService;
- private final DisplayContent mDisplayContent;
-
- @VisibleForTesting
- final TransitionAnimation mTransitionAnimation;
-
- private @TransitionFlags int mNextAppTransitionFlags = 0;
- private final ArrayList<Integer> mNextAppTransitionRequests = new ArrayList<>();
- private @TransitionOldType int mLastUsedAppTransition = TRANSIT_OLD_UNSET;
- private String mLastOpeningApp;
- private String mLastClosingApp;
- private String mLastChangingApp;
-
- private static final int NEXT_TRANSIT_TYPE_NONE = 0;
- private static final int NEXT_TRANSIT_TYPE_CUSTOM = 1;
- private static final int NEXT_TRANSIT_TYPE_SCALE_UP = 2;
- private static final int NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP = 3;
- private static final int NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN = 4;
- private static final int NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP = 5;
- private static final int NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN = 6;
- private static final int NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE = 7;
- private static final int NEXT_TRANSIT_TYPE_CLIP_REVEAL = 8;
-
- /**
- * Refers to the transition to activity started by using {@link
- * android.content.pm.crossprofile.CrossProfileApps#startMainActivity(ComponentName, UserHandle)
- * }.
- */
- private static final int NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS = 9;
- private static final int NEXT_TRANSIT_TYPE_REMOTE = 10;
-
- private int mNextAppTransitionType = NEXT_TRANSIT_TYPE_NONE;
- private boolean mNextAppTransitionOverrideRequested;
-
- private String mNextAppTransitionPackage;
- // Used for thumbnail transitions. True if we're scaling up, false if scaling down
- private boolean mNextAppTransitionScaleUp;
- private IRemoteCallback mNextAppTransitionCallback;
- private IRemoteCallback mNextAppTransitionFutureCallback;
- private IRemoteCallback mAnimationFinishedCallback;
- private int mNextAppTransitionEnter;
- private int mNextAppTransitionExit;
- private @ColorInt int mNextAppTransitionBackgroundColor;
- private int mNextAppTransitionInPlace;
- private boolean mNextAppTransitionIsSync;
-
- // Keyed by WindowContainer hashCode.
- private final SparseArray<AppTransitionAnimationSpec> mNextAppTransitionAnimationsSpecs
- = new SparseArray<>();
- private IAppTransitionAnimationSpecsFuture mNextAppTransitionAnimationsSpecsFuture;
- private boolean mNextAppTransitionAnimationsSpecsPending;
- private AppTransitionAnimationSpec mDefaultNextAppTransitionAnimationSpec;
-
- private final Rect mTmpRect = new Rect();
-
- private final static int APP_STATE_IDLE = 0;
- private final static int APP_STATE_READY = 1;
- private final static int APP_STATE_RUNNING = 2;
- private final static int APP_STATE_TIMEOUT = 3;
- private int mAppTransitionState = APP_STATE_IDLE;
-
- private final ArrayList<AppTransitionListener> mListeners = new ArrayList<>();
- private final ExecutorService mDefaultExecutor = Executors.newSingleThreadExecutor();
-
- private final int mDefaultWindowAnimationStyleResId;
- private boolean mOverrideTaskTransition;
-
- final Handler mHandler;
- final Runnable mHandleAppTransitionTimeoutRunnable = () -> handleAppTransitionTimeout();
-
- AppTransition(Context context, WindowManagerService service, DisplayContent displayContent) {
- mContext = context;
- mService = service;
- mHandler = new Handler(service.mH.getLooper());
- mDisplayContent = displayContent;
- mTransitionAnimation = new TransitionAnimation(
- context, ProtoLog.isEnabled(WM_DEBUG_ANIM, LogLevel.DEBUG), TAG);
-
- final TypedArray windowStyle = mContext.getTheme().obtainStyledAttributes(
- com.android.internal.R.styleable.Window);
- mDefaultWindowAnimationStyleResId = windowStyle.getResourceId(
- com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
- windowStyle.recycle();
- }
-
- boolean isTransitionSet() {
- return !mNextAppTransitionRequests.isEmpty();
- }
-
- boolean isUnoccluding() {
- return mNextAppTransitionRequests.contains(TRANSIT_KEYGUARD_UNOCCLUDE);
- }
-
- boolean transferFrom(AppTransition other) {
- mNextAppTransitionRequests.addAll(other.mNextAppTransitionRequests);
- return prepare();
- }
-
- void setLastAppTransition(@TransitionOldType int transit, ActivityRecord openingApp,
- ActivityRecord closingApp, ActivityRecord changingApp) {
- mLastUsedAppTransition = transit;
- mLastOpeningApp = "" + openingApp;
- mLastClosingApp = "" + closingApp;
- mLastChangingApp = "" + changingApp;
- }
-
- boolean isReady() {
- return mAppTransitionState == APP_STATE_READY
- || mAppTransitionState == APP_STATE_TIMEOUT;
- }
-
- void setReady() {
- setAppTransitionState(APP_STATE_READY);
- fetchAppTransitionSpecsFromFuture();
- }
-
- boolean isRunning() {
- return mAppTransitionState == APP_STATE_RUNNING;
- }
-
- void setIdle() {
- setAppTransitionState(APP_STATE_IDLE);
- }
-
- boolean isIdle() {
- return mAppTransitionState == APP_STATE_IDLE;
- }
-
- boolean isTimeout() {
- return mAppTransitionState == APP_STATE_TIMEOUT;
- }
-
- void setTimeout() {
- setAppTransitionState(APP_STATE_TIMEOUT);
- }
-
- /**
- * Gets the animation overridden by app via {@link #overridePendingAppTransition}.
- */
- @Nullable
- Animation getNextAppRequestedAnimation(boolean enter) {
- final Animation a = mTransitionAnimation.loadAppTransitionAnimation(
- mNextAppTransitionPackage,
- enter ? mNextAppTransitionEnter : mNextAppTransitionExit);
- if (mNextAppTransitionBackgroundColor != 0 && a != null) {
- a.setBackdropColor(mNextAppTransitionBackgroundColor);
- }
- return a;
- }
-
- /**
- * Gets the animation background color overridden by app via
- * {@link #overridePendingAppTransition}.
- */
- @ColorInt int getNextAppTransitionBackgroundColor() {
- return mNextAppTransitionBackgroundColor;
- }
-
- @VisibleForTesting
- boolean isNextAppTransitionOverrideRequested() {
- return mNextAppTransitionOverrideRequested;
- }
-
- HardwareBuffer getAppTransitionThumbnailHeader(WindowContainer container) {
- AppTransitionAnimationSpec spec = mNextAppTransitionAnimationsSpecs.get(
- container.hashCode());
- if (spec == null) {
- spec = mDefaultNextAppTransitionAnimationSpec;
- }
- return spec != null ? spec.buffer : null;
- }
-
- /** Returns whether the next thumbnail transition is aspect scaled up. */
- boolean isNextThumbnailTransitionAspectScaled() {
- return mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP ||
- mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN;
- }
-
- /** Returns whether the next thumbnail transition is scaling up. */
- boolean isNextThumbnailTransitionScaleUp() {
- return mNextAppTransitionScaleUp;
- }
-
- boolean isNextAppTransitionThumbnailUp() {
- return mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP ||
- mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP;
- }
-
- boolean isNextAppTransitionThumbnailDown() {
- return mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN ||
- mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN;
- }
-
- boolean isNextAppTransitionOpenCrossProfileApps() {
- return mNextAppTransitionType == NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS;
- }
-
- /**
- * @return true if and only if we are currently fetching app transition specs from the future
- * passed into {@link #overridePendingAppTransitionMultiThumbFuture}
- */
- boolean isFetchingAppTransitionsSpecs() {
- return mNextAppTransitionAnimationsSpecsPending;
- }
-
- private boolean prepare() {
- if (!isRunning()) {
- setAppTransitionState(APP_STATE_IDLE);
- notifyAppTransitionPendingLocked();
- return true;
- }
- return false;
- }
-
- /**
- * @return bit-map of WindowManagerPolicy#FINISH_LAYOUT_REDO_* to indicate whether another
- * layout pass needs to be done
- */
- int goodToGo(@TransitionOldType int transit, ActivityRecord topOpeningApp) {
- mNextAppTransitionFlags = 0;
- mNextAppTransitionRequests.clear();
- setAppTransitionState(APP_STATE_RUNNING);
- final WindowContainer wc =
- topOpeningApp != null ? topOpeningApp.getAnimatingContainer() : null;
- final AnimationAdapter topOpeningAnim = wc != null ? wc.getAnimation() : null;
-
- int redoLayout = notifyAppTransitionStartingLocked(
- topOpeningAnim != null
- ? topOpeningAnim.getStatusBarTransitionsStartTime()
- : SystemClock.uptimeMillis(),
- AnimationAdapter.STATUS_BAR_TRANSITION_DURATION);
-
- if ((isTaskOpenTransitOld(transit) || transit == TRANSIT_OLD_WALLPAPER_CLOSE)
- && topOpeningAnim != null) {
- if (mDisplayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()) {
- final NavBarFadeAnimationController controller =
- new NavBarFadeAnimationController(mDisplayContent);
- // For remote animation case, the nav bar fades out and in is controlled by the
- // remote side. For non-remote animation case, we play the fade out/in animation
- // here. We play the nav bar fade-out animation when the app transition animation
- // starts and play the fade-in animation sequentially once the fade-out is finished.
- controller.fadeOutAndInSequentially(topOpeningAnim.getDurationHint(),
- null /* fadeOutParent */, topOpeningApp.getSurfaceControl());
- }
- }
- return redoLayout;
- }
-
- void clear() {
- clear(true /* clearAppOverride */);
- }
-
- private void clear(boolean clearAppOverride) {
- mNextAppTransitionType = NEXT_TRANSIT_TYPE_NONE;
- mNextAppTransitionOverrideRequested = false;
- mNextAppTransitionAnimationsSpecs.clear();
- mNextAppTransitionAnimationsSpecsFuture = null;
- mDefaultNextAppTransitionAnimationSpec = null;
- mAnimationFinishedCallback = null;
- mOverrideTaskTransition = false;
- mNextAppTransitionIsSync = false;
- if (clearAppOverride) {
- mNextAppTransitionPackage = null;
- mNextAppTransitionEnter = 0;
- mNextAppTransitionExit = 0;
- mNextAppTransitionBackgroundColor = 0;
- }
- }
-
- void freeze() {
- final boolean keyguardGoingAwayCancelled = mNextAppTransitionRequests.contains(
- TRANSIT_KEYGUARD_GOING_AWAY);
-
- mNextAppTransitionRequests.clear();
- clear();
- setReady();
- notifyAppTransitionCancelledLocked(keyguardGoingAwayCancelled);
- }
-
- private void setAppTransitionState(int state) {
- mAppTransitionState = state;
- updateBooster();
- }
-
- /**
- * Updates whether we currently boost wm locked sections and the animation thread. We want to
- * boost the priorities to a more important value whenever an app transition is going to happen
- * soon or an app transition is running.
- */
- void updateBooster() {
- WindowManagerService.sThreadPriorityBooster.setAppTransitionRunning(needsBoosting());
- }
-
- private boolean needsBoosting() {
- return !mNextAppTransitionRequests.isEmpty()
- || mAppTransitionState == APP_STATE_READY
- || mAppTransitionState == APP_STATE_RUNNING;
- }
-
- void registerListenerLocked(AppTransitionListener listener) {
- mListeners.add(listener);
- }
-
- void unregisterListener(AppTransitionListener listener) {
- mListeners.remove(listener);
- }
-
- public void notifyAppTransitionFinishedLocked(IBinder token) {
- for (int i = 0; i < mListeners.size(); i++) {
- mListeners.get(i).onAppTransitionFinishedLocked(token);
- }
- }
-
- private void notifyAppTransitionPendingLocked() {
- for (int i = 0; i < mListeners.size(); i++) {
- mListeners.get(i).onAppTransitionPendingLocked();
- }
- }
-
- private void notifyAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled) {
- for (int i = 0; i < mListeners.size(); i++) {
- mListeners.get(i).onAppTransitionCancelledLocked(keyguardGoingAwayCancelled);
- }
- }
-
- private void notifyAppTransitionTimeoutLocked() {
- for (int i = 0; i < mListeners.size(); i++) {
- mListeners.get(i).onAppTransitionTimeoutLocked();
- }
- }
-
- private int notifyAppTransitionStartingLocked(long statusBarAnimationStartTime,
- long statusBarAnimationDuration) {
- int redoLayout = 0;
- for (int i = 0; i < mListeners.size(); i++) {
- redoLayout |= mListeners.get(i).onAppTransitionStartingLocked(
- statusBarAnimationStartTime, statusBarAnimationDuration);
- }
- return redoLayout;
- }
-
- @VisibleForTesting
- int getDefaultWindowAnimationStyleResId() {
- return mDefaultWindowAnimationStyleResId;
- }
-
- /** Returns window animation style ID from {@link LayoutParams} or from system in some cases */
- @VisibleForTesting
- int getAnimationStyleResId(@NonNull LayoutParams lp) {
- return mTransitionAnimation.getAnimationStyleResId(lp);
- }
-
- @VisibleForTesting
- @Nullable
- Animation loadAnimationSafely(Context context, int resId) {
- return TransitionAnimation.loadAnimationSafely(context, resId, TAG);
- }
-
- private static int mapOpenCloseTransitTypes(int transit, boolean enter) {
- int animAttr = 0;
- switch (transit) {
- case TRANSIT_OLD_ACTIVITY_OPEN:
- case TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN:
- animAttr = enter
- ? WindowAnimation_activityOpenEnterAnimation
- : WindowAnimation_activityOpenExitAnimation;
- break;
- case TRANSIT_OLD_ACTIVITY_CLOSE:
- case TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE:
- animAttr = enter
- ? WindowAnimation_activityCloseEnterAnimation
- : WindowAnimation_activityCloseExitAnimation;
- break;
- case TRANSIT_OLD_TASK_OPEN:
- animAttr = enter
- ? WindowAnimation_taskOpenEnterAnimation
- : WindowAnimation_taskOpenExitAnimation;
- break;
- case TRANSIT_OLD_TASK_CLOSE:
- animAttr = enter
- ? WindowAnimation_taskCloseEnterAnimation
- : WindowAnimation_taskCloseExitAnimation;
- break;
- case TRANSIT_OLD_TASK_TO_FRONT:
- animAttr = enter
- ? WindowAnimation_taskToFrontEnterAnimation
- : WindowAnimation_taskToFrontExitAnimation;
- break;
- case TRANSIT_OLD_TASK_TO_BACK:
- animAttr = enter
- ? WindowAnimation_taskToBackEnterAnimation
- : WindowAnimation_taskToBackExitAnimation;
- break;
- case TRANSIT_OLD_WALLPAPER_OPEN:
- animAttr = enter
- ? WindowAnimation_wallpaperOpenEnterAnimation
- : WindowAnimation_wallpaperOpenExitAnimation;
- break;
- case TRANSIT_OLD_WALLPAPER_CLOSE:
- animAttr = enter
- ? WindowAnimation_wallpaperCloseEnterAnimation
- : WindowAnimation_wallpaperCloseExitAnimation;
- break;
- case TRANSIT_OLD_WALLPAPER_INTRA_OPEN:
- animAttr = enter
- ? WindowAnimation_wallpaperIntraOpenEnterAnimation
- : WindowAnimation_wallpaperIntraOpenExitAnimation;
- break;
- case TRANSIT_OLD_WALLPAPER_INTRA_CLOSE:
- animAttr = enter
- ? WindowAnimation_wallpaperIntraCloseEnterAnimation
- : WindowAnimation_wallpaperIntraCloseExitAnimation;
- break;
- case TRANSIT_OLD_TASK_OPEN_BEHIND:
- animAttr = enter
- ? WindowAnimation_launchTaskBehindSourceAnimation
- : WindowAnimation_launchTaskBehindTargetAnimation;
- break;
- // TODO(b/189386466): Use activity transition as the fallback. Investigate if we
- // need new TaskFragment transition.
- case TRANSIT_OLD_TASK_FRAGMENT_OPEN:
- animAttr = enter
- ? WindowAnimation_activityOpenEnterAnimation
- : WindowAnimation_activityOpenExitAnimation;
- break;
- // TODO(b/189386466): Use activity transition as the fallback. Investigate if we
- // need new TaskFragment transition.
- case TRANSIT_OLD_TASK_FRAGMENT_CLOSE:
- animAttr = enter
- ? WindowAnimation_activityCloseEnterAnimation
- : WindowAnimation_activityCloseExitAnimation;
- break;
- case TRANSIT_OLD_DREAM_ACTIVITY_OPEN:
- animAttr = enter
- ? WindowAnimation_dreamActivityOpenEnterAnimation
- : WindowAnimation_dreamActivityOpenExitAnimation;
- break;
- case TRANSIT_OLD_DREAM_ACTIVITY_CLOSE:
- animAttr = enter
- ? 0
- : WindowAnimation_dreamActivityCloseExitAnimation;
- break;
- }
-
- return animAttr;
- }
-
- @Nullable
- Animation loadAnimationAttr(LayoutParams lp, int animAttr, int transit) {
- return mTransitionAnimation.loadAnimationAttr(lp, animAttr, transit);
- }
-
- private void getDefaultNextAppTransitionStartRect(Rect rect) {
- if (mDefaultNextAppTransitionAnimationSpec == null ||
- mDefaultNextAppTransitionAnimationSpec.rect == null) {
- Slog.e(TAG, "Starting rect for app requested, but none available", new Throwable());
- rect.setEmpty();
- } else {
- rect.set(mDefaultNextAppTransitionAnimationSpec.rect);
- }
- }
-
- private void putDefaultNextAppTransitionCoordinates(int left, int top, int width, int height,
- HardwareBuffer buffer) {
- mDefaultNextAppTransitionAnimationSpec = new AppTransitionAnimationSpec(-1 /* taskId */,
- buffer, new Rect(left, top, left + width, top + height));
- }
-
- /**
- * Creates an overlay with a background color and a thumbnail for the cross profile apps
- * animation.
- */
- HardwareBuffer createCrossProfileAppsThumbnail(
- Drawable thumbnailDrawable, Rect frame) {
- return mTransitionAnimation.createCrossProfileAppsThumbnail(thumbnailDrawable, frame);
- }
-
- Animation createCrossProfileAppsThumbnailAnimationLocked(Rect appRect) {
- return mTransitionAnimation.createCrossProfileAppsThumbnailAnimationLocked(appRect);
- }
-
- /**
- * This animation runs for the thumbnail that gets cross faded with the enter/exit activity
- * when a thumbnail is specified with the pending animation override.
- */
- Animation createThumbnailAspectScaleAnimationLocked(Rect appRect, @Nullable Rect contentInsets,
- HardwareBuffer thumbnailHeader, WindowContainer container, int orientation) {
- AppTransitionAnimationSpec spec = mNextAppTransitionAnimationsSpecs.get(
- container.hashCode());
- return mTransitionAnimation.createThumbnailAspectScaleAnimationLocked(appRect,
- contentInsets, thumbnailHeader, orientation, spec != null ? spec.rect : null,
- mDefaultNextAppTransitionAnimationSpec != null
- ? mDefaultNextAppTransitionAnimationSpec.rect : null,
- mNextAppTransitionScaleUp);
- }
-
- private AnimationSet createAspectScaledThumbnailFreeformAnimationLocked(Rect sourceFrame,
- Rect destFrame, @Nullable Rect surfaceInsets, boolean enter) {
- final float sourceWidth = sourceFrame.width();
- final float sourceHeight = sourceFrame.height();
- final float destWidth = destFrame.width();
- final float destHeight = destFrame.height();
- final float scaleH = enter ? sourceWidth / destWidth : destWidth / sourceWidth;
- final float scaleV = enter ? sourceHeight / destHeight : destHeight / sourceHeight;
- AnimationSet set = new AnimationSet(true);
- final int surfaceInsetsH = surfaceInsets == null
- ? 0 : surfaceInsets.left + surfaceInsets.right;
- final int surfaceInsetsV = surfaceInsets == null
- ? 0 : surfaceInsets.top + surfaceInsets.bottom;
- // We want the scaling to happen from the center of the surface. In order to achieve that,
- // we need to account for surface insets that will be used to enlarge the surface.
- final float scaleHCenter = ((enter ? destWidth : sourceWidth) + surfaceInsetsH) / 2;
- final float scaleVCenter = ((enter ? destHeight : sourceHeight) + surfaceInsetsV) / 2;
- final ScaleAnimation scale = enter ?
- new ScaleAnimation(scaleH, 1, scaleV, 1, scaleHCenter, scaleVCenter)
- : new ScaleAnimation(1, scaleH, 1, scaleV, scaleHCenter, scaleVCenter);
- final int sourceHCenter = sourceFrame.left + sourceFrame.width() / 2;
- final int sourceVCenter = sourceFrame.top + sourceFrame.height() / 2;
- final int destHCenter = destFrame.left + destFrame.width() / 2;
- final int destVCenter = destFrame.top + destFrame.height() / 2;
- final int fromX = enter ? sourceHCenter - destHCenter : destHCenter - sourceHCenter;
- final int fromY = enter ? sourceVCenter - destVCenter : destVCenter - sourceVCenter;
- final TranslateAnimation translation = enter ? new TranslateAnimation(fromX, 0, fromY, 0)
- : new TranslateAnimation(0, fromX, 0, fromY);
- set.addAnimation(scale);
- set.addAnimation(translation);
- setAppTransitionFinishedCallbackIfNeeded(set);
- return set;
- }
-
- /**
- * @return true if and only if the first frame of the transition can be skipped, i.e. the first
- * frame of the transition doesn't change the visuals on screen, so we can start
- * directly with the second one
- */
- boolean canSkipFirstFrame() {
- return mNextAppTransitionType != NEXT_TRANSIT_TYPE_CUSTOM
- && !mNextAppTransitionOverrideRequested
- && mNextAppTransitionType != NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE
- && mNextAppTransitionType != NEXT_TRANSIT_TYPE_CLIP_REVEAL
- && !mNextAppTransitionRequests.contains(TRANSIT_KEYGUARD_GOING_AWAY);
- }
-
- /**
- *
- * @param frame These are the bounds of the window when it finishes the animation. This is where
- * the animation must usually finish in entrance animation, as the next frame will
- * display the window at these coordinates. In case of exit animation, this is
- * where the animation must start, as the frame before the animation is displaying
- * the window at these bounds.
- * @param insets Knowing where the window will be positioned is not enough. Some parts of the
- * window might be obscured, usually by the system windows (status bar and
- * navigation bar) and we use content insets to convey that information. This
- * usually affects the animation aspects vertically, as the system decoration is
- * at the top and the bottom. For example when we animate from full screen to
- * recents, we want to exclude the covered parts, because they won't match the
- * thumbnail after the last frame is executed.
- * @param surfaceInsets In rare situation the surface is larger than the content and we need to
- * know about this to make the animation frames match. We currently use
- * this for freeform windows, which have larger surfaces to display
- * shadows. When we animate them from recents, we want to match the content
- * to the recents thumbnail and hence need to account for the surface being
- * bigger.
- */
- @Nullable
- Animation loadAnimation(LayoutParams lp, int transit, boolean enter, int uiMode,
- int orientation, Rect frame, Rect displayFrame, Rect insets,
- @Nullable Rect surfaceInsets, @Nullable Rect stableInsets, boolean isVoiceInteraction,
- boolean freeform, WindowContainer container) {
-
- final boolean canCustomizeAppTransition = container.canCustomizeAppTransition();
-
- if (mNextAppTransitionOverrideRequested) {
- if (canCustomizeAppTransition || mOverrideTaskTransition) {
- mNextAppTransitionType = NEXT_TRANSIT_TYPE_CUSTOM;
- } else {
- ProtoLog.e(WM_DEBUG_APP_TRANSITIONS_ANIM, "applyAnimation: "
- + " override requested, but it is prohibited by policy.");
- }
- }
-
- Animation a;
- if (isKeyguardGoingAwayTransitOld(transit) && enter) {
- a = mTransitionAnimation.loadKeyguardExitAnimation(mNextAppTransitionFlags,
- transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER);
- } else if (transit == TRANSIT_OLD_KEYGUARD_OCCLUDE
- || transit == TRANSIT_OLD_KEYGUARD_OCCLUDE_BY_DREAM) {
- a = null;
- } else if (transit == TRANSIT_OLD_KEYGUARD_UNOCCLUDE && !enter) {
- a = mTransitionAnimation.loadKeyguardUnoccludeAnimation();
- } else if (transit == TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE) {
- a = null;
- } else if (isVoiceInteraction && (transit == TRANSIT_OLD_ACTIVITY_OPEN
- || transit == TRANSIT_OLD_TASK_OPEN
- || transit == TRANSIT_OLD_TASK_TO_FRONT)) {
- a = mTransitionAnimation.loadVoiceActivityOpenAnimation(enter);
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
- "applyAnimation voice: anim=%s transit=%s isEntrance=%b Callers=%s", a,
- appTransitionOldToString(transit), enter, Debug.getCallers(3));
- } else if (isVoiceInteraction && (transit == TRANSIT_OLD_ACTIVITY_CLOSE
- || transit == TRANSIT_OLD_TASK_CLOSE
- || transit == TRANSIT_OLD_TASK_TO_BACK)) {
- a = mTransitionAnimation.loadVoiceActivityExitAnimation(enter);
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
- "applyAnimation voice: anim=%s transit=%s isEntrance=%b Callers=%s", a,
- appTransitionOldToString(transit), enter, Debug.getCallers(3));
- } else if (transit == TRANSIT_OLD_ACTIVITY_RELAUNCH) {
- a = mTransitionAnimation.createRelaunchAnimation(frame, insets,
- mDefaultNextAppTransitionAnimationSpec != null
- ? mDefaultNextAppTransitionAnimationSpec.rect : null);
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
- "applyAnimation: anim=%s transit=%s Callers=%s", a,
- appTransitionOldToString(transit), Debug.getCallers(3));
- } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM) {
- a = getNextAppRequestedAnimation(enter);
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
- "applyAnimation: anim=%s nextAppTransition=ANIM_CUSTOM transit=%s "
- + "isEntrance=%b Callers=%s",
- a, appTransitionOldToString(transit), enter, Debug.getCallers(3));
- } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE) {
- a = mTransitionAnimation.loadAppTransitionAnimation(
- mNextAppTransitionPackage, mNextAppTransitionInPlace);
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
- "applyAnimation: anim=%s nextAppTransition=ANIM_CUSTOM_IN_PLACE "
- + "transit=%s Callers=%s",
- a, appTransitionOldToString(transit), Debug.getCallers(3));
- } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CLIP_REVEAL) {
- a = mTransitionAnimation.createClipRevealAnimationLockedCompat(
- transit, enter, frame, displayFrame,
- mDefaultNextAppTransitionAnimationSpec != null
- ? mDefaultNextAppTransitionAnimationSpec.rect : null);
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
- "applyAnimation: anim=%s nextAppTransition=ANIM_CLIP_REVEAL "
- + "transit=%s Callers=%s",
- a, appTransitionOldToString(transit), Debug.getCallers(3));
- } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_SCALE_UP) {
- a = mTransitionAnimation.createScaleUpAnimationLockedCompat(transit, enter, frame,
- mDefaultNextAppTransitionAnimationSpec != null
- ? mDefaultNextAppTransitionAnimationSpec.rect : null);
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
- "applyAnimation: anim=%s nextAppTransition=ANIM_SCALE_UP transit=%s "
- + "isEntrance=%s Callers=%s",
- a, appTransitionOldToString(transit), enter, Debug.getCallers(3));
- } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP ||
- mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN) {
- mNextAppTransitionScaleUp =
- (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP);
- final HardwareBuffer thumbnailHeader = getAppTransitionThumbnailHeader(container);
- a = mTransitionAnimation.createThumbnailEnterExitAnimationLockedCompat(enter,
- mNextAppTransitionScaleUp, frame, transit, thumbnailHeader,
- mDefaultNextAppTransitionAnimationSpec != null
- ? mDefaultNextAppTransitionAnimationSpec.rect : null);
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
- "applyAnimation: anim=%s nextAppTransition=%s transit=%s isEntrance=%b "
- + "Callers=%s",
- a, mNextAppTransitionScaleUp
- ? "ANIM_THUMBNAIL_SCALE_UP" : "ANIM_THUMBNAIL_SCALE_DOWN",
- appTransitionOldToString(transit), enter, Debug.getCallers(3));
- } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP ||
- mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN) {
- mNextAppTransitionScaleUp =
- (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP);
- AppTransitionAnimationSpec spec = mNextAppTransitionAnimationsSpecs.get(
- container.hashCode());
- a = mTransitionAnimation.createAspectScaledThumbnailEnterExitAnimationLocked(enter,
- mNextAppTransitionScaleUp, orientation, transit, frame, insets, surfaceInsets,
- stableInsets, freeform, spec != null ? spec.rect : null,
- mDefaultNextAppTransitionAnimationSpec != null
- ? mDefaultNextAppTransitionAnimationSpec.rect : null);
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
- "applyAnimation: anim=%s nextAppTransition=%s transit=%s isEntrance=%b "
- + "Callers=%s",
- a, mNextAppTransitionScaleUp
- ? "ANIM_THUMBNAIL_ASPECT_SCALE_UP"
- : "ANIM_THUMBNAIL_ASPECT_SCALE_DOWN",
- appTransitionOldToString(transit), enter, Debug.getCallers(3));
- } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS && enter) {
- a = mTransitionAnimation.loadCrossProfileAppEnterAnimation();
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
- "applyAnimation NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS: "
- + "anim=%s transit=%s isEntrance=true Callers=%s",
- a, appTransitionOldToString(transit), Debug.getCallers(3));
- } else if (isChangeTransitOld(transit)) {
- // In the absence of a specific adapter, we just want to keep everything stationary.
- a = new AlphaAnimation(1.f, 1.f);
- a.setDuration(WindowChangeAnimationSpec.ANIMATION_DURATION);
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
- "applyAnimation: anim=%s transit=%s isEntrance=%b Callers=%s",
- a, appTransitionOldToString(transit), enter, Debug.getCallers(3));
- } else {
- int animAttr = mapOpenCloseTransitTypes(transit, enter);
- if (animAttr != 0) {
- final CustomAppTransition customAppTransition =
- getCustomAppTransition(animAttr, container);
- if (customAppTransition != null) {
- a = loadCustomActivityAnimation(customAppTransition, enter, container);
- } else {
- if (canCustomizeAppTransition) {
- a = loadAnimationAttr(lp, animAttr, transit);
- } else {
- a = mTransitionAnimation.loadDefaultAnimationAttr(animAttr, transit);
- }
- }
- } else {
- a = null;
- }
-
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
- "applyAnimation: anim=%s animAttr=0x%x transit=%s isEntrance=%b "
- + " canCustomizeAppTransition=%b Callers=%s",
- a, animAttr, appTransitionOldToString(transit), enter,
- canCustomizeAppTransition, Debug.getCallers(3));
- }
- setAppTransitionFinishedCallbackIfNeeded(a);
-
- return a;
- }
-
- CustomAppTransition getCustomAppTransition(int animAttr, WindowContainer container) {
- ActivityRecord customAnimationSource = container.asActivityRecord();
- if (customAnimationSource == null) {
- return null;
- }
-
- // Only top activity can customize activity animation.
- // If the animation is for the one below, try to get from the above activity.
- if (animAttr == WindowAnimation_activityOpenExitAnimation
- || animAttr == WindowAnimation_activityCloseEnterAnimation) {
- customAnimationSource = customAnimationSource.getTask()
- .getActivityAbove(customAnimationSource);
- if (customAnimationSource == null) {
- return null;
- }
- }
- switch (animAttr) {
- case WindowAnimation_activityOpenEnterAnimation:
- case WindowAnimation_activityOpenExitAnimation:
- return customAnimationSource.getCustomAnimation(true /* open */);
- case WindowAnimation_activityCloseEnterAnimation:
- case WindowAnimation_activityCloseExitAnimation:
- return customAnimationSource.getCustomAnimation(false /* open */);
- }
- return null;
- }
- private Animation loadCustomActivityAnimation(@NonNull CustomAppTransition custom,
- boolean enter, WindowContainer container) {
- final ActivityRecord customAnimationSource = container.asActivityRecord();
- final Animation a = mTransitionAnimation.loadAppTransitionAnimation(
- customAnimationSource.packageName, enter
- ? custom.mEnterAnim : custom.mExitAnim);
- if (a != null && custom.mBackgroundColor != 0) {
- a.setBackdropColor(custom.mBackgroundColor);
- a.setShowBackdrop(true);
- }
- return a;
- }
-
- int getAppRootTaskClipMode() {
- return mNextAppTransitionRequests.contains(TRANSIT_RELAUNCH)
- || mNextAppTransitionRequests.contains(TRANSIT_KEYGUARD_GOING_AWAY)
- || mNextAppTransitionType == NEXT_TRANSIT_TYPE_CLIP_REVEAL
- ? ROOT_TASK_CLIP_NONE
- : ROOT_TASK_CLIP_AFTER_ANIM;
- }
-
- @TransitionFlags
- public int getTransitFlags() {
- return mNextAppTransitionFlags;
- }
-
- void postAnimationCallback() {
- if (mNextAppTransitionCallback != null) {
- mHandler.sendMessage(PooledLambda.obtainMessage(AppTransition::doAnimationCallback,
- mNextAppTransitionCallback));
- mNextAppTransitionCallback = null;
- }
- }
-
- void overridePendingAppTransition(String packageName, int enterAnim, int exitAnim,
- @ColorInt int backgroundColor, IRemoteCallback startedCallback,
- IRemoteCallback endedCallback, boolean overrideTaskTransaction) {
- if (canOverridePendingAppTransition()) {
- clear();
- mNextAppTransitionOverrideRequested = true;
- mNextAppTransitionPackage = packageName;
- mNextAppTransitionEnter = enterAnim;
- mNextAppTransitionExit = exitAnim;
- mNextAppTransitionBackgroundColor = backgroundColor;
- postAnimationCallback();
- mNextAppTransitionCallback = startedCallback;
- mAnimationFinishedCallback = endedCallback;
- mOverrideTaskTransition = overrideTaskTransaction;
- }
- }
-
- void overridePendingAppTransitionScaleUp(int startX, int startY, int startWidth,
- int startHeight) {
- if (canOverridePendingAppTransition()) {
- clear();
- mNextAppTransitionType = NEXT_TRANSIT_TYPE_SCALE_UP;
- putDefaultNextAppTransitionCoordinates(startX, startY, startWidth, startHeight, null);
- postAnimationCallback();
- }
- }
-
- void overridePendingAppTransitionClipReveal(int startX, int startY,
- int startWidth, int startHeight) {
- if (canOverridePendingAppTransition()) {
- clear();
- mNextAppTransitionType = NEXT_TRANSIT_TYPE_CLIP_REVEAL;
- putDefaultNextAppTransitionCoordinates(startX, startY, startWidth, startHeight, null);
- postAnimationCallback();
- }
- }
-
- void overridePendingAppTransitionThumb(HardwareBuffer srcThumb, int startX, int startY,
- IRemoteCallback startedCallback, boolean scaleUp) {
- if (canOverridePendingAppTransition()) {
- clear();
- mNextAppTransitionType = scaleUp ? NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP
- : NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN;
- mNextAppTransitionScaleUp = scaleUp;
- putDefaultNextAppTransitionCoordinates(startX, startY, 0, 0, srcThumb);
- postAnimationCallback();
- mNextAppTransitionCallback = startedCallback;
- }
- }
-
- void overridePendingAppTransitionAspectScaledThumb(HardwareBuffer srcThumb, int startX,
- int startY, int targetWidth, int targetHeight, IRemoteCallback startedCallback,
- boolean scaleUp) {
- if (canOverridePendingAppTransition()) {
- clear();
- mNextAppTransitionType = scaleUp ? NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP
- : NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN;
- mNextAppTransitionScaleUp = scaleUp;
- putDefaultNextAppTransitionCoordinates(startX, startY, targetWidth, targetHeight,
- srcThumb);
- postAnimationCallback();
- mNextAppTransitionCallback = startedCallback;
- }
- }
-
- void overridePendingAppTransitionMultiThumb(AppTransitionAnimationSpec[] specs,
- IRemoteCallback onAnimationStartedCallback, IRemoteCallback onAnimationFinishedCallback,
- boolean scaleUp) {
- if (canOverridePendingAppTransition()) {
- clear();
- mNextAppTransitionType = scaleUp ? NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP
- : NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN;
- mNextAppTransitionScaleUp = scaleUp;
- if (specs != null) {
- for (int i = 0; i < specs.length; i++) {
- AppTransitionAnimationSpec spec = specs[i];
- if (spec != null) {
- final PooledPredicate p = PooledLambda.obtainPredicate(
- Task::isTaskId, PooledLambda.__(Task.class), spec.taskId);
- final WindowContainer container = mDisplayContent.getTask(p);
- p.recycle();
- if (container == null) {
- continue;
- }
- mNextAppTransitionAnimationsSpecs.put(container.hashCode(), spec);
- if (i == 0) {
- // In full screen mode, the transition code depends on the default spec
- // to be set.
- Rect rect = spec.rect;
- putDefaultNextAppTransitionCoordinates(rect.left, rect.top,
- rect.width(), rect.height(), spec.buffer);
- }
- }
- }
- }
- postAnimationCallback();
- mNextAppTransitionCallback = onAnimationStartedCallback;
- mAnimationFinishedCallback = onAnimationFinishedCallback;
- }
- }
-
- void overridePendingAppTransitionMultiThumbFuture(
- IAppTransitionAnimationSpecsFuture specsFuture, IRemoteCallback callback,
- boolean scaleUp) {
- if (canOverridePendingAppTransition()) {
- clear();
- mNextAppTransitionType = scaleUp ? NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP
- : NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN;
- mNextAppTransitionAnimationsSpecsFuture = specsFuture;
- mNextAppTransitionScaleUp = scaleUp;
- mNextAppTransitionFutureCallback = callback;
- if (isReady()) {
- fetchAppTransitionSpecsFromFuture();
- }
- }
- }
-
- void overridePendingAppTransitionRemote(RemoteAnimationAdapter remoteAnimationAdapter) {
- overridePendingAppTransitionRemote(remoteAnimationAdapter, false /* sync */,
- false /* isActivityEmbedding*/);
- }
-
- void overridePendingAppTransitionRemote(RemoteAnimationAdapter remoteAnimationAdapter,
- boolean sync, boolean isActivityEmbedding) {
- }
-
- void overrideInPlaceAppTransition(String packageName, int anim) {
- if (canOverridePendingAppTransition()) {
- clear();
- mNextAppTransitionType = NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE;
- mNextAppTransitionPackage = packageName;
- mNextAppTransitionInPlace = anim;
- }
- }
-
- /**
- * @see {@link #NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS}
- */
- void overridePendingAppTransitionStartCrossProfileApps() {
- if (canOverridePendingAppTransition()) {
- clear();
- mNextAppTransitionType = NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS;
- postAnimationCallback();
- }
- }
-
- private boolean canOverridePendingAppTransition() {
- // Remote animations always take precedence
- return isTransitionSet() && mNextAppTransitionType != NEXT_TRANSIT_TYPE_REMOTE;
- }
-
- /**
- * If a future is set for the app transition specs, fetch it in another thread.
- */
- private void fetchAppTransitionSpecsFromFuture() {
- if (mNextAppTransitionAnimationsSpecsFuture != null) {
- mNextAppTransitionAnimationsSpecsPending = true;
- final IAppTransitionAnimationSpecsFuture future
- = mNextAppTransitionAnimationsSpecsFuture;
- mNextAppTransitionAnimationsSpecsFuture = null;
- mDefaultExecutor.execute(() -> {
- AppTransitionAnimationSpec[] specs = null;
- try {
- Binder.allowBlocking(future.asBinder());
- specs = future.get();
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to fetch app transition specs: " + e);
- }
- synchronized (mService.mGlobalLock) {
- mNextAppTransitionAnimationsSpecsPending = false;
- overridePendingAppTransitionMultiThumb(specs,
- mNextAppTransitionFutureCallback, null /* finishedCallback */,
- mNextAppTransitionScaleUp);
- mNextAppTransitionFutureCallback = null;
- mService.requestTraversal();
- }
- });
- }
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append("mNextAppTransitionRequests=[");
-
- boolean separator = false;
- for (Integer transit : mNextAppTransitionRequests) {
- if (separator) {
- sb.append(", ");
- }
- sb.append(appTransitionToString(transit));
- separator = true;
- }
- sb.append("]");
- sb.append(", mNextAppTransitionFlags="
- + appTransitionFlagsToString(mNextAppTransitionFlags));
- return sb.toString();
- }
-
- /**
- * Returns the human readable name of a old window transition.
- *
- * @param transition The old window transition.
- * @return The transition symbolic name.
- */
- public static String appTransitionOldToString(@TransitionOldType int transition) {
- switch (transition) {
- case TRANSIT_OLD_UNSET: {
- return "TRANSIT_OLD_UNSET";
- }
- case TRANSIT_OLD_NONE: {
- return "TRANSIT_OLD_NONE";
- }
- case TRANSIT_OLD_ACTIVITY_OPEN: {
- return "TRANSIT_OLD_ACTIVITY_OPEN";
- }
- case TRANSIT_OLD_ACTIVITY_CLOSE: {
- return "TRANSIT_OLD_ACTIVITY_CLOSE";
- }
- case TRANSIT_OLD_TASK_OPEN: {
- return "TRANSIT_OLD_TASK_OPEN";
- }
- case TRANSIT_OLD_TASK_CLOSE: {
- return "TRANSIT_OLD_TASK_CLOSE";
- }
- case TRANSIT_OLD_TASK_TO_FRONT: {
- return "TRANSIT_OLD_TASK_TO_FRONT";
- }
- case TRANSIT_OLD_TASK_TO_BACK: {
- return "TRANSIT_OLD_TASK_TO_BACK";
- }
- case TRANSIT_OLD_WALLPAPER_CLOSE: {
- return "TRANSIT_OLD_WALLPAPER_CLOSE";
- }
- case TRANSIT_OLD_WALLPAPER_OPEN: {
- return "TRANSIT_OLD_WALLPAPER_OPEN";
- }
- case TRANSIT_OLD_WALLPAPER_INTRA_OPEN: {
- return "TRANSIT_OLD_WALLPAPER_INTRA_OPEN";
- }
- case TRANSIT_OLD_WALLPAPER_INTRA_CLOSE: {
- return "TRANSIT_OLD_WALLPAPER_INTRA_CLOSE";
- }
- case TRANSIT_OLD_TASK_OPEN_BEHIND: {
- return "TRANSIT_OLD_TASK_OPEN_BEHIND";
- }
- case TRANSIT_OLD_ACTIVITY_RELAUNCH: {
- return "TRANSIT_OLD_ACTIVITY_RELAUNCH";
- }
- case TRANSIT_OLD_KEYGUARD_GOING_AWAY: {
- return "TRANSIT_OLD_KEYGUARD_GOING_AWAY";
- }
- case TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER: {
- return "TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER";
- }
- case TRANSIT_OLD_KEYGUARD_OCCLUDE: {
- return "TRANSIT_OLD_KEYGUARD_OCCLUDE";
- }
- case TRANSIT_OLD_KEYGUARD_OCCLUDE_BY_DREAM: {
- return "TRANSIT_OLD_KEYGUARD_OCCLUDE_BY_DREAM";
- }
- case TRANSIT_OLD_KEYGUARD_UNOCCLUDE: {
- return "TRANSIT_OLD_KEYGUARD_UNOCCLUDE";
- }
- case TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN: {
- return "TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN";
- }
- case TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE: {
- return "TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE";
- }
- case TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE: {
- return "TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE";
- }
- case TRANSIT_OLD_TASK_FRAGMENT_OPEN: {
- return "TRANSIT_OLD_TASK_FRAGMENT_OPEN";
- }
- case TRANSIT_OLD_TASK_FRAGMENT_CLOSE: {
- return "TRANSIT_OLD_TASK_FRAGMENT_CLOSE";
- }
- case TRANSIT_OLD_TASK_FRAGMENT_CHANGE: {
- return "TRANSIT_OLD_TASK_FRAGMENT_CHANGE";
- }
- case TRANSIT_OLD_DREAM_ACTIVITY_OPEN: {
- return "TRANSIT_OLD_DREAM_ACTIVITY_OPEN";
- }
- case TRANSIT_OLD_DREAM_ACTIVITY_CLOSE: {
- return "TRANSIT_OLD_DREAM_ACTIVITY_CLOSE";
- }
- default: {
- return "<UNKNOWN: " + transition + ">";
- }
- }
- }
-
- /**
- * Returns the human readable name of a window transition.
- *
- * @param transition The window transition.
- * @return The transition symbolic name.
- */
- public static String appTransitionToString(@TransitionType int transition) {
- switch (transition) {
- case TRANSIT_NONE: {
- return "TRANSIT_NONE";
- }
- case TRANSIT_OPEN: {
- return "TRANSIT_OPEN";
- }
- case TRANSIT_CLOSE: {
- return "TRANSIT_CLOSE";
- }
- case TRANSIT_TO_FRONT: {
- return "TRANSIT_TO_FRONT";
- }
- case TRANSIT_TO_BACK: {
- return "TRANSIT_TO_BACK";
- }
- case TRANSIT_RELAUNCH: {
- return "TRANSIT_RELAUNCH";
- }
- case TRANSIT_CHANGE: {
- return "TRANSIT_CHANGE";
- }
- case TRANSIT_KEYGUARD_GOING_AWAY: {
- return "TRANSIT_KEYGUARD_GOING_AWAY";
- }
- case TRANSIT_KEYGUARD_OCCLUDE: {
- return "TRANSIT_KEYGUARD_OCCLUDE";
- }
- case TRANSIT_KEYGUARD_UNOCCLUDE: {
- return "TRANSIT_KEYGUARD_UNOCCLUDE";
- }
- default: {
- return "<UNKNOWN: " + transition + ">";
- }
- }
- }
-
- private String appStateToString() {
- switch (mAppTransitionState) {
- case APP_STATE_IDLE:
- return "APP_STATE_IDLE";
- case APP_STATE_READY:
- return "APP_STATE_READY";
- case APP_STATE_RUNNING:
- return "APP_STATE_RUNNING";
- case APP_STATE_TIMEOUT:
- return "APP_STATE_TIMEOUT";
- default:
- return "unknown state=" + mAppTransitionState;
- }
- }
-
- private String transitTypeToString() {
- switch (mNextAppTransitionType) {
- case NEXT_TRANSIT_TYPE_NONE:
- return "NEXT_TRANSIT_TYPE_NONE";
- case NEXT_TRANSIT_TYPE_CUSTOM:
- return "NEXT_TRANSIT_TYPE_CUSTOM";
- case NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE:
- return "NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE";
- case NEXT_TRANSIT_TYPE_SCALE_UP:
- return "NEXT_TRANSIT_TYPE_SCALE_UP";
- case NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP:
- return "NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP";
- case NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN:
- return "NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN";
- case NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP:
- return "NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP";
- case NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN:
- return "NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN";
- case NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS:
- return "NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS";
- default:
- return "unknown type=" + mNextAppTransitionType;
- }
- }
-
- private static final ArrayList<Pair<Integer, String>> sFlagToString;
-
- static {
- sFlagToString = new ArrayList<>();
- sFlagToString.add(new Pair<>(TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE,
- "TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE"));
- sFlagToString.add(new Pair<>(TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION,
- "TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION"));
- sFlagToString.add(new Pair<>(TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER,
- "TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER"));
- sFlagToString.add(new Pair<>(TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION,
- "TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION"));
- sFlagToString.add(new Pair<>(
- TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_LAUNCHER_CLEAR_SNAPSHOT,
- "TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_LAUNCHER_WITH_IN_WINDOW_ANIMATIONS"));
- sFlagToString.add(new Pair<>(TRANSIT_FLAG_APP_CRASHED,
- "TRANSIT_FLAG_APP_CRASHED"));
- sFlagToString.add(new Pair<>(TRANSIT_FLAG_OPEN_BEHIND,
- "TRANSIT_FLAG_OPEN_BEHIND"));
- }
-
- /**
- * Returns the human readable names of transit flags.
- *
- * @param flags a bitmask combination of transit flags.
- * @return The combination of symbolic names.
- */
- public static String appTransitionFlagsToString(int flags) {
- String sep = "";
- StringBuilder sb = new StringBuilder();
- for (Pair<Integer, String> pair : sFlagToString) {
- if ((flags & pair.first) != 0) {
- sb.append(sep);
- sb.append(pair.second);
- sep = " | ";
- }
- }
- return sb.toString();
- }
-
- void dumpDebug(ProtoOutputStream proto, long fieldId) {
- final long token = proto.start(fieldId);
- proto.write(APP_TRANSITION_STATE, mAppTransitionState);
- proto.write(LAST_USED_APP_TRANSITION, mLastUsedAppTransition);
- proto.end(token);
- }
-
- @Override
- public void dump(PrintWriter pw, String prefix) {
- pw.print(prefix); pw.println(this);
- pw.print(prefix); pw.print("mAppTransitionState="); pw.println(appStateToString());
- if (mNextAppTransitionType != NEXT_TRANSIT_TYPE_NONE) {
- pw.print(prefix); pw.print("mNextAppTransitionType=");
- pw.println(transitTypeToString());
- }
- if (mNextAppTransitionOverrideRequested
- || mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM) {
- pw.print(prefix); pw.print("mNextAppTransitionPackage=");
- pw.println(mNextAppTransitionPackage);
- pw.print(prefix); pw.print("mNextAppTransitionEnter=0x");
- pw.print(Integer.toHexString(mNextAppTransitionEnter));
- pw.print(" mNextAppTransitionExit=0x");
- pw.println(Integer.toHexString(mNextAppTransitionExit));
- pw.print(" mNextAppTransitionBackgroundColor=0x");
- pw.println(Integer.toHexString(mNextAppTransitionBackgroundColor));
- }
- switch (mNextAppTransitionType) {
- case NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE:
- pw.print(prefix); pw.print("mNextAppTransitionPackage=");
- pw.println(mNextAppTransitionPackage);
- pw.print(prefix); pw.print("mNextAppTransitionInPlace=0x");
- pw.print(Integer.toHexString(mNextAppTransitionInPlace));
- break;
- case NEXT_TRANSIT_TYPE_SCALE_UP: {
- getDefaultNextAppTransitionStartRect(mTmpRect);
- pw.print(prefix); pw.print("mNextAppTransitionStartX=");
- pw.print(mTmpRect.left);
- pw.print(" mNextAppTransitionStartY=");
- pw.println(mTmpRect.top);
- pw.print(prefix); pw.print("mNextAppTransitionStartWidth=");
- pw.print(mTmpRect.width());
- pw.print(" mNextAppTransitionStartHeight=");
- pw.println(mTmpRect.height());
- break;
- }
- case NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP:
- case NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN:
- case NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP:
- case NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN: {
- pw.print(prefix); pw.print("mDefaultNextAppTransitionAnimationSpec=");
- pw.println(mDefaultNextAppTransitionAnimationSpec);
- pw.print(prefix); pw.print("mNextAppTransitionAnimationsSpecs=");
- pw.println(mNextAppTransitionAnimationsSpecs);
- pw.print(prefix); pw.print("mNextAppTransitionScaleUp=");
- pw.println(mNextAppTransitionScaleUp);
- break;
- }
- }
- if (mNextAppTransitionCallback != null) {
- pw.print(prefix); pw.print("mNextAppTransitionCallback=");
- pw.println(mNextAppTransitionCallback);
- }
- if (mLastUsedAppTransition != TRANSIT_OLD_NONE) {
- pw.print(prefix); pw.print("mLastUsedAppTransition=");
- pw.println(appTransitionOldToString(mLastUsedAppTransition));
- pw.print(prefix); pw.print("mLastOpeningApp=");
- pw.println(mLastOpeningApp);
- pw.print(prefix); pw.print("mLastClosingApp=");
- pw.println(mLastClosingApp);
- pw.print(prefix); pw.print("mLastChangingApp=");
- pw.println(mLastChangingApp);
- }
- }
-
- boolean prepareAppTransition(@TransitionType int transit, @TransitionFlags int flags) {
- if (WindowManagerService.sEnableShellTransitions) {
- return false;
- }
- mNextAppTransitionRequests.add(transit);
- mNextAppTransitionFlags |= flags;
- updateBooster();
- removeAppTransitionTimeoutCallbacks();
- mHandler.postDelayed(mHandleAppTransitionTimeoutRunnable,
- APP_TRANSITION_TIMEOUT_MS);
- return prepare();
- }
-
- /**
- * @return true if {@param transit} is representing a transition in which Keyguard is going
- * away, false otherwise
- */
- public static boolean isKeyguardGoingAwayTransitOld(int transit) {
- return transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY
- || transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
- }
-
- static boolean isKeyguardOccludeTransitOld(@TransitionOldType int transit) {
- return transit == TRANSIT_OLD_KEYGUARD_OCCLUDE
- || transit == TRANSIT_OLD_KEYGUARD_OCCLUDE_BY_DREAM
- || transit == TRANSIT_OLD_KEYGUARD_UNOCCLUDE;
- }
-
- static boolean isKeyguardTransitOld(@TransitionOldType int transit) {
- return isKeyguardGoingAwayTransitOld(transit) || isKeyguardOccludeTransitOld(transit);
- }
-
- static boolean isTaskTransitOld(@TransitionOldType int transit) {
- return isTaskOpenTransitOld(transit)
- || isTaskCloseTransitOld(transit);
- }
-
- static boolean isTaskCloseTransitOld(@TransitionOldType int transit) {
- return transit == TRANSIT_OLD_TASK_CLOSE
- || transit == TRANSIT_OLD_TASK_TO_BACK;
- }
-
- private static boolean isTaskOpenTransitOld(@TransitionOldType int transit) {
- return transit == TRANSIT_OLD_TASK_OPEN
- || transit == TRANSIT_OLD_TASK_OPEN_BEHIND
- || transit == TRANSIT_OLD_TASK_TO_FRONT;
- }
-
- static boolean isActivityTransitOld(@TransitionOldType int transit) {
- return transit == TRANSIT_OLD_ACTIVITY_OPEN
- || transit == TRANSIT_OLD_ACTIVITY_CLOSE
- || transit == TRANSIT_OLD_ACTIVITY_RELAUNCH;
- }
-
- static boolean isTaskFragmentTransitOld(@TransitionOldType int transit) {
- return transit == TRANSIT_OLD_TASK_FRAGMENT_OPEN
- || transit == TRANSIT_OLD_TASK_FRAGMENT_CLOSE
- || transit == TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
- }
-
- static boolean isChangeTransitOld(@TransitionOldType int transit) {
- return transit == TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE
- || transit == TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
- }
-
- static boolean isClosingTransitOld(@TransitionOldType int transit) {
- return transit == TRANSIT_OLD_ACTIVITY_CLOSE
- || transit == TRANSIT_OLD_TASK_CLOSE
- || transit == TRANSIT_OLD_WALLPAPER_CLOSE
- || transit == TRANSIT_OLD_WALLPAPER_INTRA_CLOSE
- || transit == TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE
- || transit == TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE;
- }
-
- static boolean isNormalTransit(@TransitionType int transit) {
- return transit == TRANSIT_OPEN
- || transit == TRANSIT_CLOSE
- || transit == TRANSIT_TO_FRONT
- || transit == TRANSIT_TO_BACK;
- }
-
- static boolean isKeyguardTransit(@TransitionType int transit) {
- return transit == TRANSIT_KEYGUARD_GOING_AWAY
- || transit == TRANSIT_KEYGUARD_OCCLUDE
- || transit == TRANSIT_KEYGUARD_UNOCCLUDE;
- }
-
- @TransitionType int getKeyguardTransition() {
- if (mNextAppTransitionRequests.indexOf(TRANSIT_KEYGUARD_GOING_AWAY) != -1) {
- return TRANSIT_KEYGUARD_GOING_AWAY;
- }
- final int unoccludeIndex = mNextAppTransitionRequests.indexOf(TRANSIT_KEYGUARD_UNOCCLUDE);
- final int occludeIndex = mNextAppTransitionRequests.indexOf(TRANSIT_KEYGUARD_OCCLUDE);
- // No keyguard related transition requests.
- if (unoccludeIndex == -1 && occludeIndex == -1) {
- return TRANSIT_NONE;
- }
- // In case we unocclude Keyguard and occlude it again, meaning that we never actually
- // unoccclude/occlude Keyguard, but just run a normal transition.
- if (unoccludeIndex != -1 && unoccludeIndex < occludeIndex) {
- return TRANSIT_NONE;
- }
- return unoccludeIndex != -1 ? TRANSIT_KEYGUARD_UNOCCLUDE : TRANSIT_KEYGUARD_OCCLUDE;
- }
-
- @TransitionType int getFirstAppTransition() {
- for (int i = 0; i < mNextAppTransitionRequests.size(); ++i) {
- final @TransitionType int transit = mNextAppTransitionRequests.get(i);
- if (transit != TRANSIT_NONE && !isKeyguardTransit(transit)) {
- return transit;
- }
- }
- return TRANSIT_NONE;
- }
-
- boolean containsTransitRequest(@TransitionType int transit) {
- return mNextAppTransitionRequests.contains(transit);
- }
-
- private void handleAppTransitionTimeout() {
- }
-
- private static void doAnimationCallback(@NonNull IRemoteCallback callback) {
- try {
- ((IRemoteCallback) callback).sendResult(null);
- } catch (RemoteException e) {
- }
- }
-
- private void setAppTransitionFinishedCallbackIfNeeded(Animation anim) {
- final IRemoteCallback callback = mAnimationFinishedCallback;
- if (callback != null && anim != null) {
- anim.setAnimationListener(new Animation.AnimationListener() {
- @Override
- public void onAnimationStart(Animation animation) { }
-
- @Override
- public void onAnimationEnd(Animation animation) {
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppTransition::doAnimationCallback, callback));
- }
-
- @Override
- public void onAnimationRepeat(Animation animation) { }
- });
- }
- }
-
- void removeAppTransitionTimeoutCallbacks() {
- mHandler.removeCallbacks(mHandleAppTransitionTimeoutRunnable);
- }
-}
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index e9d9ace..e9b7649 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -691,8 +691,8 @@
return false;
}
if (window.mAttrs.windowAnimations != 0) {
- final TransitionAnimation transitionAnimation = window.getDisplayContent()
- .mAppTransition.mTransitionAnimation;
+ final TransitionAnimation transitionAnimation = window.mDisplayContent
+ .mTransitionAnimation;
final int attr = com.android.internal.R.styleable
.WindowAnimation_activityCloseExitAnimation;
final int appResId = transitionAnimation.getAnimationResId(
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index f51e60c..36686fc 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -312,7 +312,6 @@
private final @ActivityManager.ProcessState int mCallingUidProcState;
private final boolean mIsCallingUidPersistentSystemProcess;
final BackgroundStartPrivileges mBalAllowedByPiSender;
- final BackgroundStartPrivileges mBalAllowedByPiCreatorWithHardening;
final BackgroundStartPrivileges mBalAllowedByPiCreator;
private final String mRealCallingPackage;
private final int mRealCallingUid;
@@ -379,22 +378,14 @@
if (mAutoOptInCaller) {
// grant BAL privileges unless explicitly opted out
- mBalAllowedByPiCreatorWithHardening = mBalAllowedByPiCreator =
+ mBalAllowedByPiCreator =
callerBackgroundActivityStartMode == MODE_BACKGROUND_ACTIVITY_START_DENIED
? BackgroundStartPrivileges.NONE
: BackgroundStartPrivileges.ALLOW_BAL;
} else {
// for PendingIntents we restrict BAL based on target_sdk
- mBalAllowedByPiCreatorWithHardening = getBackgroundStartPrivilegesAllowedByCreator(
+ mBalAllowedByPiCreator = getBackgroundStartPrivilegesAllowedByCreator(
callingUid, callingPackage, checkedOptions);
- final BackgroundStartPrivileges mBalAllowedByPiCreatorWithoutHardening =
- callerBackgroundActivityStartMode
- == MODE_BACKGROUND_ACTIVITY_START_DENIED
- ? BackgroundStartPrivileges.NONE
- : BackgroundStartPrivileges.ALLOW_BAL;
- mBalAllowedByPiCreator = balRequireOptInByPendingIntentCreator()
- ? mBalAllowedByPiCreatorWithHardening
- : mBalAllowedByPiCreatorWithoutHardening;
}
if (mAutoOptInReason != null) {
@@ -585,9 +576,8 @@
if (mCallerApp != null) {
sb.append("; inVisibleTask: ").append(mCallerApp.hasActivityInVisibleTask());
}
- sb.append("; balAllowedByPiCreator: ").append(mBalAllowedByPiCreator);
- sb.append("; balAllowedByPiCreatorWithHardening: ")
- .append(mBalAllowedByPiCreatorWithHardening);
+ sb.append("; balAllowedByPiCreator: ")
+ .append(mBalAllowedByPiCreator);
if (mResultForCaller != null) {
sb.append("; resultIfPiCreatorAllowsBal: ")
.append(balCodeToString(mResultForCaller.mCode));
@@ -638,14 +628,13 @@
}
static class BalVerdict {
- static final BalVerdict BLOCK = new BalVerdict(BAL_BLOCK, false, "Blocked");
+ static final BalVerdict BLOCK = new BalVerdict(BAL_BLOCK, "Blocked");
static final BalVerdict ALLOW_BY_DEFAULT =
- new BalVerdict(BAL_ALLOW_DEFAULT, false, "Default");
+ new BalVerdict(BAL_ALLOW_DEFAULT, "Default");
// Careful using this - it will bypass all ASM checks.
static final BalVerdict ALLOW_PRIVILEGED =
- new BalVerdict(BAL_ALLOW_ALLOWLISTED_UID, false, "PRIVILEGED");
+ new BalVerdict(BAL_ALLOW_ALLOWLISTED_UID, "PRIVILEGED");
private final @BalCode int mCode;
- private final boolean mBackground;
private final String mMessage;
private String mProcessInfo;
// indicates BAL would be blocked because only creator of the PI has the privilege to allow
@@ -654,8 +643,7 @@
/** indicates that this verdict is based on the real calling UID and not the calling UID */
private boolean mBasedOnRealCaller;
- BalVerdict(@BalCode int balCode, boolean background, String message) {
- this.mBackground = background;
+ BalVerdict(@BalCode int balCode, String message) {
this.mCode = balCode;
this.mMessage = message;
}
@@ -708,16 +696,7 @@
builder.append(" [realCaller]");
}
if (DEBUG_ACTIVITY_STARTS) {
- builder.append(" (");
- if (mBackground) {
- builder.append("Background ");
- }
- builder.append("Activity start ");
- if (mCode == BAL_BLOCK) {
- builder.append("denied");
- } else {
- builder.append("allowed: ").append(mMessage);
- }
+ builder.append(" (").append(mMessage);
if (mProcessInfo != null) {
builder.append(" ");
builder.append(mProcessInfo);
@@ -795,7 +774,6 @@
// to realCallingUid when calculating resultForRealCaller below.
if (getService().hasActiveVisibleWindow(realCallingSdkSandboxUidToAppUid)) {
state.setResultForRealCaller(new BalVerdict(BAL_ALLOW_SDK_SANDBOX,
- /*background*/ false,
"uid in SDK sandbox has visible (non-toast) window"));
return allowBasedOnRealCaller(state);
}
@@ -1059,8 +1037,7 @@
|| state.mAppSwitchState == APP_SWITCH_FG_ONLY
|| isHomeApp(state.mCallingUid, state.mCallingPackage);
if (appSwitchAllowedOrFg && state.mCallingUidHasVisibleActivity) {
- return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
- /*background*/ false, "callingUid has visible window");
+ return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "callingUid has visible window");
}
return BalVerdict.BLOCK;
};
@@ -1068,7 +1045,7 @@
private final BalExemptionCheck mCheckCallerNonAppVisible = state -> {
if (state.mCallingUidHasNonAppVisibleWindow) {
return new BalVerdict(BAL_ALLOW_NON_APP_VISIBLE_WINDOW,
- /*background*/ false, "callingUid has non-app visible window "
+ "callingUid has non-app visible window "
+ getService().mActiveUids.getNonAppVisibleWindowDetails(state.mCallingUid));
}
return BalVerdict.BLOCK;
@@ -1080,9 +1057,7 @@
if (state.mCallingUid == Process.ROOT_UID
|| callingAppId == Process.SYSTEM_UID
|| callingAppId == Process.NFC_UID) {
- return new BalVerdict(
- BAL_ALLOW_ALLOWLISTED_UID, /*background*/ false,
- "Important callingUid");
+ return new BalVerdict(BAL_ALLOW_ALLOWLISTED_UID, "Important callingUid");
}
return BalVerdict.BLOCK;
};
@@ -1090,9 +1065,7 @@
private final BalExemptionCheck mCheckCallerIsAllowlistedComponent = state -> {
// Always allow home application to start activities.
if (isHomeApp(state.mCallingUid, state.mCallingPackage)) {
- return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
- /*background*/ false,
- "Home app");
+ return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, "Home app");
}
final int callingAppId = UserHandle.getAppId(state.mCallingUid);
@@ -1100,37 +1073,31 @@
final WindowState imeWindow =
getService().mRootWindowContainer.getCurrentInputMethodWindow();
if (imeWindow != null && callingAppId == imeWindow.mOwnerUid) {
- return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
- /*background*/ false,
- "Active ime");
+ return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, "Active ime");
}
// don't abort if the callingUid is a persistent system process
if (state.mIsCallingUidPersistentSystemProcess) {
return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
- /*background*/ false, "callingUid is persistent system process");
+ "callingUid is persistent system process");
}
// don't abort if the caller has the same uid as the recents component
if (getSupervisor().mRecentTasks.isCallerRecents(state.mCallingUid)) {
- return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
- /*background*/ true, "Recents Component");
+ return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, "Recents Component");
}
// don't abort if the callingUid is the device owner
if (getService().isDeviceOwner(state.mCallingUid)) {
- return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
- /*background*/ true, "Device Owner");
+ return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, "Device Owner");
}
// don't abort if the callingUid is a affiliated profile owner
if (getService().isAffiliatedProfileOwner(state.mCallingUid)) {
- return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
- /*background*/ true, "Affiliated Profile Owner");
+ return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, "Affiliated Profile Owner");
}
// don't abort if the callingUid has companion device
final int callingUserId = UserHandle.getUserId(state.mCallingUid);
if (getService().isAssociatedCompanionApp(callingUserId, state.mCallingUid)) {
- return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
- /*background*/ true, "Companion App");
+ return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, "Companion App");
}
return BalVerdict.BLOCK;
};
@@ -1139,7 +1106,6 @@
// don't abort if the callingUid has START_ACTIVITIES_FROM_BACKGROUND permission
if (hasBalPermission(state.mCallingUid, state.mCallingPid)) {
return new BalVerdict(BAL_ALLOW_PERMISSION,
- /*background*/ true,
"START_ACTIVITIES_FROM_BACKGROUND permission granted");
}
return BalVerdict.BLOCK;
@@ -1149,7 +1115,7 @@
if (getService().hasSystemAlertWindowPermission(state.mCallingUid, state.mCallingPid,
state.mCallingPackage)) {
return new BalVerdict(BAL_ALLOW_SAW_PERMISSION,
- /*background*/ true, "SYSTEM_ALERT_WINDOW permission is granted");
+ "SYSTEM_ALERT_WINDOW permission is granted");
}
return BalVerdict.BLOCK;
};
@@ -1159,7 +1125,7 @@
if (isSystemExemptFlagEnabled() && getService().getAppOpsManager().checkOpNoThrow(
AppOpsManager.OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION,
state.mCallingUid, state.mCallingPackage) == AppOpsManager.MODE_ALLOWED) {
- return new BalVerdict(BAL_ALLOW_PERMISSION, /*background*/ true,
+ return new BalVerdict(BAL_ALLOW_PERMISSION,
"OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION appop is granted");
}
return BalVerdict.BLOCK;
@@ -1200,8 +1166,7 @@
|| state.mAppSwitchState == APP_SWITCH_FG_ONLY
|| isHomeApp(state.mRealCallingUid, state.mRealCallingPackage);
if (appSwitchAllowedOrFg && state.mRealCallingUidHasVisibleActivity) {
- return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
- /*background*/ false, "realCallingUid has visible window");
+ return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "realCallingUid has visible window");
}
return BalVerdict.BLOCK;
};
@@ -1209,9 +1174,9 @@
private final BalExemptionCheck mCheckRealCallerNonAppVisible = state -> {
if (state.mRealCallingUidHasNonAppVisibleWindow) {
return new BalVerdict(BAL_ALLOW_NON_APP_VISIBLE_WINDOW,
- /*background*/ false, "realCallingUid has non-app visible window "
- + getService().mActiveUids.getNonAppVisibleWindowDetails(
- state.mRealCallingUid));
+ "realCallingUid has non-app visible window "
+ + getService().mActiveUids.getNonAppVisibleWindowDetails(
+ state.mRealCallingUid));
}
return BalVerdict.BLOCK;
};
@@ -1230,9 +1195,7 @@
== MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS;
if (allowAlways
&& hasBalPermission(state.mRealCallingUid, state.mRealCallingPid)) {
- return new BalVerdict(BAL_ALLOW_PERMISSION,
- /*background*/ false,
- "realCallingUid has BAL permission.");
+ return new BalVerdict(BAL_ALLOW_PERMISSION, "realCallingUid has BAL permission.");
}
return BalVerdict.BLOCK;
};
@@ -1245,7 +1208,7 @@
&& getService().hasSystemAlertWindowPermission(state.mRealCallingUid,
state.mRealCallingPid, state.mRealCallingPackage)) {
return new BalVerdict(BAL_ALLOW_SAW_PERMISSION,
- /*background*/ true, "SYSTEM_ALERT_WINDOW permission is granted");
+ "SYSTEM_ALERT_WINDOW permission is granted");
}
return BalVerdict.BLOCK;
};
@@ -1258,7 +1221,6 @@
if ((allowAlways || state.mAllowBalExemptionForSystemProcess)
&& state.mIsRealCallingUidPersistentSystemProcess) {
return new BalVerdict(BAL_ALLOW_ALLOWLISTED_UID,
- /*background*/ false,
"realCallingUid is persistent system process AND intent "
+ "sender forced to allow.");
}
@@ -1270,7 +1232,6 @@
if (getService().isAssociatedCompanionApp(
UserHandle.getUserId(state.mRealCallingUid), state.mRealCallingUid)) {
return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT,
- /*background*/ false,
"realCallingUid is a companion app.");
}
return BalVerdict.BLOCK;
diff --git a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
index ccf1aed..31b2394 100644
--- a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
+++ b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
@@ -125,27 +125,27 @@
long lastActivityFinishTime) {
// Allow if the proc is instrumenting with background activity starts privs.
if (checkConfiguration.checkOtherExemptions && hasBackgroundActivityStartPrivileges) {
- return new BalVerdict(BAL_ALLOW_PERMISSION, /*background*/ true,
+ return new BalVerdict(BAL_ALLOW_PERMISSION, /*background*/
"process instrumenting with background activity starts privileges");
}
// Allow if the flag was explicitly set.
if (checkConfiguration.checkOtherExemptions && isBackgroundStartAllowedByToken(uid,
packageName, checkConfiguration.isCheckingForFgsStart)) {
return new BalVerdict(balImprovedMetrics() ? BAL_ALLOW_TOKEN : BAL_ALLOW_PERMISSION,
- /*background*/ true, "process allowed by token");
+ /*background*/ "process allowed by token");
}
// Allow if the caller is bound by a UID that's currently foreground.
// But still respect the appSwitchState.
if (checkConfiguration.checkVisibility && appSwitchState != APP_SWITCH_DISALLOW
&& isBoundByForegroundUid()) {
return new BalVerdict(balImprovedMetrics() ? BAL_ALLOW_BOUND_BY_FOREGROUND
- : BAL_ALLOW_VISIBLE_WINDOW, /*background*/ false,
+ : BAL_ALLOW_VISIBLE_WINDOW, /*background*/
"process bound by foreground uid");
}
// Allow if the caller has an activity in any foreground task.
if (checkConfiguration.checkOtherExemptions && hasActivityInVisibleTask
&& appSwitchState != APP_SWITCH_DISALLOW) {
- return new BalVerdict(BAL_ALLOW_FOREGROUND, /*background*/ false,
+ return new BalVerdict(BAL_ALLOW_FOREGROUND, /*background*/
"process has activity in foreground task");
}
@@ -160,7 +160,7 @@
long timeSinceLastStartOrFinish = now - Math.max(lastActivityLaunchTime,
lastActivityFinishTime);
if (timeSinceLastStartOrFinish < checkConfiguration.gracePeriod) {
- return new BalVerdict(BAL_ALLOW_GRACE_PERIOD, /*background*/ true,
+ return new BalVerdict(BAL_ALLOW_GRACE_PERIOD, /*background*/
"within " + checkConfiguration.gracePeriod + "ms grace period ("
+ timeSinceLastStartOrFinish + "ms)");
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 64c19ff..70dc0b4 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -82,7 +82,6 @@
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManager.REMOVE_CONTENT_MODE_DESTROY;
import static android.view.WindowManager.TRANSIT_CHANGE;
-import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.inputmethod.ImeTracker.DEBUG_IME_VISIBILITY;
@@ -108,7 +107,7 @@
import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY;
import static com.android.server.wm.DisplayContentProto.APP_TRANSITION;
-import static com.android.server.wm.DisplayContentProto.CURRENT_FOCUS;
+import static com.android.server.wm.DisplayContentProto.CURRENT_FOCUS_IDENTIFIER;
import static com.android.server.wm.DisplayContentProto.DISPLAY_FRAMES;
import static com.android.server.wm.DisplayContentProto.DISPLAY_INFO;
import static com.android.server.wm.DisplayContentProto.DISPLAY_READY;
@@ -118,9 +117,9 @@
import static com.android.server.wm.DisplayContentProto.FOCUSED_ROOT_TASK_ID;
import static com.android.server.wm.DisplayContentProto.ID;
import static com.android.server.wm.DisplayContentProto.IME_POLICY;
-import static com.android.server.wm.DisplayContentProto.INPUT_METHOD_CONTROL_TARGET;
-import static com.android.server.wm.DisplayContentProto.INPUT_METHOD_INPUT_TARGET;
-import static com.android.server.wm.DisplayContentProto.INPUT_METHOD_TARGET;
+import static com.android.server.wm.DisplayContentProto.INPUT_METHOD_CONTROL_TARGET_IDENTIFIER;
+import static com.android.server.wm.DisplayContentProto.INPUT_METHOD_INPUT_TARGET_IDENTIFIER;
+import static com.android.server.wm.DisplayContentProto.INPUT_METHOD_LAYERING_TARGET_IDENTIFIER;
import static com.android.server.wm.DisplayContentProto.IS_SLEEPING;
import static com.android.server.wm.DisplayContentProto.KEEP_CLEAR_AREAS;
import static com.android.server.wm.DisplayContentProto.MIN_SIZE_OF_RESIZEABLE_TASK_DP;
@@ -156,7 +155,6 @@
import static com.android.server.wm.utils.DisplayInfoOverrides.copyDisplayInfoFields;
import static com.android.server.wm.utils.RegionUtils.forEachRectReverse;
import static com.android.server.wm.utils.RegionUtils.rectListToRegion;
-import static com.android.window.flags.Flags.enablePersistingDensityScaleForConnectedDisplays;
import static com.android.window.flags.Flags.enablePresentationForConnectedDisplays;
import android.annotation.IntDef;
@@ -237,6 +235,7 @@
import android.view.WindowManager.DisplayImePolicy;
import android.view.WindowManagerPolicyConstants.PointerEventListener;
import android.view.inputmethod.ImeTracker;
+import android.window.DesktopExperienceFlags;
import android.window.DisplayWindowPolicyController;
import android.window.IDisplayAreaOrganizer;
import android.window.ScreenCapture;
@@ -248,6 +247,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.policy.TransitionAnimation;
import com.android.internal.protolog.ProtoLog;
import com.android.internal.util.ToBooleanFunction;
import com.android.internal.util.function.pooled.PooledLambda;
@@ -365,7 +365,8 @@
private boolean mTmpInitial;
private int mMaxUiWidth = 0;
- final AppTransition mAppTransition;
+ // TODO(b/400335290): extract the needed methods and remove this field.
+ final TransitionAnimation mTransitionAnimation;
final UnknownAppVisibilityController mUnknownAppVisibilityController;
@@ -1180,7 +1181,7 @@
mHoldScreenWakeLock.setReferenceCounted(false);
mFixedRotationTransitionListener = new FixedRotationTransitionListener(mDisplayId);
- mAppTransition = new AppTransition(mWmService.mContext, mWmService, this);
+ mTransitionAnimation = new TransitionAnimation(mWmService.mContext, false /* debug */, TAG);
mTransitionController.registerLegacyListener(mFixedRotationTransitionListener);
mUnknownAppVisibilityController = new UnknownAppVisibilityController(mWmService, this);
mRemoteDisplayChangeController = new RemoteDisplayChangeController(this);
@@ -3154,7 +3155,7 @@
}
}
// Update the base density if there is a forced density ratio.
- if (enablePersistingDensityScaleForConnectedDisplays()
+ if (DesktopExperienceFlags.ENABLE_PERSISTING_DISPLAY_SIZE_FOR_CONNECTED_DISPLAYS.isTrue()
&& mIsDensityForced && mExternalDisplayForcedDensityRatio != 0.0f) {
mBaseDisplayDensity = (int)
(mInitialDisplayDensity * mExternalDisplayForcedDensityRatio + 0.5);
@@ -3189,7 +3190,7 @@
density = 0;
}
// Save the new density ratio to settings for external displays.
- if (enablePersistingDensityScaleForConnectedDisplays()
+ if (DesktopExperienceFlags.ENABLE_PERSISTING_DISPLAY_SIZE_FOR_CONNECTED_DISPLAYS.isTrue()
&& mDisplayInfo.type == TYPE_EXTERNAL) {
mExternalDisplayForcedDensityRatio = (float)
mBaseDisplayDensity / getInitialDisplayDensity();
@@ -3612,18 +3613,20 @@
}
if (mImeLayeringTarget != null) {
- mImeLayeringTarget.dumpDebug(proto, INPUT_METHOD_TARGET, logLevel);
+ mImeLayeringTarget.writeIdentifierToProto(
+ proto, INPUT_METHOD_LAYERING_TARGET_IDENTIFIER);
}
- if (mImeInputTarget != null) {
- mImeInputTarget.dumpProto(proto, INPUT_METHOD_INPUT_TARGET, logLevel);
+ if (mImeInputTarget != null && mImeInputTarget.getWindowState() != null) {
+ mImeInputTarget.getWindowState().writeIdentifierToProto(
+ proto, INPUT_METHOD_INPUT_TARGET_IDENTIFIER);
}
if (mImeControlTarget != null
&& mImeControlTarget.getWindow() != null) {
- mImeControlTarget.getWindow().dumpDebug(proto, INPUT_METHOD_CONTROL_TARGET,
- logLevel);
+ mImeControlTarget.getWindow().writeIdentifierToProto(
+ proto, INPUT_METHOD_CONTROL_TARGET_IDENTIFIER);
}
if (mCurrentFocus != null) {
- mCurrentFocus.dumpDebug(proto, CURRENT_FOCUS, logLevel);
+ mCurrentFocus.writeIdentifierToProto(proto, CURRENT_FOCUS_IDENTIFIER);
}
if (mInsetsStateController != null) {
mInsetsStateController.dumpDebug(proto, logLevel);
@@ -5634,29 +5637,11 @@
}
/**
- * @deprecated new transition should use {@link #requestTransitionAndLegacyPrepare(int, int)}
- */
- @Deprecated
- void prepareAppTransition(@WindowManager.TransitionType int transit) {
- prepareAppTransition(transit, 0 /* flags */);
- }
-
- /**
- * @deprecated new transition should use {@link #requestTransitionAndLegacyPrepare(int, int)}
- */
- @Deprecated
- void prepareAppTransition(@WindowManager.TransitionType int transit,
- @WindowManager.TransitionFlags int flags) {
- mAppTransition.prepareAppTransition(transit, flags);
- }
-
- /**
* Helper that both requests a transition (using the new transition system) and prepares
* the legacy transition system. Use this when both systems have the same start-point.
*
* @see TransitionController#requestTransitionIfNeeded(int, int, WindowContainer,
* WindowContainer)
- * @see AppTransition#prepareAppTransition
*/
void requestTransitionAndLegacyPrepare(@WindowManager.TransitionType int transit,
@WindowManager.TransitionFlags int flags, @Nullable WindowContainer trigger) {
@@ -6544,19 +6529,9 @@
// If keyguard is not locked, the change of flags won't affect activity visibility.
return;
}
- // We might change the visibilities here, so prepare an empty app transition which might be
- // overridden later if we actually change visibilities.
- final boolean wasTransitionSet = mAppTransition.isTransitionSet();
- if (!wasTransitionSet) {
- prepareAppTransition(TRANSIT_NONE);
- }
mRootWindowContainer.ensureActivitiesVisible();
-
- // If there was a transition set already we don't want to interfere with it as we might be
- // starting it too early.
- if (!wasTransitionSet) {
- executeAppTransition();
- }
+ // In case there is a visibility change.
+ executeAppTransition();
}
/**
@@ -6575,6 +6550,22 @@
.getKeyguardController().isKeyguardLocked(mDisplayId);
}
+ boolean isKeyguardLockedOrAodShowing() {
+ return isKeyguardLocked() || isAodShowing();
+ }
+
+ /**
+ * @return whether aod is showing for this display
+ */
+ boolean isAodShowing() {
+ final boolean isAodShowing = mRootWindowContainer.mTaskSupervisor
+ .getKeyguardController().isAodShowing(mDisplayId);
+ if (mDisplayId == DEFAULT_DISPLAY && isAodShowing) {
+ return !isKeyguardGoingAway();
+ }
+ return isAodShowing;
+ }
+
/**
* @return whether keyguard is going away on this display
*/
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index ec5b503f..c27aefa 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -107,6 +107,7 @@
import android.view.InsetsFrameProvider;
import android.view.InsetsSource;
import android.view.InsetsState;
+import android.view.PrivacyIndicatorBounds;
import android.view.Surface;
import android.view.View;
import android.view.ViewDebug;
@@ -2121,6 +2122,8 @@
}
private static class Cache {
+ static final int TYPE_REGULAR_BARS = WindowInsets.Type.statusBars()
+ | WindowInsets.Type.navigationBars();
/**
* If {@link #mPreserveId} is this value, it is in the middle of updating display
* configuration before a transition is started. Then the active cache should be used.
@@ -2130,6 +2133,14 @@
int mPreserveId;
boolean mActive;
+ /**
+ * When display switches, mRegularBarsInsets will assign to mPreservedInsets, and the
+ * insets sources of previous device state will copy to mRegularBarsInsets.
+ */
+ ArrayList<InsetsSource> mPreservedInsets;
+ ArrayList<InsetsSource> mRegularBarsInsets;
+ PrivacyIndicatorBounds mPrivacyIndicatorBounds;
+
Cache(DisplayContent dc) {
mDecorInsets = new DecorInsets(dc);
}
@@ -2138,6 +2149,17 @@
return mPreserveId == ID_UPDATING_CONFIG || mDecorInsets.mDisplayContent
.mTransitionController.inTransition(mPreserveId);
}
+
+ static ArrayList<InsetsSource> copyRegularBarInsets(InsetsState srcState) {
+ final ArrayList<InsetsSource> state = new ArrayList<>();
+ for (int i = srcState.sourceSize() - 1; i >= 0; i--) {
+ final InsetsSource source = srcState.sourceAt(i);
+ if ((source.getType() & TYPE_REGULAR_BARS) != 0) {
+ state.add(new InsetsSource(source));
+ }
+ }
+ return state;
+ }
}
}
@@ -2213,24 +2235,60 @@
@VisibleForTesting
void updateCachedDecorInsets() {
DecorInsets prevCache = null;
+ PrivacyIndicatorBounds privacyIndicatorBounds = null;
if (mCachedDecorInsets == null) {
mCachedDecorInsets = new DecorInsets.Cache(mDisplayContent);
} else {
prevCache = new DecorInsets(mDisplayContent);
prevCache.setTo(mCachedDecorInsets.mDecorInsets);
+ privacyIndicatorBounds = mCachedDecorInsets.mPrivacyIndicatorBounds;
+ mCachedDecorInsets.mPreservedInsets = mCachedDecorInsets.mRegularBarsInsets;
}
// Set a special id to preserve it before a real id is available from transition.
mCachedDecorInsets.mPreserveId = DecorInsets.Cache.ID_UPDATING_CONFIG;
// Cache the current insets.
mCachedDecorInsets.mDecorInsets.setTo(mDecorInsets);
+ if (com.android.window.flags.Flags.useCachedInsetsForDisplaySwitch()) {
+ mCachedDecorInsets.mRegularBarsInsets = DecorInsets.Cache.copyRegularBarInsets(
+ mDisplayContent.mDisplayFrames.mInsetsState);
+ mCachedDecorInsets.mPrivacyIndicatorBounds =
+ mDisplayContent.mCurrentPrivacyIndicatorBounds;
+ } else {
+ mCachedDecorInsets.mRegularBarsInsets = null;
+ mCachedDecorInsets.mPrivacyIndicatorBounds = null;
+ }
// Switch current to previous cache.
if (prevCache != null) {
mDecorInsets.setTo(prevCache);
+ if (privacyIndicatorBounds != null) {
+ mDisplayContent.mCurrentPrivacyIndicatorBounds = privacyIndicatorBounds;
+ }
mCachedDecorInsets.mActive = true;
}
}
/**
+ * This returns a new InsetsState with replacing the insets in target device state when the
+ * display is switching (e.g. fold/unfold). Otherwise, it returns the original state. This is
+ * to avoid dispatching old insets source before the insets providers update new insets.
+ */
+ InsetsState replaceInsetsSourcesIfNeeded(InsetsState originalState, boolean copyState) {
+ if (mCachedDecorInsets == null || mCachedDecorInsets.mPreservedInsets == null
+ || !shouldKeepCurrentDecorInsets()) {
+ return originalState;
+ }
+ final ArrayList<InsetsSource> preservedSources = mCachedDecorInsets.mPreservedInsets;
+ final InsetsState state = copyState ? new InsetsState(originalState) : originalState;
+ for (int i = preservedSources.size() - 1; i >= 0; i--) {
+ final InsetsSource cacheSource = preservedSources.get(i);
+ if (state.peekSource(cacheSource.getId()) != null) {
+ state.addSource(new InsetsSource(cacheSource));
+ }
+ }
+ return state;
+ }
+
+ /**
* Called after the display configuration is updated according to the physical change. Suppose
* there should be a display change transition, so associate the cached decor insets with the
* transition to limit the lifetime of using the cache.
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index f52446f..2cac63c 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -22,7 +22,7 @@
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_IME;
import static com.android.server.wm.DisplayContent.IME_TARGET_CONTROL;
import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
-import static com.android.server.wm.ImeInsetsSourceProviderProto.IME_TARGET_FROM_IME;
+import static com.android.server.wm.ImeInsetsSourceProviderProto.IME_TARGET_FROM_IME_IDENTIFIER;
import static com.android.server.wm.ImeInsetsSourceProviderProto.INSETS_SOURCE_PROVIDER;
import static com.android.server.wm.WindowManagerService.H.UPDATE_MULTI_WINDOW_STACKS;
@@ -104,7 +104,8 @@
if (!mGivenInsetsReady && isServerVisible() && !givenInsetsPending
&& mControlTarget != null) {
ProtoLog.d(WM_DEBUG_IME,
- "onPostLayout: IME control ready to be dispatched, ws=%s", ws);
+ "onPostLayout: IME control ready to be dispatched, controlTarget=%s",
+ mControlTarget);
mGivenInsetsReady = true;
ImeTracker.forLogging().onProgress(mStatsToken,
ImeTracker.PHASE_WM_POST_LAYOUT_NOTIFY_CONTROLS_CHANGED);
@@ -115,13 +116,15 @@
// If the server visibility didn't change (still visible), and mGivenInsetsReady
// is set, we won't call into notifyControlChanged. Therefore, we can reset the
// statsToken, if available.
- ProtoLog.w(WM_DEBUG_IME, "onPostLayout cancel statsToken, ws=%s", ws);
+ ProtoLog.w(WM_DEBUG_IME, "onPostLayout cancel statsToken, controlTarget=%s",
+ mControlTarget);
ImeTracker.forLogging().onCancelled(mStatsToken,
ImeTracker.PHASE_WM_POST_LAYOUT_NOTIFY_CONTROLS_CHANGED);
mStatsToken = null;
} else if (wasServerVisible && !isServerVisible()) {
- ProtoLog.d(WM_DEBUG_IME, "onPostLayout: setImeShowing(false) was: %s, ws=%s",
- isImeShowing(), ws);
+ ProtoLog.d(WM_DEBUG_IME,
+ "onPostLayout: setImeShowing(false) was: %s, controlTarget=%s",
+ isImeShowing(), mControlTarget);
setImeShowing(false);
}
}
@@ -774,7 +777,7 @@
final WindowState imeRequesterWindow =
mImeRequester != null ? mImeRequester.getWindow() : null;
if (imeRequesterWindow != null) {
- imeRequesterWindow.dumpDebug(proto, IME_TARGET_FROM_IME, logLevel);
+ imeRequesterWindow.writeIdentifierToProto(proto, IME_TARGET_FROM_IME_IDENTIFIER);
}
proto.end(token);
}
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 2872214..06754c4 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -229,6 +229,7 @@
state = originalState;
}
state = adjustVisibilityForIme(target, state, state == originalState);
+ state = mPolicy.replaceInsetsSourcesIfNeeded(state, state == originalState);
return adjustInsetsForRoundedCorners(target.mToken, state, state == originalState);
}
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index d1585d0..b748902 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -21,16 +21,16 @@
import static com.android.server.wm.InsetsSourceProviderProto.CLIENT_VISIBLE;
import static com.android.server.wm.InsetsSourceProviderProto.CONTROL;
import static com.android.server.wm.InsetsSourceProviderProto.CONTROLLABLE;
-import static com.android.server.wm.InsetsSourceProviderProto.CONTROL_TARGET;
+import static com.android.server.wm.InsetsSourceProviderProto.CONTROL_TARGET_IDENTIFIER;
import static com.android.server.wm.InsetsSourceProviderProto.FAKE_CONTROL;
-import static com.android.server.wm.InsetsSourceProviderProto.FAKE_CONTROL_TARGET;
+import static com.android.server.wm.InsetsSourceProviderProto.FAKE_CONTROL_TARGET_IDENTIFIER;
import static com.android.server.wm.InsetsSourceProviderProto.FRAME;
import static com.android.server.wm.InsetsSourceProviderProto.IS_LEASH_READY_FOR_DISPATCHING;
-import static com.android.server.wm.InsetsSourceProviderProto.PENDING_CONTROL_TARGET;
+import static com.android.server.wm.InsetsSourceProviderProto.PENDING_CONTROL_TARGET_IDENTIFIER;
import static com.android.server.wm.InsetsSourceProviderProto.SEAMLESS_ROTATING;
import static com.android.server.wm.InsetsSourceProviderProto.SERVER_VISIBLE;
import static com.android.server.wm.InsetsSourceProviderProto.SOURCE;
-import static com.android.server.wm.InsetsSourceProviderProto.SOURCE_WINDOW_STATE;
+import static com.android.server.wm.InsetsSourceProviderProto.SOURCE_WINDOW_STATE_IDENTIFIER;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_INSETS_CONTROL;
import android.annotation.NonNull;
@@ -803,14 +803,16 @@
mControl.dumpDebug(proto, CONTROL);
}
if (mControlTarget != null && mControlTarget.getWindow() != null) {
- mControlTarget.getWindow().dumpDebug(proto, CONTROL_TARGET, logLevel);
+ mControlTarget.getWindow().writeIdentifierToProto(proto, CONTROL_TARGET_IDENTIFIER);
}
if (mPendingControlTarget != null && mPendingControlTarget != mControlTarget
&& mPendingControlTarget.getWindow() != null) {
- mPendingControlTarget.getWindow().dumpDebug(proto, PENDING_CONTROL_TARGET, logLevel);
+ mPendingControlTarget.getWindow().writeIdentifierToProto(
+ proto, PENDING_CONTROL_TARGET_IDENTIFIER);
}
if (mFakeControlTarget != null && mFakeControlTarget.getWindow() != null) {
- mFakeControlTarget.getWindow().dumpDebug(proto, FAKE_CONTROL_TARGET, logLevel);
+ mFakeControlTarget.getWindow().writeIdentifierToProto(
+ proto, FAKE_CONTROL_TARGET_IDENTIFIER);
}
if (mAdapter != null && mAdapter.mCapturedLeash != null) {
mAdapter.mCapturedLeash.dumpDebug(proto, CAPTURED_LEASH);
@@ -821,7 +823,8 @@
proto.write(SEAMLESS_ROTATING, mSeamlessRotating);
proto.write(CONTROLLABLE, mControllable);
if (mWindowContainer != null && mWindowContainer.asWindowState() != null) {
- mWindowContainer.asWindowState().dumpDebug(proto, SOURCE_WINDOW_STATE, logLevel);
+ mWindowContainer.asWindowState().writeIdentifierToProto(
+ proto, SOURCE_WINDOW_STATE_IDENTIFIER);
}
proto.end(token);
}
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 6d73739..ff1e400 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -18,6 +18,7 @@
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.TRANSIT_FLAG_AOD_APPEARING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
@@ -27,7 +28,6 @@
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_OCCLUDING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_UNOCCLUDING;
-import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
import static android.view.WindowManager.TRANSIT_OPEN;
@@ -216,6 +216,9 @@
} else if (keyguardShowing && !state.mKeyguardShowing) {
transition.addFlag(TRANSIT_FLAG_KEYGUARD_APPEARING);
}
+ if (mWindowManager.mFlags.mAodTransition && aodShowing && !state.mAodShowing) {
+ transition.addFlag(TRANSIT_FLAG_AOD_APPEARING);
+ }
}
}
// Update the task snapshot if the screen will not be turned off. To make sure that the
@@ -238,19 +241,28 @@
state.mAodShowing = aodShowing;
state.writeEventLog("setKeyguardShown");
- if (keyguardChanged) {
- // Irrelevant to AOD.
- state.mKeyguardGoingAway = false;
- if (keyguardShowing) {
- state.mDismissalRequested = false;
+ if (keyguardChanged || (mWindowManager.mFlags.mAodTransition && aodChanged)) {
+ if (keyguardChanged) {
+ // Irrelevant to AOD.
+ state.mKeyguardGoingAway = false;
+ if (keyguardShowing) {
+ state.mDismissalRequested = false;
+ }
}
if (goingAwayRemoved
- || (keyguardShowing && !Display.isOffState(dc.getDisplayInfo().state))) {
+ || (keyguardShowing && !Display.isOffState(dc.getDisplayInfo().state))
+ || (mWindowManager.mFlags.mAodTransition && aodShowing)) {
// Keyguard decided to show or stopped going away. Send a transition to animate back
// to the locked state before holding the sleep token again
if (!ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) {
- dc.requestTransitionAndLegacyPrepare(
- TRANSIT_TO_FRONT, TRANSIT_FLAG_KEYGUARD_APPEARING, /* trigger= */ null);
+ if (keyguardChanged) {
+ dc.requestTransitionAndLegacyPrepare(TRANSIT_TO_FRONT,
+ TRANSIT_FLAG_KEYGUARD_APPEARING, /* trigger= */ null);
+ }
+ if (mWindowManager.mFlags.mAodTransition && aodChanged && aodShowing) {
+ dc.requestTransitionAndLegacyPrepare(TRANSIT_TO_FRONT,
+ TRANSIT_FLAG_AOD_APPEARING, /* trigger= */ null);
+ }
}
dc.mWallpaperController.adjustWallpaperWindows();
dc.executeAppTransition();
@@ -297,7 +309,6 @@
state.writeEventLog("keyguardGoingAway");
final int transitFlags = convertTransitFlags(flags);
final DisplayContent dc = mRootWindowContainer.getDefaultDisplay();
- dc.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY, transitFlags);
// We are deprecating TRANSIT_KEYGUARD_GOING_AWAY for Shell transition and use
// TRANSIT_FLAG_KEYGUARD_GOING_AWAY to indicate that it should animate keyguard going
// away.
@@ -477,8 +488,6 @@
if (trigger != null) {
transition.collect(trigger);
}
- } else {
- dc.prepareAppTransition(transitType, transitFlags);
}
} else {
if (tc.inTransition()) {
@@ -502,7 +511,6 @@
private void handleDismissInsecureKeyguard(DisplayContent dc) {
mService.deferWindowLayout();
try {
- dc.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY, 0 /* transitFlags */);
// We are deprecating TRANSIT_KEYGUARD_GOING_AWAY for Shell transition and use
// TRANSIT_FLAG_KEYGUARD_GOING_AWAY to indicate that it should animate keyguard going
// away.
@@ -529,14 +537,6 @@
mWindowManager.dismissKeyguard(null /* callback */, null /* message */);
final KeyguardDisplayState state = getDisplayState(displayId);
state.mDismissalRequested = true;
-
- // If we are about to unocclude the Keyguard, but we can dismiss it without security,
- // we immediately dismiss the Keyguard so the activity gets shown without a flicker.
- final DisplayContent dc = mRootWindowContainer.getDefaultDisplay();
- if (state.mKeyguardShowing && canDismissKeyguard()
- && dc.mAppTransition.containsTransitRequest(TRANSIT_KEYGUARD_UNOCCLUDE)) {
- mWindowManager.executeAppTransition();
- }
}
ActivityRecord getTopOccludingActivity(int displayId) {
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index 29c0c7b..3a4d2ca 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -54,7 +54,6 @@
private final Supplier<SurfaceControl.Builder> mSurfaceControlFactory;
private final Supplier<SurfaceControl.Transaction> mTransactionFactory;
- private final Supplier<SurfaceControl> mParentSurfaceSupplier;
private final Rect mOuter = new Rect();
private final Rect mInner = new Rect();
@@ -83,13 +82,11 @@
public Letterbox(Supplier<SurfaceControl.Builder> surfaceControlFactory,
Supplier<SurfaceControl.Transaction> transactionFactory,
@NonNull AppCompatReachabilityPolicy appCompatReachabilityPolicy,
- @NonNull AppCompatLetterboxOverrides appCompatLetterboxOverrides,
- Supplier<SurfaceControl> parentSurface) {
+ @NonNull AppCompatLetterboxOverrides appCompatLetterboxOverrides) {
mSurfaceControlFactory = surfaceControlFactory;
mTransactionFactory = transactionFactory;
mAppCompatReachabilityPolicy = appCompatReachabilityPolicy;
mAppCompatLetterboxOverrides = appCompatLetterboxOverrides;
- mParentSurfaceSupplier = parentSurface;
}
/**
@@ -343,7 +340,6 @@
private SurfaceControl mInputSurface;
private Color mColor;
private boolean mHasWallpaperBackground;
- private SurfaceControl mParentSurface;
private final Rect mSurfaceFrameRelative = new Rect();
private final Rect mLayoutFrameGlobal = new Rect();
@@ -437,9 +433,8 @@
}
mColor = mAppCompatLetterboxOverrides.getLetterboxBackgroundColor();
- mParentSurface = mParentSurfaceSupplier.get();
t.setColor(mSurface, getRgbColorArray());
- setPositionAndReparent(t, mSurface);
+ setPositionAndCrop(t, mSurface);
mHasWallpaperBackground = mAppCompatLetterboxOverrides
.hasWallpaperBackgroundForLetterbox();
@@ -448,7 +443,7 @@
t.show(mSurface);
if (mInputSurface != null) {
- setPositionAndReparent(inputT, mInputSurface);
+ setPositionAndCrop(inputT, mInputSurface);
inputT.setTrustedOverlay(mInputSurface, true);
inputT.show(mInputSurface);
}
@@ -470,12 +465,11 @@
}
}
- private void setPositionAndReparent(@NonNull SurfaceControl.Transaction t,
+ private void setPositionAndCrop(@NonNull SurfaceControl.Transaction t,
@NonNull SurfaceControl surface) {
t.setPosition(surface, mSurfaceFrameRelative.left, mSurfaceFrameRelative.top);
t.setWindowCrop(surface, mSurfaceFrameRelative.width(),
mSurfaceFrameRelative.height());
- t.reparent(surface, mParentSurface);
}
private void updateAlphaAndBlur(SurfaceControl.Transaction t) {
@@ -511,14 +505,13 @@
public boolean needsApplySurfaceChanges() {
return !mSurfaceFrameRelative.equals(mLayoutFrameRelative)
- // If mSurfaceFrameRelative is empty then mHasWallpaperBackground, mColor,
- // and mParentSurface may never be updated in applySurfaceChanges but this
- // doesn't mean that update is needed.
+ // If mSurfaceFrameRelative is empty, then mHasWallpaperBackground and mColor
+ // may never be updated in applySurfaceChanges but this doesn't mean that
+ // update is needed.
|| !mSurfaceFrameRelative.isEmpty()
&& (mAppCompatLetterboxOverrides.hasWallpaperBackgroundForLetterbox()
!= mHasWallpaperBackground
- || !mAppCompatLetterboxOverrides.getLetterboxBackgroundColor().equals(mColor)
- || mParentSurfaceSupplier.get() != mParentSurface);
+ || !mAppCompatLetterboxOverrides.getLetterboxBackgroundColor().equals(mColor));
}
}
}
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
index bbc35a3..fba29d4 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
-import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.util.TimeUtils.NANOS_PER_MS;
import static android.view.Choreographer.CALLBACK_TRAVERSAL;
import static android.view.Choreographer.getSfInstance;
@@ -27,25 +26,13 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.Nullable;
-import android.graphics.BitmapShader;
-import android.graphics.Canvas;
-import android.graphics.Insets;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
import android.hardware.power.Boost;
import android.os.Handler;
import android.os.PowerManagerInternal;
-import android.os.Trace;
import android.util.ArrayMap;
-import android.util.Log;
import android.view.Choreographer;
-import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
-import android.view.animation.Animation;
-import android.view.animation.Transformation;
-import android.window.ScreenCapture;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -53,9 +40,6 @@
import com.android.server.AnimationThread;
import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
-import java.util.ArrayList;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
import java.util.function.Supplier;
/**
@@ -73,12 +57,6 @@
*/
private final Object mCancelLock = new Object();
- /**
- * Lock for synchronizing {@link #mEdgeExtensions} to prevent race conditions when managing
- * created edge extension surfaces.
- */
- private final Object mEdgeExtensionLock = new Object();
-
@VisibleForTesting
Choreographer mChoreographer;
@@ -91,12 +69,6 @@
private final PowerManagerInternal mPowerManagerInternal;
private boolean mApplyScheduled;
- // Executor to perform the edge extension.
- // With two threads because in practice we will want to extend two surfaces in one animation,
- // in which case we want to be able to parallelize those two extensions to cut down latency in
- // starting the animation.
- private final ExecutorService mEdgeExtensionExecutor = Executors.newFixedThreadPool(2);
-
@GuardedBy("mLock")
@VisibleForTesting
final ArrayMap<SurfaceControl, RunningAnimation> mPendingAnimations = new ArrayMap<>();
@@ -112,11 +84,6 @@
@GuardedBy("mLock")
private boolean mAnimationStartDeferred;
- // Mapping animation leashes to a list of edge extension surfaces associated with them
- @GuardedBy("mEdgeExtensionLock")
- private final ArrayMap<SurfaceControl, ArrayList<SurfaceControl>> mEdgeExtensions =
- new ArrayMap<>();
-
/**
* There should only ever be one instance of this class. Usual spot for it is with
* {@link WindowManagerService}
@@ -175,64 +142,9 @@
synchronized (mLock) {
final RunningAnimation runningAnim = new RunningAnimation(a, animationLeash,
finishCallback);
- boolean requiresEdgeExtension = requiresEdgeExtension(a);
-
- if (requiresEdgeExtension) {
- final ArrayList<SurfaceControl> extensionSurfaces = new ArrayList<>();
- synchronized (mEdgeExtensionLock) {
- mEdgeExtensions.put(animationLeash, extensionSurfaces);
- }
-
- mPreProcessingAnimations.put(animationLeash, runningAnim);
-
- // We must wait for t to be committed since otherwise the leash doesn't have the
- // windows we want to screenshot and extend as children.
- t.addTransactionCommittedListener(mEdgeExtensionExecutor, () -> {
- if (!animationLeash.isValid()) {
- Log.e(TAG, "Animation leash is not valid");
- synchronized (mEdgeExtensionLock) {
- mEdgeExtensions.remove(animationLeash);
- }
- synchronized (mLock) {
- mPreProcessingAnimations.remove(animationLeash);
- }
- return;
- }
- final WindowAnimationSpec animationSpec = a.asWindowAnimationSpec();
-
- final Transaction edgeExtensionCreationTransaction = new Transaction();
- edgeExtendWindow(animationLeash,
- animationSpec.getRootTaskBounds(), animationSpec.getAnimation(),
- edgeExtensionCreationTransaction);
-
- synchronized (mLock) {
- // only run if animation is not yet canceled by this point
- if (mPreProcessingAnimations.get(animationLeash) == runningAnim) {
- // In the case the animation is cancelled, edge extensions are removed
- // onAnimationLeashLost which is called before onAnimationCancelled.
- // So we need to check if the edge extensions have already been removed
- // or not, and if so we don't want to apply the transaction.
- synchronized (mEdgeExtensionLock) {
- if (!mEdgeExtensions.isEmpty()) {
- edgeExtensionCreationTransaction.apply();
- }
- }
-
- mPreProcessingAnimations.remove(animationLeash);
- mPendingAnimations.put(animationLeash, runningAnim);
- if (!mAnimationStartDeferred && mPreProcessingAnimations.isEmpty()) {
- mChoreographer.postFrameCallback(this::startAnimations);
- }
- }
- }
- });
- }
-
- if (!requiresEdgeExtension) {
- mPendingAnimations.put(animationLeash, runningAnim);
- if (!mAnimationStartDeferred && mPreProcessingAnimations.isEmpty()) {
- mChoreographer.postFrameCallback(this::startAnimations);
- }
+ mPendingAnimations.put(animationLeash, runningAnim);
+ if (!mAnimationStartDeferred && mPreProcessingAnimations.isEmpty()) {
+ mChoreographer.postFrameCallback(this::startAnimations);
}
// Some animations (e.g. move animations) require the initial transform to be
@@ -241,10 +153,6 @@
}
}
- private boolean requiresEdgeExtension(AnimationSpec a) {
- return a.asWindowAnimationSpec() != null && a.asWindowAnimationSpec().hasExtension();
- }
-
void onAnimationCancelled(SurfaceControl leash) {
synchronized (mLock) {
if (mPendingAnimations.containsKey(leash)) {
@@ -374,161 +282,6 @@
mApplyScheduled = false;
}
- private void edgeExtendWindow(SurfaceControl leash, Rect bounds, Animation a,
- Transaction transaction) {
- final Transformation transformationAtStart = new Transformation();
- a.getTransformationAt(0, transformationAtStart);
- final Transformation transformationAtEnd = new Transformation();
- a.getTransformationAt(1, transformationAtEnd);
-
- // We want to create an extension surface that is the maximal size and the animation will
- // take care of cropping any part that overflows.
- final Insets maxExtensionInsets = Insets.min(
- transformationAtStart.getInsets(), transformationAtEnd.getInsets());
-
- final int targetSurfaceHeight = bounds.height();
- final int targetSurfaceWidth = bounds.width();
-
- if (maxExtensionInsets.left < 0) {
- final Rect edgeBounds = new Rect(bounds.left, bounds.top, bounds.left + 1,
- bounds.bottom);
- final Rect extensionRect = new Rect(0, 0,
- -maxExtensionInsets.left, targetSurfaceHeight);
- final int xPos = bounds.left + maxExtensionInsets.left;
- final int yPos = bounds.top;
- createExtensionSurface(leash, edgeBounds,
- extensionRect, xPos, yPos, "Left Edge Extension", transaction);
- }
-
- if (maxExtensionInsets.top < 0) {
- final Rect edgeBounds = new Rect(bounds.left, bounds.top, targetSurfaceWidth,
- bounds.top + 1);
- final Rect extensionRect = new Rect(0, 0,
- targetSurfaceWidth, -maxExtensionInsets.top);
- final int xPos = bounds.left;
- final int yPos = bounds.top + maxExtensionInsets.top;
- createExtensionSurface(leash, edgeBounds,
- extensionRect, xPos, yPos, "Top Edge Extension", transaction);
- }
-
- if (maxExtensionInsets.right < 0) {
- final Rect edgeBounds = new Rect(bounds.right - 1, bounds.top, bounds.right,
- bounds.bottom);
- final Rect extensionRect = new Rect(0, 0,
- -maxExtensionInsets.right, targetSurfaceHeight);
- final int xPos = bounds.right;
- final int yPos = bounds.top;
- createExtensionSurface(leash, edgeBounds,
- extensionRect, xPos, yPos, "Right Edge Extension", transaction);
- }
-
- if (maxExtensionInsets.bottom < 0) {
- final Rect edgeBounds = new Rect(bounds.left, bounds.bottom - 1,
- bounds.right, bounds.bottom);
- final Rect extensionRect = new Rect(0, 0,
- targetSurfaceWidth, -maxExtensionInsets.bottom);
- final int xPos = bounds.left;
- final int yPos = bounds.bottom;
- createExtensionSurface(leash, edgeBounds,
- extensionRect, xPos, yPos, "Bottom Edge Extension", transaction);
- }
- }
-
- private void createExtensionSurface(SurfaceControl leash, Rect edgeBounds,
- Rect extensionRect, int xPos, int yPos, String layerName,
- Transaction startTransaction) {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "createExtensionSurface");
- doCreateExtensionSurface(leash, edgeBounds, extensionRect, xPos, yPos, layerName,
- startTransaction);
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- }
-
- private void doCreateExtensionSurface(SurfaceControl leash, Rect edgeBounds,
- Rect extensionRect, int xPos, int yPos, String layerName,
- Transaction startTransaction) {
- ScreenCapture.LayerCaptureArgs captureArgs =
- new ScreenCapture.LayerCaptureArgs.Builder(leash /* surfaceToExtend */)
- .setSourceCrop(edgeBounds)
- .setFrameScale(1)
- .setPixelFormat(PixelFormat.RGBA_8888)
- .setChildrenOnly(true)
- .setAllowProtected(true)
- .setCaptureSecureLayers(true)
- .build();
- final ScreenCapture.ScreenshotHardwareBuffer edgeBuffer =
- ScreenCapture.captureLayers(captureArgs);
-
- if (edgeBuffer == null) {
- // The leash we are trying to screenshot may have been removed by this point, which is
- // likely the reason for ending up with a null edgeBuffer, in which case we just want to
- // return and do nothing.
- Log.e(TAG, "Failed to create edge extension - edge buffer is null");
- return;
- }
-
- final SurfaceControl edgeExtensionLayer = new SurfaceControl.Builder()
- .setName(layerName)
- .setHidden(true)
- .setCallsite("DefaultTransitionHandler#startAnimation")
- .setOpaque(true)
- .setBufferSize(extensionRect.width(), extensionRect.height())
- .build();
-
- BitmapShader shader = new BitmapShader(edgeBuffer.asBitmap(),
- android.graphics.Shader.TileMode.CLAMP,
- android.graphics.Shader.TileMode.CLAMP);
- final Paint paint = new Paint();
- paint.setShader(shader);
-
- final Surface surface = new Surface(edgeExtensionLayer);
- Canvas c = surface.lockHardwareCanvas();
- c.drawRect(extensionRect, paint);
- surface.unlockCanvasAndPost(c);
- surface.release();
-
- synchronized (mEdgeExtensionLock) {
- if (!mEdgeExtensions.containsKey(leash)) {
- // The animation leash has already been removed, so we don't want to attach the
- // edgeExtension layer and should immediately remove it instead.
- startTransaction.remove(edgeExtensionLayer);
- return;
- }
-
- startTransaction.reparent(edgeExtensionLayer, leash);
- startTransaction.setLayer(edgeExtensionLayer, Integer.MIN_VALUE);
- startTransaction.setPosition(edgeExtensionLayer, xPos, yPos);
- startTransaction.setVisibility(edgeExtensionLayer, true);
-
- mEdgeExtensions.get(leash).add(edgeExtensionLayer);
- }
- }
-
- private float getScaleXForExtensionSurface(Rect edgeBounds, Rect extensionRect) {
- if (edgeBounds.width() == extensionRect.width()) {
- // Top or bottom edge extension, no need to scale the X axis of the extension surface.
- return 1;
- }
- if (edgeBounds.width() == 1) {
- // Left or right edge extension, scale the surface to be the extensionRect's width.
- return extensionRect.width();
- }
-
- throw new RuntimeException("Unexpected edgeBounds and extensionRect widths");
- }
-
- private float getScaleYForExtensionSurface(Rect edgeBounds, Rect extensionRect) {
- if (edgeBounds.height() == extensionRect.height()) {
- // Left or right edge extension, no need to scale the Y axis of the extension surface.
- return 1;
- }
- if (edgeBounds.height() == 1) {
- // Top or bottom edge extension, scale the surface to be the extensionRect's height.
- return extensionRect.height();
- }
-
- throw new RuntimeException("Unexpected edgeBounds and extensionRect heights");
- }
-
private static final class RunningAnimation {
final AnimationSpec mAnimSpec;
final SurfaceControl mLeash;
@@ -545,22 +298,6 @@
}
}
- protected void onAnimationLeashLost(SurfaceControl animationLeash,
- Transaction t) {
- synchronized (mEdgeExtensionLock) {
- if (!mEdgeExtensions.containsKey(animationLeash)) {
- return;
- }
-
- final ArrayList<SurfaceControl> edgeExtensions = mEdgeExtensions.get(animationLeash);
- for (int i = 0; i < edgeExtensions.size(); i++) {
- final SurfaceControl extension = edgeExtensions.get(i);
- t.remove(extension);
- }
- mEdgeExtensions.remove(animationLeash);
- }
- }
-
@VisibleForTesting
interface AnimatorFactory {
ValueAnimator makeAnimator();
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index f95698a..5183c6b 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -88,6 +88,7 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.DisplayMetrics;
@@ -1751,67 +1752,80 @@
}
}
- try {
- final IApplicationThread appThread = next.app.getThread();
- // Deliver all pending results.
- final ArrayList<ResultInfo> a = next.results;
- if (a != null) {
- final int size = a.size();
- if (!next.finishing && size > 0) {
- if (DEBUG_RESULTS) {
- Slog.v(TAG_RESULTS, "Delivering results to " + next + ": " + a);
- }
- final ActivityResultItem item = new ActivityResultItem(next.token, a);
- mAtmService.getLifecycleManager().scheduleTransactionItem(appThread, item);
+ final IApplicationThread appThread = next.app.getThread();
+ // Deliver all pending results.
+ final ArrayList<ResultInfo> a = next.results;
+ if (a != null) {
+ final int size = a.size();
+ if (!next.finishing && size > 0) {
+ if (DEBUG_RESULTS) {
+ Slog.v(TAG_RESULTS, "Delivering results to " + next + ": " + a);
+ }
+ final ActivityResultItem item = new ActivityResultItem(next.token, a);
+ boolean isSuccessful;
+ try {
+ isSuccessful = mAtmService.getLifecycleManager().scheduleTransactionItem(
+ appThread, item);
+ } catch (RemoteException e) {
+ // TODO(b/323801078): remove Exception when cleanup
+ isSuccessful = false;
+ }
+ if (!isSuccessful) {
+ onResumeTopActivityRemoteFailure(lastState, next, lastResumedActivity,
+ lastFocusedRootTask);
+ return true;
}
}
+ }
- if (next.newIntents != null) {
- final NewIntentItem item =
- new NewIntentItem(next.token, next.newIntents, true /* resume */);
- mAtmService.getLifecycleManager().scheduleTransactionItem(appThread, item);
+ if (next.newIntents != null) {
+ final NewIntentItem item =
+ new NewIntentItem(next.token, next.newIntents, true /* resume */);
+ boolean isSuccessful;
+ try {
+ isSuccessful = mAtmService.getLifecycleManager().scheduleTransactionItem(
+ appThread, item);
+ } catch (RemoteException e) {
+ // TODO(b/323801078): remove Exception when cleanup
+ isSuccessful = false;
}
+ if (!isSuccessful) {
+ onResumeTopActivityRemoteFailure(lastState, next, lastResumedActivity,
+ lastFocusedRootTask);
+ return true;
+ }
+ }
- // Well the app will no longer be stopped.
- // Clear app token stopped state in window manager if needed.
- next.notifyAppResumed();
+ // Well the app will no longer be stopped.
+ // Clear app token stopped state in window manager if needed.
+ next.notifyAppResumed();
- EventLogTags.writeWmResumeActivity(next.mUserId, System.identityHashCode(next),
- next.getTask().mTaskId, next.shortComponentName);
+ EventLogTags.writeWmResumeActivity(next.mUserId, System.identityHashCode(next),
+ next.getTask().mTaskId, next.shortComponentName);
- mAtmService.getAppWarningsLocked().onResumeActivity(next);
- final int topProcessState = mAtmService.mTopProcessState;
- next.app.setPendingUiCleanAndForceProcessStateUpTo(topProcessState);
- next.abortAndClearOptionsAnimation();
- final ResumeActivityItem resumeActivityItem = new ResumeActivityItem(
- next.token, topProcessState, dc.isNextTransitionForward(),
- next.shouldSendCompatFakeFocus());
- mAtmService.getLifecycleManager().scheduleTransactionItem(
+ mAtmService.getAppWarningsLocked().onResumeActivity(next);
+ final int topProcessState = mAtmService.mTopProcessState;
+ next.app.setPendingUiCleanAndForceProcessStateUpTo(topProcessState);
+ next.abortAndClearOptionsAnimation();
+ final ResumeActivityItem resumeActivityItem = new ResumeActivityItem(
+ next.token, topProcessState, dc.isNextTransitionForward(),
+ next.shouldSendCompatFakeFocus());
+ boolean isSuccessful;
+ try {
+ isSuccessful = mAtmService.getLifecycleManager().scheduleTransactionItem(
appThread, resumeActivityItem);
-
- ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Resumed %s", next);
- } catch (Exception e) {
- // Whoops, need to restart this activity!
- ProtoLog.v(WM_DEBUG_STATES, "Resume failed; resetting state to %s: "
- + "%s", lastState, next);
- next.setState(lastState, "resumeTopActivityInnerLocked");
-
- // lastResumedActivity being non-null implies there is a lastStack present.
- if (lastResumedActivity != null) {
- lastResumedActivity.setState(RESUMED, "resumeTopActivityInnerLocked");
- }
-
- Slog.i(TAG, "Restarting because process died: " + next);
- if (!next.hasBeenLaunched) {
- next.hasBeenLaunched = true;
- } else if (SHOW_APP_STARTING_PREVIEW && lastFocusedRootTask != null
- && lastFocusedRootTask.isTopRootTaskInDisplayArea()) {
- next.showStartingWindow(false /* taskSwitch */);
- }
- mTaskSupervisor.startSpecificActivity(next, true, false);
+ } catch (RemoteException e) {
+ // TODO(b/323801078): remove Exception when cleanup
+ isSuccessful = false;
+ }
+ if (!isSuccessful) {
+ onResumeTopActivityRemoteFailure(lastState, next, lastResumedActivity,
+ lastFocusedRootTask);
return true;
}
+ ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Resumed %s", next);
+
next.completeResumeLocked();
} else {
// Whoops, need to restart this activity!
@@ -1830,6 +1844,29 @@
return true;
}
+ /** Likely app process has been killed. Needs to restart this activity. */
+ private void onResumeTopActivityRemoteFailure(@NonNull ActivityRecord.State lastState,
+ @NonNull ActivityRecord next, @Nullable ActivityRecord lastResumedActivity,
+ @Nullable Task lastFocusedRootTask) {
+ ProtoLog.v(WM_DEBUG_STATES, "Resume failed; resetting state to %s: "
+ + "%s", lastState, next);
+ next.setState(lastState, "resumeTopActivityInnerLocked");
+
+ // lastResumedActivity being non-null implies there is a lastStack present.
+ if (lastResumedActivity != null) {
+ lastResumedActivity.setState(RESUMED, "resumeTopActivityInnerLocked");
+ }
+
+ Slog.i(TAG, "Restarting because process died: " + next);
+ if (!next.hasBeenLaunched) {
+ next.hasBeenLaunched = true;
+ } else if (SHOW_APP_STARTING_PREVIEW && lastFocusedRootTask != null
+ && lastFocusedRootTask.isTopRootTaskInDisplayArea()) {
+ next.showStartingWindow(false /* taskSwitch */);
+ }
+ mTaskSupervisor.startSpecificActivity(next, true, false);
+ }
+
boolean shouldSleepOrShutDownActivities() {
return shouldSleepActivities() || mAtmService.mShuttingDown;
}
@@ -2034,17 +2071,23 @@
void schedulePauseActivity(ActivityRecord prev, boolean userLeaving,
boolean pauseImmediately, boolean autoEnteringPip, String reason) {
ProtoLog.v(WM_DEBUG_STATES, "Enqueueing pending pause: %s", prev);
- try {
- prev.mPauseSchedulePendingForPip = false;
- EventLogTags.writeWmPauseActivity(prev.mUserId, System.identityHashCode(prev),
- prev.shortComponentName, "userLeaving=" + userLeaving, reason);
+ prev.mPauseSchedulePendingForPip = false;
+ EventLogTags.writeWmPauseActivity(prev.mUserId, System.identityHashCode(prev),
+ prev.shortComponentName, "userLeaving=" + userLeaving, reason);
- final PauseActivityItem item = new PauseActivityItem(prev.token, prev.finishing,
- userLeaving, pauseImmediately, autoEnteringPip);
- mAtmService.getLifecycleManager().scheduleTransactionItem(prev.app.getThread(), item);
- } catch (Exception e) {
+ final PauseActivityItem item = new PauseActivityItem(prev.token, prev.finishing,
+ userLeaving, pauseImmediately, autoEnteringPip);
+ boolean isSuccessful;
+ try {
+ isSuccessful = mAtmService.getLifecycleManager().scheduleTransactionItem(
+ prev.app.getThread(), item);
+ } catch (RemoteException e) {
+ // TODO(b/323801078): remove Exception when cleanup
// Ignore exception, if process died other code will cleanup.
Slog.w(TAG, "Exception thrown during pause", e);
+ isSuccessful = false;
+ }
+ if (!isSuccessful) {
mPausingActivity = null;
mLastPausedActivity = null;
mTaskSupervisor.mNoHistoryActivities.remove(prev);
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 3db1d50..c78cdaa 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -36,6 +36,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_FLAG_AOD_APPEARING;
import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
import static android.view.WindowManager.TRANSIT_OPEN;
@@ -980,6 +981,10 @@
return false;
}
+ boolean isInAodAppearTransition() {
+ return (mFlags & TRANSIT_FLAG_AOD_APPEARING) != 0;
+ }
+
/**
* Specifies configuration change explicitly for the window container, so it can be chosen as
* transition target. This is usually used with transition mode
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 11c5c93..9b3b445 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -526,6 +526,19 @@
return false;
}
+ boolean isInAodAppearTransition() {
+ if (mCollectingTransition != null && mCollectingTransition.isInAodAppearTransition()) {
+ return true;
+ }
+ for (int i = mWaitingTransitions.size() - 1; i >= 0; --i) {
+ if (mWaitingTransitions.get(i).isInAodAppearTransition()) return true;
+ }
+ for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
+ if (mPlayingTransitions.get(i).isInAodAppearTransition()) return true;
+ }
+ return false;
+ }
+
/**
* @return A pair of the transition and restore-behind target for the given {@param container}.
* @param container An ancestor of a transient-launch activity
diff --git a/services/core/java/com/android/server/wm/ViewServer.java b/services/core/java/com/android/server/wm/ViewServer.java
index ecf5652..971e6f9 100644
--- a/services/core/java/com/android/server/wm/ViewServer.java
+++ b/services/core/java/com/android/server/wm/ViewServer.java
@@ -19,7 +19,10 @@
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowManagerInternal.WindowFocusChangeListener;
+import static com.android.server.wm.WindowManagerService.WindowChangeListener;
+import android.os.IBinder;
import android.util.Slog;
import java.net.ServerSocket;
@@ -206,7 +209,7 @@
return result;
}
- class ViewServerWorker implements Runnable, WindowManagerService.WindowChangeListener {
+ class ViewServerWorker implements Runnable, WindowChangeListener, WindowFocusChangeListener {
private Socket mClient;
private boolean mNeedWindowListUpdate;
private boolean mNeedFocusedWindowUpdate;
@@ -284,7 +287,7 @@
}
}
- public void focusChanged() {
+ public void focusChanged(IBinder focusedWindowToken) {
synchronized(this) {
mNeedFocusedWindowUpdate = true;
notifyAll();
@@ -293,6 +296,7 @@
private boolean windowManagerAutolistLoop() {
mWindowManager.addWindowChangeListener(this);
+ mWindowManager.addWindowFocusChangeListener(this);
BufferedWriter out = null;
try {
out = new BufferedWriter(new OutputStreamWriter(mClient.getOutputStream()));
@@ -332,6 +336,7 @@
}
}
mWindowManager.removeWindowChangeListener(this);
+ mWindowManager.removeWindowFocusChangeListener(this);
}
return true;
}
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index a8b9fed..3a4d9d2 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -21,7 +21,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_WALLPAPER;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
@@ -118,42 +117,20 @@
private boolean mShouldOffsetWallpaperCenter;
private final ToBooleanFunction<WindowState> mFindWallpaperTargetFunction = w -> {
- final boolean useShellTransition = w.mTransitionController.isShellTransitionsEnabled();
- if (!useShellTransition) {
- if (w.mActivityRecord != null && !w.mActivityRecord.isVisible()
- && !w.mActivityRecord.isAnimating(TRANSITION | PARENTS)) {
- // If this window's app token is hidden and not animating, it is of no interest.
- if (DEBUG_WALLPAPER) Slog.v(TAG, "Skipping hidden and not animating token: " + w);
- return false;
- }
- } else {
- final ActivityRecord ar = w.mActivityRecord;
- // The animating window can still be visible on screen if it is in transition, so we
- // should check whether this window can be wallpaper target even when visibleRequested
- // is false.
- if (ar != null && !ar.isVisibleRequested() && !ar.isVisible()) {
- // An activity that is not going to remain visible shouldn't be the target.
- return false;
- }
+ final ActivityRecord ar = w.mActivityRecord;
+ // The animating window can still be visible on screen if it is in transition, so we
+ // should check whether this window can be wallpaper target even when visibleRequested
+ // is false.
+ if (ar != null && !ar.isVisibleRequested() && !ar.isVisible()) {
+ // An activity that is not going to remain visible shouldn't be the target.
+ return false;
}
if (DEBUG_WALLPAPER) Slog.v(TAG, "Win " + w + ": isOnScreen=" + w.isOnScreen()
+ " mDrawState=" + w.mWinAnimator.mDrawState);
- final WindowContainer animatingContainer = w.mActivityRecord != null
- ? w.mActivityRecord.getAnimatingContainer() : null;
- if (!useShellTransition && animatingContainer != null
- && animatingContainer.isAnimating(TRANSITION | PARENTS)
- && AppTransition.isKeyguardGoingAwayTransitOld(animatingContainer.mTransit)
- && (animatingContainer.mTransitFlags
- & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0) {
- // Keep the wallpaper visible when Keyguard is going away.
- mFindResults.setUseTopWallpaperAsTarget(true);
- }
-
if (mService.mPolicy.isKeyguardLocked()) {
if (w.canShowWhenLocked()) {
- if (mService.mPolicy.isKeyguardOccluded() || (useShellTransition
- ? w.inTransition() : mService.mPolicy.isKeyguardUnoccluding())) {
+ if (mService.mPolicy.isKeyguardOccluded() || w.inTransition()) {
// The lowest show-when-locked window decides whether to show wallpaper.
mFindResults.mNeedsShowWhenLockedWallpaper = !isFullscreen(w.mAttrs)
|| (w.mActivityRecord != null && !w.mActivityRecord.fillsParent());
@@ -166,17 +143,21 @@
mFindResults.setWallpaperTarget(w);
return false;
}
+ } else if (mService.mFlags.mAodTransition
+ && mDisplayContent.isKeyguardLockedOrAodShowing()) {
+ if (mService.mPolicy.isKeyguardHostWindow(w.mAttrs)
+ && w.mTransitionController.isInAodAppearTransition()) {
+ if (DEBUG_WALLPAPER) Slog.v(TAG, "Found aod transition wallpaper target: " + w);
+ mFindResults.setWallpaperTarget(w);
+ return true;
+ }
}
- final boolean animationWallpaper = animatingContainer != null
- && animatingContainer.getAnimation() != null
- && animatingContainer.getAnimation().getShowWallpaper();
- final boolean hasWallpaper = w.hasWallpaper() || animationWallpaper;
if (isBackNavigationTarget(w)) {
if (DEBUG_WALLPAPER) Slog.v(TAG, "Found back animation wallpaper target: " + w);
mFindResults.setWallpaperTarget(w);
return true;
- } else if (hasWallpaper
+ } else if (w.hasWallpaper()
&& (w.mActivityRecord != null ? w.isOnScreen() : w.isReadyForDisplay())) {
if (DEBUG_WALLPAPER) Slog.v(TAG, "Found wallpaper target: " + w);
mFindResults.setWallpaperTarget(w);
@@ -678,7 +659,8 @@
private WallpaperWindowToken getTokenForTarget(WindowState target) {
if (target == null) return null;
WindowState window = mFindResults.getTopWallpaper(
- target.canShowWhenLocked() && mService.isKeyguardLocked());
+ (target.canShowWhenLocked() && mService.isKeyguardLocked())
+ || (mService.mFlags.mAodTransition && mDisplayContent.isAodShowing()));
return window == null ? null : window.mToken.asWallpaperToken();
}
@@ -721,7 +703,9 @@
if (mFindResults.wallpaperTarget == null && mFindResults.useTopWallpaperAsTarget) {
mFindResults.setWallpaperTarget(
- mFindResults.getTopWallpaper(mDisplayContent.isKeyguardLocked()));
+ mFindResults.getTopWallpaper(mService.mFlags.mAodTransition
+ ? mDisplayContent.isKeyguardLockedOrAodShowing()
+ : mDisplayContent.isKeyguardLocked()));
}
}
@@ -885,11 +869,17 @@
if (mDisplayContent.mWmService.mFlags.mEnsureWallpaperInTransitions) {
visibleRequested = mWallpaperTarget != null && mWallpaperTarget.isVisibleRequested();
}
- updateWallpaperTokens(visibleRequested, mDisplayContent.isKeyguardLocked());
+ updateWallpaperTokens(visibleRequested,
+ mService.mFlags.mAodTransition
+ ? mDisplayContent.isKeyguardLockedOrAodShowing()
+ : mDisplayContent.isKeyguardLocked());
ProtoLog.v(WM_DEBUG_WALLPAPER,
"Wallpaper at display %d - visibility: %b, keyguardLocked: %b",
- mDisplayContent.getDisplayId(), visible, mDisplayContent.isKeyguardLocked());
+ mDisplayContent.getDisplayId(), visible,
+ mService.mFlags.mAodTransition
+ ? mDisplayContent.isKeyguardLockedOrAodShowing()
+ : mDisplayContent.isKeyguardLocked());
if (visible && mLastFrozen != mFindResults.isWallpaperTargetForLetterbox) {
mLastFrozen = mFindResults.isWallpaperTargetForLetterbox;
diff --git a/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java b/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java
deleted file mode 100644
index d3530c5..0000000
--- a/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Copyright (C) 2018 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.server.wm;
-
-import static com.android.server.wm.AnimationAdapter.STATUS_BAR_TRANSITION_DURATION;
-import static com.android.server.wm.AnimationSpecProto.WINDOW;
-import static com.android.server.wm.WindowAnimationSpecProto.ANIMATION;
-
-import android.graphics.Matrix;
-import android.graphics.Rect;
-import android.os.SystemClock;
-import android.util.proto.ProtoOutputStream;
-import android.view.DisplayInfo;
-import android.view.SurfaceControl;
-import android.view.SurfaceControl.Transaction;
-import android.view.animation.AlphaAnimation;
-import android.view.animation.Animation;
-import android.view.animation.AnimationSet;
-import android.view.animation.ClipRectAnimation;
-import android.view.animation.ScaleAnimation;
-import android.view.animation.Transformation;
-import android.view.animation.TranslateAnimation;
-
-import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
-
-import java.io.PrintWriter;
-
-/**
- * Animation spec for changing window animations.
- */
-public class WindowChangeAnimationSpec implements AnimationSpec {
-
- private final ThreadLocal<TmpValues> mThreadLocalTmps = ThreadLocal.withInitial(TmpValues::new);
- private final boolean mIsAppAnimation;
- private final Rect mStartBounds;
- private final Rect mEndBounds;
- private final Rect mTmpRect = new Rect();
-
- private Animation mAnimation;
- private final boolean mIsThumbnail;
-
- static final int ANIMATION_DURATION = AppTransition.DEFAULT_APP_TRANSITION_DURATION;
-
- public WindowChangeAnimationSpec(Rect startBounds, Rect endBounds, DisplayInfo displayInfo,
- float durationScale, boolean isAppAnimation, boolean isThumbnail) {
- mStartBounds = new Rect(startBounds);
- mEndBounds = new Rect(endBounds);
- mIsAppAnimation = isAppAnimation;
- mIsThumbnail = isThumbnail;
- createBoundsInterpolator((int) (ANIMATION_DURATION * durationScale), displayInfo);
- }
-
- @Override
- public boolean getShowWallpaper() {
- return false;
- }
-
- @Override
- public long getDuration() {
- return mAnimation.getDuration();
- }
-
- /**
- * This animator behaves slightly differently depending on whether the window is growing
- * or shrinking:
- * If growing, it will do a clip-reveal after quicker fade-out/scale of the smaller (old)
- * snapshot.
- * If shrinking, it will do an opposite clip-reveal on the old snapshot followed by a quicker
- * fade-out of the bigger (old) snapshot while simultaneously shrinking the new window into
- * place.
- * @param duration
- * @param displayInfo
- */
- private void createBoundsInterpolator(long duration, DisplayInfo displayInfo) {
- boolean growing = mEndBounds.width() - mStartBounds.width()
- + mEndBounds.height() - mStartBounds.height() >= 0;
- float scalePart = 0.7f;
- long scalePeriod = (long) (duration * scalePart);
- float startScaleX = scalePart * ((float) mStartBounds.width()) / mEndBounds.width()
- + (1.f - scalePart);
- float startScaleY = scalePart * ((float) mStartBounds.height()) / mEndBounds.height()
- + (1.f - scalePart);
- if (mIsThumbnail) {
- AnimationSet animSet = new AnimationSet(true);
- Animation anim = new AlphaAnimation(1.f, 0.f);
- anim.setDuration(scalePeriod);
- if (!growing) {
- anim.setStartOffset(duration - scalePeriod);
- }
- animSet.addAnimation(anim);
- float endScaleX = 1.f / startScaleX;
- float endScaleY = 1.f / startScaleY;
- anim = new ScaleAnimation(endScaleX, endScaleX, endScaleY, endScaleY);
- anim.setDuration(duration);
- animSet.addAnimation(anim);
- mAnimation = animSet;
- mAnimation.initialize(mStartBounds.width(), mStartBounds.height(),
- mEndBounds.width(), mEndBounds.height());
- } else {
- AnimationSet animSet = new AnimationSet(true);
- final Animation scaleAnim = new ScaleAnimation(startScaleX, 1, startScaleY, 1);
- scaleAnim.setDuration(scalePeriod);
- if (!growing) {
- scaleAnim.setStartOffset(duration - scalePeriod);
- }
- animSet.addAnimation(scaleAnim);
- final Animation translateAnim = new TranslateAnimation(mStartBounds.left,
- mEndBounds.left, mStartBounds.top, mEndBounds.top);
- translateAnim.setDuration(duration);
- animSet.addAnimation(translateAnim);
- Rect startClip = new Rect(mStartBounds);
- Rect endClip = new Rect(mEndBounds);
- startClip.offsetTo(0, 0);
- endClip.offsetTo(0, 0);
- final Animation clipAnim = new ClipRectAnimation(startClip, endClip);
- clipAnim.setDuration(duration);
- animSet.addAnimation(clipAnim);
- mAnimation = animSet;
- mAnimation.initialize(mStartBounds.width(), mStartBounds.height(),
- displayInfo.appWidth, displayInfo.appHeight);
- }
- }
-
- @Override
- public void apply(Transaction t, SurfaceControl leash, long currentPlayTime) {
- final TmpValues tmp = mThreadLocalTmps.get();
- if (mIsThumbnail) {
- mAnimation.getTransformation(currentPlayTime, tmp.mTransformation);
- t.setMatrix(leash, tmp.mTransformation.getMatrix(), tmp.mFloats);
- t.setAlpha(leash, tmp.mTransformation.getAlpha());
- } else {
- mAnimation.getTransformation(currentPlayTime, tmp.mTransformation);
- final Matrix matrix = tmp.mTransformation.getMatrix();
- t.setMatrix(leash, matrix, tmp.mFloats);
-
- // The following applies an inverse scale to the clip-rect so that it crops "after" the
- // scale instead of before.
- tmp.mVecs[1] = tmp.mVecs[2] = 0;
- tmp.mVecs[0] = tmp.mVecs[3] = 1;
- matrix.mapVectors(tmp.mVecs);
- tmp.mVecs[0] = 1.f / tmp.mVecs[0];
- tmp.mVecs[3] = 1.f / tmp.mVecs[3];
- final Rect clipRect = tmp.mTransformation.getClipRect();
- mTmpRect.left = (int) (clipRect.left * tmp.mVecs[0] + 0.5f);
- mTmpRect.right = (int) (clipRect.right * tmp.mVecs[0] + 0.5f);
- mTmpRect.top = (int) (clipRect.top * tmp.mVecs[3] + 0.5f);
- mTmpRect.bottom = (int) (clipRect.bottom * tmp.mVecs[3] + 0.5f);
- t.setWindowCrop(leash, mTmpRect);
- }
- }
-
- @Override
- public long calculateStatusBarTransitionStartTime() {
- long uptime = SystemClock.uptimeMillis();
- return Math.max(uptime, uptime + ((long) (((float) mAnimation.getDuration()) * 0.99f))
- - STATUS_BAR_TRANSITION_DURATION);
- }
-
- @Override
- public boolean canSkipFirstFrame() {
- return false;
- }
-
- @Override
- public boolean needsEarlyWakeup() {
- return mIsAppAnimation;
- }
-
- @Override
- public void dump(PrintWriter pw, String prefix) {
- pw.print(prefix); pw.println(mAnimation.getDuration());
- }
-
- @Override
- public void dumpDebugInner(ProtoOutputStream proto) {
- final long token = proto.start(WINDOW);
- proto.write(ANIMATION, mAnimation.toString());
- proto.end(token);
- }
-
- private static class TmpValues {
- final Transformation mTransformation = new Transformation();
- final float[] mFloats = new float[9];
- final float[] mVecs = new float[4];
- }
-}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 1c3510d..466ed78 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -97,7 +97,6 @@
import com.android.server.wm.SurfaceAnimator.AnimationType;
import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
import com.android.server.wm.utils.AlwaysTruePredicate;
-import com.android.window.flags.Flags;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
@@ -2630,7 +2629,7 @@
if (!mTransitionController.canAssignLayers(this)) return;
final boolean changed = layer != mLastLayer || mLastRelativeToLayer != null;
if (mSurfaceControl != null && changed) {
- if (Flags.useSelfSyncTransactionForLayer() && mSyncState != SYNC_STATE_NONE) {
+ if (mSyncState != SYNC_STATE_NONE) {
// When this container needs to be synced, assign layer with its own sync
// transaction to avoid out of ordering when merge.
// Still use the passed-in transaction for non-sync case, such as building finish
@@ -2647,7 +2646,7 @@
boolean forceUpdate) {
final boolean changed = layer != mLastLayer || mLastRelativeToLayer != relativeTo;
if (mSurfaceControl != null && (changed || forceUpdate)) {
- if (Flags.useSelfSyncTransactionForLayer() && mSyncState != SYNC_STATE_NONE) {
+ if (mSyncState != SYNC_STATE_NONE) {
// When this container needs to be synced, assign layer with its own sync
// transaction to avoid out of ordering when merge.
// Still use the passed-in transaction for non-sync case, such as building finish
@@ -3067,7 +3066,6 @@
@Override
public void onAnimationLeashLost(Transaction t) {
mLastLayer = -1;
- mWmService.mSurfaceAnimationRunner.onAnimationLeashLost(mAnimationLeash, t);
mAnimationLeash = null;
mNeedsZBoost = false;
reassignLayer(t);
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 6e224f0..4b5a3a0 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -152,6 +152,30 @@
}
}
+ /** Interface for clients to receive callbacks related to window change. */
+ public interface WindowFocusChangeListener {
+ /**
+ * Notify on focus changed.
+ *
+ * @param focusedWindowToken the token of the newly focused window.
+ */
+ void focusChanged(@NonNull IBinder focusedWindowToken);
+ }
+
+ /**
+ * Registers a listener to be notified about window focus changes.
+ *
+ * @param listener the {@link WindowFocusChangeListener} to register.
+ */
+ public abstract void registerWindowFocusChangeListener(WindowFocusChangeListener listener);
+
+ /**
+ * Unregisters a listener that was registered via {@link #registerWindowFocusChangeListener}.
+ *
+ * @param listener the {@link WindowFocusChangeListener} to unregister.
+ */
+ public abstract void unregisterWindowFocusChangeListener(WindowFocusChangeListener listener);
+
/**
* Interface to receive a callback when the windows reported for
* accessibility changed.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 28f2825..9fc0339 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -17,7 +17,6 @@
package com.android.server.wm;
import static android.Manifest.permission.ACCESS_SURFACE_FLINGER;
-import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
import static android.Manifest.permission.INPUT_CONSUMER;
import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
import static android.Manifest.permission.MANAGE_APP_TOKENS;
@@ -88,7 +87,6 @@
import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED;
-import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.fixScale;
@@ -147,6 +145,7 @@
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerInternal.OnWindowRemovedListener;
+import static com.android.server.wm.WindowManagerInternal.WindowFocusChangeListener;
import static com.android.server.wm.WindowManagerServiceDumpProto.BACK_NAVIGATION;
import static com.android.server.wm.WindowManagerServiceDumpProto.FOCUSED_APP;
import static com.android.server.wm.WindowManagerServiceDumpProto.FOCUSED_DISPLAY_ID;
@@ -1080,14 +1079,12 @@
private ViewServer mViewServer;
final ArrayList<WindowChangeListener> mWindowChangeListeners = new ArrayList<>();
+ final ArrayList<WindowFocusChangeListener> mWindowFocusChangeListeners = new ArrayList<>();
boolean mWindowsChanged = false;
- public interface WindowChangeListener {
+ interface WindowChangeListener {
/** Notify on windows changed */
void windowsChanged();
-
- /** Notify on focus changed */
- void focusChanged();
}
final HighRefreshRateDenylist mHighRefreshRateDenylist;
@@ -3237,49 +3234,17 @@
}
}
- // TODO(multi-display): remove when no default display use case.
- void prepareAppTransitionNone() {
- if (!checkCallingPermission(MANAGE_APP_TOKENS, "prepareAppTransition()")) {
- throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
- }
- getDefaultDisplayContentLocked().prepareAppTransition(TRANSIT_NONE);
- }
-
@Override
public void overridePendingAppTransitionMultiThumbFuture(
IAppTransitionAnimationSpecsFuture specsFuture, IRemoteCallback callback,
boolean scaleUp, int displayId) {
- synchronized (mGlobalLock) {
- final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
- if (displayContent == null) {
- Slog.w(TAG, "Attempted to call overridePendingAppTransitionMultiThumbFuture"
- + " for the display " + displayId + " that does not exist.");
- return;
- }
- displayContent.mAppTransition.overridePendingAppTransitionMultiThumbFuture(specsFuture,
- callback, scaleUp);
- }
+ // TODO(b/365884835): remove this method and callers.
}
@Override
public void overridePendingAppTransitionRemote(RemoteAnimationAdapter remoteAnimationAdapter,
int displayId) {
- if (!checkCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,
- "overridePendingAppTransitionRemote()")) {
- throw new SecurityException(
- "Requires CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS permission");
- }
- synchronized (mGlobalLock) {
- final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
- if (displayContent == null) {
- Slog.w(TAG, "Attempted to call overridePendingAppTransitionRemote"
- + " for the display " + displayId + " that does not exist.");
- return;
- }
- remoteAnimationAdapter.setCallingPidUid(Binder.getCallingPid(), Binder.getCallingUid());
- displayContent.mAppTransition.overridePendingAppTransitionRemote(
- remoteAnimationAdapter);
- }
+ // TODO(b/365884835): remove this method and callers.
}
@Override
@@ -3396,11 +3361,6 @@
}
@Override
- public boolean isAppTransitionStateIdle() {
- return getDefaultDisplayContentLocked().mAppTransition.isIdle();
- }
-
- @Override
public void disableKeyguard(IBinder token, String tag, int userId) {
userId = mAmInternal.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
userId, false /* allowAll */, ALLOW_FULL_ONLY, "disableKeyguard", null);
@@ -5345,18 +5305,30 @@
return success;
}
- public void addWindowChangeListener(WindowChangeListener listener) {
+ void addWindowChangeListener(WindowChangeListener listener) {
synchronized (mGlobalLock) {
mWindowChangeListeners.add(listener);
}
}
- public void removeWindowChangeListener(WindowChangeListener listener) {
+ void removeWindowChangeListener(WindowChangeListener listener) {
synchronized (mGlobalLock) {
mWindowChangeListeners.remove(listener);
}
}
+ void addWindowFocusChangeListener(WindowFocusChangeListener listener) {
+ synchronized (mGlobalLock) {
+ mWindowFocusChangeListeners.add(listener);
+ }
+ }
+
+ void removeWindowFocusChangeListener(WindowFocusChangeListener listener) {
+ synchronized (mGlobalLock) {
+ mWindowFocusChangeListeners.remove(listener);
+ }
+ }
+
private void notifyWindowRemovedListeners(IBinder client) {
OnWindowRemovedListener[] windowRemovedListeners;
synchronized (mGlobalLock) {
@@ -5389,18 +5361,19 @@
}
}
- private void notifyFocusChanged() {
- WindowChangeListener[] windowChangeListeners;
+ private void notifyFocusChanged(IBinder focusedWindowToken) {
+ WindowFocusChangeListener[] windowFocusChangeListeners;
synchronized (mGlobalLock) {
- if(mWindowChangeListeners.isEmpty()) {
+ if(mWindowFocusChangeListeners.isEmpty()) {
return;
}
- windowChangeListeners = new WindowChangeListener[mWindowChangeListeners.size()];
- windowChangeListeners = mWindowChangeListeners.toArray(windowChangeListeners);
+ windowFocusChangeListeners =
+ new WindowFocusChangeListener[mWindowFocusChangeListeners.size()];
+ mWindowFocusChangeListeners.toArray(windowFocusChangeListeners);
}
- int N = windowChangeListeners.length;
+ int N = windowFocusChangeListeners.length;
for(int i = 0; i < N; i++) {
- windowChangeListeners[i].focusChanged();
+ windowFocusChangeListeners[i].focusChanged(focusedWindowToken);
}
}
@@ -5675,7 +5648,7 @@
if (newFocusedWindow != null && newFocusedWindow.mInputChannelToken == newToken) {
mAnrController.onFocusChanged(newFocusedWindow);
newFocusedWindow.reportFocusChangedSerialized(true);
- notifyFocusChanged();
+ notifyFocusChanged(newTarget.getWindowToken());
}
WindowState lastFocusedWindow = lastTarget != null ? lastTarget.getWindowState() : null;
@@ -6863,7 +6836,7 @@
pw.print(' '); pw.println(imeControlTarget);
}
pw.print(" Minimum task size of display#"); pw.print(displayId);
- pw.print(' '); pw.print(dc.mMinSizeOfResizeableTaskDp);
+ pw.print(' '); pw.println(dc.mMinSizeOfResizeableTaskDp);
});
pw.print(" mBlurEnabled="); pw.println(mBlurController.getBlurEnabled());
pw.print(" mDisableSecureWindows="); pw.println(mDisableSecureWindows);
@@ -7968,7 +7941,6 @@
@Override
public void registerAppTransitionListener(AppTransitionListener listener) {
synchronized (mGlobalLock) {
- getDefaultDisplayContentLocked().mAppTransition.registerListenerLocked(listener);
mAtmService.getTransitionController().registerLegacyListener(listener);
}
}
@@ -8690,6 +8662,16 @@
}
@Override
+ public void registerWindowFocusChangeListener(WindowFocusChangeListener listener) {
+ WindowManagerService.this.addWindowFocusChangeListener(listener);
+ }
+
+ @Override
+ public void unregisterWindowFocusChangeListener(WindowFocusChangeListener listener) {
+ WindowManagerService.this.removeWindowFocusChangeListener(listener);
+ }
+
+ @Override
public void registerOnWindowRemovedListener(OnWindowRemovedListener listener) {
synchronized (mGlobalLock) {
mOnWindowRemovedListeners.add(listener);
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 4b4736e..ea1f35a 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -472,6 +472,7 @@
transition.setAllReady();
}
+ // TODO(b/365884835): remove this method and callers.
@Override
public int startLegacyTransition(int type, @NonNull RemoteAnimationAdapter adapter,
@NonNull IWindowContainerTransactionCallback callback,
@@ -489,16 +490,6 @@
throw new IllegalArgumentException("Can't use legacy transitions in"
+ " when shell transitions are enabled.");
}
- final DisplayContent dc =
- mService.mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY);
- if (dc.mAppTransition.isTransitionSet()) {
- // a transition already exists, so the callback probably won't be called.
- return -1;
- }
- adapter.setCallingPidUid(caller.mPid, caller.mUid);
- dc.prepareAppTransition(type);
- dc.mAppTransition.overridePendingAppTransitionRemote(adapter, true /* sync */,
- false /* isActivityEmbedding */);
syncId = startSyncWithOrganizer(callback);
applyTransaction(t, syncId, mService.mChainTracker.startLegacy("legacyTransit"),
caller);
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 270de01..bdd1372 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -36,8 +36,8 @@
import static com.android.server.wm.ActivityRecord.State.PAUSING;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityRecord.State.STARTED;
-import static com.android.server.wm.ActivityRecord.State.STOPPING;
import static com.android.server.wm.ActivityRecord.State.STOPPED;
+import static com.android.server.wm.ActivityRecord.State.STOPPING;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RELEASE;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_CONFIGURATION;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RELEASE;
@@ -69,7 +69,6 @@
import android.content.res.Configuration;
import android.os.Binder;
import android.os.Build;
-import android.os.DeadObjectException;
import android.os.FactoryTest;
import android.os.LocaleList;
import android.os.Message;
@@ -458,6 +457,7 @@
mAtm.getLifecycleManager().scheduleTransactionItemNow(
thread, configurationChangeItem);
} catch (Exception e) {
+ // TODO(b/323801078): remove Exception when cleanup
Slog.e(TAG_CONFIGURATION, "Failed to schedule ConfigurationChangeItem="
+ configurationChangeItem + " owner=" + mOwner, e);
}
@@ -1793,13 +1793,11 @@
// Non-UI process can handle the change directly.
mAtm.getLifecycleManager().scheduleTransactionItemNow(thread, transactionItem);
}
- } catch (DeadObjectException e) {
+ } catch (RemoteException e) {
+ // TODO(b/323801078): remove Exception when cleanup
// Expected if the process has been killed.
Slog.w(TAG_CONFIGURATION, "Failed for dead process. ClientTransactionItem="
+ transactionItem + " owner=" + mOwner);
- } catch (Exception e) {
- Slog.e(TAG_CONFIGURATION, "Failed to schedule ClientTransactionItem="
- + transactionItem + " owner=" + mOwner, e);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowProcessControllerMap.java b/services/core/java/com/android/server/wm/WindowProcessControllerMap.java
index 424b043..ac675b8 100644
--- a/services/core/java/com/android/server/wm/WindowProcessControllerMap.java
+++ b/services/core/java/com/android/server/wm/WindowProcessControllerMap.java
@@ -72,9 +72,6 @@
}
private void removeProcessFromUidMap(WindowProcessController proc) {
- if (proc == null) {
- return;
- }
final int uid = proc.mUid;
ArraySet<WindowProcessController> procSet = mUidMap.get(uid);
if (procSet != null) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 3b7d312..1022d18 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5447,7 +5447,7 @@
@Override
void assignLayer(Transaction t, int layer) {
if (mStartingData != null) {
- if (Flags.useSelfSyncTransactionForLayer() && mSyncState != SYNC_STATE_NONE) {
+ if (mSyncState != SYNC_STATE_NONE) {
// When this container needs to be synced, assign layer with its own sync
// transaction to avoid out of ordering when merge.
// Still use the passed-in transaction for non-sync case, such as building finish
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 1d8d867..0d434f5 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -561,7 +561,7 @@
break;
}
if (attr >= 0) {
- a = mWin.getDisplayContent().mAppTransition.loadAnimationAttr(
+ a = mWin.mDisplayContent.mTransitionAnimation.loadAnimationAttr(
mWin.mAttrs, attr, TRANSIT_OLD_NONE);
}
}
diff --git a/services/core/jni/stats/OWNERS b/services/core/jni/stats/OWNERS
index 8d87925..03086b3 100644
--- a/services/core/jni/stats/OWNERS
+++ b/services/core/jni/stats/OWNERS
@@ -1,6 +1,5 @@
jeffreyhuang@google.com
muhammadq@google.com
-sharaieko@google.com
singhtejinder@google.com
tsaichristine@google.com
yaochen@google.com
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 014f0a2..42e457c 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -31,6 +31,7 @@
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.res.Resources;
import android.credentials.ClearCredentialStateRequest;
import android.credentials.CreateCredentialException;
import android.credentials.CreateCredentialRequest;
@@ -50,6 +51,7 @@
import android.credentials.PrepareGetCredentialResponseInternal;
import android.credentials.RegisterCredentialDescriptionRequest;
import android.credentials.UnregisterCredentialDescriptionRequest;
+import android.credentials.flags.Flags;
import android.os.Binder;
import android.os.CancellationSignal;
import android.os.IBinder;
@@ -79,6 +81,7 @@
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -535,6 +538,33 @@
final int userId = UserHandle.getCallingUserId();
final int callingUid = Binder.getCallingUid();
+ if (Flags.safeguardCandidateCredentialsApiCaller()) {
+ try {
+ String credentialManagerAutofillCompName = mContext.getResources().getString(
+ R.string.config_defaultCredentialManagerAutofillService);
+ ComponentName componentName = ComponentName.unflattenFromString(
+ credentialManagerAutofillCompName);
+ if (componentName == null) {
+ throw new SecurityException(
+ "Credential Autofill service does not exist on this device.");
+ }
+ PackageManager pm = mContext.createContextAsUser(
+ UserHandle.getUserHandleForUid(callingUid), 0).getPackageManager();
+ String callingProcessPackage = pm.getNameForUid(callingUid);
+ if (callingProcessPackage == null) {
+ throw new SecurityException(
+ "Couldn't determine the identity of the caller.");
+ }
+ if (!Objects.equals(componentName.getPackageName(), callingProcessPackage)) {
+ throw new SecurityException(callingProcessPackage
+ + " is not the device's credential autofill package.");
+ }
+ } catch (Resources.NotFoundException e) {
+ throw new SecurityException(
+ "Credential Autofill service does not exist on this device.");
+ }
+ }
+
// New request session, scoped for this request only.
final GetCandidateRequestSession session =
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index aee32a0..215d6ca 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3582,14 +3582,7 @@
@GuardedBy("getLockObject()")
private boolean maybeMigrateMemoryTaggingLocked(String backupId) {
- if (!Flags.setMtePolicyCoexistence()) {
- Slog.i(LOG_TAG, "Memory Tagging not migrated because coexistence "
- + "support is disabled.");
- return false;
- }
if (mOwners.isMemoryTaggingMigrated()) {
- // TODO: Remove log after Flags.setMtePolicyCoexistence full rollout.
- Slog.v(LOG_TAG, "Memory Tagging was previously migrated to policy engine.");
return false;
}
@@ -16354,7 +16347,7 @@
private static <V> PolicyDefinition<V> getPolicyDefinitionForIdentifier(
@NonNull String identifier) {
Objects.requireNonNull(identifier);
- if (Flags.setMtePolicyCoexistence() && MEMORY_TAGGING_POLICY.equals(identifier)) {
+ if (MEMORY_TAGGING_POLICY.equals(identifier)) {
return (PolicyDefinition<V>) PolicyDefinition.MEMORY_TAGGING;
} else {
return (PolicyDefinition<V>) getPolicyDefinitionForRestriction(identifier);
@@ -23759,46 +23752,21 @@
Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
}
- if (Flags.setMtePolicyCoexistence()) {
- enforcePermission(MANAGE_DEVICE_POLICY_MTE, caller.getPackageName(),
- UserHandle.USER_ALL);
- } else {
- Preconditions.checkCallAuthorization(
- isDefaultDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
- }
+ enforcePermission(MANAGE_DEVICE_POLICY_MTE, caller.getPackageName(),
+ UserHandle.USER_ALL);
synchronized (getLockObject()) {
- if (Flags.setMtePolicyCoexistence()) {
- final EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(null,
- MANAGE_DEVICE_POLICY_MTE, callerPackageName, caller.getUserId());
- if (flags != DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
- mDevicePolicyEngine.setGlobalPolicy(
- PolicyDefinition.MEMORY_TAGGING,
- admin,
- new IntegerPolicyValue(flags));
- } else {
- mDevicePolicyEngine.removeGlobalPolicy(
- PolicyDefinition.MEMORY_TAGGING,
- admin);
- }
+ final EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(null,
+ MANAGE_DEVICE_POLICY_MTE, callerPackageName, caller.getUserId());
+ if (flags != DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
+ mDevicePolicyEngine.setGlobalPolicy(
+ PolicyDefinition.MEMORY_TAGGING,
+ admin,
+ new IntegerPolicyValue(flags));
} else {
- ActiveAdmin admin =
- getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
- if (admin != null) {
- final String memtagProperty = "arm64.memtag.bootctl";
- if (flags == DevicePolicyManager.MTE_ENABLED) {
- mInjector.systemPropertiesSet(memtagProperty, "memtag");
- } else if (flags == DevicePolicyManager.MTE_DISABLED) {
- mInjector.systemPropertiesSet(memtagProperty, "memtag-off");
- } else if (flags == DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
- if (admin.mtePolicy != DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
- mInjector.systemPropertiesSet(memtagProperty, "default");
- }
- }
- admin.mtePolicy = flags;
- saveSettingsLocked(caller.getUserId());
- }
+ mDevicePolicyEngine.removeGlobalPolicy(
+ PolicyDefinition.MEMORY_TAGGING,
+ admin);
}
DevicePolicyEventLogger.createEvent(DevicePolicyEnums.SET_MTE_POLICY)
@@ -23817,10 +23785,6 @@
Preconditions.checkCallAuthorization(isSystemUid(getCallerIdentity()),
"Only system services can call setMtePolicyBySystem");
- if (!Flags.setMtePolicyCoexistence()) {
- throw new UnsupportedOperationException("System can not set MTE policy only");
- }
-
EnforcingAdmin admin = EnforcingAdmin.createSystemEnforcingAdmin(systemEntity);
if (policy != DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
mDevicePolicyEngine.setGlobalPolicy(
@@ -23858,31 +23822,16 @@
@Override
public int getMtePolicy(String callerPackageName) {
final CallerIdentity caller = getCallerIdentity(callerPackageName);
- if (Flags.setMtePolicyCoexistence()) {
- enforcePermission(MANAGE_DEVICE_POLICY_MTE, caller.getPackageName(),
- UserHandle.USER_ALL);
- } else {
- Preconditions.checkCallAuthorization(
- isDefaultDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller)
- || isSystemUid(caller));
- }
+ enforcePermission(MANAGE_DEVICE_POLICY_MTE, caller.getPackageName(),
+ UserHandle.USER_ALL);
synchronized (getLockObject()) {
- if (Flags.setMtePolicyCoexistence()) {
- final EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(null,
- MANAGE_DEVICE_POLICY_MTE, callerPackageName, caller.getUserId());
- final Integer policyFromAdmin = mDevicePolicyEngine.getGlobalPolicySetByAdmin(
- PolicyDefinition.MEMORY_TAGGING, admin);
- return (policyFromAdmin != null ? policyFromAdmin
- : DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY);
- } else {
- ActiveAdmin admin =
- getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
- return admin != null
- ? admin.mtePolicy
- : DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY;
- }
+ final EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(null,
+ MANAGE_DEVICE_POLICY_MTE, callerPackageName, caller.getUserId());
+ final Integer policyFromAdmin = mDevicePolicyEngine.getGlobalPolicySetByAdmin(
+ PolicyDefinition.MEMORY_TAGGING, admin);
+ return (policyFromAdmin != null ? policyFromAdmin
+ : DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY);
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
index caaf096..6dfe08c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
@@ -433,10 +433,8 @@
out.attributeBoolean(null, ATTR_RESET_PASSWORD_WITH_TOKEN_MIGRATED,
mResetPasswordWithTokenMigrated);
}
- if (Flags.setMtePolicyCoexistence()) {
- out.attributeBoolean(null, ATTR_MEMORY_TAGGING_MIGRATED,
- mMemoryTaggingMigrated);
- }
+ out.attributeBoolean(null, ATTR_MEMORY_TAGGING_MIGRATED,
+ mMemoryTaggingMigrated);
if (Flags.setKeyguardDisabledFeaturesCoexistence()) {
out.attributeBoolean(null, ATTR_SET_KEYGUARD_DISABLED_FEATURES_MIGRATED,
mSetKeyguardDisabledFeaturesMigrated);
@@ -514,8 +512,7 @@
mResetPasswordWithTokenMigrated = Flags.resetPasswordWithTokenCoexistence()
&& parser.getAttributeBoolean(null,
ATTR_RESET_PASSWORD_WITH_TOKEN_MIGRATED, false);
- mMemoryTaggingMigrated = Flags.setMtePolicyCoexistence()
- && parser.getAttributeBoolean(null,
+ mMemoryTaggingMigrated = parser.getAttributeBoolean(null,
ATTR_MEMORY_TAGGING_MIGRATED, false);
mSetKeyguardDisabledFeaturesMigrated =
Flags.setKeyguardDisabledFeaturesCoexistence()
diff --git a/services/proguard.flags b/services/proguard.flags
index 8d8b418..dd3757c 100644
--- a/services/proguard.flags
+++ b/services/proguard.flags
@@ -59,6 +59,7 @@
# Referenced in wear-service
-keep public class com.android.server.wm.WindowManagerInternal { *; }
+-keep public class com.android.server.wm.WindowManagerInternal$WindowFocusChangeListener { *; }
# JNI keep rules
# The global keep rule for native methods allows stripping of such methods if they're unreferenced
diff --git a/services/supervision/java/com/android/server/supervision/SupervisionService.java b/services/supervision/java/com/android/server/supervision/SupervisionService.java
index f731b50..bb6339c 100644
--- a/services/supervision/java/com/android/server/supervision/SupervisionService.java
+++ b/services/supervision/java/com/android/server/supervision/SupervisionService.java
@@ -64,6 +64,18 @@
public class SupervisionService extends ISupervisionManager.Stub {
private static final String LOG_TAG = "SupervisionService";
+ /**
+ * Activity action: Requests user confirmation of supervision credentials.
+ *
+ * <p>Use {@link Activity#startActivityForResult} to launch this activity. The result will be
+ * {@link Activity#RESULT_OK} if credentials are valid.
+ *
+ * <p>If supervision credentials are not configured, this action initiates the setup flow.
+ */
+ @VisibleForTesting
+ static final String ACTION_CONFIRM_SUPERVISION_CREDENTIALS =
+ "android.app.supervision.action.CONFIRM_SUPERVISION_CREDENTIALS";
+
// TODO(b/362756788): Does this need to be a LockGuard lock?
private final Object mLockDoNoUseDirectly = new Object();
@@ -81,25 +93,6 @@
}
/**
- * Creates an {@link Intent} that can be used with {@link Context#startActivity(Intent)} to
- * launch the activity to verify supervision credentials.
- *
- * <p>A valid {@link Intent} is always returned if supervision is enabled at the time this
- * method is called, the launched activity still need to perform validity checks as the
- * supervision state can change when it's launched. A null intent is returned if supervision is
- * disabled at the time of this method call.
- *
- * <p>A result code of {@link android.app.Activity#RESULT_OK} indicates successful verification
- * of the supervision credentials.
- */
- @Override
- @Nullable
- public Intent createConfirmSupervisionCredentialsIntent() {
- // TODO(b/392961554): Implement createAuthenticationIntent API
- throw new UnsupportedOperationException();
- }
-
- /**
* Returns whether supervision is enabled for the given user.
*
* <p>Supervision is automatically enabled when the supervision app becomes the profile owner or
@@ -139,6 +132,31 @@
}
}
+ /**
+ * Creates an {@link Intent} that can be used with {@link Context#startActivity(Intent)} to
+ * launch the activity to verify supervision credentials.
+ *
+ * <p>A valid {@link Intent} is always returned if supervision is enabled at the time this
+ * method is called, the launched activity still need to perform validity checks as the
+ * supervision state can change when it's launched. A null intent is returned if supervision is
+ * disabled at the time of this method call.
+ *
+ * <p>A result code of {@link android.app.Activity#RESULT_OK} indicates successful verification
+ * of the supervision credentials.
+ */
+ @Override
+ @Nullable
+ public Intent createConfirmSupervisionCredentialsIntent() {
+ // TODO(b/392961554): (1) Return null if supervision is not enabled.
+ // (2) check if PIN exists before return a valid intent.
+ enforceAnyPermission(QUERY_USERS, MANAGE_USERS);
+ final Intent intent = new Intent(ACTION_CONFIRM_SUPERVISION_CREDENTIALS);
+ // explicitly set the package for security
+ intent.setPackage("com.android.settings");
+
+ return intent;
+ }
+
@Override
public void onShellCommand(
@Nullable FileDescriptor in,
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml
index d6a6853..ea01fc4 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml
@@ -18,9 +18,6 @@
<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" />
@@ -44,7 +41,7 @@
<!-- Collect output of DumpOnFailure -->
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
- <option name="directory-keys" value="/data/user/0/com.android.apps.inputmethod.simpleime/files" />
+ <option name="directory-keys" value="/sdcard/DumpOnFailure" />
<option name="collect-on-run-ended-only" value="true" />
<option name="clean-up" value="true" />
</metrics_collector>
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
index 2cd860a..e263e85 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
@@ -77,6 +77,8 @@
import org.junit.rules.TestName;
import org.junit.runner.RunWith;
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
@@ -1294,6 +1296,14 @@
mInstrumentation.waitForIdleSync();
final var postScreenshot = mInstrumentation.getUiAutomation().takeScreenshot();
mDumpOnFailure.dumpOnFailure("post-getUiObject", postScreenshot);
+ try {
+ final var outputStream = new ByteArrayOutputStream();
+ mUiDevice.dumpWindowHierarchy(outputStream);
+ final String windowHierarchy = outputStream.toString(StandardCharsets.UTF_8);
+ mDumpOnFailure.dumpOnFailure("post-getUiObject", windowHierarchy);
+ } catch (Exception e) {
+ Log.i(TAG, "Failed to dump windowHierarchy", e);
+ }
assertWithMessage("UiObject with " + bySelector + " was found").that(uiObject).isNotNull();
return uiObject;
}
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml
index 00873de..b6965a4 100644
--- a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml
@@ -20,6 +20,9 @@
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+ <!-- Enable writing output of DumpOnFailure to external storage -->
+ <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+
<application android:debuggable="true"
android:label="@string/app_name">
<service
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index c151732..65585d0 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -332,6 +332,10 @@
@Override
public void destroyDisplay(IBinder displayToken) {
}
+
+ @Override
+ public void setDisplayPowerMode(IBinder displayToken, int mode) {
+ }
}, flags);
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java
index 9287b30..0bef3b89 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java
@@ -21,19 +21,29 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.hardware.display.DisplayManager;
import android.hardware.display.IVirtualDisplayCallback;
import android.hardware.display.VirtualDisplayConfig;
import android.media.projection.IMediaProjection;
+import android.os.Binder;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.Process;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.TestableContext;
import android.view.Display;
import android.view.Surface;
+import android.view.SurfaceControl;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -67,6 +77,9 @@
public final TestableContext mContext = new TestableContext(
InstrumentationRegistry.getInstrumentation().getContext());
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Mock
private VirtualDisplayAdapter.SurfaceControlDisplayFactory mMockSufaceControlDisplayFactory;
@@ -380,6 +393,69 @@
}
}
+ @EnableFlags(
+ android.companion.virtualdevice.flags.Flags.FLAG_CORRECT_VIRTUAL_DISPLAY_POWER_STATE)
+ @Test
+ public void neverBlankDisplay_alwaysOn() {
+ // A non-public non-mirror display is considered never blank.
+ DisplayDevice device = mAdapter.createVirtualDisplayLocked(mMockCallback,
+ /* projection= */ null, /* ownerUid= */ 10, /* packageName= */ "testpackage",
+ "uniqueId", /* surface= */ mSurfaceMock, /* flags= */ 0,
+ mVirtualDisplayConfigMock);
+
+ DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
+ assertThat(info.state).isEqualTo(Display.STATE_ON);
+ assertThat(info.flags & DisplayDeviceInfo.FLAG_NEVER_BLANK)
+ .isEqualTo(DisplayDeviceInfo.FLAG_NEVER_BLANK);
+ }
+
+ @EnableFlags(
+ android.companion.virtualdevice.flags.Flags.FLAG_CORRECT_VIRTUAL_DISPLAY_POWER_STATE)
+ @Test
+ public void virtualDisplayStateChange_propagatesToSurfaceControl() throws Exception {
+ final String uniqueId = "uniqueId";
+ final IBinder displayToken = new Binder();
+ when(mMockSufaceControlDisplayFactory.createDisplay(
+ any(), anyBoolean(), eq(uniqueId), anyFloat()))
+ .thenReturn(displayToken);
+
+ // The display needs to be public, otherwise it will be considered never blank.
+ DisplayDevice device = mAdapter.createVirtualDisplayLocked(mMockCallback,
+ /* projection= */ null, /* ownerUid= */ 10, /* packageName= */ "testpackage",
+ uniqueId, /* surface= */ mSurfaceMock, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,
+ mVirtualDisplayConfigMock);
+
+ DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
+ assertThat(info.state).isEqualTo(Display.STATE_UNKNOWN);
+ assertThat(info.flags & DisplayDeviceInfo.FLAG_NEVER_BLANK).isEqualTo(0);
+
+ // Any initial state change is processed because the display state is initially UNKNOWN
+ Runnable stateOnRunnable = device.requestDisplayStateLocked(
+ Display.STATE_ON, /* brightnessState= */ 1.0f, /* sdrBrightnessState= */ 1.0f,
+ /* displayOffloadSession= */ null);
+ assertThat(stateOnRunnable).isNotNull();
+ stateOnRunnable.run();
+ verify(mMockSufaceControlDisplayFactory)
+ .setDisplayPowerMode(displayToken, SurfaceControl.POWER_MODE_NORMAL);
+ verify(mMockCallback).onResumed();
+
+ // Requesting the same display state is a no-op
+ Runnable stateOnSecondRunnable = device.requestDisplayStateLocked(
+ Display.STATE_ON, /* brightnessState= */ 1.0f, /* sdrBrightnessState= */ 1.0f,
+ /* displayOffloadSession= */ null);
+ assertThat(stateOnSecondRunnable).isNull();
+
+ // A change to the display state is processed
+ Runnable stateOffRunnable = device.requestDisplayStateLocked(
+ Display.STATE_OFF, /* brightnessState= */ 1.0f, /* sdrBrightnessState= */ 1.0f,
+ /* displayOffloadSession= */ null);
+ assertThat(stateOffRunnable).isNotNull();
+ stateOffRunnable.run();
+ verify(mMockSufaceControlDisplayFactory)
+ .setDisplayPowerMode(displayToken, SurfaceControl.POWER_MODE_OFF);
+ verify(mMockCallback).onPaused();
+ }
+
private IVirtualDisplayCallback createCallback() {
return new IVirtualDisplayCallback.Stub() {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt
index 6929690..d7c0477 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt
@@ -17,7 +17,6 @@
import android.os.UserHandle
import android.platform.test.annotations.RequiresFlagsEnabled
-import android.provider.Settings
import android.testing.TestableContext
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.display.DisplayDeviceConfig
@@ -46,8 +45,6 @@
private var mockDisplayDeviceConfig = mock<DisplayDeviceConfig>()
private val LOW_LUX_BRIGHTNESS = 0.1f
- private val TRANSITION_POINT = 0.25f
- private val NORMAL_RANGE_BRIGHTNESS = 0.3f
@Before
fun setUp() {
@@ -73,48 +70,17 @@
whenever(mockDisplayDeviceConfig.getBrightnessFromBacklight(/* backlight = */ 0.15f))
.thenReturn(0.24f)
- // values above transition point (normal range)
- // nits: 10 -> backlight 0.2 -> brightness -> 0.3
- whenever(mockDisplayDeviceConfig.getBacklightFromNits(/* nits= */ 10f))
- .thenReturn(0.2f)
- whenever(mockDisplayDeviceConfig.getBrightnessFromBacklight(/* backlight = */ 0.2f))
- .thenReturn(NORMAL_RANGE_BRIGHTNESS)
-
// min nits when lux of 400
whenever(mockDisplayDeviceConfig.getMinNitsFromLux(/* lux= */ 400f))
.thenReturn(1.0f)
-
- whenever(mockDisplayDeviceConfig.evenDimmerTransitionPoint).thenReturn(TRANSITION_POINT)
-
testHandler.flush()
}
@Test
- fun testSettingOffDisablesModifier() {
- // test transition point ensures brightness doesn't drop when setting is off.
- Settings.Secure.putIntForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_ACTIVATED, 0, USER_ID)
- modifier.recalculateLowerBound()
- testHandler.flush()
- assertThat(modifier.brightnessLowerBound).isEqualTo(TRANSITION_POINT)
- assertThat(modifier.brightnessReason).isEqualTo(0) // no reason - ie off
- modifier.setAmbientLux(3000f)
-
- testHandler.flush()
- assertThat(modifier.isActive).isFalse()
- assertThat(modifier.brightnessLowerBound).isEqualTo(TRANSITION_POINT)
- assertThat(modifier.brightnessReason).isEqualTo(0) // no reason - ie off
- }
-
- @Test
@RequiresFlagsEnabled(Flags.FLAG_EVEN_DIMMER)
fun testLuxRestrictsBrightnessRange() {
// test that high lux prevents low brightness range.
- Settings.Secure.putIntForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, USER_ID)
- Settings.Secure.putFloatForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_MIN_NITS, 0.1f, USER_ID)
modifier.setAmbientLux(400f)
testHandler.flush()
@@ -127,88 +93,8 @@
@Test
@RequiresFlagsEnabled(Flags.FLAG_EVEN_DIMMER)
- fun testUserRestrictsBrightnessRange() {
- // test that user minimum nits setting prevents low brightness range.
- Settings.Secure.putIntForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, USER_ID)
- Settings.Secure.putFloatForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_MIN_NITS, 10.0f, USER_ID)
- modifier.recalculateLowerBound()
- testHandler.flush()
-
- // Test restriction from user setting
- assertThat(modifier.isActive).isTrue()
- assertThat(modifier.brightnessReason)
- .isEqualTo(BrightnessReason.MODIFIER_MIN_USER_SET_LOWER_BOUND)
- assertThat(modifier.brightnessLowerBound).isEqualTo(NORMAL_RANGE_BRIGHTNESS)
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_EVEN_DIMMER)
- fun testOnToOff() {
- // test that high lux prevents low brightness range.
- Settings.Secure.putIntForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, USER_ID) // on
- Settings.Secure.putFloatForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_MIN_NITS, 1.0f, USER_ID)
- modifier.setAmbientLux(400f)
-
- testHandler.flush()
-
- assertThat(modifier.isActive).isTrue()
- // Test restriction from lux setting
- assertThat(modifier.brightnessReason).isEqualTo(BrightnessReason.MODIFIER_MIN_LUX)
- assertThat(modifier.brightnessLowerBound).isEqualTo(LOW_LUX_BRIGHTNESS)
-
- Settings.Secure.putIntForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_ACTIVATED, 0, USER_ID) // off
-
- modifier.recalculateLowerBound()
- testHandler.flush()
-
- assertThat(modifier.isActive).isFalse()
- assertThat(modifier.brightnessLowerBound).isEqualTo(TRANSITION_POINT)
- assertThat(modifier.brightnessReason).isEqualTo(0) // no reason - ie off
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_EVEN_DIMMER)
- fun testOffToOn() {
- // test that high lux prevents low brightness range.
- Settings.Secure.putIntForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_ACTIVATED, 0, USER_ID) // off
- Settings.Secure.putFloatForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_MIN_NITS, 1.0f, USER_ID)
- modifier.setAmbientLux(400f)
-
- testHandler.flush()
-
- assertThat(modifier.isActive).isFalse()
- assertThat(modifier.brightnessLowerBound).isEqualTo(TRANSITION_POINT)
- assertThat(modifier.brightnessReason).isEqualTo(0) // no reason - ie off
-
-
-
- Settings.Secure.putIntForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, USER_ID) // on
- modifier.recalculateLowerBound()
- testHandler.flush()
-
- assertThat(modifier.isActive).isTrue()
- // Test restriction from lux setting
- assertThat(modifier.brightnessReason).isEqualTo(BrightnessReason.MODIFIER_MIN_LUX)
- assertThat(modifier.brightnessLowerBound).isEqualTo(LOW_LUX_BRIGHTNESS)
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_EVEN_DIMMER)
fun testEnabledEvenWhenAutobrightnessIsOff() {
// test that high lux prevents low brightness range.
- Settings.Secure.putIntForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, USER_ID) // on
- Settings.Secure.putFloatForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_MIN_NITS, 1.0f, USER_ID)
-
modifier.setAmbientLux(400f)
testHandler.flush()
@@ -225,37 +111,5 @@
assertThat(modifier.brightnessReason).isEqualTo(BrightnessReason.MODIFIER_MIN_LUX)
assertThat(modifier.brightnessLowerBound).isEqualTo(LOW_LUX_BRIGHTNESS)
}
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_EVEN_DIMMER)
- fun testUserSwitch() {
- // nits: 0.5 -> backlight 0.01 -> brightness -> 0.05
- whenever(mockDisplayDeviceConfig.getBacklightFromNits(/* nits= */ 0.5f))
- .thenReturn(0.01f)
- whenever(mockDisplayDeviceConfig.getBrightnessFromBacklight(/* backlight = */ 0.01f))
- .thenReturn(0.05f)
-
- Settings.Secure.putIntForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_ACTIVATED, 0, USER_ID) // off
- Settings.Secure.putFloatForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_MIN_NITS, 1.0f, USER_ID)
-
- modifier.recalculateLowerBound()
-
- assertThat(modifier.isActive).isFalse()
- assertThat(modifier.brightnessLowerBound).isEqualTo(TRANSITION_POINT)
- assertThat(modifier.brightnessReason).isEqualTo(0) // no reason - i.e. off
-
- Settings.Secure.putIntForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, USER_ID) // on
- Settings.Secure.putFloatForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_MIN_NITS, 0.5f, USER_ID)
- modifier.onSwitchUser()
-
- assertThat(modifier.isActive).isTrue()
- assertThat(modifier.brightnessReason).isEqualTo(
- BrightnessReason.MODIFIER_MIN_USER_SET_LOWER_BOUND)
- assertThat(modifier.brightnessLowerBound).isEqualTo(0.05f)
- }
}
diff --git a/services/tests/media/mediarouterservicetest/Android.bp b/services/tests/media/mediarouterservicetest/Android.bp
index aed3af6..f149f2e 100644
--- a/services/tests/media/mediarouterservicetest/Android.bp
+++ b/services/tests/media/mediarouterservicetest/Android.bp
@@ -9,6 +9,10 @@
android_test {
name: "MediaRouterServiceTests",
+ defaults: [
+ // For ExtendedMockito dependencies.
+ "modules-utils-testable-device-config-defaults",
+ ],
srcs: [
"src/**/*.java",
],
@@ -23,12 +27,17 @@
"services.core",
"truth",
],
+ libs: [
+ "android.test.base.stubs",
+ "android.test.runner.stubs",
+ ],
platform_apis: true,
test_suites: [
// "device-tests",
"general-tests",
+ "mts-statsd",
],
certificate: "platform",
@@ -36,4 +45,5 @@
optimize: {
enabled: false,
},
+ min_sdk_version: "30",
}
diff --git a/services/tests/media/mediarouterservicetest/AndroidTest.xml b/services/tests/media/mediarouterservicetest/AndroidTest.xml
index b065681..646812b 100644
--- a/services/tests/media/mediarouterservicetest/AndroidTest.xml
+++ b/services/tests/media/mediarouterservicetest/AndroidTest.xml
@@ -17,6 +17,7 @@
<option name="test-tag" value="MediaRouterServiceTests" />
<option name="test-suite-tag" value="apct" />
<option name="test-suite-tag" value="apct-instrumentation" />
+ <option name="test-suite-tag" value="mts" />
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
@@ -26,7 +27,7 @@
<option name="install-arg" value="-t" />
</target_preparer>
- <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.server.media.tests" />
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
<option name="hidden-api-checks" value="false"/>
diff --git a/services/tests/media/mediarouterservicetest/src/com/android/server/media/MediaRouterMetricLoggerTest.java b/services/tests/media/mediarouterservicetest/src/com/android/server/media/MediaRouterMetricLoggerTest.java
new file mode 100644
index 0000000..5e401ae
--- /dev/null
+++ b/services/tests/media/mediarouterservicetest/src/com/android/server/media/MediaRouterMetricLoggerTest.java
@@ -0,0 +1,140 @@
+/*
+ * 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.
+ */
+
+package com.android.server.media;
+
+import static android.media.MediaRoute2ProviderService.REASON_FAILED_TO_REROUTE_SYSTEM_MEDIA;
+import static android.media.MediaRoute2ProviderService.REASON_INVALID_COMMAND;
+import static android.media.MediaRoute2ProviderService.REASON_NETWORK_ERROR;
+import static android.media.MediaRoute2ProviderService.REASON_REJECTED;
+import static android.media.MediaRoute2ProviderService.REASON_ROUTE_NOT_AVAILABLE;
+import static android.media.MediaRoute2ProviderService.REASON_UNIMPLEMENTED;
+import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_CREATE_SESSION;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_FAILED_TO_REROUTE_SYSTEM_MEDIA;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_INVALID_COMMAND;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_NETWORK_ERROR;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_REJECTED;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_ROUTE_NOT_AVAILABLE;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_SUCCESS;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNIMPLEMENTED;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNKNOWN_ERROR;
+import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNSPECIFIED;
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.runner.AndroidJUnit4;
+import com.android.modules.utils.testing.ExtendedMockitoRule;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class MediaRouterMetricLoggerTest {
+ @Rule
+ public final ExtendedMockitoRule mExtendedMockitoRule =
+ new ExtendedMockitoRule.Builder(this).mockStatic(MediaRouterStatsLog.class).build();
+
+ private MediaRouterMetricLogger mLogger;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mLogger = new MediaRouterMetricLogger();
+ }
+
+ @Test
+ public void addRequestInfo_addsRequestInfoToCache() {
+ long requestId = 123;
+ int eventType = MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_CREATE_SESSION;
+
+ mLogger.addRequestInfo(requestId, eventType);
+
+ assertThat(mLogger.getRequestCacheSize()).isEqualTo(1);
+ }
+
+ @Test
+ public void removeRequestInfo_removesRequestInfoFromCache() {
+ long requestId = 123;
+ int eventType = MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_CREATE_SESSION;
+ mLogger.addRequestInfo(requestId, eventType);
+
+ mLogger.removeRequestInfo(requestId);
+
+ assertThat(mLogger.getRequestCacheSize()).isEqualTo(0);
+ }
+
+ @Test
+ public void logOperationFailure_logsOperationFailure() {
+ int eventType = MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_CREATE_SESSION;
+ int result = MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_REJECTED;
+ mLogger.logOperationFailure(eventType, result);
+ verify(
+ () ->
+ MediaRouterStatsLog.write( // Use ExtendedMockito.verify and lambda
+ MEDIA_ROUTER_EVENT_REPORTED, eventType, result));
+ }
+
+ @Test
+ public void logRequestResult_logsRequestResult() {
+ long requestId = 123;
+ int eventType = MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_CREATE_SESSION;
+ int result = MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_SUCCESS;
+ mLogger.addRequestInfo(requestId, eventType);
+
+ mLogger.logRequestResult(requestId, result);
+
+ assertThat(mLogger.getRequestCacheSize()).isEqualTo(0);
+ verify(
+ () ->
+ MediaRouterStatsLog.write( // Use ExtendedMockito.verify and lambda
+ MEDIA_ROUTER_EVENT_REPORTED, eventType, result));
+ }
+
+ @Test
+ public void convertResultFromReason_returnsCorrectResult() {
+ assertThat(MediaRouterMetricLogger.convertResultFromReason(REASON_UNKNOWN_ERROR))
+ .isEqualTo(MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNKNOWN_ERROR);
+ assertThat(MediaRouterMetricLogger.convertResultFromReason(REASON_REJECTED))
+ .isEqualTo(MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_REJECTED);
+ assertThat(MediaRouterMetricLogger.convertResultFromReason(REASON_NETWORK_ERROR))
+ .isEqualTo(MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_NETWORK_ERROR);
+ assertThat(MediaRouterMetricLogger.convertResultFromReason(REASON_ROUTE_NOT_AVAILABLE))
+ .isEqualTo(MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_ROUTE_NOT_AVAILABLE);
+ assertThat(MediaRouterMetricLogger.convertResultFromReason(REASON_INVALID_COMMAND))
+ .isEqualTo(MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_INVALID_COMMAND);
+ assertThat(MediaRouterMetricLogger.convertResultFromReason(REASON_UNIMPLEMENTED))
+ .isEqualTo(MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNIMPLEMENTED);
+ assertThat(
+ MediaRouterMetricLogger.convertResultFromReason(
+ REASON_FAILED_TO_REROUTE_SYSTEM_MEDIA))
+ .isEqualTo(
+ MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_FAILED_TO_REROUTE_SYSTEM_MEDIA);
+ assertThat(MediaRouterMetricLogger.convertResultFromReason(-1))
+ .isEqualTo(MEDIA_ROUTER_EVENT_REPORTED__RESULT__RESULT_UNSPECIFIED);
+ }
+
+ @Test
+ public void getRequestCacheSize_returnsCorrectSize() {
+ assertThat(mLogger.getRequestCacheSize()).isEqualTo(0);
+ mLogger.addRequestInfo(
+ 123, MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_CREATE_SESSION);
+ assertThat(mLogger.getRequestCacheSize()).isEqualTo(1);
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastProcessedEventRecordTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastProcessedEventRecordTest.java
new file mode 100644
index 0000000..7d8d9a5
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastProcessedEventRecordTest.java
@@ -0,0 +1,141 @@
+/*
+ * 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.server.am;
+
+import static android.app.AppProtoEnums.BROADCAST_TYPE_FOREGROUND;
+import static android.app.AppProtoEnums.BROADCAST_TYPE_STICKY;
+import static android.os.Process.SYSTEM_UID;
+
+import static com.android.internal.util.FrameworkStatsLog.BROADCAST_PROCESSED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.eq;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.modules.utils.testing.ExtendedMockitoRule;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+@SmallTest
+public class BroadcastProcessedEventRecordTest {
+
+ private static final String ACTION = "action";
+ private static final String PROCESS_NAME = "process";
+ private static final int[] BROADCAST_TYPES =
+ new int[]{BROADCAST_TYPE_FOREGROUND, BROADCAST_TYPE_STICKY};
+
+ private BroadcastProcessedEventRecord mBroadcastProcessedEventRecord;
+
+ @Rule
+ public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(
+ this).mockStatic(FrameworkStatsLog.class).build();
+
+ @Before
+ public void setUp() {
+ mBroadcastProcessedEventRecord = createBroadcastProcessEventRecord();
+ }
+
+ @Test
+ public void addReceiverFinishDetails_withNewRecord_updatesBroadcastRecordEventTime() {
+ assertThat(mBroadcastProcessedEventRecord.getReceiverUidForTest()).isEqualTo(SYSTEM_UID);
+ assertThat(mBroadcastProcessedEventRecord.getSenderUidForTest()).isEqualTo(SYSTEM_UID);
+ assertThat(mBroadcastProcessedEventRecord.getIntentActionForTest()).isEqualTo(ACTION);
+ assertThat(mBroadcastProcessedEventRecord.getReceiverProcessNameForTest()).isEqualTo(
+ PROCESS_NAME);
+ assertThat(mBroadcastProcessedEventRecord.getBroadcastTypesForTest()).isEqualTo(
+ BROADCAST_TYPES);
+
+ mBroadcastProcessedEventRecord.addReceiverFinishTime(20);
+ verifyBroadcastProcessEventUpdateRecord(
+ /* numberOfReceivers= */ 1,
+ /* totalBroadcastFinishTimeMillis= */ 20,
+ /* maxReceiverFinishTimeMillis= */ 20);
+
+ mBroadcastProcessedEventRecord.addReceiverFinishTime(25);
+ verifyBroadcastProcessEventUpdateRecord(
+ /* numberOfReceivers= */ 2,
+ /* totalBroadcastFinishTimeMillis= */ 45,
+ /* maxReceiverFinishTimeMillis= */ 25);
+
+ mBroadcastProcessedEventRecord.addReceiverFinishTime(10);
+ verifyBroadcastProcessEventUpdateRecord(
+ /* numberOfReceivers= */ 3,
+ /* totalBroadcastFinishTimeMillis= */ 55,
+ /* maxReceiverFinishTimeMillis= */ 25);
+ }
+
+ @Test
+ public void logToStatsD_loggingSuccessful() {
+ mBroadcastProcessedEventRecord.addReceiverFinishTime(20);
+ mBroadcastProcessedEventRecord.logToStatsD();
+
+ ExtendedMockito.verify(() -> FrameworkStatsLog.write(eq(BROADCAST_PROCESSED),
+ eq(ACTION),
+ eq(SYSTEM_UID),
+ eq(SYSTEM_UID),
+ eq(1),
+ eq(PROCESS_NAME),
+ eq(20L),
+ eq(20L),
+ eq(BROADCAST_TYPES)));
+ }
+
+ @Test
+ public void logToStatsD_withTotalTimeLessThanTenMs_NoLogging() {
+ mBroadcastProcessedEventRecord.addReceiverFinishTime(8);
+ mBroadcastProcessedEventRecord.logToStatsD();
+
+ ExtendedMockito.verify(() -> FrameworkStatsLog.write(eq(BROADCAST_PROCESSED),
+ eq(ACTION),
+ eq(SYSTEM_UID),
+ eq(SYSTEM_UID),
+ eq(1),
+ eq(PROCESS_NAME),
+ eq(8L),
+ eq(8L),
+ eq(BROADCAST_TYPES)), Mockito.never());
+ }
+
+ private BroadcastProcessedEventRecord createBroadcastProcessEventRecord() {
+ return new BroadcastProcessedEventRecord()
+ .setBroadcastTypes(BROADCAST_TYPES)
+ .setIntentAction(ACTION)
+ .setReceiverProcessName(PROCESS_NAME)
+ .setReceiverUid(SYSTEM_UID)
+ .setSenderUid(SYSTEM_UID);
+ }
+
+ private void verifyBroadcastProcessEventUpdateRecord(
+ int numberOfReceivers,
+ long totalBroadcastFinishTimeMillis,
+ long maxReceiverFinishTimeMillis) {
+ assertThat(mBroadcastProcessedEventRecord.getNumberOfReceiversForTest())
+ .isEqualTo(numberOfReceivers);
+ assertThat(mBroadcastProcessedEventRecord.getTotalBroadcastFinishTimeMillisForTest())
+ .isEqualTo(totalBroadcastFinishTimeMillis);
+ assertThat(mBroadcastProcessedEventRecord.getMaxReceiverFinishTimeMillisForTest())
+ .isEqualTo(maxReceiverFinishTimeMillis);
+ }
+
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueImplTest.java
index b32ce49..6726088 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueImplTest.java
@@ -87,6 +87,7 @@
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
+import org.mockito.Mockito;
import java.io.PrintWriter;
import java.io.Writer;
@@ -1769,6 +1770,33 @@
}
@Test
+ public void testBroadcastProcessedEventRecord_broadcastDelivered_processedEventLogged()
+ throws Exception {
+ testBroadcastProcessedEventRecordLogged(
+ /* isAssumedDelivered= */ false,
+ /* isDelivered= */ true,
+ /* numberOfInvocations= */ 1);
+ }
+
+ @Test
+ public void testBroadcastProcessedEventRecord_broadcastDeliveryFailed_eventNotLogged()
+ throws Exception {
+ testBroadcastProcessedEventRecordLogged(
+ /* isAssumedDelivered= */ false,
+ /* isDelivered= */ false,
+ /* numberOfInvocations= */ 0);
+ }
+
+ @Test
+ public void testBroadcastProcessedEventRecord_broadcastAssumedDelivered_eventNotLogged()
+ throws Exception {
+ testBroadcastProcessedEventRecordLogged(
+ /* isAssumedDelivered= */ true,
+ /* isDelivered= */ true,
+ /* numberOfInvocations= */ 0);
+ }
+
+ @Test
public void testGetPreferredSchedulingGroup() throws Exception {
final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
@@ -2310,6 +2338,28 @@
assertFalse(mImpl.isProcessFreezable(greenProcess));
}
+ @SuppressWarnings("GuardedBy")
+ private void testBroadcastProcessedEventRecordLogged(
+ boolean isAssumedDelivered,
+ boolean isDelivered,
+ int numberOfInvocations) throws Exception {
+ final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
+ final BroadcastOptions optionsTimeTick = BroadcastOptions.makeBasic();
+ optionsTimeTick.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
+
+ final BroadcastRecord broadcastRecordSpy = Mockito.spy(
+ makeBroadcastRecord(timeTick, optionsTimeTick));
+ mImpl.enqueueBroadcastLocked(broadcastRecordSpy);
+
+ doReturn(isDelivered).when(broadcastRecordSpy).wasDelivered(anyInt());
+ doReturn(isAssumedDelivered).when(broadcastRecordSpy).isAssumedDelivered(anyInt());
+ waitForIdle();
+
+ verify(broadcastRecordSpy, times(numberOfInvocations)).updateBroadcastProcessedEventRecord(
+ any(), anyLong());
+ verify(broadcastRecordSpy).logBroadcastProcessedEventRecord();
+ }
+
BroadcastFilter makeRegisteredReceiver(ProcessRecord app, int priority) {
final IIntentReceiver receiver = mock(IIntentReceiver.class);
final ReceiverList receiverList = new ReceiverList(mAms, app, app.getPid(), app.info.uid,
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
index 8482fd6..3ea83de 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
@@ -30,14 +30,20 @@
import static com.android.server.am.BroadcastRecord.calculateUrgent;
import static com.android.server.am.BroadcastRecord.isReceiverEquals;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import android.app.BackgroundStartPrivileges;
import android.app.BroadcastOptions;
@@ -66,6 +72,7 @@
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.junit.MockitoJUnitRunner;
@@ -93,6 +100,9 @@
private static final String PACKAGE2 = "pkg2";
private static final String PACKAGE3 = "pkg3";
+ private static final String PROCESS1 = "process1";
+ private static final String PROCESS2 = "process2";
+
private static final int SYSTEM_UID = android.os.Process.SYSTEM_UID;
private static final int APP_UID = android.os.Process.FIRST_APPLICATION_UID;
@@ -1005,6 +1015,142 @@
createResolveInfo(PACKAGE3, getAppId(3)))));
}
+
+ @Test
+ @DisableFlags(Flags.FLAG_LOG_BROADCAST_PROCESSED_EVENT)
+ public void testUpdateBroadcastProcessedEventRecord_flagDisabled() {
+ final ResolveInfo receiver = createResolveInfo(PACKAGE1, getAppId(1));
+ final BroadcastRecord record = createBroadcastRecord(
+ new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED),
+ List.of(receiver));
+
+ record.updateBroadcastProcessedEventRecord(receiver, 10);
+
+ assertThat(record.getBroadcastProcessedRecordsForTest()).isEmpty();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_LOG_BROADCAST_PROCESSED_EVENT)
+ public void testUpdateBroadcastProcessedEventRecord_withNewReceiver_newBroadcastProcessedEventRecordCreated() {
+ final ResolveInfo receiver =
+ createResolveInfoWithProcessName(PACKAGE1, getAppId(1), PROCESS1);
+ final BroadcastRecord record = createBroadcastRecord(
+ new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED),
+ List.of(receiver));
+
+ record.updateBroadcastProcessedEventRecord(receiver, 10);
+
+ assertThat(record.getBroadcastProcessedRecordsForTest()).isNotEmpty();
+ final BroadcastProcessedEventRecord broadcastProcessedEventRecord =
+ record.getBroadcastProcessedRecordsForTest().get(
+ BroadcastRecord.getReceiverProcessName(receiver));
+
+ assertBroadcastProcessedEvent(
+ broadcastProcessedEventRecord,
+ 10001,
+ PROCESS1,
+ 1,
+ 2,
+ 10,
+ 10);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_LOG_BROADCAST_PROCESSED_EVENT)
+ public void testUpdateBroadcastProcessedEventRecord_withNewAndExistingReceiver_multipleBroadcastProcessedEventRecordCreated() {
+ final ResolveInfo receiver1 =
+ createResolveInfoWithProcessName(PACKAGE1, getAppId(1), PROCESS1);
+
+ final ResolveInfo receiver2 =
+ createResolveInfoWithProcessName(PACKAGE2, getAppId(2), PROCESS2);
+
+ final BroadcastRecord record = createBroadcastRecord(
+ new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED),
+ List.of(receiver1, receiver2));
+
+ record.updateBroadcastProcessedEventRecord(receiver1, 11);
+ record.updateBroadcastProcessedEventRecord(receiver2, 11);
+ record.updateBroadcastProcessedEventRecord(receiver1, 20);
+
+ assertThat(record.getBroadcastProcessedRecordsForTest().size()).isEqualTo(2);
+ final BroadcastProcessedEventRecord broadcastProcessedEventRecord1 =
+ record.getBroadcastProcessedRecordsForTest().get(
+ BroadcastRecord.getReceiverProcessName(receiver1));
+ final BroadcastProcessedEventRecord broadcastProcessedEventRecord2 =
+ record.getBroadcastProcessedRecordsForTest().get(
+ BroadcastRecord.getReceiverProcessName(receiver2));
+
+ assertBroadcastProcessedEvent(
+ broadcastProcessedEventRecord1,
+ 10001,
+ PROCESS1,
+ 2,
+ 1,
+ 31,
+ 20);
+ assertBroadcastProcessedEvent(
+ broadcastProcessedEventRecord2,
+ 10002,
+ PROCESS2,
+ 1,
+ 1,
+ 11,
+ 11);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_LOG_BROADCAST_PROCESSED_EVENT)
+ public void testLogBroadcastProcessedEventRecord_flagDisabled() {
+ testLogBroadcastProcessedEventRecord(0);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_LOG_BROADCAST_PROCESSED_EVENT)
+ public void testLogBroadcastProcessedEventRecord_flagEnabled_allBroadcastProcessedEventLogged() {
+ testLogBroadcastProcessedEventRecord(1);
+ }
+
+ private void testLogBroadcastProcessedEventRecord(int times) {
+ final ResolveInfo receiver = createResolveInfo(PACKAGE1, getAppId(1));
+ final BroadcastRecord record = createBroadcastRecord(
+ new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED),
+ List.of(receiver));
+
+ final BroadcastProcessedEventRecord broadcastProcessedEventRecord = Mockito.mock(
+ BroadcastProcessedEventRecord.class);
+ record.getBroadcastProcessedRecordsForTest()
+ .put("process", broadcastProcessedEventRecord);
+ record.logBroadcastProcessedEventRecord();
+ doNothing().when(broadcastProcessedEventRecord).logToStatsD();
+
+ verify(broadcastProcessedEventRecord, times(times)).logToStatsD();
+ }
+
+ private void assertBroadcastProcessedEvent(
+ BroadcastProcessedEventRecord broadcastProcessedEventRecord,
+ int receiverUid,
+ String processName,
+ int numberOfReceivers,
+ int broadcastTypeLength,
+ long totalBroadcastFinishTimeMillis,
+ long maxReceiverFinishTimeMillis) {
+ assertNotNull(broadcastProcessedEventRecord);
+ assertThat(broadcastProcessedEventRecord.getReceiverUidForTest()).isEqualTo(receiverUid);
+ assertThat(broadcastProcessedEventRecord.getSenderUidForTest()).isEqualTo(0);
+ assertThat(broadcastProcessedEventRecord.getIntentActionForTest())
+ .isEqualTo(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ assertThat(broadcastProcessedEventRecord.getReceiverProcessNameForTest())
+ .isEqualTo(processName);
+ assertThat(broadcastProcessedEventRecord.getBroadcastTypesForTest().length)
+ .isEqualTo(broadcastTypeLength);
+ assertThat(broadcastProcessedEventRecord.getNumberOfReceiversForTest())
+ .isEqualTo(numberOfReceivers);
+ assertThat(broadcastProcessedEventRecord.getTotalBroadcastFinishTimeMillisForTest())
+ .isEqualTo(totalBroadcastFinishTimeMillis);
+ assertThat(broadcastProcessedEventRecord.getMaxReceiverFinishTimeMillisForTest())
+ .isEqualTo(maxReceiverFinishTimeMillis);
+ }
+
private boolean[] calculateChangeState(List<Object> receivers) {
return BroadcastRecord.calculateChangeStateForReceivers(receivers,
LIMIT_PRIORITY_SCOPE, mPlatformCompat);
@@ -1049,6 +1195,16 @@
return createResolveInfo(PACKAGE1, getAppId(1), priority);
}
+ private static ResolveInfo createResolveInfoWithProcessName(
+ String packageName,
+ int uid,
+ String processName) {
+ final ResolveInfo resolveInfo = createResolveInfo(packageName, uid);
+ resolveInfo.activityInfo.processName = processName;
+
+ return resolveInfo;
+ }
+
private static ResolveInfo createResolveInfo(String packageName, int uid) {
return createResolveInfo(packageName, uid, 0);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 340115a..5d8f578 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -43,6 +43,7 @@
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_NONE;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@@ -107,6 +108,7 @@
import android.os.Process;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
@@ -698,7 +700,7 @@
@SuppressWarnings("GuardedBy")
@Test
- @EnableFlags(Flags.FLAG_USE_CPU_TIME_CAPABILITY)
+ @EnableFlags({Flags.FLAG_USE_CPU_TIME_CAPABILITY, Flags.FLAG_PROTOTYPE_AGGRESSIVE_FREEZING})
public void testUpdateOomAdjFreezeState_bindingFromShortFgs() {
// Setting up a started short FGS within app1.
final ServiceRecord s = ServiceRecord.newEmptyInstanceForTest(mService);
@@ -744,6 +746,44 @@
@SuppressWarnings("GuardedBy")
@Test
@EnableFlags(Flags.FLAG_USE_CPU_TIME_CAPABILITY)
+ @DisableFlags(Flags.FLAG_PROTOTYPE_AGGRESSIVE_FREEZING)
+ public void testUpdateOomAdjFreezeState_bindingFromFgs() {
+ final ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+ MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
+ mProcessStateController.setHasForegroundServices(app.mServices, true,
+ FOREGROUND_SERVICE_TYPE_SPECIAL_USE, false);
+
+ final ProcessRecord app2 = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
+ MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
+ // App with a foreground service binds to app2
+ bindService(app2, app, null, null, 0, mock(IBinder.class));
+
+ setProcessesToLru(app, app2);
+ updateOomAdj(app);
+
+ assertCpuTime(app);
+ assertCpuTime(app2);
+ }
+
+ @SuppressWarnings("GuardedBy")
+ @Test
+ @EnableFlags(Flags.FLAG_USE_CPU_TIME_CAPABILITY)
+ @DisableFlags(Flags.FLAG_PROTOTYPE_AGGRESSIVE_FREEZING)
+ public void testUpdateOomAdjFreezeState_soloFgs() {
+ final ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+ MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
+ mProcessStateController.setHasForegroundServices(app.mServices, true,
+ FOREGROUND_SERVICE_TYPE_SPECIAL_USE, false);
+
+ setProcessesToLru(app);
+ updateOomAdj(app);
+
+ assertCpuTime(app);
+ }
+
+ @SuppressWarnings("GuardedBy")
+ @Test
+ @EnableFlags(Flags.FLAG_USE_CPU_TIME_CAPABILITY)
public void testUpdateOomAdjFreezeState_receivers() {
final ProcessRecord app = makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java
index d6349fc..ab3784b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java
@@ -32,7 +32,6 @@
import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_USER_STOPPED;
import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER;
import static com.android.server.am.PendingIntentRecord.cancelReasonToString;
-import static com.android.window.flags.Flags.balClearAllowlistDuration;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -216,9 +215,7 @@
pir.getAllowlistDurationLocked(token);
assertNotNull(allowlistDurationLockedAfterClear);
assertEquals(1000, allowlistDurationLockedAfterClear.duration);
- assertEquals(balClearAllowlistDuration()
- ? TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED
- : TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
+ assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED,
allowlistDurationLocked.type);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp
index e030b3f..16adf8f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp
+++ b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp
@@ -60,4 +60,8 @@
"mts-crashrecovery",
],
min_sdk_version: "36",
+
+ // Test coverage system runs on different devices. Need to
+ // compile for all architecture.
+ compile_multilib: "both",
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceMockedTest.java
similarity index 99%
rename from services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
rename to services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceMockedTest.java
index 6ad3df1..ac11216 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceMockedTest.java
@@ -102,11 +102,12 @@
import java.io.IOException;
/**
- * Run as {@code atest FrameworksMockingServicesTests:com.android.server.pm.UserManagerServiceTest}
+ * Run as {@code atest
+ * FrameworksMockingServicesTests:com.android.server.pm.UserManagerServiceMockedTest}
*/
-public final class UserManagerServiceTest {
+public final class UserManagerServiceMockedTest {
- private static final String TAG = UserManagerServiceTest.class.getSimpleName();
+ private static final String TAG = UserManagerServiceMockedTest.class.getSimpleName();
/**
* Id for a simple user (that doesn't have profiles).
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp b/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp
index 36b064b..7c02370 100644
--- a/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp
@@ -58,6 +58,10 @@
"mts-crashrecovery",
],
min_sdk_version: "36",
+
+ // Test coverage system runs on different devices. Need to
+ // compile for all architecture.
+ compile_multilib: "both",
}
test_module_config {
diff --git a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/Android.bp b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/Android.bp
index ca5952b..e438100 100644
--- a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/Android.bp
+++ b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/Android.bp
@@ -34,7 +34,6 @@
srcs: ["**/*.java"],
platform_apis: true,
- certificate: "platform",
dxflags: ["--multi-dex"],
optimize: {
enabled: false,
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java
index 17d8882..ea83825 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java
@@ -571,6 +571,95 @@
assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isNotEqualTo(-1);
}
+ @Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void pauseButton_panelNotHovered_clickNotTriggeredWhenPaused() {
+ injectFakeMouseActionHoverMoveEvent();
+
+ // Pause autoclick and ensure the panel is not hovered.
+ AutoclickTypePanel mockAutoclickTypePanel = mock(AutoclickTypePanel.class);
+ when(mockAutoclickTypePanel.isPaused()).thenReturn(true);
+ when(mockAutoclickTypePanel.isHovered()).thenReturn(false);
+ mController.mAutoclickTypePanel = mockAutoclickTypePanel;
+
+ // Send hover move event.
+ MotionEvent hoverMove = MotionEvent.obtain(
+ /* downTime= */ 0,
+ /* eventTime= */ 100,
+ /* action= */ MotionEvent.ACTION_HOVER_MOVE,
+ /* x= */ 30f,
+ /* y= */ 0f,
+ /* metaState= */ 0);
+ hoverMove.setSource(InputDevice.SOURCE_MOUSE);
+ mController.onMotionEvent(hoverMove, hoverMove, /* policyFlags= */ 0);
+
+ // Verify click is not triggered.
+ assertThat(mController.mClickScheduler.getIsActiveForTesting()).isFalse();
+ assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isEqualTo(-1);
+ }
+
+ @Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void pauseButton_panelHovered_clickTriggeredWhenPaused() {
+ injectFakeMouseActionHoverMoveEvent();
+
+ // Pause autoclick and hover the panel.
+ AutoclickTypePanel mockAutoclickTypePanel = mock(AutoclickTypePanel.class);
+ when(mockAutoclickTypePanel.isPaused()).thenReturn(true);
+ when(mockAutoclickTypePanel.isHovered()).thenReturn(true);
+ mController.mAutoclickTypePanel = mockAutoclickTypePanel;
+
+ // Send hover move event.
+ MotionEvent hoverMove = MotionEvent.obtain(
+ /* downTime= */ 0,
+ /* eventTime= */ 100,
+ /* action= */ MotionEvent.ACTION_HOVER_MOVE,
+ /* x= */ 30f,
+ /* y= */ 0f,
+ /* metaState= */ 0);
+ hoverMove.setSource(InputDevice.SOURCE_MOUSE);
+ mController.onMotionEvent(hoverMove, hoverMove, /* policyFlags= */ 0);
+
+ // Verify click is triggered.
+ assertThat(mController.mClickScheduler.getIsActiveForTesting()).isTrue();
+ assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isNotEqualTo(-1);
+ }
+
+ @Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void pauseButton_unhoveringCancelsClickWhenPaused() {
+ injectFakeMouseActionHoverMoveEvent();
+
+ // Pause autoclick and hover the panel.
+ AutoclickTypePanel mockAutoclickTypePanel = mock(AutoclickTypePanel.class);
+ when(mockAutoclickTypePanel.isPaused()).thenReturn(true);
+ when(mockAutoclickTypePanel.isHovered()).thenReturn(true);
+ mController.mAutoclickTypePanel = mockAutoclickTypePanel;
+
+ // Send hover move event.
+ MotionEvent hoverMove = MotionEvent.obtain(
+ /* downTime= */ 0,
+ /* eventTime= */ 100,
+ /* action= */ MotionEvent.ACTION_HOVER_MOVE,
+ /* x= */ 30f,
+ /* y= */ 0f,
+ /* metaState= */ 0);
+ hoverMove.setSource(InputDevice.SOURCE_MOUSE);
+ mController.onMotionEvent(hoverMove, hoverMove, /* policyFlags= */ 0);
+
+ // Verify click is triggered.
+ assertThat(mController.mClickScheduler.getIsActiveForTesting()).isTrue();
+ assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isNotEqualTo(-1);
+
+ // Now simulate the pointer being moved outside the panel.
+ when(mockAutoclickTypePanel.isHovered()).thenReturn(false);
+ mController.clickPanelController.onHoverChange(/* hovered= */ false);
+
+ // Verify pending click is canceled.
+ assertThat(mController.mClickScheduler.getIsActiveForTesting()).isFalse();
+ assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isEqualTo(-1);
+ }
+
private void injectFakeMouseActionHoverMoveEvent() {
MotionEvent event = getFakeMotionHoverMoveEvent();
event.setSource(InputDevice.SOURCE_MOUSE);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickLinearLayoutTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickLinearLayoutTest.java
new file mode 100644
index 0000000..9e629f7c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickLinearLayoutTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.
+ */
+
+package com.android.server.accessibility.autoclick;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.view.MotionEvent;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Test cases for {@link AutoclickLinearLayout}. */
+@RunWith(AndroidTestingRunner.class)
+public class AutoclickLinearLayoutTest {
+ private boolean mHovered;
+
+ private final AutoclickLinearLayout.OnHoverChangedListener mListener =
+ new AutoclickLinearLayout.OnHoverChangedListener() {
+ @Override
+ public void onHoverChanged(boolean hovered) {
+ mHovered = hovered;
+ }
+ };
+
+ @Rule
+ public TestableContext mTestableContext =
+ new TestableContext(getInstrumentation().getContext());
+ private AutoclickLinearLayout mAutoclickLinearLayout;
+
+ @Before
+ public void setUp() {
+ mAutoclickLinearLayout = new AutoclickLinearLayout(mTestableContext);
+ }
+
+ @Test
+ public void autoclickLinearLayout_hoverChangedListener_setHovered() {
+ mHovered = false;
+ mAutoclickLinearLayout.setOnHoverChangedListener(mListener);
+ mAutoclickLinearLayout.onHoverChanged(/* hovered= */ true);
+ assertThat(mHovered).isTrue();
+ }
+
+ @Test
+ public void autoclickLinearLayout_hoverChangedListener_setNotHovered() {
+ mHovered = true;
+
+ mAutoclickLinearLayout.setOnHoverChangedListener(mListener);
+ mAutoclickLinearLayout.onHoverChanged(/* hovered= */ false);
+ assertThat(mHovered).isFalse();
+ }
+
+ @Test
+ public void autoclickLinearLayout_onInterceptHoverEvent_hovered() {
+ mAutoclickLinearLayout.setHovered(false);
+ mAutoclickLinearLayout.onInterceptHoverEvent(
+ getFakeMotionEvent(MotionEvent.ACTION_HOVER_ENTER));
+ assertThat(mAutoclickLinearLayout.isHovered()).isTrue();
+
+ mAutoclickLinearLayout.setHovered(false);
+ mAutoclickLinearLayout.onInterceptHoverEvent(
+ getFakeMotionEvent(MotionEvent.ACTION_HOVER_MOVE));
+ assertThat(mAutoclickLinearLayout.isHovered()).isTrue();
+ }
+
+ @Test
+ public void autoclickLinearLayout_onInterceptHoverEvent_hoveredExit() {
+ mAutoclickLinearLayout.setHovered(true);
+ mAutoclickLinearLayout.onInterceptHoverEvent(
+ getFakeMotionEvent(MotionEvent.ACTION_HOVER_EXIT));
+ assertThat(mAutoclickLinearLayout.isHovered()).isFalse();
+ }
+
+ private MotionEvent getFakeMotionEvent(int motionEventAction) {
+ return MotionEvent.obtain(
+ /* downTime= */ 0,
+ /* eventTime= */ 0,
+ /* action= */ motionEventAction,
+ /* x= */ 0,
+ /* y= */ 0,
+ /* metaState= */ 0);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java
index 9e12340..dd089fc 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java
@@ -78,6 +78,7 @@
private @AutoclickType int mActiveClickType = AUTOCLICK_TYPE_LEFT_CLICK;
private boolean mPaused;
+ private boolean mHovered;
private final ClickPanelControllerInterface clickPanelController =
new ClickPanelControllerInterface() {
@@ -90,6 +91,11 @@
public void toggleAutoclickPause(boolean paused) {
mPaused = paused;
}
+
+ @Override
+ public void onHoverChange(boolean hovered) {
+ mHovered = hovered;
+ }
};
@Before
@@ -195,11 +201,11 @@
public void moveToNextCorner_positionButton_rotatesThroughAllPositions() {
// Define all positions in sequence
int[][] expectedPositions = {
- {0, Gravity.END | Gravity.BOTTOM, /*x=*/ 15, /*y=*/ 90},
- {1, Gravity.START | Gravity.BOTTOM, /*x=*/ 15, /*y=*/ 90},
- {2, Gravity.START | Gravity.TOP, /*x=*/ 15, /*y=*/ 30},
- {3, Gravity.END | Gravity.TOP, /*x=*/ 15, /*y=*/ 30},
- {0, Gravity.END | Gravity.BOTTOM, /*x=*/ 15, /*y=*/ 90}
+ {CORNER_BOTTOM_RIGHT, Gravity.END | Gravity.BOTTOM, /*x=*/ 15, /*y=*/ 90},
+ {CORNER_BOTTOM_LEFT, Gravity.START | Gravity.BOTTOM, /*x=*/ 15, /*y=*/ 90},
+ {CORNER_TOP_LEFT, Gravity.START | Gravity.TOP, /*x=*/ 15, /*y=*/ 30},
+ {CORNER_TOP_RIGHT, Gravity.END | Gravity.TOP, /*x=*/ 15, /*y=*/ 30},
+ {CORNER_BOTTOM_RIGHT, Gravity.END | Gravity.BOTTOM, /*x=*/ 15, /*y=*/ 90}
};
// Check initial position
@@ -264,7 +270,7 @@
int screenWidth = mTestableContext.getResources().getDisplayMetrics().widthPixels;
// Verify initial corner is bottom-right.
- assertThat(mAutoclickTypePanel.getCurrentCornerIndexForTesting())
+ assertThat(mAutoclickTypePanel.getCurrentCornerForTesting())
.isEqualTo(CORNER_BOTTOM_RIGHT);
dispatchDragSequence(contentView,
@@ -273,7 +279,7 @@
// Verify snapping to the right.
assertThat(params.gravity).isEqualTo(Gravity.END | Gravity.TOP);
- assertThat(mAutoclickTypePanel.getCurrentCornerIndexForTesting())
+ assertThat(mAutoclickTypePanel.getCurrentCornerForTesting())
.isEqualTo(CORNER_TOP_RIGHT);
}
@@ -287,7 +293,7 @@
int screenWidth = mTestableContext.getResources().getDisplayMetrics().widthPixels;
// Verify initial corner is bottom-right.
- assertThat(mAutoclickTypePanel.getCurrentCornerIndexForTesting())
+ assertThat(mAutoclickTypePanel.getCurrentCornerForTesting())
.isEqualTo(CORNER_BOTTOM_RIGHT);
dispatchDragSequence(contentView,
@@ -296,7 +302,7 @@
// Verify snapping to the left.
assertThat(params.gravity).isEqualTo(Gravity.START | Gravity.TOP);
- assertThat(mAutoclickTypePanel.getCurrentCornerIndexForTesting())
+ assertThat(mAutoclickTypePanel.getCurrentCornerForTesting())
.isEqualTo(CORNER_BOTTOM_LEFT);
}
@@ -313,7 +319,7 @@
// Verify panel is positioned at default bottom-right corner.
WindowManager.LayoutParams params = panel.getLayoutParamsForTesting();
- assertThat(panel.getCurrentCornerIndexForTesting()).isEqualTo(CORNER_BOTTOM_RIGHT);
+ assertThat(panel.getCurrentCornerForTesting()).isEqualTo(CORNER_BOTTOM_RIGHT);
assertThat(params.gravity).isEqualTo(Gravity.END | Gravity.BOTTOM);
assertThat(params.x).isEqualTo(15); // Default edge margin.
assertThat(params.y).isEqualTo(90); // Default bottom offset.
@@ -347,7 +353,7 @@
assertThat(params.gravity).isEqualTo(Gravity.START | Gravity.TOP);
assertThat(params.x).isEqualTo(15);
assertThat(params.y).isEqualTo(30);
- assertThat(mAutoclickTypePanel.getCurrentCornerIndexForTesting()).isEqualTo(
+ assertThat(mAutoclickTypePanel.getCurrentCornerForTesting()).isEqualTo(
CORNER_TOP_LEFT);
}
@@ -386,7 +392,7 @@
assertThat(params.gravity).isEqualTo(Gravity.START | Gravity.TOP);
assertThat(params.x).isEqualTo(15); // PANEL_EDGE_MARGIN
assertThat(params.y).isEqualTo(panelLocation[1] + 10);
- assertThat(mAutoclickTypePanel.getCurrentCornerIndexForTesting()).isEqualTo(
+ assertThat(mAutoclickTypePanel.getCurrentCornerForTesting()).isEqualTo(
CORNER_BOTTOM_LEFT);
}
@@ -412,6 +418,33 @@
upEvent.recycle();
}
+ @Test
+ public void hovered_IsHovered() {
+ AutoclickLinearLayout mContext = mAutoclickTypePanel.getContentViewForTesting();
+
+ assertThat(mAutoclickTypePanel.isHovered()).isFalse();
+ mContext.onInterceptHoverEvent(getFakeMotionHoverMoveEvent());
+ assertThat(mAutoclickTypePanel.isHovered()).isTrue();
+ }
+
+ @Test
+ public void hovered_OnHoverChange_isHovered() {
+ AutoclickLinearLayout mContext = mAutoclickTypePanel.getContentViewForTesting();
+
+ mHovered = false;
+ mContext.onHoverChanged(true);
+ assertThat(mHovered).isTrue();
+ }
+
+ @Test
+ public void hovered_OnHoverChange_isNotHovered() {
+ AutoclickLinearLayout mContext = mAutoclickTypePanel.getContentViewForTesting();
+
+ mHovered = true;
+ mContext.onHoverChanged(false);
+ assertThat(mHovered).isFalse();
+ }
+
private void verifyButtonHasSelectedStyle(@NonNull LinearLayout button) {
GradientDrawable gradientDrawable = (GradientDrawable) button.getBackground();
assertThat(gradientDrawable.getColor().getDefaultColor())
@@ -420,10 +453,20 @@
private void verifyPanelPosition(int[] expectedPosition) {
WindowManager.LayoutParams params = mAutoclickTypePanel.getLayoutParamsForTesting();
- assertThat(mAutoclickTypePanel.getCurrentCornerIndexForTesting()).isEqualTo(
+ assertThat(mAutoclickTypePanel.getCurrentCornerForTesting()).isEqualTo(
expectedPosition[0]);
assertThat(params.gravity).isEqualTo(expectedPosition[1]);
assertThat(params.x).isEqualTo(expectedPosition[2]);
assertThat(params.y).isEqualTo(expectedPosition[3]);
}
+
+ private MotionEvent getFakeMotionHoverMoveEvent() {
+ return MotionEvent.obtain(
+ /* downTime= */ 0,
+ /* eventTime= */ 0,
+ /* action= */ MotionEvent.ACTION_HOVER_MOVE,
+ /* x= */ 0,
+ /* y= */ 0,
+ /* metaState= */ 0);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index ac27a97..9ec99c6 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -30,7 +30,10 @@
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -248,6 +251,40 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_WITH_POINTER_MOTION_FILTER)
+ public void testRegister_RegistersPointerMotionFilter() {
+ register(DISPLAY_0);
+
+ verify(mMockInputManager).registerAccessibilityPointerMotionFilter(
+ any(InputManagerInternal.AccessibilityPointerMotionFilter.class));
+
+ // If a filter is already registered, adding a display won't invoke another filter
+ // registration.
+ clearInvocations(mMockInputManager);
+ register(DISPLAY_1);
+ register(INVALID_DISPLAY);
+
+ verify(mMockInputManager, times(0)).registerAccessibilityPointerMotionFilter(
+ any(InputManagerInternal.AccessibilityPointerMotionFilter.class));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_WITH_POINTER_MOTION_FILTER)
+ public void testUnregister_UnregistersPointerMotionFilter() {
+ register(DISPLAY_0);
+ register(DISPLAY_1);
+ clearInvocations(mMockInputManager);
+
+ mFullScreenMagnificationController.unregister(DISPLAY_1);
+ // There's still an active display. Don't unregister yet.
+ verify(mMockInputManager, times(0)).registerAccessibilityPointerMotionFilter(
+ nullable(InputManagerInternal.AccessibilityPointerMotionFilter.class));
+
+ mFullScreenMagnificationController.unregister(DISPLAY_0);
+ verify(mMockInputManager, times(1)).registerAccessibilityPointerMotionFilter(isNull());
+ }
+
+ @Test
public void testInitialState_noMagnificationAndMagnificationRegionReadFromWindowManager() {
for (int i = 0; i < DISPLAY_COUNT; i++) {
initialState_noMagnificationAndMagnificationRegionReadFromWindowManager(i);
@@ -699,6 +736,63 @@
}
@Test
+ public void testSetOffset_whileMagnifying_offsetsMove() {
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ setOffset_whileMagnifying_offsetsMove(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void setOffset_whileMagnifying_offsetsMove(int displayId) {
+ register(displayId);
+ PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
+ for (final float scale : new float[]{2.0f, 2.5f, 3.0f}) {
+ assertTrue(mFullScreenMagnificationController
+ .setScaleAndCenter(displayId, scale, startCenter.x, startCenter.y, true, false,
+ SERVICE_ID_1));
+ mMessageCapturingHandler.sendAllMessages();
+
+ for (final PointF center : new PointF[]{
+ INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER,
+ INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER}) {
+ Mockito.clearInvocations(mMockWindowManager);
+ PointF newOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, center, scale);
+ mFullScreenMagnificationController.setOffset(displayId, newOffsets.x, newOffsets.y,
+ SERVICE_ID_1);
+ mMessageCapturingHandler.sendAllMessages();
+
+ MagnificationSpec expectedSpec = getMagnificationSpec(scale, newOffsets);
+ verify(mMockWindowManager)
+ .setMagnificationSpec(eq(displayId), argThat(closeTo(expectedSpec)));
+ assertEquals(center.x, mFullScreenMagnificationController.getCenterX(displayId),
+ 0.0);
+ assertEquals(center.y, mFullScreenMagnificationController.getCenterY(displayId),
+ 0.0);
+ verify(mMockValueAnimator, times(0)).start();
+ }
+ }
+ }
+
+ @Test
+ public void testSetOffset_whileNotMagnifying_hasNoEffect() {
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ setOffset_whileNotMagnifying_hasNoEffect(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void setOffset_whileNotMagnifying_hasNoEffect(int displayId) {
+ register(displayId);
+ Mockito.reset(mMockWindowManager);
+ MagnificationSpec startSpec = getCurrentMagnificationSpec(displayId);
+ mFullScreenMagnificationController.setOffset(displayId, 100, 100, SERVICE_ID_1);
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(startSpec));
+ mFullScreenMagnificationController.setOffset(displayId, 200, 200, SERVICE_ID_1);
+ assertThat(getCurrentMagnificationSpec(displayId), closeTo(startSpec));
+ verifyNoMoreInteractions(mMockWindowManager);
+ }
+
+ @Test
@RequiresFlagsEnabled(Flags.FLAG_FULLSCREEN_FLING_GESTURE)
public void testStartFling_whileMagnifying_flings() throws InterruptedException {
for (int i = 0; i < DISPLAY_COUNT; i++) {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index 5c126d1..4ea5fcf 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -1419,6 +1419,12 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_WITH_POINTER_MOTION_FILTER)
+ public void testMouseMoveEventsDoNotMoveMagnifierViewport() {
+ runMoveEventsDoNotMoveMagnifierViewport(InputDevice.SOURCE_MOUSE);
+ }
+
+ @Test
public void testStylusMoveEventsDoNotMoveMagnifierViewport() {
runMoveEventsDoNotMoveMagnifierViewport(InputDevice.SOURCE_STYLUS);
}
@@ -1467,11 +1473,28 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_WITH_POINTER_MOTION_FILTER)
+ public void testMouseHoverMoveEventsDoNotMoveMagnifierViewport() {
+ // Note that this means mouse hover shouldn't be handled here.
+ // FullScreenMagnificationPointerMotionEventFilter handles mouse input events.
+ runHoverMoveEventsDoNotMoveMagnifierViewport(InputDevice.SOURCE_MOUSE);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_WITH_POINTER_MOTION_FILTER)
+ public void testStylusHoverMoveEventsDoNotMoveMagnifierViewport() {
+ // TODO(b/398984690): We will revisit the behavior.
+ runHoverMoveEventsDoNotMoveMagnifierViewport(InputDevice.SOURCE_STYLUS);
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_WITH_POINTER_MOTION_FILTER)
public void testMouseHoverMoveEventsMoveMagnifierViewport() {
runHoverMovesViewportTest(InputDevice.SOURCE_MOUSE);
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_WITH_POINTER_MOTION_FILTER)
public void testStylusHoverMoveEventsMoveMagnifierViewport() {
runHoverMovesViewportTest(InputDevice.SOURCE_STYLUS);
}
@@ -1497,6 +1520,7 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_WITH_POINTER_MOTION_FILTER)
public void testMouseMoveEventsMoveMagnifierViewport() {
final EventCaptor eventCaptor = new EventCaptor();
mMgh.setNext(eventCaptor);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationPointerMotionEventFilterTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationPointerMotionEventFilterTest.java
new file mode 100644
index 0000000..a8315d4
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationPointerMotionEventFilterTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.server.accessibility.magnification;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class FullScreenMagnificationPointerMotionEventFilterTest {
+ @Mock
+ private FullScreenMagnificationController mMockFullScreenMagnificationController;
+
+ private FullScreenMagnificationPointerMotionEventFilter mFilter;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mFilter = new FullScreenMagnificationPointerMotionEventFilter(
+ mMockFullScreenMagnificationController);
+ }
+
+ @Test
+ public void inactiveDisplay_doNothing() {
+ when(mMockFullScreenMagnificationController.isActivated(anyInt())).thenReturn(false);
+
+ float[] delta = new float[]{1.f, 2.f};
+ float[] result = mFilter.filterPointerMotionEvent(delta[0], delta[1], 3.0f, 4.0f, 0);
+ assertThat(result).isEqualTo(delta);
+ }
+
+ @Test
+ public void testContinuousMove() {
+ when(mMockFullScreenMagnificationController.isActivated(anyInt())).thenReturn(true);
+ when(mMockFullScreenMagnificationController.getScale(anyInt())).thenReturn(3.f);
+
+ float[] delta = new float[]{5.f, 10.f};
+ float[] result = mFilter.filterPointerMotionEvent(delta[0], delta[1], 20.f, 30.f, 0);
+ assertThat(result).isEqualTo(delta);
+ // At the first cursor move, it goes to (20, 30) + (5, 10) = (25, 40). The scale is 3.0.
+ // The expected offset is (-25 * (3-1), -40 * (3-1)) = (-50, -80).
+ verify(mMockFullScreenMagnificationController)
+ .setOffset(eq(0), eq(-50.f), eq(-80.f), anyInt());
+
+ float[] delta2 = new float[]{10.f, 5.f};
+ float[] result2 = mFilter.filterPointerMotionEvent(delta2[0], delta2[1], 25.f, 40.f, 0);
+ assertThat(result2).isEqualTo(delta2);
+ // At the second cursor move, it goes to (25, 40) + (10, 5) = (35, 40). The scale is 3.0.
+ // The expected offset is (-35 * (3-1), -45 * (3-1)) = (-70, -90).
+ verify(mMockFullScreenMagnificationController)
+ .setOffset(eq(0), eq(-70.f), eq(-90.f), anyInt());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpSqlPersistenceTest.java b/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpSqlPersistenceTest.java
index 8471307..01fee7f 100644
--- a/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpSqlPersistenceTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpSqlPersistenceTest.java
@@ -226,9 +226,9 @@
mDiscreteRegistry.recordDiscreteAccess(event2);
}
- /** This clears in-memory cache and push records into the database. */
private void flushDiscreteOpsToDatabase() {
- mDiscreteRegistry.writeAndClearOldAccessHistory();
+ // This clears in-memory cache and push records from cache into the database.
+ mDiscreteRegistry.shutdown();
}
/**
diff --git a/services/tests/servicestests/src/com/android/server/appop/DiscreteOpsMigrationAndRollbackTest.java b/services/tests/servicestests/src/com/android/server/appop/DiscreteOpsMigrationAndRollbackTest.java
index 21cc3ba..8eea1c7 100644
--- a/services/tests/servicestests/src/com/android/server/appop/DiscreteOpsMigrationAndRollbackTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/DiscreteOpsMigrationAndRollbackTest.java
@@ -106,7 +106,8 @@
opEvent.getDuration(), opEvent.getAttributionFlags(),
(int) opEvent.getChainId(), DiscreteOpsRegistry.ACCESS_TYPE_NOTE_OP);
}
- sqlRegistry.writeAndClearOldAccessHistory();
+ // flush records from cache to the database.
+ sqlRegistry.shutdown();
assertThat(sqlRegistry.getAllDiscreteOps().size()).isEqualTo(RECORD_COUNT);
assertThat(sqlRegistry.getLargestAttributionChainId()).isEqualTo(RECORD_COUNT);
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
index 50cfa75..57e9cf4 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
@@ -95,8 +95,8 @@
private static final String TEST_LOCALES_XML_TAG = "locales";
private static final int DEFAULT_USER_ID = 0;
private static final int WORK_PROFILE_USER_ID = 10;
- private static final int DEFAULT_UID = Binder.getCallingUid() + 100;
- private static final int WORK_PROFILE_UID = Binder.getCallingUid() + 1000100;
+ private static final int DEFAULT_UID = 100;
+ private static final int WORK_PROFILE_UID = 1000100;
private static final long DEFAULT_CREATION_TIME_MILLIS = 1000;
private static final Duration RETENTION_PERIOD = Duration.ofDays(3);
private static final LocaleList DEFAULT_LOCALES =
diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java
index 1de864c..565a9b6 100644
--- a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java
@@ -17,6 +17,7 @@
package com.android.server.location.contexthub;
import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.timeout;
@@ -42,12 +43,10 @@
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.util.Log;
+
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
-import java.util.Collections;
-import java.util.List;
-
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -57,6 +56,9 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.util.Collections;
+import java.util.List;
+
@RunWith(AndroidJUnit4.class)
@Presubmit
public class ContextHubEndpointTest {
@@ -73,6 +75,12 @@
private static final String TARGET_ENDPOINT_NAME = "Example target endpoint";
private static final int TARGET_ENDPOINT_ID = 1;
+ private static final int SAMPLE_MESSAGE_TYPE = 1234;
+ private static final HubMessage SAMPLE_MESSAGE =
+ new HubMessage.Builder(SAMPLE_MESSAGE_TYPE, new byte[] {1, 2, 3, 4, 5})
+ .setResponseRequired(true)
+ .build();
+
private ContextHubClientManager mClientManager;
private ContextHubEndpointManager mEndpointManager;
private HubInfoRegistry mHubInfoRegistry;
@@ -229,23 +237,34 @@
assertThat(mTransactionManager.numReliableMessageTransactionPending()).isEqualTo(0);
}
+ @Test
+ public void testDuplicateMessageRejected() throws RemoteException {
+ IContextHubEndpoint endpoint = registerExampleEndpoint();
+ int sessionId = openTestSession(endpoint);
+
+ mEndpointManager.onMessageReceived(sessionId, SAMPLE_MESSAGE);
+ ArgumentCaptor<HubMessage> messageCaptor = ArgumentCaptor.forClass(HubMessage.class);
+ verify(mMockCallback).onMessageReceived(eq(sessionId), messageCaptor.capture());
+ assertThat(messageCaptor.getValue()).isEqualTo(SAMPLE_MESSAGE);
+
+ // Send a duplicate message and confirm it can be rejected
+ mEndpointManager.onMessageReceived(sessionId, SAMPLE_MESSAGE);
+ ArgumentCaptor<MessageDeliveryStatus> statusCaptor =
+ ArgumentCaptor.forClass(MessageDeliveryStatus.class);
+ verify(mMockEndpointCommunications)
+ .sendMessageDeliveryStatusToEndpoint(eq(sessionId), statusCaptor.capture());
+ assertThat(statusCaptor.getValue().messageSequenceNumber)
+ .isEqualTo(SAMPLE_MESSAGE.getMessageSequenceNumber());
+ assertThat(statusCaptor.getValue().errorCode).isEqualTo(ErrorCode.TRANSIENT_ERROR);
+
+ unregisterExampleEndpoint(endpoint);
+ }
+
/** A helper method to create a session and validates reliable message sending. */
private void testMessageTransactionInternal(
IContextHubEndpoint endpoint, boolean deliverMessageStatus) throws RemoteException {
- HubEndpointInfo targetInfo =
- new HubEndpointInfo(
- TARGET_ENDPOINT_NAME,
- TARGET_ENDPOINT_ID,
- ENDPOINT_PACKAGE_NAME,
- Collections.emptyList());
- int sessionId = endpoint.openSession(targetInfo, /* serviceDescriptor= */ null);
- mEndpointManager.onEndpointSessionOpenComplete(sessionId);
+ int sessionId = openTestSession(endpoint);
- final int messageType = 1234;
- HubMessage message =
- new HubMessage.Builder(messageType, new byte[] {1, 2, 3, 4, 5})
- .setResponseRequired(true)
- .build();
IContextHubTransactionCallback callback =
new IContextHubTransactionCallback.Stub() {
@Override
@@ -258,13 +277,13 @@
Log.i(TAG, "Received onTransactionComplete callback, result=" + result);
}
};
- endpoint.sendMessage(sessionId, message, callback);
+ endpoint.sendMessage(sessionId, SAMPLE_MESSAGE, callback);
ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
verify(mMockEndpointCommunications, timeout(1000))
.sendMessageToEndpoint(eq(sessionId), messageCaptor.capture());
Message halMessage = messageCaptor.getValue();
- assertThat(halMessage.type).isEqualTo(message.getMessageType());
- assertThat(halMessage.content).isEqualTo(message.getMessageBody());
+ assertThat(halMessage.type).isEqualTo(SAMPLE_MESSAGE.getMessageType());
+ assertThat(halMessage.content).isEqualTo(SAMPLE_MESSAGE.getMessageBody());
assertThat(mTransactionManager.numReliableMessageTransactionPending()).isEqualTo(1);
if (deliverMessageStatus) {
@@ -308,4 +327,16 @@
.isEqualTo(expectedInfo.getIdentifier().getHub());
assertThat(mEndpointManager.getNumRegisteredClients()).isEqualTo(0);
}
+
+ private int openTestSession(IContextHubEndpoint endpoint) throws RemoteException {
+ HubEndpointInfo targetInfo =
+ new HubEndpointInfo(
+ TARGET_ENDPOINT_NAME,
+ TARGET_ENDPOINT_ID,
+ ENDPOINT_PACKAGE_NAME,
+ Collections.emptyList());
+ int sessionId = endpoint.openSession(targetInfo, /* serviceDescriptor= */ null);
+ mEndpointManager.onEndpointSessionOpenComplete(sessionId);
+ return sessionId;
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/OWNERS b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/OWNERS
index bb487fb..c8b8e48 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/OWNERS
@@ -1,4 +1,3 @@
aseemk@google.com
-bozhu@google.com
dementyev@google.com
robertberry@google.com
diff --git a/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
index da02278..fbf9065 100644
--- a/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
+++ b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
@@ -39,6 +39,7 @@
import com.android.server.LocalServices
import com.android.server.SystemService.TargetUser
import com.android.server.pm.UserManagerInternal
+import com.android.server.supervision.SupervisionService.ACTION_CONFIRM_SUPERVISION_CREDENTIALS
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
@@ -247,6 +248,13 @@
assertThat(userData.supervisionLockScreenOptions).isNull()
}
+ @Test
+ fun createConfirmSupervisionCredentialsIntent() {
+ val intent = checkNotNull(service.createConfirmSupervisionCredentialsIntent())
+ assertThat(intent.action).isEqualTo(ACTION_CONFIRM_SUPERVISION_CREDENTIALS)
+ assertThat(intent.getPackage()).isEqualTo("com.android.settings")
+ }
+
private val systemSupervisionPackage: String
get() = context.getResources().getString(R.string.config_systemSupervision)
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
index 4a977be..4eac1d1 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
@@ -66,6 +66,8 @@
import android.annotation.SuppressLint;
import android.app.Notification;
import android.app.NotificationChannel;
+import android.app.PendingIntent;
+import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.drawable.AdaptiveIconDrawable;
@@ -620,6 +622,7 @@
}
@Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
public void testAutoGrouped_singleOngoing_removeOngoingChild() {
final String pkg = "package";
@@ -639,7 +642,7 @@
}
// remove ongoing
- mGroupHelper.onNotificationRemoved(notifications.get(0));
+ mGroupHelper.onNotificationRemoved(notifications.get(0), new ArrayList<>(), false);
// Summary is no longer ongoing
verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), anyString(),
@@ -776,7 +779,7 @@
}
// remove ongoing
- mGroupHelper.onNotificationRemoved(notifications.get(1));
+ mGroupHelper.onNotificationRemoved(notifications.get(1), new ArrayList<>(), false);
// Summary is still ongoing
verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), anyString(),
@@ -936,7 +939,7 @@
mGroupHelper.onNotificationPosted(r, false);
}
- mGroupHelper.onNotificationRemoved(notifications.get(0));
+ mGroupHelper.onNotificationRemoved(notifications.get(0), new ArrayList<>(), false);
// Summary should still be autocancelable
verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), anyString(),
@@ -944,6 +947,7 @@
}
@Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
@DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
public void testDropToZeroRemoveGroup_disableFlag() {
final String pkg = "package";
@@ -963,19 +967,20 @@
Mockito.reset(mCallback);
for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
- mGroupHelper.onNotificationRemoved(posted.remove(0));
+ mGroupHelper.onNotificationRemoved(posted.remove(0), new ArrayList<>(), false);
}
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
Mockito.reset(mCallback);
- mGroupHelper.onNotificationRemoved(posted.remove(0));
+ mGroupHelper.onNotificationRemoved(posted.remove(0), new ArrayList<>(), false);
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), anyString(), anyString());
}
@Test
- @EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+ android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST})
public void testDropToZeroRemoveGroup() {
final String pkg = "package";
ArrayList<NotificationRecord> posted = new ArrayList<>();
@@ -994,13 +999,13 @@
Mockito.reset(mCallback);
for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
- mGroupHelper.onNotificationRemoved(posted.remove(0));
+ mGroupHelper.onNotificationRemoved(posted.remove(0), new ArrayList<>(), false);
}
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
Mockito.reset(mCallback);
- mGroupHelper.onNotificationRemoved(posted.remove(0));
+ mGroupHelper.onNotificationRemoved(posted.remove(0), new ArrayList<>(), false);
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), anyString(), anyString());
}
@@ -1072,6 +1077,7 @@
}
@Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
@DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
public void testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled_alwaysGroup() {
final String pkg = "package";
@@ -1091,7 +1097,7 @@
Mockito.reset(mCallback);
for (int i = posted.size() - 2; i >= 0; i--) {
- mGroupHelper.onNotificationRemoved(posted.remove(i));
+ mGroupHelper.onNotificationRemoved(posted.remove(i), new ArrayList<>(), false);
}
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
@@ -1114,7 +1120,8 @@
}
@Test
- @EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+ android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST})
public void testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled() {
final String pkg = "package";
ArrayList<NotificationRecord> posted = new ArrayList<>();
@@ -1134,7 +1141,7 @@
Mockito.reset(mCallback);
for (int i = posted.size() - 2; i >= 0; i--) {
- mGroupHelper.onNotificationRemoved(posted.remove(i));
+ mGroupHelper.onNotificationRemoved(posted.remove(i), new ArrayList<>(), false);
}
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
@@ -1481,7 +1488,7 @@
}
// Remove last notification (the only one with different icon and color)
- mGroupHelper.onNotificationRemoved(notifications.get(lastIdx));
+ mGroupHelper.onNotificationRemoved(notifications.get(lastIdx), new ArrayList<>(), false);
// Summary should be updated to the common icon and color
verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), anyString(),
@@ -2162,7 +2169,7 @@
NotificationRecord r = getNotificationRecord(pkg, i, String.valueOf(i),
UserHandle.SYSTEM, "testGrp " + i, false);
r.setOverrideGroupKey(expectedGroupKey);
- mGroupHelper.onNotificationRemoved(r, notificationList);
+ mGroupHelper.onNotificationRemoved(r, notificationList, false);
}
// Check that the autogroup summary is removed
verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), eq(pkg),
@@ -2206,7 +2213,7 @@
Mockito.reset(mCallback);
for (NotificationRecord r: childrenToRemove) {
notificationList.remove(r);
- mGroupHelper.onNotificationRemoved(r, notificationList);
+ mGroupHelper.onNotificationRemoved(r, notificationList, false);
}
// Only call onGroupedNotificationRemovedWithDelay with the summary notification
mGroupHelper.onGroupedNotificationRemovedWithDelay(summary, notificationList,
@@ -2270,7 +2277,7 @@
Mockito.reset(mCallback);
for (NotificationRecord r: childrenToRemove) {
notificationList.remove(r);
- mGroupHelper.onNotificationRemoved(r, notificationList);
+ mGroupHelper.onNotificationRemoved(r, notificationList, false);
}
// Only call onGroupedNotificationRemovedWithDelay with the summary notification
mGroupHelper.onGroupedNotificationRemovedWithDelay(summary, notificationList,
@@ -2327,7 +2334,7 @@
for (NotificationRecord r: notificationList) {
if (r.getGroupKey().contains(groupToRemove)) {
r.isCanceled = true;
- mGroupHelper.onNotificationRemoved(r, notificationList);
+ mGroupHelper.onNotificationRemoved(r, notificationList, false);
}
}
// Only call onGroupedNotificationRemovedWithDelay with the summary notification
@@ -2452,7 +2459,7 @@
true);
assertThat(GroupHelper.getSection(notifToInvalidate)).isNull();
notificationList.remove(notifToInvalidate);
- mGroupHelper.onNotificationRemoved(notifToInvalidate, notificationList);
+ mGroupHelper.onNotificationRemoved(notifToInvalidate, notificationList, false);
// Check that the autogroup was updated
verify(mCallback, never()).removeAutoGroup(anyString());
@@ -4023,7 +4030,7 @@
//Cancel child 0 => remove cached summary
childToRemove.isCanceled = true;
notificationListAfterGrouping.remove(childToRemove);
- mGroupHelper.onNotificationRemoved(childToRemove, notificationListAfterGrouping);
+ mGroupHelper.onNotificationRemoved(childToRemove, notificationListAfterGrouping, false);
CachedSummary cachedSummary = mGroupHelper.findCanceledSummary(pkg, String.valueOf(id), id,
UserHandle.SYSTEM.getIdentifier());
assertThat(cachedSummary).isNull();
@@ -4399,4 +4406,41 @@
"PeopleSection(priority)");
}
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void onNotificationRemoved_lastChildOfCachedSummary_firesCachedSummaryDeleteIntent() {
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ final String pkg = "package";
+ NotificationRecord onlyChildOfFirstGroup = null;
+ PendingIntent deleteIntentofFirstSummary = PendingIntent.getActivity(mContext, 1,
+ new Intent(), PendingIntent.FLAG_IMMUTABLE);
+ // Post singleton groups, above forced group limit, so they are force grouped
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ NotificationRecord summary = getNotificationRecord(pkg, i,
+ String.valueOf(i), UserHandle.SYSTEM, "testGrp " + i, true);
+ notificationList.add(summary);
+ NotificationRecord child = getNotificationRecord(pkg, i + 42,
+ String.valueOf(i + 42), UserHandle.SYSTEM, "testGrp " + i, false);
+ notificationList.add(child);
+ summaryByGroup.put(summary.getGroupKey(), summary);
+ if (i == 0) {
+ onlyChildOfFirstGroup = child;
+ summary.getNotification().deleteIntent = deleteIntentofFirstSummary;
+ }
+ mGroupHelper.onNotificationPostedWithDelay(child, notificationList, summaryByGroup);
+ summary.isCanceled = true; // simulate removing the app summary
+ mGroupHelper.onNotificationPostedWithDelay(summary, notificationList, summaryByGroup);
+ }
+ // Sparse group autogrouping would've removed the summary.
+ notificationList.remove(0);
+
+ // Now remove the only child of the first (force-grouped, cuz sparse) group.
+ notificationList.remove(0);
+ onlyChildOfFirstGroup.isCanceled = true;
+ mGroupHelper.onNotificationRemoved(onlyChildOfFirstGroup, notificationList, true);
+
+ verify(mCallback).sendAppProvidedSummaryDeleteIntent(eq(pkg),
+ eq(deleteIntentofFirstSummary));
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 37ab541..dd5c601 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -349,6 +349,7 @@
import com.google.android.collect.Lists;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
@@ -2788,8 +2789,8 @@
nr1.getSbn().getId(), nr1.getSbn().getUserId());
waitForIdle();
- verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr0), any());
- verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr1), any());
+ verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr0), any(), eq(false));
+ verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr1), any(), eq(false));
// GroupHelper would send 'remove summary' event
mService.clearAutogroupSummaryLocked(nr1.getUserId(), nr1.getSbn().getPackageName(),
@@ -3155,8 +3156,8 @@
waitForIdle();
// Check that onGroupedNotificationRemovedWithDelay was called only once
- verify(mGroupHelper, times(1)).onNotificationRemoved(eq(r1), any());
- verify(mGroupHelper, times(1)).onNotificationRemoved(eq(r2), any());
+ verify(mGroupHelper, times(1)).onNotificationRemoved(eq(r1), any(), eq(false));
+ verify(mGroupHelper, times(1)).onNotificationRemoved(eq(r2), any(), eq(false));
verify(mGroupHelper, times(1)).onGroupedNotificationRemovedWithDelay(eq(summary), any(),
any());
}
@@ -3201,9 +3202,9 @@
waitForIdle();
// Check that onGroupedNotificationRemovedWithDelay was never called: summary was canceled
- verify(mGroupHelper, times(1)).onNotificationRemoved(eq(r1), any());
- verify(mGroupHelper, times(1)).onNotificationRemoved(eq(r2), any());
- verify(mGroupHelper, times(1)).onNotificationRemoved(eq(summary), any());
+ verify(mGroupHelper, times(1)).onNotificationRemoved(eq(r1), any(), eq(false));
+ verify(mGroupHelper, times(1)).onNotificationRemoved(eq(r2), any(), eq(false));
+ verify(mGroupHelper, times(1)).onNotificationRemoved(eq(summary), any(), eq(false));
verify(mGroupHelper, never()).onGroupedNotificationRemovedWithDelay(any(), any(), any());
}
@@ -5210,41 +5211,6 @@
}
@Test
- public void
- updateNotificationChannelFromPrivilegedListener_oldSoundNoUriPerm_newSoundHasUriPerm()
- throws Exception {
- mService.setPreferencesHelper(mPreferencesHelper);
- when(mCompanionMgr.getAssociations(mPkg, mUserId))
- .thenReturn(singletonList(mock(AssociationInfo.class)));
- when(mPreferencesHelper.getNotificationChannel(eq(mPkg), anyInt(),
- eq(mTestNotificationChannel.getId()), anyBoolean()))
- .thenReturn(mTestNotificationChannel);
-
- // Missing Uri permissions for the old channel sound
- final Uri oldSoundUri = Settings.System.DEFAULT_NOTIFICATION_URI;
- doThrow(new SecurityException("no access")).when(mUgmInternal)
- .checkGrantUriPermission(eq(Process.myUid()), any(), eq(oldSoundUri),
- anyInt(), eq(Process.myUserHandle().getIdentifier()));
-
- // Has Uri permissions for the old channel sound
- final Uri newSoundUri = Uri.parse("content://media/test/sound/uri");
- final NotificationChannel updatedNotificationChannel = new NotificationChannel(
- TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT);
- updatedNotificationChannel.setSound(newSoundUri,
- updatedNotificationChannel.getAudioAttributes());
-
- mBinderService.updateNotificationChannelFromPrivilegedListener(
- null, mPkg, Process.myUserHandle(), updatedNotificationChannel);
-
- verify(mPreferencesHelper, times(1)).updateNotificationChannel(
- anyString(), anyInt(), any(), anyBoolean(), anyInt(), anyBoolean());
-
- verify(mListeners, never()).notifyNotificationChannelChanged(eq(mPkg),
- eq(Process.myUserHandle()), eq(mTestNotificationChannel),
- eq(NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED));
- }
-
- @Test
public void testGetNotificationChannelFromPrivilegedListener_cdm_success() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
when(mCompanionMgr.getAssociations(mPkg, mUserId))
@@ -14122,9 +14088,10 @@
waitForIdle();
// Check that child notifications are also removed
- verify(mGroupHelper, times(1)).onNotificationRemoved(eq(aggregateSummary), any());
- verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr0), any());
- verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr1), any());
+ verify(mGroupHelper, times(1)).onNotificationRemoved(eq(aggregateSummary), any(),
+ eq(false));
+ verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr0), any(), eq(false));
+ verify(mGroupHelper, times(1)).onNotificationRemoved(eq(nr1), any(), eq(false));
// Make sure the summary was removed and not re-posted
assertThat(mService.getNotificationRecordCount()).isEqualTo(0);
@@ -18659,4 +18626,35 @@
}
}
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void clearAll_fromUser_willSendDeleteIntentForCachedSummaries() throws Exception {
+ NotificationRecord n = generateNotificationRecord(
+ mTestNotificationChannel, 1, "group", true);
+ mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag",
+ n.getSbn().getId(), n.getSbn().getNotification(), n.getSbn().getUserId());
+ waitForIdle();
+ n = Iterables.getOnlyElement(mService.mNotificationList);
+
+ mService.mNotificationDelegate.onClearAll(mUid, Binder.getCallingPid(), n.getUserId());
+ waitForIdle();
+
+ verify(mGroupHelper).onNotificationRemoved(eq(n), any(), eq(true));
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+ public void cancel_fromApp_willNotSendDeleteIntentForCachedSummaries() throws Exception {
+ NotificationRecord n = generateNotificationRecord(
+ mTestNotificationChannel, 1, "group", true);
+ mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag",
+ n.getSbn().getId(), n.getSbn().getNotification(), n.getSbn().getUserId());
+ waitForIdle();
+ n = Iterables.getOnlyElement(mService.mNotificationList);
+
+ mBinderService.cancelAllNotifications(mPkg, mUserId);
+ waitForIdle();
+
+ verify(mGroupHelper).onNotificationRemoved(eq(n), any(), eq(false));
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
index 4391152..e9cf036 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
@@ -1009,6 +1009,65 @@
}
@Test
+ @EnableFlags(Flags.FLAG_NM_SUMMARIZATION)
+ public void testSummarization_null() {
+ StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
+ true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
+ false /* lights */, false /* defaultLights */, groupId /* group */);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+ assertThat(record.getSummarization()).isNull();
+
+ Bundle signals = new Bundle();
+ signals.putCharSequence(Adjustment.KEY_SUMMARIZATION, null);
+ record.addAdjustment(new Adjustment(mPkg, record.getKey(), signals, null, sbn.getUserId()));
+
+ record.applyAdjustments();
+
+ assertThat(record.getSummarization()).isNull();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NM_SUMMARIZATION)
+ public void testSummarization_charSequence() {
+ CharSequence summary = "hello";
+ StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
+ true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
+ false /* lights */, false /* defaultLights */, groupId /* group */);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+ assertThat(record.getSummarization()).isNull();
+
+ Bundle signals = new Bundle();
+ signals.putCharSequence(Adjustment.KEY_SUMMARIZATION, summary);
+ record.addAdjustment(new Adjustment(mPkg, record.getKey(), signals, null, sbn.getUserId()));
+
+ record.applyAdjustments();
+
+ assertThat(record.getSummarization()).isEqualTo(summary.toString());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NM_SUMMARIZATION)
+ public void testSummarization_string() {
+ String summary = "hello";
+ StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
+ true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
+ false /* lights */, false /* defaultLights */, groupId /* group */);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+ assertThat(record.getSummarization()).isNull();
+
+ Bundle signals = new Bundle();
+ signals.putCharSequence(Adjustment.KEY_SUMMARIZATION, summary);
+ record.addAdjustment(new Adjustment(mPkg, record.getKey(), signals, null, sbn.getUserId()));
+
+ record.applyAdjustments();
+
+ assertThat(record.getSummarization()).isEqualTo(summary);
+ }
+
+ @Test
public void testSensitiveContent() {
StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 5dea44d..a02f628 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -46,13 +46,11 @@
import static android.app.NotificationManager.IMPORTANCE_NONE;
import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE;
-import static android.content.ContentResolver.SCHEME_ANDROID_RESOURCE;
-import static android.content.ContentResolver.SCHEME_CONTENT;
-import static android.content.ContentResolver.SCHEME_FILE;
import static android.media.AudioAttributes.CONTENT_TYPE_SONIFICATION;
import static android.media.AudioAttributes.USAGE_NOTIFICATION;
import static android.os.UserHandle.USER_ALL;
import static android.os.UserHandle.USER_SYSTEM;
+
import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
import static android.service.notification.Adjustment.TYPE_CONTENT_RECOMMENDATION;
import static android.service.notification.Adjustment.TYPE_NEWS;
@@ -66,6 +64,7 @@
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED;
+import static com.android.server.notification.Flags.FLAG_ALL_NOTIFS_NEED_TTL;
import static com.android.server.notification.Flags.FLAG_PERSIST_INCOMPLETE_RESTORE_DATA;
import static com.android.server.notification.NotificationChannelLogger.NotificationChannelEvent.NOTIFICATION_CHANNEL_UPDATED_BY_USER;
import static com.android.server.notification.PreferencesHelper.DEFAULT_BUBBLE_PREFERENCE;
@@ -92,7 +91,6 @@
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -385,10 +383,10 @@
mHelper = new TestPreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- mUgmInternal, false, mClock);
+ false, mClock);
mXmlHelper = new TestPreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- mUgmInternal, false, mClock);
+ false, mClock);
resetZenModeHelper();
mAudioAttributes = new AudioAttributes.Builder()
@@ -803,7 +801,7 @@
public void testReadXml_oldXml_migrates() throws Exception {
mXmlHelper = new TestPreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- mUgmInternal, /* showReviewPermissionsNotification= */ true, mClock);
+ /* showReviewPermissionsNotification= */ true, mClock);
String xml = "<ranking version=\"2\">\n"
+ "<package name=\"" + PKG_N_MR1 + "\" uid=\"" + UID_N_MR1
@@ -939,7 +937,7 @@
public void testReadXml_newXml_noMigration_showPermissionNotification() throws Exception {
mXmlHelper = new TestPreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- mUgmInternal, /* showReviewPermissionsNotification= */ true, mClock);
+ /* showReviewPermissionsNotification= */ true, mClock);
String xml = "<ranking version=\"3\">\n"
+ "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
@@ -998,7 +996,7 @@
public void testReadXml_newXml_permissionNotificationOff() throws Exception {
mHelper = new TestPreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- mUgmInternal, /* showReviewPermissionsNotification= */ false, mClock);
+ /* showReviewPermissionsNotification= */ false, mClock);
String xml = "<ranking version=\"3\">\n"
+ "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
@@ -1057,7 +1055,7 @@
public void testReadXml_newXml_noMigration_noPermissionNotification() throws Exception {
mHelper = new TestPreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- mUgmInternal, /* showReviewPermissionsNotification= */ true, mClock);
+ /* showReviewPermissionsNotification= */ true, mClock);
String xml = "<ranking version=\"4\">\n"
+ "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
@@ -1219,7 +1217,8 @@
ByteArrayOutputStream baos = writeXmlAndPurge(
PKG_N_MR1, UID_N_MR1, false, USER_SYSTEM);
- String expected = "<ranking version=\"4\">\n"
+ String expected = "<ranking version=\"4\" "
+ + "last_bubbles_version_upgrade=\"" + Build.VERSION.SDK_INT + "\">\n"
+ "<package name=\"com.example.o\" show_badge=\"true\" "
+ "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
+ "sent_valid_msg=\"false\" user_demote_msg_app=\"false\" sent_valid_bubble"
@@ -1303,7 +1302,8 @@
ByteArrayOutputStream baos = writeXmlAndPurge(
PKG_N_MR1, UID_N_MR1, true, USER_SYSTEM);
- String expected = "<ranking version=\"4\">\n"
+ String expected = "<ranking version=\"4\" "
+ + "last_bubbles_version_upgrade=\"" + Build.VERSION.SDK_INT + "\">\n"
// Importance 0 because off in permissionhelper
+ "<package name=\"com.example.o\" importance=\"0\" show_badge=\"true\" "
+ "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
@@ -1389,7 +1389,8 @@
ByteArrayOutputStream baos = writeXmlAndPurge(
PKG_N_MR1, UID_N_MR1, true, USER_SYSTEM);
- String expected = "<ranking version=\"4\">\n"
+ String expected = "<ranking version=\"4\" "
+ + "last_bubbles_version_upgrade=\"" + Build.VERSION.SDK_INT + "\">\n"
// Importance 0 because off in permissionhelper
+ "<package name=\"com.example.o\" importance=\"0\" show_badge=\"true\" "
+ "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
@@ -1440,7 +1441,8 @@
ByteArrayOutputStream baos = writeXmlAndPurge(
PKG_N_MR1, UID_N_MR1, true, USER_SYSTEM);
- String expected = "<ranking version=\"4\">\n"
+ String expected = "<ranking version=\"4\" "
+ + "last_bubbles_version_upgrade=\"" + Build.VERSION.SDK_INT + "\">\n"
// Packages that exist solely in permissionhelper
+ "<package name=\"" + PKG_P + "\" importance=\"3\" />\n"
+ "<package name=\"" + PKG_O + "\" importance=\"0\" />\n"
@@ -1651,7 +1653,7 @@
// simulate load after reboot
mXmlHelper = new TestPreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- mUgmInternal, false, mClock);
+ false, mClock);
loadByteArrayXml(baos.toByteArray(), false, USER_ALL);
// Trigger 2nd restore pass
@@ -1706,7 +1708,7 @@
// simulate load after reboot
mXmlHelper = new TestPreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- mUgmInternal, false, mClock);
+ false, mClock);
loadByteArrayXml(xml.getBytes(), false, USER_ALL);
// Trigger 2nd restore pass
@@ -1784,10 +1786,10 @@
mHelper = new TestPreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- mUgmInternal, false, mClock);
+ false, mClock);
mXmlHelper = new TestPreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- mUgmInternal, false, mClock);
+ false, mClock);
NotificationChannel channel =
new NotificationChannel("id", "name", IMPORTANCE_LOW);
@@ -3259,61 +3261,6 @@
}
@Test
- public void testCreateChannel_noSoundUriPermission_contentSchemeVerified() {
- final Uri sound = Uri.parse(SCHEME_CONTENT + "://media/test/sound/uri");
-
- doThrow(new SecurityException("no access")).when(mUgmInternal)
- .checkGrantUriPermission(eq(UID_N_MR1), any(), eq(sound),
- anyInt(), eq(UserHandle.getUserId(UID_N_MR1)));
-
- final NotificationChannel channel = new NotificationChannel("id2", "name2",
- NotificationManager.IMPORTANCE_DEFAULT);
- channel.setSound(sound, mAudioAttributes);
-
- assertThrows(SecurityException.class,
- () -> mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel,
- true, false, UID_N_MR1, false));
- assertThat(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel.getId(), true))
- .isNull();
- }
-
- @Test
- public void testCreateChannel_noSoundUriPermission_fileSchemaIgnored() {
- final Uri sound = Uri.parse(SCHEME_FILE + "://path/sound");
-
- doThrow(new SecurityException("no access")).when(mUgmInternal)
- .checkGrantUriPermission(eq(UID_N_MR1), any(), any(),
- anyInt(), eq(UserHandle.getUserId(UID_N_MR1)));
-
- final NotificationChannel channel = new NotificationChannel("id2", "name2",
- NotificationManager.IMPORTANCE_DEFAULT);
- channel.setSound(sound, mAudioAttributes);
-
- mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false, UID_N_MR1,
- false);
- assertThat(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel.getId(), true)
- .getSound()).isEqualTo(sound);
- }
-
- @Test
- public void testCreateChannel_noSoundUriPermission_resourceSchemaIgnored() {
- final Uri sound = Uri.parse(SCHEME_ANDROID_RESOURCE + "://resId/sound");
-
- doThrow(new SecurityException("no access")).when(mUgmInternal)
- .checkGrantUriPermission(eq(UID_N_MR1), any(), any(),
- anyInt(), eq(UserHandle.getUserId(UID_N_MR1)));
-
- final NotificationChannel channel = new NotificationChannel("id2", "name2",
- NotificationManager.IMPORTANCE_DEFAULT);
- channel.setSound(sound, mAudioAttributes);
-
- mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false, UID_N_MR1,
- false);
- assertThat(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel.getId(), true)
- .getSound()).isEqualTo(sound);
- }
-
- @Test
public void testPermanentlyDeleteChannels() throws Exception {
NotificationChannel channel1 =
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
@@ -4474,7 +4421,7 @@
}
@Test
- public void testBubblePreference_upgradeWithSAWPermission() throws Exception {
+ public void testBubblePreference_noLastVersionWithSAWPermission() throws Exception {
when(mAppOpsManager.noteOpNoThrow(eq(OP_SYSTEM_ALERT_WINDOW), anyInt(),
anyString(), eq(null), anyString())).thenReturn(MODE_ALLOWED);
@@ -4495,6 +4442,51 @@
}
@Test
+ public void testBubblePreference_differentLastVersionWithSAWPermission() throws Exception {
+ when(mAppOpsManager.noteOpNoThrow(eq(OP_SYSTEM_ALERT_WINDOW), anyInt(),
+ anyString(), eq(null), anyString())).thenReturn(MODE_ALLOWED);
+ final String xml = "<ranking version=\"4\" last_bubbles_version_upgrade=\"34\">\n"
+ + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\">\n"
+ + "<channel id=\"someId\" name=\"hi\""
+ + " importance=\"3\"/>"
+ + "</package>"
+ + "</ranking>";
+ TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())),
+ null);
+ parser.nextTag();
+ mHelper.readXml(parser, false, UserHandle.USER_ALL);
+
+ assertEquals(BUBBLE_PREFERENCE_ALL, mHelper.getBubblePreference(PKG_O, UID_O));
+ assertEquals(0, mHelper.getAppLockedFields(PKG_O, UID_O));
+ }
+
+ @Test
+ public void testBubblePreference_sameLastVersionWithSAWPermission() throws Exception {
+ when(mAppOpsManager.noteOpNoThrow(eq(OP_SYSTEM_ALERT_WINDOW), anyInt(),
+ anyString(), eq(null), anyString())).thenReturn(MODE_ALLOWED);
+
+ final String xml = "<ranking version=\"4\" "
+ + "last_bubbles_version_upgrade=\"" + Build.VERSION.SDK_INT + "\">\n"
+ + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\">\n"
+ + "<channel id=\"someId\" name=\"hi\""
+ + " importance=\"3\"/>"
+ + "</package>"
+ + "</ranking>";
+ TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())),
+ null);
+ parser.nextTag();
+ mHelper.readXml(parser, false, UserHandle.USER_ALL);
+
+ assertEquals(DEFAULT_BUBBLE_PREFERENCE, mHelper.getBubblePreference(PKG_O, UID_O));
+ assertEquals(0, mHelper.getAppLockedFields(PKG_O, UID_O));
+ // Version was the same SAW check should not have happened
+ verify(mAppOpsManager, never()).noteOpNoThrow(eq(OP_SYSTEM_ALERT_WINDOW), anyInt(),
+ anyString(), eq(null), anyString());
+ }
+
+ @Test
public void testBubblePreference_upgradeWithSAWThenUserOverride() throws Exception {
when(mAppOpsManager.noteOpNoThrow(eq(OP_SYSTEM_ALERT_WINDOW), anyInt(),
anyString(), eq(null), anyString())).thenReturn(MODE_ALLOWED);
@@ -7037,10 +7029,9 @@
ZenModeHelper zenHelper, PermissionHelper permHelper, PermissionManager permManager,
NotificationChannelLogger notificationChannelLogger,
AppOpsManager appOpsManager, ManagedServices.UserProfiles userProfiles,
- UriGrantsManagerInternal ugmInternal,
boolean showReviewPermissionsNotification, Clock clock) {
super(context, pm, rankingHandler, zenHelper, permHelper, permManager,
- notificationChannelLogger, appOpsManager, userProfiles, ugmInternal,
+ notificationChannelLogger, appOpsManager, userProfiles,
showReviewPermissionsNotification, clock);
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 51891ef..5377102 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -203,6 +203,7 @@
import com.google.protobuf.InvalidProtocolBufferException;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -2478,6 +2479,8 @@
}
@Test
+ @Ignore("TODO: b/398023814 - disabled due to taking a long time; restore when we have a "
+ + "better approach to not timing out")
public void testAddAutomaticZenRule_claimedSystemOwner() {
// Make sure anything that claims to have a "system" owner but not actually part of the
// system package still gets limited on number of rules
diff --git a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
index 6d8a487..3ca0197 100644
--- a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
@@ -150,8 +150,6 @@
{"Meta + Left arrow -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_DPAD_LEFT},
KeyGestureEvent.KEY_GESTURE_TYPE_BACK, KeyEvent.KEYCODE_DPAD_LEFT,
META_ON},
- {"Meta + Del -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_DEL},
- KeyGestureEvent.KEY_GESTURE_TYPE_BACK, KeyEvent.KEYCODE_DEL, META_ON},
{"APP_SWITCH key -> Open App switcher", new int[]{KeyEvent.KEYCODE_APP_SWITCH},
KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH,
KeyEvent.KEYCODE_APP_SWITCH, 0},
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 9d4fe9e..8992c2e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -32,6 +32,7 @@
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_ALLOWLISTED;
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER;
+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.RESIZE_MODE_UNRESIZEABLE;
@@ -133,6 +134,7 @@
import android.os.PersistableBundle;
import android.os.Process;
import android.os.RemoteException;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.provider.DeviceConfig;
@@ -722,6 +724,7 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @DisableFlags(Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT)
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testOrientation_allowFixedOrientationForCameraCompatInFreeformWindowing() {
final ActivityRecord activity = setupDisplayAndActivityForCameraCompat(
@@ -736,6 +739,8 @@
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @DisableFlags(Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT)
public void testOrientation_dontAllowFixedOrientationForCameraCompatFreeformIfNotEnabled() {
final ActivityRecord activity = setupDisplayAndActivityForCameraCompat(
/* isCameraRunning= */ true, WINDOWING_MODE_FREEFORM);
@@ -749,6 +754,22 @@
}
@Test
+ @EnableFlags({Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING,
+ Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT})
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_SIMULATE_REQUESTED_ORIENTATION})
+ public void testOrientation_dontAllowFixedOrientationForCameraCompatFreeformIfOptedOut() {
+ final ActivityRecord activity = setupDisplayAndActivityForCameraCompat(
+ /* isCameraRunning= */ true, WINDOWING_MODE_FREEFORM);
+
+ // Task in landscape.
+ assertEquals(ORIENTATION_LANDSCAPE, activity.getTask().getConfiguration().orientation);
+ // Activity is not letterboxed.
+ assertEquals(ORIENTATION_LANDSCAPE, activity.getConfiguration().orientation);
+ assertFalse(activity.mAppCompatController.getAspectRatioPolicy()
+ .isLetterboxedForFixedOrientationAndAspectRatio());
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testOrientation_noFixedOrientationForCameraCompatFreeformIfCameraNotRunning() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
index d016e735..8fe0855 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
@@ -251,10 +251,6 @@
doReturn(mTaskStack.top()).when(mActivityStack.top()).getOrganizedTask();
}
- void setIsInLetterboxAnimation(boolean inAnimation) {
- doReturn(inAnimation).when(mActivityStack.top()).isInLetterboxAnimation();
- }
-
void setTopTaskInMultiWindowMode(boolean inMultiWindowMode) {
doReturn(inMultiWindowMode).when(mTaskStack.top()).inMultiWindowMode();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxPolicyTest.java
index d38f3b0..7bcf4ea 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxPolicyTest.java
@@ -66,7 +66,6 @@
runTestScenario((robot) -> {
robot.configureWindowStateWithTaskBar(/* hasTaskBarInsetsRoundedCorners */ true);
robot.activity().createActivityWithComponent();
- robot.setTopActivityInLetterboxAnimation(/* inLetterboxAnimation */ false);
robot.activity().setTopActivityVisible(/* isVisible */ true);
robot.setIsLetterboxedForFixedOrientationAndAspectRatio(/* inLetterbox */ true);
robot.conf().setLetterboxActivityCornersRounded(/* rounded */ true);
@@ -87,7 +86,6 @@
runTestScenario((robot) -> {
robot.configureWindowStateWithTaskBar(/* hasTaskBarInsetsRoundedCorners */ false);
robot.activity().createActivityWithComponent();
- robot.setTopActivityInLetterboxAnimation(/* inLetterboxAnimation */ false);
robot.activity().setTopActivityVisible(/* isVisible */ true);
robot.setIsLetterboxedForFixedOrientationAndAspectRatio(/* inLetterbox */ true);
robot.conf().setLetterboxActivityCornersRounded(/* rounded */ true);
@@ -109,7 +107,6 @@
runTestScenario((robot) -> {
robot.configureWindowStateWithTaskBar(/* hasTaskBarInsetsRoundedCorners */ true);
robot.activity().createActivityWithComponent();
- robot.setTopActivityInLetterboxAnimation(/* inLetterboxAnimation */ false);
robot.activity().setTopActivityVisible(/* isVisible */ true);
robot.setIsLetterboxedForFixedOrientationAndAspectRatio(/* inLetterbox */ true);
robot.conf().setLetterboxActivityCornersRounded(/* rounded */ true);
@@ -131,7 +128,6 @@
runTestScenario((robot) -> {
robot.configureWindowStateWithTaskBar(/* hasTaskBarInsetsRoundedCorners */ true);
robot.activity().createActivityWithComponent();
- robot.setTopActivityInLetterboxAnimation(/* inLetterboxAnimation */ false);
robot.activity().setTopActivityVisible(/* isVisible */ true);
robot.setIsLetterboxedForFixedOrientationAndAspectRatio(/* inLetterbox */ true);
robot.conf().setLetterboxActivityCornersRounded(/* rounded */ true);
@@ -157,7 +153,6 @@
robot.conf().setLetterboxActivityCornersRadius(-1);
robot.configureWindowState();
robot.activity().createActivityWithComponent();
- robot.setTopActivityInLetterboxAnimation(/* inLetterboxAnimation */ false);
robot.activity().setTopActivityVisible(/* isVisible */ true);
robot.setIsLetterboxedForFixedOrientationAndAspectRatio(/* inLetterbox */ true);
robot.conf().setLetterboxActivityCornersRounded(/* rounded */ true);
@@ -184,25 +179,17 @@
robot.conf().setLetterboxActivityCornersRadius(15);
robot.configureWindowState();
robot.activity().createActivityWithComponent();
- robot.setTopActivityInLetterboxAnimation(/* inLetterboxAnimation */ false);
robot.activity().setTopActivityVisible(/* isVisible */ true);
robot.setIsLetterboxedForFixedOrientationAndAspectRatio(/* inLetterbox */ true);
robot.conf().setLetterboxActivityCornersRounded(/* rounded */ true);
robot.resources().configureGetDimensionPixelSize(R.dimen.taskbar_frame_height, 20);
robot.setInvCompatState(/* scale */ 0.5f);
- robot.setTopActivityInLetterboxAnimation(/* inLetterboxAnimation */ true);
- robot.checkWindowStateRoundedCornersRadius(/* expected */ 7);
-
- robot.setTopActivityInLetterboxAnimation(/* inLetterboxAnimation */ false);
robot.checkWindowStateRoundedCornersRadius(/* expected */ 7);
robot.activity().setTopActivityVisibleRequested(/* isVisibleRequested */ false);
robot.activity().setTopActivityVisible(/* isVisible */ false);
robot.checkWindowStateRoundedCornersRadius(/* expected */ 0);
-
- robot.setTopActivityInLetterboxAnimation(/* inLetterboxAnimation */ true);
- robot.checkWindowStateRoundedCornersRadius(/* expected */ 7);
});
}
@@ -212,7 +199,6 @@
robot.conf().setLetterboxActivityCornersRadius(15);
robot.configureWindowState();
robot.activity().createActivityWithComponent();
- robot.setTopActivityInLetterboxAnimation(/* inLetterboxAnimation */ false);
robot.activity().setTopActivityVisible(/* isVisible */ true);
robot.setIsLetterboxedForFixedOrientationAndAspectRatio(/* inLetterbox */ true);
robot.conf().setLetterboxActivityCornersRounded(/* rounded */ true);
@@ -236,7 +222,6 @@
robot.conf().setLetterboxActivityCornersRadius(15);
robot.configureWindowState();
robot.activity().createActivityWithComponent();
- robot.setTopActivityInLetterboxAnimation(/* inLetterboxAnimation */ false);
robot.activity().setTopActivityVisible(/* isVisible */ true);
robot.setIsLetterboxedForFixedOrientationAndAspectRatio(/* inLetterbox */ true);
robot.conf().setLetterboxActivityCornersRounded(/* rounded */ true);
@@ -260,7 +245,6 @@
robot.conf().setLetterboxActivityCornersRadius(15);
robot.configureWindowState();
robot.activity().createActivityWithComponent();
- robot.setTopActivityInLetterboxAnimation(/* inLetterboxAnimation */ false);
robot.activity().setTopActivityVisible(/* isVisible */ true);
robot.setIsLetterboxedForFixedOrientationAndAspectRatio(/* inLetterbox */ true);
robot.conf().setLetterboxActivityCornersRounded(/* rounded */ true);
@@ -362,10 +346,6 @@
mWindowState.mInvGlobalScale = scale;
}
- void setTopActivityInLetterboxAnimation(boolean inLetterboxAnimation) {
- doReturn(inLetterboxAnimation).when(activity().top()).isInLetterboxAnimation();
- }
-
void setTopActivityTransparentPolicyRunning(boolean running) {
doReturn(running).when(getTransparentPolicy()).isRunning();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxUtilsTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxUtilsTest.java
index a4a63d2..ac707d2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxUtilsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxUtilsTest.java
@@ -63,25 +63,10 @@
}
@Test
- public void positionIsFromTaskWhenLetterboxAnimationIsRunning() {
+ public void positionIsFromActivity() {
runTestScenario((robot) -> {
robot.activity().createActivityWithComponent();
robot.setTopActivityLetterboxPolicyRunning(true);
- robot.activity().setIsInLetterboxAnimation(true);
- robot.activity().configureTaskBounds(
- new Rect(/* left */ 100, /* top */ 200, /* right */ 300, /* bottom */ 400));
- robot.getLetterboxPosition();
-
- robot.assertPosition(/* x */ 100, /* y */ 200);
- });
- }
-
- @Test
- public void positionIsFromActivityWhenLetterboxAnimationIsNotRunning() {
- runTestScenario((robot) -> {
- robot.activity().createActivityWithComponent();
- robot.setTopActivityLetterboxPolicyRunning(true);
- robot.activity().setIsInLetterboxAnimation(false);
robot.activity().configureTopActivityBounds(
new Rect(/* left */ 200, /* top */ 400, /* right */ 300, /* bottom */ 400));
robot.getLetterboxPosition();
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index e081971..bdee3c3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -225,10 +225,10 @@
CrossActivityTestCase testCase = createTopTaskWithTwoActivities();
IOnBackInvokedCallback callback = withSystemCallback(testCase.task);
testCase.windowFront.mAttrs.windowAnimations = 0x10;
- spyOn(mDisplayContent.mAppTransition.mTransitionAnimation);
- doReturn(0xffff00AB).when(mDisplayContent.mAppTransition.mTransitionAnimation)
+ spyOn(mDisplayContent.mTransitionAnimation);
+ doReturn(0xffff00AB).when(mDisplayContent.mTransitionAnimation)
.getAnimationResId(any(), anyInt(), anyInt());
- doReturn(0xffff00CD).when(mDisplayContent.mAppTransition.mTransitionAnimation)
+ doReturn(0xffff00CD).when(mDisplayContent.mTransitionAnimation)
.getDefaultAnimationResId(anyInt(), anyInt());
BackNavigationInfo backNavigationInfo = startBackNavigation();
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java
index c934c55..e3a8776 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java
@@ -486,7 +486,7 @@
// setup state
when(mCallerApp.areBackgroundActivityStartsAllowed(anyInt(), any())).thenReturn(
- new BalVerdict(BAL_ALLOW_FOREGROUND, false, "allowed"));
+ new BalVerdict(BAL_ALLOW_FOREGROUND, "allowed"));
when(mService.getBalAppSwitchesState()).thenReturn(APP_SWITCH_ALLOW);
// prepare call
@@ -523,7 +523,7 @@
mCallerApp);
when(mService.getBalAppSwitchesState()).thenReturn(APP_SWITCH_ALLOW);
when(mCallerApp.areBackgroundActivityStartsAllowed(anyInt(), any())).thenReturn(
- new BalVerdict(BAL_ALLOW_FOREGROUND, false, "allowed"));
+ new BalVerdict(BAL_ALLOW_FOREGROUND, "allowed"));
// prepare call
PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
@@ -572,7 +572,7 @@
when(mCallerApp.areBackgroundActivityStartsAllowed(anyInt(), any())).thenReturn(
BalVerdict.BLOCK);
when(otherProcess.areBackgroundActivityStartsAllowed(anyInt(), any())).thenReturn(
- new BalVerdict(BAL_ALLOW_FOREGROUND, false, "allowed"));
+ new BalVerdict(BAL_ALLOW_FOREGROUND, "allowed"));
// prepare call
PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerLogTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerLogTests.java
index 99e730a..cd5f391 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerLogTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerLogTests.java
@@ -92,7 +92,7 @@
@Test
public void intent_visible_noLog() {
useIntent();
- BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, "visible");
+ BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "visible");
mState.setResultForCaller(finalVerdict);
mState.setResultForRealCaller(BalVerdict.BLOCK);
assertThat(mController.shouldLogStats(finalVerdict, mState)).isFalse();
@@ -101,7 +101,7 @@
@Test
public void intent_saw_log() {
useIntent();
- BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, false, "SAW");
+ BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, "SAW");
mState.setResultForCaller(finalVerdict);
mState.setResultForRealCaller(BalVerdict.BLOCK);
assertThat(mController.shouldLogStats(finalVerdict, mState)).isTrue();
@@ -111,7 +111,7 @@
@Test
public void pendingIntent_callerOnly_saw_log() {
usePendingIntent();
- BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, false, "SAW");
+ BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, "SAW");
mState.setResultForCaller(finalVerdict);
mState.setResultForRealCaller(BalVerdict.BLOCK);
assertThat(mController.shouldLogStats(finalVerdict, mState)).isTrue();
@@ -121,7 +121,7 @@
@Test
public void pendingIntent_realCallerOnly_saw_log() {
usePendingIntent();
- BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, false, "SAW")
+ BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, "SAW")
.setBasedOnRealCaller();
mState.setResultForCaller(BalVerdict.BLOCK);
mState.setResultForRealCaller(finalVerdict);
@@ -131,7 +131,7 @@
@Test
public void intent_shouldLogIntentActivity() {
- BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, false, "SAW");
+ BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, "SAW");
useIntent(APP1_UID);
assertThat(mController.shouldLogIntentActivity(finalVerdict, mState)).isFalse();
useIntent(SYSTEM_UID);
@@ -140,7 +140,7 @@
@Test
public void pendingIntent_shouldLogIntentActivityForCaller() {
- BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, false, "SAW");
+ BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, "SAW");
usePendingIntent(APP1_UID, APP2_UID);
assertThat(mController.shouldLogIntentActivity(finalVerdict, mState)).isFalse();
usePendingIntent(SYSTEM_UID, SYSTEM_UID);
@@ -153,7 +153,7 @@
@Test
public void pendingIntent_shouldLogIntentActivityForRealCaller() {
- BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, false,
+ BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION,
"SAW").setBasedOnRealCaller();
usePendingIntent(APP1_UID, APP2_UID);
assertThat(mController.shouldLogIntentActivity(finalVerdict, mState)).isFalse();
@@ -168,7 +168,7 @@
@Test
public void pendingIntent_realCallerOnly_visible_noLog() {
usePendingIntent();
- BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false,
+ BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
"visible").setBasedOnRealCaller();
mState.setResultForCaller(BalVerdict.BLOCK);
mState.setResultForRealCaller(finalVerdict);
@@ -178,7 +178,7 @@
@Test
public void pendingIntent_callerOnly_visible_noLog() {
usePendingIntent();
- BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, "visible");
+ BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "visible");
mState.setResultForCaller(finalVerdict);
mState.setResultForRealCaller(BalVerdict.BLOCK);
assertThat(mController.shouldLogStats(finalVerdict, mState)).isTrue();
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
index 51706d7..fe9a6e7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
@@ -305,7 +305,7 @@
@Test
public void testRegularActivityStart_allowedByCaller_isAllowed() {
// setup state
- BalVerdict callerVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false,
+ BalVerdict callerVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
"CallerIsVisible");
mController.setCallerVerdict(callerVerdict);
mController.setRealCallerVerdict(BalVerdict.BLOCK);
@@ -340,7 +340,7 @@
@Test
public void testRegularActivityStart_allowedByRealCaller_isAllowed() {
// setup state
- BalVerdict realCallerVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false,
+ BalVerdict realCallerVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
"RealCallerIsVisible");
mController.setCallerVerdict(BalVerdict.BLOCK);
mController.setRealCallerVerdict(realCallerVerdict);
@@ -373,9 +373,9 @@
public void testRegularActivityStart_allowedByCallerAndRealCaller_returnsCallerVerdict() {
// setup state
BalVerdict callerVerdict =
- new BalVerdict(BAL_ALLOW_PERMISSION, false, "CallerHasPermission");
+ new BalVerdict(BAL_ALLOW_PERMISSION, "CallerHasPermission");
BalVerdict realCallerVerdict =
- new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, "RealCallerIsVisible");
+ new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "RealCallerIsVisible");
mController.setCallerVerdict(callerVerdict);
mController.setRealCallerVerdict(realCallerVerdict);
@@ -411,9 +411,9 @@
public void testPendingIntent_allowedByCallerAndRealCallerButOptOut_isBlocked() {
// setup state
BalVerdict callerVerdict =
- new BalVerdict(BAL_ALLOW_PERMISSION, false, "CallerhasPermission");
+ new BalVerdict(BAL_ALLOW_PERMISSION, "CallerhasPermission");
BalVerdict realCallerVerdict =
- new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, "RealCallerIsVisible");
+ new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "RealCallerIsVisible");
mController.setCallerVerdict(callerVerdict);
mController.setRealCallerVerdict(realCallerVerdict);
@@ -452,7 +452,7 @@
public void testPendingIntent_allowedByCallerAndOptIn_isAllowed() {
// setup state
BalVerdict callerVerdict =
- new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, "CallerIsVisible");
+ new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "CallerIsVisible");
mController.setCallerVerdict(callerVerdict);
mController.setRealCallerVerdict(BalVerdict.BLOCK);
@@ -489,7 +489,7 @@
public void testPendingIntent_allowedByRealCallerAndOptIn_isAllowed() {
// setup state
BalVerdict realCallerVerdict =
- new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, "RealCallerIsVisible");
+ new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "RealCallerIsVisible");
mController.setCallerVerdict(BalVerdict.BLOCK);
mController.setRealCallerVerdict(realCallerVerdict);
@@ -571,7 +571,6 @@
+ "callerApp: mCallerApp; "
+ "inVisibleTask: false; "
+ "balAllowedByPiCreator: BSP.ALLOW_BAL; "
- + "balAllowedByPiCreatorWithHardening: BSP.ALLOW_BAL; "
+ "callerStartMode: MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; "
+ "hasRealCaller: true; "
+ "isCallForResult: false; "
@@ -674,7 +673,6 @@
+ "callerApp: mCallerApp; "
+ "inVisibleTask: false; "
+ "balAllowedByPiCreator: BSP.NONE; "
- + "balAllowedByPiCreatorWithHardening: BSP.NONE; "
+ "callerStartMode: MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; "
+ "hasRealCaller: true; "
+ "isCallForResult: false; "
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 50876c7..f5bec04 100644
--- a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
@@ -192,7 +192,8 @@
@Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
- public void testIsFreeformLetterboxingForCameraAllowed_overrideDisabled_returnsFalse() {
+ @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT)
+ public void testIsFreeformLetterboxingForCameraAllowed_optInMechanism_notOptedIn_retFalse() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -201,6 +202,17 @@
}
@Test
+ @EnableFlags({FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING,
+ FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT})
+ public void testIsFreeformLetterboxingForCameraAllowed_notOptedOut_returnsTrue() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertTrue(mCameraCompatFreeformPolicy.isFreeformLetterboxingForCameraAllowed(mActivity));
+ }
+
+ @Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testIsFreeformLetterboxingForCameraAllowed_cameraNotRunning_returnsFalse() {
@@ -222,6 +234,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 testIsFreeformLetterboxingForCameraAllowed_optInFreeformCameraRunning_true() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
@@ -346,6 +359,7 @@
@Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT)
public void testShouldApplyCameraCompatFreeformTreatment_overrideNotEnabled_returnsFalse() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
@@ -356,6 +370,18 @@
}
@Test
+ @EnableFlags({FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING,
+ FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT})
+ public void testShouldApplyCameraCompatFreeformTreatment_notOptedOut_returnsTrue() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertTrue(mCameraCompatFreeformPolicy.isTreatmentEnabledForActivity(mActivity,
+ /* checkOrientation */ true));
+ }
+
+ @Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@EnableCompatChanges(OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT)
public void testShouldApplyCameraCompatFreeformTreatment_enabledByOverride_returnsTrue() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 61ed0b5..4c81f73 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -87,7 +87,7 @@
import static com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING;
import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE;
import static com.android.server.display.feature.flags.Flags.FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT;
-import static com.android.window.flags.Flags.FLAG_ENABLE_PERSISTING_DENSITY_SCALE_FOR_CONNECTED_DISPLAYS;
+import static com.android.window.flags.Flags.FLAG_ENABLE_PERSISTING_DISPLAY_SIZE_FOR_CONNECTED_DISPLAYS;
import static com.google.common.truth.Truth.assertThat;
@@ -2631,10 +2631,19 @@
final BooleanSupplier keyguardGoingAway = () -> keyguard.isKeyguardGoingAway(displayId);
final BooleanSupplier appVisible = activity::isVisibleRequested;
- // Begin locked and in AOD
+ // Begin unlocked.
+ keyguard.setKeyguardShown(displayId, false /* keyguard */, false /* aod */);
+ transitions.flush();
+
+ // Lock and go to AOD.
keyguard.setKeyguardShown(displayId, true /* keyguard */, true /* aod */);
assertFalse(keyguardGoingAway.getAsBoolean());
assertFalse(appVisible.getAsBoolean());
+ if (Flags.aodTransition()) {
+ assertThat(transitions.mLastTransit).flags().contains(TRANSIT_FLAG_AOD_APPEARING);
+ } else {
+ assertThat(transitions.mLastTransit).flags().doesNotContain(TRANSIT_FLAG_AOD_APPEARING);
+ }
transitions.flush();
// Start unlocking from AOD.
@@ -2654,14 +2663,7 @@
keyguard.setKeyguardShown(displayId, true /* keyguard */, false /* aod */);
assertTrue(keyguardGoingAway.getAsBoolean());
assertTrue(appVisible.getAsBoolean());
-
- if (Flags.aodTransition()) {
- assertThat(transitions.mLastTransit).flags()
- .containsExactly(TRANSIT_FLAG_AOD_APPEARING);
- } else {
- assertThat(transitions.mLastTransit).isNull();
- }
- transitions.flush();
+ assertThat(transitions.mLastTransit).isNull();
// Finish unlock
keyguard.setKeyguardShown(displayId, false /* keyguard */, false /* aod */);
@@ -2684,10 +2686,19 @@
final BooleanSupplier keyguardGoingAway = () -> keyguard.isKeyguardGoingAway(displayId);
final BooleanSupplier appVisible = activity::isVisibleRequested;
- // Begin locked and in AOD
+ // Begin unlocked.
+ keyguard.setKeyguardShown(displayId, false /* keyguard */, false /* aod */);
+ transitions.flush();
+
+ // Lock and go to AOD.
keyguard.setKeyguardShown(displayId, true /* keyguard */, true /* aod */);
assertFalse(keyguardGoingAway.getAsBoolean());
assertFalse(appVisible.getAsBoolean());
+ if (Flags.aodTransition()) {
+ assertThat(transitions.mLastTransit).flags().contains(TRANSIT_FLAG_AOD_APPEARING);
+ } else {
+ assertThat(transitions.mLastTransit).flags().doesNotContain(TRANSIT_FLAG_AOD_APPEARING);
+ }
transitions.flush();
// Start unlocking from AOD.
@@ -2705,14 +2716,7 @@
keyguard.setKeyguardShown(displayId, true /* keyguard */, false /* aod */);
assertTrue(keyguardGoingAway.getAsBoolean());
assertTrue(appVisible.getAsBoolean());
-
- if (Flags.aodTransition()) {
- assertThat(transitions.mLastTransit).flags()
- .containsExactly(TRANSIT_FLAG_AOD_APPEARING);
- } else {
- assertThat(transitions.mLastTransit).isNull();
- }
- transitions.flush();
+ assertThat(transitions.mLastTransit).isNull();
// Same API call a second time cancels the unlock, because AOD isn't changing.
keyguard.setKeyguardShown(displayId, true /* keyguard */, false /* aod */);
@@ -2921,7 +2925,7 @@
assertFalse(dc.mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(dc));
}
- @EnableFlags(FLAG_ENABLE_PERSISTING_DENSITY_SCALE_FOR_CONNECTED_DISPLAYS)
+ @EnableFlags(FLAG_ENABLE_PERSISTING_DISPLAY_SIZE_FOR_CONNECTED_DISPLAYS)
@Test
public void testForcedDensityRatioSetForExternalDisplays_persistDensityScaleFlagEnabled() {
final DisplayInfo displayInfo = new DisplayInfo(mDisplayInfo);
@@ -2951,7 +2955,7 @@
displayContent.mExternalDisplayForcedDensityRatio, 0.01);
}
- @EnableFlags(FLAG_ENABLE_PERSISTING_DENSITY_SCALE_FOR_CONNECTED_DISPLAYS)
+ @EnableFlags(FLAG_ENABLE_PERSISTING_DISPLAY_SIZE_FOR_CONNECTED_DISPLAYS)
@Test
public void testForcedDensityUpdateForExternalDisplays_persistDensityScaleFlagEnabled() {
final DisplayInfo displayInfo = new DisplayInfo(mDisplayInfo);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index 4854f0d..862158e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -362,7 +362,19 @@
@Test
public void testSwitchDecorInsets() {
- createNavBarWithProvidedInsets(mDisplayContent);
+ final WindowState win = createApplicationWindow();
+ final WindowState bar = createNavBarWithProvidedInsets(mDisplayContent);
+ bar.getFrame().set(0, mDisplayContent.mDisplayFrames.mHeight - NAV_BAR_HEIGHT,
+ mDisplayContent.mDisplayFrames.mWidth, mDisplayContent.mDisplayFrames.mHeight);
+ final int insetsId = bar.mAttrs.providedInsets[0].getId();
+ final InsetsSourceProvider provider = mDisplayContent.getInsetsStateController()
+ .getOrCreateSourceProvider(insetsId, bar.mAttrs.providedInsets[0].getType());
+ provider.setServerVisible(true);
+ provider.updateSourceFrame(bar.getFrame());
+
+ final InsetsState prevInsetsState = new InsetsState();
+ prevInsetsState.addSource(new InsetsSource(provider.getSource()));
+
final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
final DisplayInfo info = mDisplayContent.getDisplayInfo();
final int w = info.logicalWidth;
@@ -385,6 +397,18 @@
// The current insets are restored from cache directly.
assertEquals(prevConfigFrame, displayPolicy.getDecorInsetsInfo(info.rotation,
info.logicalWidth, info.logicalHeight).mConfigFrame);
+ // Assume that the InsetsSource in current InsetsState is not updated yet. And it will be
+ // replaced by the one in cache.
+ InsetsState currentInsetsState = new InsetsState();
+ final InsetsSource prevSource = new InsetsSource(provider.getSource());
+ prevSource.getFrame().scale(0.5f);
+ currentInsetsState.addSource(prevSource);
+ currentInsetsState = mDisplayContent.getInsetsPolicy().adjustInsetsForWindow(
+ win, currentInsetsState);
+ if (com.android.window.flags.Flags.useCachedInsetsForDisplaySwitch()) {
+ assertEquals(prevInsetsState.peekSource(insetsId),
+ currentInsetsState.peekSource(insetsId));
+ }
// If screen is not fully turned on, then the cache should be preserved.
displayPolicy.screenTurnedOff(false /* acquireSleepToken */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
index a57577a96..81e487a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
@@ -274,7 +274,7 @@
mSecondaryDisplay.mBaseDisplayDensity);
}
- @EnableFlags(Flags.FLAG_ENABLE_PERSISTING_DENSITY_SCALE_FOR_CONNECTED_DISPLAYS)
+ @EnableFlags(Flags.FLAG_ENABLE_PERSISTING_DISPLAY_SIZE_FOR_CONNECTED_DISPLAYS)
@Test
public void testSetForcedDensityRatio() {
mDisplayWindowSettings.setForcedDensity(mSecondaryDisplay.getDisplayInfo(),
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxAttachInputTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxAttachInputTest.java
index 51e0240..8ee5999 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxAttachInputTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxAttachInputTest.java
@@ -38,7 +38,6 @@
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.view.InputWindowHandle;
-import android.view.SurfaceControl;
import android.view.WindowManager;
import com.android.server.testutils.StubTransaction;
@@ -76,8 +75,7 @@
doReturn(0.5f).when(letterboxOverrides).getLetterboxWallpaperDarkScrimAlpha();
mWindowState = createWindowState();
mLetterbox = new Letterbox(mSurfaces, StubTransaction::new,
- mock(AppCompatReachabilityPolicy.class), letterboxOverrides,
- () -> mock(SurfaceControl.class));
+ mock(AppCompatReachabilityPolicy.class), letterboxOverrides);
mTransaction = spy(StubTransaction.class);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
index a51a44f..e5533b6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
@@ -68,7 +68,6 @@
private SurfaceControlMocker mSurfaces;
private SurfaceControl.Transaction mTransaction;
- private SurfaceControl mParentSurface = mock(SurfaceControl.class);
private AppCompatLetterboxOverrides mLetterboxOverrides;
private WindowState mWindowState;
@@ -84,7 +83,7 @@
doReturn(0.5f).when(mLetterboxOverrides).getLetterboxWallpaperDarkScrimAlpha();
mWindowState = mock(WindowState.class);
mLetterbox = new Letterbox(mSurfaces, StubTransaction::new,
- mock(AppCompatReachabilityPolicy.class), mLetterboxOverrides, () -> mParentSurface);
+ mock(AppCompatReachabilityPolicy.class), mLetterboxOverrides);
mTransaction = spy(StubTransaction.class);
}
@@ -259,22 +258,6 @@
}
@Test
- public void testNeedsApplySurfaceChanges_setParentSurface() {
- mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
- applySurfaceChanges();
-
- verify(mTransaction).reparent(mSurfaces.top, mParentSurface);
- assertFalse(mLetterbox.needsApplySurfaceChanges());
-
- mParentSurface = mock(SurfaceControl.class);
-
- assertTrue(mLetterbox.needsApplySurfaceChanges());
-
- applySurfaceChanges();
- verify(mTransaction).reparent(mSurfaces.top, mParentSurface);
- }
-
- @Test
public void testApplySurfaceChanges_cornersNotRounded_surfaceFullWindowSurfaceNotCreated() {
mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
applySurfaceChanges();
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 4e09d6a..33a48aa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -67,6 +67,7 @@
import static com.android.server.wm.AppCompatUtils.computeAspectRatio;
import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
+import static com.android.window.flags.Flags.FLAG_ENABLE_SIZE_COMPAT_MODE_IMPROVEMENTS_FOR_CONNECTED_DISPLAYS;
import static com.google.common.truth.Truth.assertThat;
@@ -109,6 +110,7 @@
import android.provider.DeviceConfig.Properties;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
+import android.view.InputDevice;
import android.view.InsetsFrameProvider;
import android.view.InsetsSource;
import android.view.InsetsState;
@@ -561,6 +563,41 @@
assertDownScaled();
}
+ @EnableFlags(FLAG_ENABLE_SIZE_COMPAT_MODE_IMPROVEMENTS_FOR_CONNECTED_DISPLAYS)
+ @Test
+ public void testFixedMiscConfigurationWhenMovingToDisplay() {
+ setUpDisplaySizeWithApp(1000, 2500);
+
+ final DisplayContent newDisplay =
+ new TestDisplayContent.Builder(mAtm, 1000, 2000).build();
+ final InputDevice device = new InputDevice.Builder()
+ .setAssociatedDisplayId(newDisplay.mDisplayId)
+ .setSources(InputDevice.SOURCE_TOUCHSCREEN | InputDevice.SOURCE_TRACKBALL
+ | InputDevice.KEYBOARD_TYPE_ALPHABETIC)
+ .build();
+ final InputDevice[] devices = {device};
+ doReturn(true).when(newDisplay.mWmService.mInputManager)
+ .canDispatchToDisplay(device.getId(), newDisplay.mDisplayId);
+ doReturn(devices).when(newDisplay.mWmService.mInputManager).getInputDevices();
+ mTask.mWmService.mIsTouchDevice = true;
+ mTask.mWmService.displayReady();
+
+ prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED);
+
+ final Configuration originalConfiguration = mActivity.getConfiguration();
+ final int originalTouchscreen = originalConfiguration.touchscreen;
+ final int originalNavigation = originalConfiguration.navigation;
+ final int originalKeyboard = originalConfiguration.keyboard;
+
+ // Move the non-resizable activity to the new display.
+ mTask.reparent(newDisplay.getDefaultTaskDisplayArea(), true /* onTop */);
+
+ final Configuration newConfiguration = mActivity.getConfiguration();
+ assertEquals(originalTouchscreen, newConfiguration.touchscreen);
+ assertEquals(originalNavigation, newConfiguration.navigation);
+ assertEquals(originalKeyboard, newConfiguration.keyboard);
+ }
+
@Test
public void testFixedScreenBoundsWhenDisplaySizeChanged() {
setUpDisplaySizeWithApp(1000, 2500);
diff --git a/services/usb/java/com/android/server/usb/UsbManagerInternal.java b/services/usb/java/com/android/server/usb/UsbManagerInternal.java
deleted file mode 100644
index 31c5986..0000000
--- a/services/usb/java/com/android/server/usb/UsbManagerInternal.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2024 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.server.usb;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.hardware.usb.IUsbOperationInternal;
-import android.hardware.usb.UsbPort;
-import android.util.ArraySet;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * UsbManagerInternal provides internal APIs for the UsbService to
- * reduce IPC overhead costs and support internal USB data signal stakers.
- *
- * @hide Only for use within the system server.
- */
-public abstract class UsbManagerInternal {
-
- public static final int OS_USB_DISABLE_REASON_AAPM = 0;
- public static final int OS_USB_DISABLE_REASON_LOCKDOWN_MODE = 1;
-
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(value = {OS_USB_DISABLE_REASON_AAPM,
- OS_USB_DISABLE_REASON_LOCKDOWN_MODE})
- public @interface OsUsbDisableReason {
- }
-
- public abstract boolean enableUsbData(String portId, boolean enable,
- int operationId, IUsbOperationInternal callback, @OsUsbDisableReason int disableReason);
-
- public abstract UsbPort[] getPorts();
-
-}
\ No newline at end of file
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index 4395b76..7808b2e 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -27,7 +27,10 @@
import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SINK;
import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SOURCE;
+import android.hardware.usb.IUsbManagerInternal;
+
import android.annotation.NonNull;
+import android.annotation.IntDef;
import android.annotation.UserIdInt;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
@@ -80,12 +83,16 @@
import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicInteger;
+
/**
* UsbService manages all USB related state, including both host and device support.
* Host related events and calls are delegated to UsbHostManager, and device related
@@ -93,6 +100,15 @@
*/
public class UsbService extends IUsbManager.Stub {
+ public static final int OS_USB_DISABLE_REASON_AAPM = 0;
+ public static final int OS_USB_DISABLE_REASON_LOCKDOWN_MODE = 1;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {OS_USB_DISABLE_REASON_AAPM,
+ OS_USB_DISABLE_REASON_LOCKDOWN_MODE})
+ public @interface OsUsbDisableReason {
+ }
+
public static class Lifecycle extends SystemService {
private UsbService mUsbService;
private final CompletableFuture<Void> mOnStartFinished = new CompletableFuture<>();
@@ -227,7 +243,7 @@
filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
mContext.registerReceiverAsUser(receiver, UserHandle.ALL, filter, null, null);
if(android.hardware.usb.flags.Flags.enableUsbDataSignalStakingInternal()) {
- LocalServices.addService(UsbManagerInternal.class, new UsbManagerInternalImpl());
+ LocalServices.addService(IUsbManagerInternal.class, new UsbManagerInternalImpl());
}
}
@@ -246,7 +262,7 @@
mPermissionManager = new UsbPermissionManager(context, this);
if(android.hardware.usb.flags.Flags.enableUsbDataSignalStakingInternal()) {
- LocalServices.addService(UsbManagerInternal.class, new UsbManagerInternalImpl());
+ LocalServices.addService(IUsbManagerInternal.class, new UsbManagerInternalImpl());
}
}
@@ -1536,24 +1552,30 @@
enableUsbDataInternal(port.getId(), !lockDownTriggeredByUser,
STRONG_AUTH_OPERATION_ID,
new IUsbOperationInternal.Default(),
- UsbManagerInternal.OS_USB_DISABLE_REASON_LOCKDOWN_MODE,
+ OS_USB_DISABLE_REASON_LOCKDOWN_MODE,
true);
}
}
}
- private class UsbManagerInternalImpl extends UsbManagerInternal {
- @Override
- public boolean enableUsbData(String portId, boolean enable,
- int operationId, IUsbOperationInternal callback,
- @OsUsbDisableReason int disableReason) {
- return enableUsbDataInternal(portId, enable, operationId, callback,
- disableReason, true);
- }
+ private class UsbManagerInternalImpl extends IUsbManagerInternal.Stub {
+ private static final AtomicInteger sUsbOperationCount = new AtomicInteger();
@Override
- public UsbPort[] getPorts() {
- return mPortManager.getPorts();
+ public boolean enableUsbDataSignal(boolean enable,
+ @OsUsbDisableReason int disableReason) {
+ boolean result = true;
+ int operationId = sUsbOperationCount.incrementAndGet() + disableReason;
+ for (UsbPort port : mPortManager.getPorts()) {
+ boolean success = enableUsbDataInternal(port.getId(), enable, operationId,
+ new IUsbOperationInternal.Default(), disableReason, true);
+ if(!success) {
+ Slog.e(TAG, "enableUsbDataInternal failed to change USB port "
+ + port.getId() + "state to " + enable);
+ }
+ result &= success;
+ }
+ return result;
}
}
}
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 531f516..9e57fd3 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -30,6 +30,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.ParcelFileDescriptor;
+import android.os.UserHandle;
import com.android.internal.telecom.IVideoProvider;
import com.android.server.telecom.flags.Flags;
@@ -680,6 +681,7 @@
private final @CallDirection int mCallDirection;
private final @Connection.VerificationStatus int mCallerNumberVerificationStatus;
private final Uri mContactPhotoUri;
+ private final UserHandle mAssociatedUser;
/**
* Whether the supplied capabilities supports the specified capability.
@@ -1081,6 +1083,16 @@
return mCallerNumberVerificationStatus;
}
+ /**
+ * Gets the user that originated the call
+ * @return The user
+ *
+ * @hide
+ */
+ public UserHandle getAssociatedUser() {
+ return mAssociatedUser;
+ }
+
@Override
public boolean equals(Object o) {
if (o instanceof Details) {
@@ -1107,7 +1119,8 @@
Objects.equals(mCallDirection, d.mCallDirection) &&
Objects.equals(mCallerNumberVerificationStatus,
d.mCallerNumberVerificationStatus) &&
- Objects.equals(mContactPhotoUri, d.mContactPhotoUri);
+ Objects.equals(mContactPhotoUri, d.mContactPhotoUri) &&
+ Objects.equals(mAssociatedUser, d.mAssociatedUser);
}
return false;
}
@@ -1133,7 +1146,8 @@
mContactDisplayName,
mCallDirection,
mCallerNumberVerificationStatus,
- mContactPhotoUri);
+ mContactPhotoUri,
+ mAssociatedUser);
}
/** {@hide} */
@@ -1158,7 +1172,8 @@
String contactDisplayName,
int callDirection,
int callerNumberVerificationStatus,
- Uri contactPhotoUri) {
+ Uri contactPhotoUri,
+ UserHandle originatingUser) {
mState = state;
mTelecomCallId = telecomCallId;
mHandle = handle;
@@ -1180,6 +1195,7 @@
mCallDirection = callDirection;
mCallerNumberVerificationStatus = callerNumberVerificationStatus;
mContactPhotoUri = contactPhotoUri;
+ mAssociatedUser = originatingUser;
}
/** {@hide} */
@@ -1205,7 +1221,8 @@
parcelableCall.getContactDisplayName(),
parcelableCall.getCallDirection(),
parcelableCall.getCallerNumberVerificationStatus(),
- parcelableCall.getContactPhotoUri()
+ parcelableCall.getContactPhotoUri(),
+ parcelableCall.getAssociatedUser()
);
}
@@ -2631,7 +2648,8 @@
mDetails.getContactDisplayName(),
mDetails.getCallDirection(),
mDetails.getCallerNumberVerificationStatus(),
- mDetails.getContactPhotoUri()
+ mDetails.getContactPhotoUri(),
+ mDetails.getAssociatedUser()
);
fireDetailsChanged(mDetails);
}
diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java
index 6a13189..bd004e5 100644
--- a/telecomm/java/android/telecom/ParcelableCall.java
+++ b/telecomm/java/android/telecom/ParcelableCall.java
@@ -16,14 +16,17 @@
package android.telecom;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.telecom.Call.Details.CallDirection;
import com.android.internal.telecom.IVideoProvider;
@@ -70,6 +73,7 @@
private String mContactDisplayName;
private String mActiveChildCallId;
private Uri mContactPhotoUri;
+ private UserHandle mAssociatedUser;
public ParcelableCallBuilder setId(String id) {
mId = id;
@@ -230,6 +234,11 @@
return this;
}
+ public ParcelableCallBuilder setAssociatedUser(UserHandle user) {
+ mAssociatedUser = user;
+ return this;
+ }
+
public ParcelableCall createParcelableCall() {
return new ParcelableCall(
mId,
@@ -262,7 +271,8 @@
mCallerNumberVerificationStatus,
mContactDisplayName,
mActiveChildCallId,
- mContactPhotoUri);
+ mContactPhotoUri,
+ mAssociatedUser);
}
public static ParcelableCallBuilder fromParcelableCall(ParcelableCall parcelableCall) {
@@ -300,6 +310,7 @@
newBuilder.mContactDisplayName = parcelableCall.mContactDisplayName;
newBuilder.mActiveChildCallId = parcelableCall.mActiveChildCallId;
newBuilder.mContactPhotoUri = parcelableCall.mContactPhotoUri;
+ newBuilder.mAssociatedUser = parcelableCall.mAssociatedUser;
return newBuilder;
}
}
@@ -336,6 +347,7 @@
private final String mContactDisplayName;
private final String mActiveChildCallId; // Only valid for CDMA conferences
private final Uri mContactPhotoUri;
+ private final UserHandle mAssociatedUser;
public ParcelableCall(
String id,
@@ -368,7 +380,8 @@
int callerNumberVerificationStatus,
String contactDisplayName,
String activeChildCallId,
- Uri contactPhotoUri
+ Uri contactPhotoUri,
+ UserHandle associatedUser
) {
mId = id;
mState = state;
@@ -401,6 +414,7 @@
mContactDisplayName = contactDisplayName;
mActiveChildCallId = activeChildCallId;
mContactPhotoUri = contactPhotoUri;
+ mAssociatedUser = associatedUser;
}
/** The unique ID of the call. */
@@ -624,6 +638,13 @@
return mContactPhotoUri;
}
+ /**
+ * @return the originating user
+ */
+ public @NonNull UserHandle getAssociatedUser() {
+ return mAssociatedUser;
+ }
+
/**
* @return On a CDMA conference with two participants, returns the ID of the child call that's
@@ -666,6 +687,9 @@
source.readList(conferenceableCallIds, classLoader, java.lang.String.class);
Bundle intentExtras = source.readBundle(classLoader);
Bundle extras = source.readBundle(classLoader);
+ if (extras == null) {
+ extras = new Bundle();
+ }
int supportedAudioRoutes = source.readInt();
boolean isRttCallChanged = source.readByte() == 1;
ParcelableRttCall rttCall = source.readParcelable(classLoader, android.telecom.ParcelableRttCall.class);
@@ -675,6 +699,8 @@
String contactDisplayName = source.readString();
String activeChildCallId = source.readString();
Uri contactPhotoUri = source.readParcelable(classLoader, Uri.class);
+ UserHandle associatedUser = source.readParcelable(classLoader, UserHandle.class);
+ extras.putParcelable(Intent.EXTRA_USER_HANDLE, associatedUser);
return new ParcelableCallBuilder()
.setId(id)
.setState(state)
@@ -707,6 +733,7 @@
.setContactDisplayName(contactDisplayName)
.setActiveChildCallId(activeChildCallId)
.setContactPhotoUri(contactPhotoUri)
+ .setAssociatedUser(associatedUser)
.createParcelableCall();
}
@@ -757,6 +784,7 @@
destination.writeString(mContactDisplayName);
destination.writeString(mActiveChildCallId);
destination.writeParcelable(mContactPhotoUri, 0);
+ destination.writeParcelable(mAssociatedUser, 0);
}
@Override
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 58833e8..50c5a6b6 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -10962,7 +10962,7 @@
sDefaults.putInt(KEY_MMS_SMS_TO_MMS_TEXT_THRESHOLD_INT, -1);
sDefaults.putInt(KEY_MMS_SUBJECT_MAX_LENGTH_INT, 40);
sDefaults.putInt(KEY_MMS_NETWORK_RELEASE_TIMEOUT_MILLIS_INT, 5 * 1000);
- sDefaults.putInt(KEY_MMS_MAX_NTN_PAYLOAD_SIZE_BYTES_INT, 3 * 1000);
+ sDefaults.putInt(KEY_MMS_MAX_NTN_PAYLOAD_SIZE_BYTES_INT, -1);
sDefaults.putString(KEY_MMS_EMAIL_GATEWAY_NUMBER_STRING, "");
sDefaults.putString(KEY_MMS_HTTP_PARAMS_STRING, "");
sDefaults.putString(KEY_MMS_NAI_SUFFIX_STRING, "");
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 1c6652d..0dd0a42 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -3203,6 +3203,31 @@
in List<String> satelliteCountryCodes, String satelliteAccessConfigurationFile);
/**
+ * This API can be used by only CTS to override the satellite access allowed state for
+ * a list of subscription IDs.
+ *
+ * @param subIdListStr The string representation of the list of subscription IDs,
+ * which are numbers separated by comma.
+ * @return {@code true} if the satellite access allowed state is set successfully,
+ * {@code false} otherwise.
+ */
+ boolean setSatelliteAccessAllowedForSubscriptions(in String subIdListStr);
+
+ /**
+ * This API can be used by only CTS to override satellite TN scanning support.
+ *
+ * @param reset {@code true} mean the overridden configs should not be used, {@code false}
+ * otherwise.
+ * @param concurrentTnScanningSupported Whether concurrent TN scanning is supported.
+ * @param tnScanningDuringSatelliteSessionAllowed Whether TN scanning is allowed during
+ * a satellite session.
+ * @return {@code true} if the TN scanning support is set successfully,
+ * {@code false} otherwise.
+ */
+ boolean setTnScanningSupport(in boolean reset, in boolean concurrentTnScanningSupported,
+ in boolean tnScanningDuringSatelliteSessionAllowed);
+
+ /**
* This API can be used in only testing to override oem-enabled satellite provision status.
*
* @param reset {@code true} mean the overriding status should not be used, {@code false}
diff --git a/tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java b/tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java
index 3498974..229c7bf 100644
--- a/tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java
+++ b/tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java
@@ -103,6 +103,10 @@
@RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
public void reportJankStats_confirmPendingStatsIncreases() {
Activity jankTrackerActivity = mJankTrackerActivityRule.launchActivity(null);
+ mDevice.wait(Until.findObject(
+ By.text(jankTrackerActivity.getString(R.string.continue_test))),
+ WAIT_FOR_TIMEOUT_MS);
+
EditText editText = jankTrackerActivity.findViewById(R.id.edit_text);
JankTracker jankTracker = editText.getJankTracker();
@@ -135,6 +139,10 @@
public void simulateWidgetStateChanges_confirmStateChangesAreTracked() {
JankTrackerActivity jankTrackerActivity =
mJankTrackerActivityRule.launchActivity(null);
+ mDevice.wait(Until.findObject(
+ By.text(jankTrackerActivity.getString(R.string.continue_test))),
+ WAIT_FOR_TIMEOUT_MS);
+
TestWidget testWidget = jankTrackerActivity.findViewById(R.id.jank_tracker_widget);
JankTracker jankTracker = testWidget.getJankTracker();
jankTracker.forceListenerRegistration();
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
index 55d6fd9..6d8ea40 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
@@ -28,10 +28,13 @@
import android.tools.helpers.SYSTEMUI_PACKAGE
import android.tools.traces.parsers.WindowManagerStateHelper
import android.tools.traces.wm.WindowingMode
+import android.view.KeyEvent.KEYCODE_DPAD_DOWN
+import android.view.KeyEvent.KEYCODE_DPAD_UP
import android.view.KeyEvent.KEYCODE_EQUALS
import android.view.KeyEvent.KEYCODE_LEFT_BRACKET
import android.view.KeyEvent.KEYCODE_MINUS
import android.view.KeyEvent.KEYCODE_RIGHT_BRACKET
+import android.view.KeyEvent.META_CTRL_ON
import android.view.KeyEvent.META_META_ON
import android.view.WindowInsets
import android.view.WindowManager
@@ -151,19 +154,42 @@
?: error("Unable to find resource $MAXIMIZE_BUTTON_VIEW\n")
}
- /** Click maximise button on the app header for the given app. */
+ /** Maximize a given app to fill the stable bounds. */
fun maximiseDesktopApp(
wmHelper: WindowManagerStateHelper,
device: UiDevice,
- usingKeyboard: Boolean = false
+ trigger: MaximizeDesktopAppTrigger = MaximizeDesktopAppTrigger.MAXIMIZE_MENU,
) {
- if (usingKeyboard) {
- val keyEventHelper = KeyEventHelper(getInstrumentation())
- keyEventHelper.press(KEYCODE_EQUALS, META_META_ON)
- } else {
- val caption = getCaptionForTheApp(wmHelper, device)
- val maximizeButton = getMaximizeButtonForTheApp(caption)
- maximizeButton.click()
+ val caption = getCaptionForTheApp(wmHelper, device)!!
+ val maximizeButton = getMaximizeButtonForTheApp(caption)
+
+ when (trigger) {
+ MaximizeDesktopAppTrigger.MAXIMIZE_MENU -> maximizeButton.click()
+ MaximizeDesktopAppTrigger.DOUBLE_TAP_APP_HEADER -> {
+ caption.click()
+ Thread.sleep(50)
+ caption.click()
+ }
+
+ MaximizeDesktopAppTrigger.KEYBOARD_SHORTCUT -> {
+ val keyEventHelper = KeyEventHelper(getInstrumentation())
+ keyEventHelper.press(KEYCODE_EQUALS, META_META_ON)
+ }
+
+ MaximizeDesktopAppTrigger.MAXIMIZE_BUTTON_IN_MENU -> {
+ maximizeButton.longClick()
+ wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
+ val buttonResId = MAXIMIZE_BUTTON_IN_MENU
+ val maximizeMenu = getDesktopAppViewByRes(MAXIMIZE_MENU)
+ val maximizeButtonInMenu =
+ maximizeMenu
+ ?.wait(
+ Until.findObject(By.res(SYSTEMUI_PACKAGE, buttonResId)),
+ TIMEOUT.toMillis()
+ )
+ ?: error("Unable to find object with resource id $buttonResId")
+ maximizeButtonInMenu.click()
+ }
}
wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
}
@@ -472,6 +498,22 @@
device.drag(startX, startY, endX, endY, 100)
}
+ fun enterDesktopModeViaKeyboard(
+ wmHelper: WindowManagerStateHelper,
+ ) {
+ val keyEventHelper = KeyEventHelper(getInstrumentation())
+ keyEventHelper.press(KEYCODE_DPAD_DOWN, META_META_ON or META_CTRL_ON)
+ wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
+ }
+
+ fun exitDesktopModeToFullScreenViaKeyboard(
+ wmHelper: WindowManagerStateHelper,
+ ) {
+ val keyEventHelper = KeyEventHelper(getInstrumentation())
+ keyEventHelper.press(KEYCODE_DPAD_UP, META_META_ON or META_CTRL_ON)
+ wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
+ }
+
fun enterDesktopModeFromAppHandleMenu(
wmHelper: WindowManagerStateHelper,
device: UiDevice
@@ -550,6 +592,13 @@
rightSideMatching
}
+ enum class MaximizeDesktopAppTrigger {
+ MAXIMIZE_MENU,
+ DOUBLE_TAP_APP_HEADER,
+ KEYBOARD_SHORTCUT,
+ MAXIMIZE_BUTTON_IN_MENU
+ }
+
private companion object {
val TIMEOUT: Duration = Duration.ofSeconds(3)
const val SNAP_RESIZE_DRAG_INSET: Int = 5 // inset to avoid dragging to display edge
@@ -561,6 +610,7 @@
const val DESKTOP_MODE_BUTTON: String = "desktop_button"
const val SNAP_LEFT_BUTTON: String = "maximize_menu_snap_left_button"
const val SNAP_RIGHT_BUTTON: String = "maximize_menu_snap_right_button"
+ const val MAXIMIZE_BUTTON_IN_MENU: String = "maximize_menu_size_toggle_button"
const val MINIMIZE_BUTTON_VIEW: String = "minimize_window"
const val HEADER_EMPTY_VIEW: String = "caption_handle"
val caption: BySelector
diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp
index 1f0bd61..544f94b 100644
--- a/tests/Input/Android.bp
+++ b/tests/Input/Android.bp
@@ -15,6 +15,7 @@
"modules-utils-testable-device-config-defaults",
],
srcs: [
+ "src/**/*.aidl",
"src/**/*.java",
"src/**/*.kt",
],
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index eac4267..7ec8f9c 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -76,7 +76,7 @@
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.verifyNoInteractions
import org.mockito.Mockito.`when`
import org.mockito.stubbing.OngoingStubbing
@@ -209,7 +209,7 @@
@Test
fun testStart() {
- verifyZeroInteractions(native)
+ verifyNoInteractions(native)
service.start()
verify(native).start()
@@ -217,7 +217,7 @@
@Test
fun testInputSettingsUpdatedOnSystemRunning() {
- verifyZeroInteractions(native)
+ verifyNoInteractions(native)
runWithShellPermissionIdentity {
service.systemRunning()
diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
index c666fb7..2799f6c 100644
--- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
@@ -382,14 +382,6 @@
intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
),
TestData(
- "META + DEL -> Back",
- intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_DEL),
- KeyGestureEvent.KEY_GESTURE_TYPE_BACK,
- intArrayOf(KeyEvent.KEYCODE_DEL),
- KeyEvent.META_META_ON,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
- ),
- TestData(
"META + ESC -> Back",
intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ESCAPE),
KeyGestureEvent.KEY_GESTURE_TYPE_BACK,
diff --git a/tests/Input/src/com/android/test/input/AnrTest.kt b/tests/Input/src/com/android/test/input/AnrTest.kt
index 73192ea..f8cb86b 100644
--- a/tests/Input/src/com/android/test/input/AnrTest.kt
+++ b/tests/Input/src/com/android/test/input/AnrTest.kt
@@ -15,32 +15,37 @@
*/
package com.android.test.input
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.filters.MediumTest
-
import android.app.ActivityManager
import android.app.ApplicationExitInfo
-import android.content.Context
-import android.graphics.Rect
+import android.app.Instrumentation
+import android.content.Intent
import android.hardware.display.DisplayManager
import android.os.Build
+import android.os.Bundle
+import android.os.IBinder
import android.os.IInputConstants.UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS
import android.os.SystemClock
-import android.server.wm.CtsWindowInfoUtils.waitForStableWindowGeometry
+import android.server.wm.CtsWindowInfoUtils.getWindowCenter
+import android.server.wm.CtsWindowInfoUtils.waitForWindowOnTop
import android.testing.PollingCheck
-
+import android.view.InputEvent
+import android.view.MotionEvent
+import android.view.MotionEvent.ACTION_DOWN
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiObject2
import androidx.test.uiautomator.Until
-
+import com.android.cts.input.BlockingQueueEventVerifier
import com.android.cts.input.DebugInputRule
import com.android.cts.input.ShowErrorDialogsRule
import com.android.cts.input.UinputTouchScreen
-
+import com.android.cts.input.inputeventmatchers.withMotionAction
import java.time.Duration
-
+import java.util.concurrent.LinkedBlockingQueue
+import java.util.function.Supplier
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
@@ -51,13 +56,34 @@
import org.junit.runner.RunWith
/**
+ * Click on the center of the window identified by the provided window token.
+ * The click is performed using "UinputTouchScreen" device.
+ * If the touchscreen device is closed too soon, it may cause the click to be dropped. Therefore,
+ * the provided runnable can ensure that the click is delivered before the device is closed, thus
+ * avoiding this race.
+ */
+private fun clickOnWindow(
+ token: IBinder,
+ displayId: Int,
+ instrumentation: Instrumentation,
+ waitForEvent: Runnable,
+) {
+ val displayManager = instrumentation.context.getSystemService(DisplayManager::class.java)
+ val display = displayManager.getDisplay(displayId)
+ val point = getWindowCenter({ token }, display.displayId)
+ UinputTouchScreen(instrumentation, display).use { touchScreen ->
+ touchScreen.touchDown(point.x, point.y).lift()
+ // If the device is allowed to close without waiting here, the injected click may be dropped
+ waitForEvent.run()
+ }
+}
+
+/**
* This test makes sure that an unresponsive gesture monitor gets an ANR.
*
* The gesture monitor must be registered from a different process than the instrumented process.
- * Otherwise, when the test runs, you will get:
- * Test failed to run to completion.
- * Reason: 'Instrumentation run failed due to 'keyDispatchingTimedOut''.
- * Check device logcat for details
+ * Otherwise, when the test runs, you will get: Test failed to run to completion. Reason:
+ * 'Instrumentation run failed due to 'keyDispatchingTimedOut''. Check device logcat for details
* RUNNER ERROR: Instrumentation run failed due to 'keyDispatchingTimedOut'
*/
@MediumTest
@@ -65,30 +91,43 @@
class AnrTest {
companion object {
private const val TAG = "AnrTest"
- private const val ALL_PIDS = 0
private const val NO_MAX = 0
}
private val instrumentation = InstrumentationRegistry.getInstrumentation()
- private var hideErrorDialogs = 0
private lateinit var PACKAGE_NAME: String
- private val DISPATCHING_TIMEOUT = (UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS *
- Build.HW_TIMEOUT_MULTIPLIER)
+ private val DISPATCHING_TIMEOUT =
+ (UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS * Build.HW_TIMEOUT_MULTIPLIER)
+ private var remoteWindowToken: IBinder? = null
+ private var remoteDisplayId: Int? = null
+ private var remotePid: Int? = null
+ private val remoteInputEvents = LinkedBlockingQueue<InputEvent>()
+ private val verifier = BlockingQueueEventVerifier(remoteInputEvents)
- @get:Rule
- val debugInputRule = DebugInputRule()
+ val binder =
+ object : IAnrTestService.Stub() {
+ override fun provideActivityInfo(token: IBinder, displayId: Int, pid: Int) {
+ remoteWindowToken = token
+ remoteDisplayId = displayId
+ remotePid = pid
+ }
- @get:Rule
- val showErrorDialogs = ShowErrorDialogsRule()
+ override fun notifyMotion(event: MotionEvent) {
+ remoteInputEvents.add(event)
+ }
+ }
+
+ @get:Rule val showErrorDialogs = ShowErrorDialogsRule()
+
+ @get:Rule val debugInputRule = DebugInputRule()
@Before
fun setUp() {
+ startUnresponsiveActivity()
PACKAGE_NAME = UnresponsiveGestureMonitorActivity::class.java.getPackage()!!.getName()
}
- @After
- fun tearDown() {
- }
+ @After fun tearDown() {}
@Test
@DebugInputRule.DebugInput(bug = 339924248)
@@ -112,7 +151,7 @@
val timestamp = System.currentTimeMillis()
val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
val closeAppButton: UiObject2? =
- uiDevice.wait(Until.findObject(By.res("android:id/aerr_close")), 20000)
+ uiDevice.wait(Until.findObject(By.res("android:id/aerr_close")), 20000)
if (closeAppButton == null) {
fail("Could not find anr dialog/close button")
return
@@ -120,10 +159,10 @@
closeAppButton.click()
/**
* We must wait for the app to be fully closed before exiting this test. This is because
- * another test may again invoke 'am start' for the same activity.
- * If the 1st process that got ANRd isn't killed by the time second 'am start' runs,
- * the killing logic will apply to the newly launched 'am start' instance, and the second
- * test will fail because the unresponsive activity will never be launched.
+ * another test may again invoke 'am start' for the same activity. If the 1st process that
+ * got ANRd isn't killed by the time second 'am start' runs, the killing logic will apply to
+ * the newly launched 'am start' instance, and the second test will fail because the
+ * unresponsive activity will never be launched.
*/
waitForNewExitReasonAfter(timestamp)
}
@@ -132,7 +171,7 @@
// Find anr dialog and tap on wait
val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
val waitButton: UiObject2? =
- uiDevice.wait(Until.findObject(By.res("android:id/aerr_wait")), 20000)
+ uiDevice.wait(Until.findObject(By.res("android:id/aerr_wait")), 20000)
if (waitButton == null) {
fail("Could not find anr dialog/wait button")
return
@@ -144,7 +183,7 @@
lateinit var infos: List<ApplicationExitInfo>
instrumentation.runOnMainSync {
val am = instrumentation.getContext().getSystemService(ActivityManager::class.java)!!
- infos = am.getHistoricalProcessExitReasons(PACKAGE_NAME, ALL_PIDS, NO_MAX)
+ infos = am.getHistoricalProcessExitReasons(PACKAGE_NAME, remotePid!!, NO_MAX)
}
return infos
}
@@ -159,37 +198,32 @@
assertEquals(ApplicationExitInfo.REASON_ANR, reasons[0].reason)
}
- private fun clickOnObject(obj: UiObject2) {
- val displayManager =
- instrumentation.context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
- val display = displayManager.getDisplay(obj.getDisplayId())
- val rect: Rect = obj.visibleBounds
- UinputTouchScreen(instrumentation, display).use { touchScreen ->
- touchScreen
- .touchDown(rect.centerX(), rect.centerY())
- .lift()
- }
- }
-
private fun triggerAnr() {
- startUnresponsiveActivity()
- val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
- val obj: UiObject2? = uiDevice.wait(Until.findObject(By.pkg(PACKAGE_NAME)), 10000)
-
- if (obj == null) {
- fail("Could not find unresponsive activity")
- return
- }
-
- clickOnObject(obj)
+ clickOnWindow(
+ remoteWindowToken!!,
+ remoteDisplayId!!,
+ instrumentation,
+ ) { verifier.assertReceivedMotion(withMotionAction(ACTION_DOWN)) }
SystemClock.sleep(DISPATCHING_TIMEOUT.toLong()) // default ANR timeout for gesture monitors
}
private fun startUnresponsiveActivity() {
- val flags = " -W -n "
- val startCmd = "am start $flags $PACKAGE_NAME/.UnresponsiveGestureMonitorActivity"
- instrumentation.uiAutomation.executeShellCommand(startCmd)
- waitForStableWindowGeometry(Duration.ofSeconds(5))
+ val intent =
+ Intent(instrumentation.targetContext, UnresponsiveGestureMonitorActivity::class.java)
+ intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_NEW_DOCUMENT
+ val bundle = Bundle()
+ bundle.putBinder("serviceBinder", binder)
+ intent.putExtra("serviceBundle", bundle)
+ instrumentation.targetContext.startActivity(intent)
+ // first, wait for the token to become valid
+ PollingCheck.check(
+ "UnresponsiveGestureMonitorActivity failed to call 'provideActivityInfo'",
+ Duration.ofSeconds(5).toMillis()) { remoteWindowToken != null }
+ // next, wait for the window of the activity to get on top
+ // we could combine the two checks above, but the current setup makes it easier to detect
+ // errors
+ assertTrue("Remote activity window did not become visible",
+ waitForWindowOnTop(Duration.ofSeconds(5), Supplier { remoteWindowToken }))
}
}
diff --git a/tests/Input/src/com/android/test/input/IAnrTestService.aidl b/tests/Input/src/com/android/test/input/IAnrTestService.aidl
new file mode 100644
index 0000000..e3caf06
--- /dev/null
+++ b/tests/Input/src/com/android/test/input/IAnrTestService.aidl
@@ -0,0 +1,17 @@
+package com.android.test.input;
+
+import android.view.MotionEvent;
+
+interface IAnrTestService {
+ /**
+ * Provide the activity information. This includes:
+ * windowToken: the windowToken of the activity window
+ * displayId: the display id on which the activity is positioned
+ * pid: the pid of the activity
+ */
+ void provideActivityInfo(IBinder windowToken, int displayId, int pid);
+ /**
+ * Provide the MotionEvent received by the remote activity.
+ */
+ void notifyMotion(in MotionEvent event);
+}
diff --git a/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt b/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt
index 1842f0a..1e44617 100644
--- a/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt
+++ b/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt
@@ -23,20 +23,24 @@
import android.hardware.input.InputManager
import android.os.Bundle
import android.os.Looper
+import android.os.Process
import android.util.Log
import android.view.InputChannel
import android.view.InputEvent
import android.view.InputEventReceiver
import android.view.InputMonitor
+import android.view.MotionEvent
-class UnresponsiveReceiver(channel: InputChannel, looper: Looper) :
- InputEventReceiver(channel, looper) {
+class UnresponsiveReceiver(channel: InputChannel, looper: Looper, val service: IAnrTestService) :
+ InputEventReceiver(channel, looper) {
companion object {
const val TAG = "UnresponsiveReceiver"
}
+
override fun onInputEvent(event: InputEvent) {
Log.i(TAG, "Received $event")
// Not calling 'finishInputEvent' in order to trigger the ANR
+ service.notifyMotion(event as MotionEvent)
}
}
@@ -44,14 +48,27 @@
companion object {
const val MONITOR_NAME = "unresponsive gesture monitor"
}
+
private lateinit var mInputEventReceiver: InputEventReceiver
private lateinit var mInputMonitor: InputMonitor
+ private lateinit var service: IAnrTestService
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ val bundle = intent.getBundleExtra("serviceBundle")!!
+ service = IAnrTestService.Stub.asInterface(bundle.getBinder("serviceBinder"))
val inputManager = checkNotNull(getSystemService(InputManager::class.java))
mInputMonitor = inputManager.monitorGestureInput(MONITOR_NAME, displayId)
- mInputEventReceiver = UnresponsiveReceiver(
- mInputMonitor.getInputChannel(), Looper.myLooper()!!)
+ mInputEventReceiver =
+ UnresponsiveReceiver(mInputMonitor.getInputChannel(), Looper.myLooper()!!, service)
+ }
+
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+ service.provideActivityInfo(
+ window.decorView.windowToken,
+ display.displayId,
+ Process.myPid(),
+ )
}
}
diff --git a/tests/PackageWatchdog/Android.bp b/tests/PackageWatchdog/Android.bp
index 16c6e3b..46cb5b7 100644
--- a/tests/PackageWatchdog/Android.bp
+++ b/tests/PackageWatchdog/Android.bp
@@ -55,4 +55,8 @@
"mts-crashrecovery",
],
min_sdk_version: "36",
+
+ // Test coverage system runs on different devices. Need to
+ // compile for all architecture.
+ compile_multilib: "both",
}
diff --git a/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java b/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java
index 51d57f0..f5d4b0c 100644
--- a/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java
+++ b/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java
@@ -24,15 +24,20 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.hardware.usb.IUsbManagerInternal;
import android.hardware.usb.IUsbOperationInternal;
import android.hardware.usb.flags.Flags;
import android.hardware.usb.UsbPort;
@@ -70,7 +75,9 @@
@Mock
private IUsbOperationInternal mCallback;
- private static final String TEST_PORT_ID = "123";
+ private static final String TEST_PORT_ID = "1";
+
+ private static final String TEST_PORT_ID_2 = "2";
private static final int TEST_TRANSACTION_ID = 1;
@@ -84,7 +91,7 @@
private UsbService mUsbService;
- private UsbManagerInternal mUsbManagerInternal;
+ private IUsbManagerInternal mIUsbManagerInternal;
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -101,9 +108,9 @@
mUsbService = new UsbService(mContext, mUsbPortManager, mUsbAlsaManager,
mUserManager, mUsbSettingsManager);
- mUsbManagerInternal = LocalServices.getService(UsbManagerInternal.class);
- assertWithMessage("LocalServices.getService(UsbManagerInternal.class)")
- .that(mUsbManagerInternal).isNotNull();
+ mIUsbManagerInternal = LocalServices.getService(IUsbManagerInternal.class);
+ assertWithMessage("LocalServices.getService(IUsbManagerInternal.class)")
+ .that(mIUsbManagerInternal).isNotNull();
}
private void assertToggleUsbSuccessfully(int requester, boolean enable,
@@ -255,30 +262,42 @@
assertToggleUsbSuccessfully(TEST_INTERNAL_REQUESTER_REASON_1, true, false);
}
- /**
- * Verify USB Manager internal calls mPortManager to get UsbPorts
- */
@Test
- public void usbManagerInternal_getPorts_callsPortManager() {
- when(mUsbPortManager.getPorts()).thenReturn(new UsbPort[] {});
-
- UsbPort[] ports = mUsbManagerInternal.getPorts();
-
- verify(mUsbPortManager).getPorts();
- assertEquals(ports.length, 0);
+ public void usbManagerInternal_enableUsbDataSignal_successfullyEnabled() {
+ assertTrue(runInternalUsbDataSignalTest(true, true, true));
}
@Test
- public void usbManagerInternal_enableUsbData_successfullyEnable() {
- boolean desiredEnableState = true;
+ public void usbManagerInternal_enableUsbDataSignal_successfullyDisabled() {
+ assertTrue(runInternalUsbDataSignalTest(false, true, true));
+ }
- assertTrue(mUsbManagerInternal.enableUsbData(TEST_PORT_ID, desiredEnableState,
- TEST_TRANSACTION_ID, mCallback, TEST_INTERNAL_REQUESTER_REASON_1));
+ @Test
+ public void usbManagerInternal_enableUsbDataSignal_returnsFalseIfOnePortFails() {
+ assertFalse(runInternalUsbDataSignalTest(true, true, false));
+ }
- verify(mUsbPortManager).enableUsbData(TEST_PORT_ID,
- desiredEnableState, TEST_TRANSACTION_ID, mCallback, null);
- verifyZeroInteractions(mCallback);
- clearInvocations(mUsbPortManager);
- clearInvocations(mCallback);
+ private boolean runInternalUsbDataSignalTest(boolean desiredEnableState, boolean portOneSuccess,
+ boolean portTwoSuccess) {
+ UsbPort port = mock(UsbPort.class);
+ UsbPort port2 = mock(UsbPort.class);
+ when(port.getId()).thenReturn(TEST_PORT_ID);
+ when(port2.getId()).thenReturn(TEST_PORT_ID_2);
+ when(mUsbPortManager.getPorts()).thenReturn(new UsbPort[] { port, port2 });
+ when(mUsbPortManager.enableUsbData(eq(TEST_PORT_ID),
+ eq(desiredEnableState), anyInt(), any(IUsbOperationInternal.class), isNull()))
+ .thenReturn(portOneSuccess);
+ when(mUsbPortManager.enableUsbData(eq(TEST_PORT_ID_2),
+ eq(desiredEnableState), anyInt(), any(IUsbOperationInternal.class), isNull()))
+ .thenReturn(portTwoSuccess);
+ try {
+ boolean result = mIUsbManagerInternal.enableUsbDataSignal(desiredEnableState,
+ TEST_INTERNAL_REQUESTER_REASON_1);
+ clearInvocations(mUsbPortManager);
+ return result;
+ } catch(RemoteException e) {
+ fail("RemoteException thrown when calling enableUsbDataSignal");
+ return false;
+ }
}
}
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index a97f9a8..3cccbc4 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -41,6 +41,7 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -1088,6 +1089,10 @@
@Test
public void testGetRestrictedTransportsFromCarrierConfig() {
+ assumeTrue(
+ "Configuring restricted transport types is only allowed on a debuggable build",
+ Build.isDebuggable());
+
final Set<Integer> restrictedTransports = new ArraySet<>();
restrictedTransports.add(TRANSPORT_CELLULAR);
restrictedTransports.add(TRANSPORT_WIFI);
@@ -1109,6 +1114,10 @@
@Test
public void testGetRestrictedTransportsFromCarrierConfig_noRestrictPolicyConfigured() {
+ assumeTrue(
+ "Configuring restricted transport types is only allowed on a debuggable build",
+ Build.isDebuggable());
+
final Set<Integer> restrictedTransports = Collections.singleton(TRANSPORT_WIFI);
final PersistableBundleWrapper carrierConfig =
@@ -1123,6 +1132,10 @@
@Test
public void testGetRestrictedTransportsFromCarrierConfig_noCarrierConfig() {
+ assumeTrue(
+ "Configuring restricted transport types is only allowed on a debuggable build",
+ Build.isDebuggable());
+
final Set<Integer> restrictedTransports = Collections.singleton(TRANSPORT_WIFI);
final TelephonySubscriptionSnapshot lastSnapshot =
@@ -1134,6 +1147,10 @@
@Test
public void testGetRestrictedTransportsFromCarrierConfigAndVcnConfig() {
+ assumeTrue(
+ "Configuring restricted transport types is only allowed on a debuggable build",
+ Build.isDebuggable());
+
// Configure restricted transport in CarrierConfig
final Set<Integer> restrictedTransportInCarrierConfig =
Collections.singleton(TRANSPORT_WIFI);
diff --git a/tools/codegen/OWNERS b/tools/codegen/OWNERS
index c9bd260..e69de29 100644
--- a/tools/codegen/OWNERS
+++ b/tools/codegen/OWNERS
@@ -1 +0,0 @@
-chiuwinson@google.com
diff --git a/tools/fonts/Android.bp b/tools/fonts/Android.bp
index f8629f9..07caa9a 100644
--- a/tools/fonts/Android.bp
+++ b/tools/fonts/Android.bp
@@ -23,11 +23,6 @@
python_defaults {
name: "fonts_python_defaults",
- version: {
- py3: {
- embedded_launcher: true,
- },
- },
}
python_binary_host {
diff --git a/tools/lint/fix/Android.bp b/tools/lint/fix/Android.bp
index ddacf57..43f2122 100644
--- a/tools/lint/fix/Android.bp
+++ b/tools/lint/fix/Android.bp
@@ -25,11 +25,6 @@
name: "lint_fix",
main: "soong_lint_fix.py",
srcs: ["soong_lint_fix.py"],
- version: {
- py3: {
- embedded_launcher: true,
- },
- },
}
python_library_host {
diff --git a/tools/lint/global/integration_tests/Android.bp b/tools/lint/global/integration_tests/Android.bp
index 05ba405..f887093 100644
--- a/tools/lint/global/integration_tests/Android.bp
+++ b/tools/lint/global/integration_tests/Android.bp
@@ -65,9 +65,4 @@
"AndroidGlobalLintTestNoAidl_py",
"AndroidGlobalLintTestMissingAnnotation_py",
],
- version: {
- py3: {
- embedded_launcher: true,
- },
- },
}