diff options
28 files changed, 1572 insertions, 1598 deletions
diff --git a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/Android.bp b/core/tests/batterystatstests/BatteryUsageStatsProtoTests/Android.bp new file mode 100644 index 000000000000..1fb5f2c0789b --- /dev/null +++ b/core/tests/batterystatstests/BatteryUsageStatsProtoTests/Android.bp @@ -0,0 +1,30 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "BatteryUsageStatsProtoTests", + srcs: ["src/**/*.java"], + + static_libs: [ + "androidx.test.rules", + "junit", + "mockito-target-minus-junit4", + "platform-test-annotations", + "platformprotosnano", + "statsdprotolite", + "truth", + ], + + libs: ["android.test.runner"], + + platform_apis: true, + certificate: "platform", + + test_suites: ["device-tests"], +} diff --git a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/AndroidManifest.xml b/core/tests/batterystatstests/BatteryUsageStatsProtoTests/AndroidManifest.xml new file mode 100644 index 000000000000..9128dca2080b --- /dev/null +++ b/core/tests/batterystatstests/BatteryUsageStatsProtoTests/AndroidManifest.xml @@ -0,0 +1,28 @@ +<?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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.frameworks.core.batteryusagestatsprototests"> + + <uses-permission android:name="android.permission.BATTERY_STATS"/> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.frameworks.core.batteryusagestatsprototests" + android:label="BatteryUsageStats Proto Tests" /> + +</manifest> diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java b/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java index 62efbc3cfa35..ac1f7d0e345f 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java +++ b/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.server.power.stats; +package com.android.internal.os; import static android.os.BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE; @@ -23,262 +23,39 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; import android.os.AggregateBatteryConsumer; import android.os.BatteryConsumer; import android.os.BatteryUsageStats; -import android.os.Process; import android.os.UidBatteryConsumer; import android.os.nano.BatteryUsageStatsAtomsProto; import android.os.nano.BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsage; -import android.platform.test.ravenwood.RavenwoodRule; -import android.util.StatsEvent; import androidx.test.filters.SmallTest; -import com.android.server.am.BatteryStatsService; - import com.google.protobuf.nano.InvalidProtocolBufferNanoException; -import org.junit.Rule; import org.junit.Test; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; -@SmallTest -public class BatteryUsageStatsAtomTest { - @Rule - public final RavenwoodRule mRavenwood = new RavenwoodRule(); +@SmallTest +public class BatteryUsageStatsPulledTest { private static final int UID_0 = 1000; private static final int UID_1 = 2000; private static final int UID_2 = 3000; private static final int UID_3 = 4000; + private static final int[] UID_USAGE_TIME_PROCESS_STATES = { + BatteryConsumer.PROCESS_STATE_FOREGROUND, + BatteryConsumer.PROCESS_STATE_BACKGROUND, + BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE + }; @Test - public void testAtom_BatteryUsageStatsPerUid() { - final BatteryUsageStats bus = buildBatteryUsageStats(); - BatteryStatsService.FrameworkStatsLogger statsLogger = - mock(BatteryStatsService.FrameworkStatsLogger.class); - - List<StatsEvent> actual = new ArrayList<>(); - new BatteryStatsService.StatsPerUidLogger(statsLogger).logStats(bus, actual); - - // Device-wide totals - verify(statsLogger).buildStatsEvent( - 1000L, - 20000L, - 10000L, - 20, - 1234L, - Process.INVALID_UID, - BatteryConsumer.PROCESS_STATE_UNSPECIFIED, - 0L, - "cpu", - 30000.0f, - 20100.0f, - 20300L - ); - verify(statsLogger).buildStatsEvent( - 1000L, - 20000L, - 10000L, - 20, - 1234L, - Process.INVALID_UID, - BatteryConsumer.PROCESS_STATE_UNSPECIFIED, - 0L, - "camera", - 30000.0f, - 20150.0f, - 0L - ); - verify(statsLogger).buildStatsEvent( - 1000L, - 20000L, - 10000L, - 20, - 1234L, - Process.INVALID_UID, - BatteryConsumer.PROCESS_STATE_UNSPECIFIED, - 0L, - "CustomConsumer1", - 30000.0f, - 20200.0f, - 20400L - ); - - // Per-proc state estimates for UID_0 - verify(statsLogger).buildStatsEvent( - 1000L, - 20000L, - 10000L, - 20, - 1234L, - UID_0, - BatteryConsumer.PROCESS_STATE_UNSPECIFIED, - 0L, - "screen", - 1650.0f, - 300.0f, - 0L - ); - verify(statsLogger).buildStatsEvent( - 1000L, - 20000L, - 10000L, - 20, - 1234L, - UID_0, - BatteryConsumer.PROCESS_STATE_UNSPECIFIED, - 0L, - "cpu", - 1650.0f, - 400.0f, - 600L - ); - verify(statsLogger).buildStatsEvent( - 1000L, - 20000L, - 10000L, - 20, - 1234L, - UID_0, - BatteryConsumer.PROCESS_STATE_FOREGROUND, - 1000L, - "cpu", - 1650.0f, - 9100.0f, - 8100L - ); - verify(statsLogger).buildStatsEvent( - 1000L, - 20000L, - 10000L, - 20, - 1234L, - UID_0, - BatteryConsumer.PROCESS_STATE_BACKGROUND, - 2000L, - "cpu", - 1650.0f, - 9200.0f, - 8200L - ); - verify(statsLogger).buildStatsEvent( - 1000L, - 20000L, - 10000L, - 20, - 1234L, - UID_0, - BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE, - 0L, - "cpu", - 1650.0f, - 9300.0f, - 8400L - ); - verify(statsLogger).buildStatsEvent( - 1000L, - 20000L, - 10000L, - 20, - 1234L, - UID_0, - BatteryConsumer.PROCESS_STATE_CACHED, - 0L, - "cpu", - 1650.0f, - 9400.0f, - 0L - ); - verify(statsLogger).buildStatsEvent( - 1000L, - 20000L, - 10000L, - 20, - 1234L, - UID_0, - BatteryConsumer.PROCESS_STATE_FOREGROUND, - 1000L, - "CustomConsumer1", - 1650.0f, - 450.0f, - 0L - ); - verify(statsLogger).buildStatsEvent( - 1000L, - 20000L, - 10000L, - 20, - 1234L, - UID_0, - BatteryConsumer.PROCESS_STATE_BACKGROUND, - 2000L, - "CustomConsumer1", - 1650.0f, - 450.0f, - 0L - ); - verify(statsLogger).buildStatsEvent( - 1000L, - 20000L, - 10000L, - 20, - 1234L, - UID_0, - BatteryConsumer.PROCESS_STATE_FOREGROUND, - 1000L, - "CustomConsumer2", - 1650.0f, - 500.0f, - 800L - ); - verify(statsLogger).buildStatsEvent( - 1000L, - 20000L, - 10000L, - 20, - 1234L, - UID_0, - BatteryConsumer.PROCESS_STATE_BACKGROUND, - 2000L, - "CustomConsumer2", - 1650.0f, - 500.0f, - 800L - ); - - // Nothing for UID_1, because its power consumption is 0 - - // Only "screen" is populated for UID_2 - verify(statsLogger).buildStatsEvent( - 1000L, - 20000L, - 10000L, - 20, - 1234L, - UID_2, - BatteryConsumer.PROCESS_STATE_UNSPECIFIED, - 0L, - "screen", - 766.0f, - 766.0f, - 0L - ); - - verifyNoMoreInteractions(statsLogger); - } - - @Test - public void testAtom_BatteryUsageStatsAtomsProto() { + public void testGetStatsProto() { final BatteryUsageStats bus = buildBatteryUsageStats(); final byte[] bytes = bus.getStatsProto(); BatteryUsageStatsAtomsProto proto; @@ -291,7 +68,9 @@ public class BatteryUsageStatsAtomTest { assertEquals(bus.getStatsStartTimestamp(), proto.sessionStartMillis); assertEquals(bus.getStatsEndTimestamp(), proto.sessionEndMillis); - assertEquals(10000, proto.sessionDurationMillis); + assertEquals( + bus.getStatsEndTimestamp() - bus.getStatsStartTimestamp(), + proto.sessionDurationMillis); assertEquals(bus.getDischargePercentage(), proto.sessionDischargePercentage); assertEquals(bus.getDischargeDurationMs(), proto.dischargeDurationMillis); @@ -311,8 +90,8 @@ public class BatteryUsageStatsAtomTest { final List<android.os.UidBatteryConsumer> uidConsumers = bus.getUidBatteryConsumers(); uidConsumers.sort((a, b) -> a.getUid() - b.getUid()); - final BatteryUsageStatsAtomsProto.UidBatteryConsumer[] uidConsumersProto = - proto.uidBatteryConsumers; + final BatteryUsageStatsAtomsProto.UidBatteryConsumer[] uidConsumersProto + = proto.uidBatteryConsumers; Arrays.sort(uidConsumersProto, (a, b) -> a.uid - b.uid); // UID_0 - After sorting, UID_0 should be in position 0 for both data structures @@ -407,12 +186,6 @@ public class BatteryUsageStatsAtomTest { } } - private static final int[] UID_USAGE_TIME_PROCESS_STATES = { - BatteryConsumer.PROCESS_STATE_FOREGROUND, - BatteryConsumer.PROCESS_STATE_BACKGROUND, - BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE - }; - private void assertSameUidBatteryConsumer( android.os.UidBatteryConsumer uidConsumer, BatteryUsageStatsAtomsProto.UidBatteryConsumer uidConsumerProto, @@ -422,10 +195,10 @@ public class BatteryUsageStatsAtomTest { assertEquals("Uid consumers had mismatched uids", uid, uidConsumer.getUid()); assertEquals("For uid " + uid, - uidConsumer.getTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_FOREGROUND), + uidConsumer.getTimeInStateMs(android.os.UidBatteryConsumer.STATE_FOREGROUND), uidConsumerProto.timeInForegroundMillis); assertEquals("For uid " + uid, - uidConsumer.getTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_BACKGROUND), + uidConsumer.getTimeInStateMs(android.os.UidBatteryConsumer.STATE_BACKGROUND), uidConsumerProto.timeInBackgroundMillis); for (int processState : UID_USAGE_TIME_PROCESS_STATES) { final long timeInStateMillis = uidConsumer.getTimeInProcessStateMs(processState); @@ -492,9 +265,7 @@ public class BatteryUsageStatsAtomTest { .setDischargePercentage(20) .setDischargedPowerRange(1000, 2000) .setDischargeDurationMs(1234) - .setStatsStartTimestamp(1000) - .setStatsEndTimestamp(20000) - .setStatsDuration(10000); + .setStatsStartTimestamp(1000); final UidBatteryConsumer.Builder uidBuilder = builder .getOrCreateUidBatteryConsumerBuilder(UID_0) .setPackageWithHighestDrain("myPackage0") 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 da886863cc1f..078694df3f5d 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 @@ -1574,7 +1574,7 @@ class DesktopTasksControllerTest : ShellTestCase() { Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION, ) - fun handleRequest_backTransition_singleActiveTaskNoToken_wallpaperDisabled_backNavDisabled_doesNotHandle() { + fun handleRequest_backTransition_singleTaskNoToken_noWallpaper_noBackNav_doesNotHandle() { val task = setUpFreeformTask() val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK)) @@ -1587,7 +1587,7 @@ class DesktopTasksControllerTest : ShellTestCase() { Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION ) - fun handleRequest_backTransition_singleActiveTaskNoToken_wallpaperEnabled_backNavEnabled_removesTask() { + fun handleRequest_backTransition_singleTaskNoToken_withWallpaper_withBackNav_removesTask() { val task = setUpFreeformTask() val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK)) @@ -1598,7 +1598,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) - fun handleRequest_backTransition_singleActiveTaskNoToken_backNavigationDisabled_doesNotHandle() { + fun handleRequest_backTransition_singleTaskNoToken_noBackNav_doesNotHandle() { val task = setUpFreeformTask() val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK)) @@ -1611,7 +1611,7 @@ class DesktopTasksControllerTest : ShellTestCase() { Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION ) - fun handleRequest_backTransition_singleActiveTaskWithToken_wallpaperDisabled_backNavDisabled_doesNotHandle() { + fun handleRequest_backTransition_singleTaskWithToken_noWallpaper_noBackNav_doesNotHandle() { val task = setUpFreeformTask() desktopModeTaskRepository.wallpaperActivityToken = MockToken().token() @@ -1625,7 +1625,7 @@ class DesktopTasksControllerTest : ShellTestCase() { Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION ) - fun handleRequest_backTransition_singleActiveTaskWithToken_wallpaperEnabled_backNavEnabled_removesWallpaperAndTask() { + fun handleRequest_backTransition_singleTask_withWallpaper_withBackNav_removesWallpaperAndTask() { val task = setUpFreeformTask() val wallpaperToken = MockToken().token() @@ -1640,7 +1640,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) - fun handleRequest_backTransition_singleActiveTaskWithToken_backNavigationDisabled_removesWallpaper() { + fun handleRequest_backTransition_singleTaskWithToken_noBackNav_removesWallpaper() { val task = setUpFreeformTask() val wallpaperToken = MockToken().token() @@ -1656,7 +1656,7 @@ class DesktopTasksControllerTest : ShellTestCase() { Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION ) - fun handleRequest_backTransition_multipleTasks_wallpaperDisabled_backNavDisabled_doesNotHandle() { + fun handleRequest_backTransition_multipleTasks_noWallpaper_noBackNav_doesNotHandle() { val task1 = setUpFreeformTask() setUpFreeformTask() @@ -1671,7 +1671,7 @@ class DesktopTasksControllerTest : ShellTestCase() { Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION ) - fun handleRequest_backTransition_multipleTasks_wallpaperEnabled_backNavEnabled_removesTask() { + fun handleRequest_backTransition_multipleTasks_withWallpaper_withBackNav_removesTask() { val task1 = setUpFreeformTask() setUpFreeformTask() @@ -1684,7 +1684,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) - fun handleRequest_backTransition_multipleTasks_backNavigationDisabled_doesNotHandle() { + fun handleRequest_backTransition_multipleTasks_noBackNav_doesNotHandle() { val task1 = setUpFreeformTask() setUpFreeformTask() @@ -1699,7 +1699,7 @@ class DesktopTasksControllerTest : ShellTestCase() { Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION ) - fun handleRequest_backTransition_multipleTasksSingleNonClosing_wallpaperEnabled_backNavEnabled_removesWallpaperAndTask() { + fun handleRequest_backTransition_multipleTasksSingleNonClosing_removesWallpaperAndTask() { val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val wallpaperToken = MockToken().token() @@ -1716,7 +1716,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) - fun handleRequest_backTransition_multipleTasksSingleNonClosing_backNavigationDisabled_removesWallpaper() { + fun handleRequest_backTransition_multipleTasksSingleNonClosing_noBackNav_removesWallpaper() { val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val wallpaperToken = MockToken().token() @@ -1734,7 +1734,7 @@ class DesktopTasksControllerTest : ShellTestCase() { Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION ) - fun handleRequest_backTransition_multipleTasksSingleNonMinimized_wallpaperEnabled_backNavEnabled_removesWallpaperAndTask() { + fun handleRequest_backTransition_multipleTasksSingleNonMinimized_removesWallpaperAndTask() { val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val wallpaperToken = MockToken().token() @@ -1751,7 +1751,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) - fun handleRequest_backTransition_multipleTasksSingleNonMinimized_backNavigationDisabled_removesWallpaper() { + fun handleRequest_backTransition_multipleTasksSingleNonMinimized_noBackNav_removesWallpaper() { val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val wallpaperToken = MockToken().token() @@ -1769,7 +1769,7 @@ class DesktopTasksControllerTest : ShellTestCase() { Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION ) - fun handleRequest_backTransition_nonMinimizadTask_wallpaperEnabled_backNavEnabled_removesWallpaper() { + fun handleRequest_backTransition_nonMinimizadTask_withWallpaper_withBackNav_removesWallpaper() { val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val wallpaperToken = MockToken().token() @@ -1789,7 +1789,7 @@ class DesktopTasksControllerTest : ShellTestCase() { Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION ) - fun handleRequest_closeTransition_singleActiveTaskNoToken_wallpaperDisabled_backNavDisabled_doesNotHandle() { + fun handleRequest_closeTransition_singleTaskNoToken_noWallpaper_noBackNav_doesNotHandle() { val task = setUpFreeformTask() val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE)) @@ -1802,7 +1802,7 @@ class DesktopTasksControllerTest : ShellTestCase() { Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION ) - fun handleRequest_closeTransition_singleActiveTaskNoToken_wallpaperEnabled_backNavEnabled_removesTask() { + fun handleRequest_closeTransition_singleTaskNoToken_withWallpaper_withBackNav_removesTask() { val task = setUpFreeformTask() val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE)) @@ -1813,7 +1813,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) - fun handleRequest_closeTransition_singleActiveTaskNoToken_backNavigationDisabled_doesNotHandle() { + fun handleRequest_closeTransition_singleTaskNoToken_noBackNav_doesNotHandle() { val task = setUpFreeformTask() val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE)) @@ -1826,7 +1826,7 @@ class DesktopTasksControllerTest : ShellTestCase() { Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION ) - fun handleRequest_closeTransition_singleActiveTaskWithToken_wallpaperDisabled_backNavDisabled_doesNotHandle() { + fun handleRequest_closeTransition_singleTaskWithToken_noWallpaper_noBackNav_doesNotHandle() { val task = setUpFreeformTask() desktopModeTaskRepository.wallpaperActivityToken = MockToken().token() @@ -1840,7 +1840,7 @@ class DesktopTasksControllerTest : ShellTestCase() { Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION ) - fun handleRequest_closeTransition_singleActiveTaskWithToken_wallpaperEnabled_backNavEnabled_removesWallpaperAndTask() { + fun handleRequest_closeTransition_singleTaskWithToken_removesWallpaperAndTask() { val task = setUpFreeformTask() val wallpaperToken = MockToken().token() @@ -1855,7 +1855,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) - fun handleRequest_closeTransition_singleActiveTaskWithToken_backNavigationDisabled_removesWallpaper() { + fun handleRequest_closeTransition_singleTaskWithToken_withWallpaper_noBackNav_removesWallpaper() { val task = setUpFreeformTask() val wallpaperToken = MockToken().token() @@ -1871,7 +1871,7 @@ class DesktopTasksControllerTest : ShellTestCase() { Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION ) - fun handleRequest_closeTransition_multipleTasks_wallpaperDisabled_backNavDisabled_doesNotHandle() { + fun handleRequest_closeTransition_multipleTasks_noWallpaper_noBackNav_doesNotHandle() { val task1 = setUpFreeformTask() setUpFreeformTask() @@ -1886,7 +1886,7 @@ class DesktopTasksControllerTest : ShellTestCase() { Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION ) - fun handleRequest_closeTransition_multipleTasks_wallpaperEnabled_backNavEnabled_removesTask() { + fun handleRequest_closeTransition_multipleTasks_withWallpaper_withBackNav_removesTask() { val task1 = setUpFreeformTask() setUpFreeformTask() @@ -1900,7 +1900,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) - fun handleRequest_closeTransition_multipleTasksFlagEnabled_backNavigationDisabled_doesNotHandle() { + fun handleRequest_closeTransition_multipleTasksFlagEnabled_noBackNav_doesNotHandle() { val task1 = setUpFreeformTask() setUpFreeformTask() @@ -1915,7 +1915,7 @@ class DesktopTasksControllerTest : ShellTestCase() { Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION ) - fun handleRequest_closeTransition_multipleTasksSingleNonClosing_wallpaperEnabled_backNavEnabled_removesWallpaperAndTask() { + fun handleRequest_closeTransition_multipleTasksSingleNonClosing_removesWallpaperAndTask() { val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val wallpaperToken = MockToken().token() @@ -1932,7 +1932,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) - fun handleRequest_closeTransition_multipleTasksSingleNonClosing_backNavigationDisabled_removesWallpaper() { + fun handleRequest_closeTransition_multipleTasksSingleNonClosing_noBackNav_removesWallpaper() { val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val wallpaperToken = MockToken().token() @@ -1950,7 +1950,7 @@ class DesktopTasksControllerTest : ShellTestCase() { Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION ) - fun handleRequest_closeTransition_multipleTasksOneNonMinimized_wallpaperEnabled_backNavEnabled_removesWallpaperAndTask() { + fun handleRequest_closeTransition_multipleTasksOneNonMinimized_removesWallpaperAndTask() { val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val wallpaperToken = MockToken().token() @@ -1967,7 +1967,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) - fun handleRequest_closeTransition_multipleTasksSingleNonMinimized_backNavigationDisabled_removesWallpaper() { + fun handleRequest_closeTransition_multipleTasksSingleNonMinimized_noBackNav_removesWallpaper() { val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val wallpaperToken = MockToken().token() @@ -1985,7 +1985,7 @@ class DesktopTasksControllerTest : ShellTestCase() { Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION ) - fun handleRequest_closeTransition_minimizadTask_wallpaperEnabled_backNavEnabled_removesWallpaper() { + fun handleRequest_closeTransition_minimizadTask_withWallpaper_withBackNav_removesWallpaper() { val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val wallpaperToken = MockToken().token() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelTest.kt index be0d899fd946..9e696011e285 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelTest.kt @@ -18,8 +18,11 @@ package com.android.systemui.keyguard.ui.viewmodel +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.Flags as AConfigFlags import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -74,9 +77,9 @@ class AodAlphaViewModelTest : SysuiTestCase() { @Test @DisableSceneContainer + @DisableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun alpha_WhenNotGone_clockMigrationFlagIsOff_emitsKeyguardAlpha() = testScope.runTest { - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) val alpha by collectLastValue(underTest.alpha) keyguardTransitionRepository.sendTransitionSteps( @@ -186,9 +189,9 @@ class AodAlphaViewModelTest : SysuiTestCase() { @Test @DisableSceneContainer + @EnableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun alpha_whenGone_equalsZero() = testScope.runTest { - mSetFlagsRule.enableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) val alpha by collectLastValue(underTest.alpha) keyguardTransitionRepository.sendTransitionStep( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt index 63d06a409522..41c5b7332a4f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt @@ -18,6 +18,8 @@ package com.android.systemui.keyguard.ui.viewmodel +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 as AConfigFlags @@ -69,10 +71,11 @@ class AodBurnInViewModelTest : SysuiTestCase() { private val burnInFlow = MutableStateFlow(BurnInModel()) @Before + @DisableFlags( + AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, + AConfigFlags.FLAG_COMPOSE_LOCKSCREEN + ) fun setUp() { - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN) - MockitoAnnotations.initMocks(this) whenever(burnInInteractor.burnIn(anyInt(), anyInt())).thenReturn(burnInFlow) kosmos.burnInInteractor = burnInInteractor @@ -174,10 +177,9 @@ class AodBurnInViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun translationAndScale_whenFullyDozing_MigrationFlagOff_staysOutOfTopInset() = testScope.runTest { - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - burnInParameters = burnInParameters.copy( minViewY = 100, @@ -226,10 +228,9 @@ class AodBurnInViewModelTest : SysuiTestCase() { } @Test + @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun translationAndScale_whenFullyDozing_MigrationFlagOn_staysOutOfTopInset() = testScope.runTest { - mSetFlagsRule.enableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - burnInParameters = burnInParameters.copy( minViewY = 100, @@ -310,104 +311,99 @@ class AodBurnInViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN) + @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun translationAndScale_composeFlagOff_weatherLargeClock() = testBurnInViewModelForClocks( isSmallClock = false, isWeatherClock = true, expectedScaleOnly = false, - enableMigrateClocksToBlueprintFlag = true, - enableComposeLockscreenFlag = false ) @Test + @DisableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN) + @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun translationAndScale_composeFlagOff_weatherSmallClock() = testBurnInViewModelForClocks( isSmallClock = true, isWeatherClock = true, expectedScaleOnly = false, - enableMigrateClocksToBlueprintFlag = true, - enableComposeLockscreenFlag = false ) @Test + @DisableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN) + @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun translationAndScale_composeFlagOff_nonWeatherLargeClock() = testBurnInViewModelForClocks( isSmallClock = false, isWeatherClock = false, expectedScaleOnly = true, - enableMigrateClocksToBlueprintFlag = true, - enableComposeLockscreenFlag = false ) @Test + @DisableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN) + @EnableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun translationAndScale_composeFlagOff_nonWeatherSmallClock() = testBurnInViewModelForClocks( isSmallClock = true, isWeatherClock = false, expectedScaleOnly = false, - enableMigrateClocksToBlueprintFlag = true, - enableComposeLockscreenFlag = false ) @Test + @EnableFlags( + AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, + AConfigFlags.FLAG_COMPOSE_LOCKSCREEN + ) fun translationAndScale_composeFlagOn_weatherLargeClock() = testBurnInViewModelForClocks( isSmallClock = false, isWeatherClock = true, expectedScaleOnly = false, - enableMigrateClocksToBlueprintFlag = true, - enableComposeLockscreenFlag = true ) @Test + @EnableFlags( + AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, + AConfigFlags.FLAG_COMPOSE_LOCKSCREEN + ) fun translationAndScale_composeFlagOn_weatherSmallClock() = testBurnInViewModelForClocks( isSmallClock = true, isWeatherClock = true, expectedScaleOnly = false, - enableMigrateClocksToBlueprintFlag = true, - enableComposeLockscreenFlag = true ) @Test + @EnableFlags( + AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, + AConfigFlags.FLAG_COMPOSE_LOCKSCREEN + ) fun translationAndScale_composeFlagOn_nonWeatherLargeClock() = testBurnInViewModelForClocks( isSmallClock = false, isWeatherClock = false, expectedScaleOnly = true, - enableMigrateClocksToBlueprintFlag = true, - enableComposeLockscreenFlag = true ) @Test + @EnableFlags( + AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, + AConfigFlags.FLAG_COMPOSE_LOCKSCREEN + ) fun translationAndScale_composeFlagOn_nonWeatherSmallClock() = testBurnInViewModelForClocks( isSmallClock = true, isWeatherClock = false, expectedScaleOnly = false, - enableMigrateClocksToBlueprintFlag = true, - enableComposeLockscreenFlag = true ) private fun testBurnInViewModelForClocks( isSmallClock: Boolean, isWeatherClock: Boolean, expectedScaleOnly: Boolean, - enableMigrateClocksToBlueprintFlag: Boolean, - enableComposeLockscreenFlag: Boolean ) = testScope.runTest { - if (enableMigrateClocksToBlueprintFlag) { - mSetFlagsRule.enableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - } else { - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - } - - if (enableComposeLockscreenFlag) { - mSetFlagsRule.enableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN) - } else { - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN) - } if (isSmallClock) { keyguardClockRepository.setClockSize(ClockSize.SMALL) // we need the following step to update stateFlow value diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt new file mode 100644 index 000000000000..6ddc07432a16 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt @@ -0,0 +1,683 @@ +/* + * 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. + */ +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.statusbar.notification.collection.coordinator + +import android.app.Notification +import android.os.UserHandle +import android.platform.test.flag.junit.FlagsParameterization +import android.provider.Settings +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.DumpManager +import com.android.systemui.flags.andSceneContainer +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.log.logcatLogBuffer +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.scene.data.repository.Idle +import com.android.systemui.scene.data.repository.setTransition +import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener +import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository +import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow +import com.android.systemui.statusbar.policy.HeadsUpManager +import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener +import com.android.systemui.util.settings.FakeSettings +import com.google.common.truth.Truth.assertThat +import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +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.same +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters + +@SmallTest +@RunWith(ParameterizedAndroidJunit4::class) +class OriginalUnseenKeyguardCoordinatorTest(flags: FlagsParameterization) : SysuiTestCase() { + + private val kosmos = Kosmos() + + private val headsUpManager: HeadsUpManager = mock() + private val keyguardRepository = FakeKeyguardRepository() + private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository + private val notifPipeline: NotifPipeline = mock() + private val statusBarStateController: StatusBarStateController = mock() + + init { + mSetFlagsRule.setFlagsParameterization(flags) + } + + @Test + fun unseenFilterSuppressesSeenNotifWhileKeyguardShowing() { + // GIVEN: Keyguard is not showing, shade is expanded, and a notification is present + keyguardRepository.setKeyguardShowing(false) + whenever(statusBarStateController.isExpanded).thenReturn(true) + runKeyguardCoordinatorTest { + val fakeEntry = NotificationEntryBuilder().build() + collectionListener.onEntryAdded(fakeEntry) + + // WHEN: The keyguard is now showing + keyguardRepository.setKeyguardShowing(true) + testScheduler.runCurrent() + + // THEN: The notification is recognized as "seen" and is filtered out. + assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue() + + // WHEN: The keyguard goes away + keyguardRepository.setKeyguardShowing(false) + testScheduler.runCurrent() + + // THEN: The notification is shown regardless + assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse() + } + } + + @Test + fun unseenFilterStopsMarkingSeenNotifWhenTransitionToAod() { + // GIVEN: Keyguard is not showing, shade is not expanded, and a notification is present + keyguardRepository.setKeyguardShowing(false) + whenever(statusBarStateController.isExpanded).thenReturn(false) + runKeyguardCoordinatorTest { + val fakeEntry = NotificationEntryBuilder().build() + collectionListener.onEntryAdded(fakeEntry) + + // WHEN: The device transitions to AOD + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.GONE, + to = KeyguardState.AOD, + this.testScheduler, + ) + testScheduler.runCurrent() + + // THEN: We are no longer listening for shade expansions + verify(statusBarStateController, never()).addCallback(any()) + } + } + + @Test + fun unseenFilter_headsUpMarkedAsSeen() { + // GIVEN: Keyguard is not showing, shade is not expanded + keyguardRepository.setKeyguardShowing(false) + whenever(statusBarStateController.isExpanded).thenReturn(false) + runKeyguardCoordinatorTest { + kosmos.setTransition( + sceneTransition = Idle(Scenes.Gone), + stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE) + ) + + // WHEN: A notification is posted + val fakeEntry = NotificationEntryBuilder().build() + collectionListener.onEntryAdded(fakeEntry) + + // WHEN: That notification is heads up + onHeadsUpChangedListener.onHeadsUpStateChanged(fakeEntry, /* isHeadsUp= */ true) + testScheduler.runCurrent() + + // WHEN: The keyguard is now showing + keyguardRepository.setKeyguardShowing(true) + kosmos.setTransition( + sceneTransition = Idle(Scenes.Lockscreen), + stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.AOD) + ) + + // THEN: The notification is recognized as "seen" and is filtered out. + assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue() + + // WHEN: The keyguard goes away + keyguardRepository.setKeyguardShowing(false) + kosmos.setTransition( + sceneTransition = Idle(Scenes.Gone), + stateTransition = TransitionStep(KeyguardState.AOD, KeyguardState.GONE) + ) + + // THEN: The notification is shown regardless + assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse() + } + } + + @Test + fun unseenFilterDoesNotSuppressSeenOngoingNotifWhileKeyguardShowing() { + // GIVEN: Keyguard is not showing, shade is expanded, and an ongoing notification is present + keyguardRepository.setKeyguardShowing(false) + whenever(statusBarStateController.isExpanded).thenReturn(true) + runKeyguardCoordinatorTest { + val fakeEntry = + NotificationEntryBuilder() + .setNotification(Notification.Builder(mContext, "id").setOngoing(true).build()) + .build() + collectionListener.onEntryAdded(fakeEntry) + + // WHEN: The keyguard is now showing + keyguardRepository.setKeyguardShowing(true) + testScheduler.runCurrent() + + // THEN: The notification is recognized as "ongoing" and is not filtered out. + assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse() + } + } + + @Test + fun unseenFilterDoesNotSuppressSeenMediaNotifWhileKeyguardShowing() { + // GIVEN: Keyguard is not showing, shade is expanded, and a media notification is present + keyguardRepository.setKeyguardShowing(false) + whenever(statusBarStateController.isExpanded).thenReturn(true) + runKeyguardCoordinatorTest { + val fakeEntry = + NotificationEntryBuilder().build().apply { + row = + mock<ExpandableNotificationRow>().apply { + whenever(isMediaRow).thenReturn(true) + } + } + collectionListener.onEntryAdded(fakeEntry) + + // WHEN: The keyguard is now showing + keyguardRepository.setKeyguardShowing(true) + testScheduler.runCurrent() + + // THEN: The notification is recognized as "media" and is not filtered out. + assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse() + } + } + + @Test + fun unseenFilterUpdatesSeenProviderWhenSuppressing() { + // GIVEN: Keyguard is not showing, shade is expanded, and a notification is present + keyguardRepository.setKeyguardShowing(false) + whenever(statusBarStateController.isExpanded).thenReturn(true) + runKeyguardCoordinatorTest { + val fakeEntry = NotificationEntryBuilder().build() + collectionListener.onEntryAdded(fakeEntry) + + // WHEN: The keyguard is now showing + keyguardRepository.setKeyguardShowing(true) + testScheduler.runCurrent() + + // THEN: The notification is recognized as "seen" and is filtered out. + assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue() + + // WHEN: The filter is cleaned up + unseenFilter.onCleanup() + + // THEN: The SeenNotificationProvider has been updated to reflect the suppression + assertThat(seenNotificationsInteractor.hasFilteredOutSeenNotifications.value).isTrue() + } + } + + @Test + fun unseenFilterInvalidatesWhenSettingChanges() { + // GIVEN: Keyguard is not showing, and shade is expanded + keyguardRepository.setKeyguardShowing(false) + whenever(statusBarStateController.isExpanded).thenReturn(true) + runKeyguardCoordinatorTest { + // GIVEN: A notification is present + val fakeEntry = NotificationEntryBuilder().build() + collectionListener.onEntryAdded(fakeEntry) + + // GIVEN: The setting for filtering unseen notifications is disabled + showOnlyUnseenNotifsOnKeyguardSetting = false + + // GIVEN: The pipeline has registered the unseen filter for invalidation + val invalidationListener: Pluggable.PluggableListener<NotifFilter> = mock() + unseenFilter.setInvalidationListener(invalidationListener) + + // WHEN: The keyguard is now showing + keyguardRepository.setKeyguardShowing(true) + testScheduler.runCurrent() + + // THEN: The notification is not filtered out + assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse() + + // WHEN: The secure setting is changed + showOnlyUnseenNotifsOnKeyguardSetting = true + + // THEN: The pipeline is invalidated + verify(invalidationListener).onPluggableInvalidated(same(unseenFilter), any()) + + // THEN: The notification is recognized as "seen" and is filtered out. + assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue() + } + } + + @Test + fun unseenFilterAllowsNewNotif() { + // GIVEN: Keyguard is showing, no notifications present + keyguardRepository.setKeyguardShowing(true) + runKeyguardCoordinatorTest { + // WHEN: A new notification is posted + val fakeEntry = NotificationEntryBuilder().build() + collectionListener.onEntryAdded(fakeEntry) + + // THEN: The notification is recognized as "unseen" and is not filtered out. + assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse() + } + } + + @Test + fun unseenFilterSeenGroupSummaryWithUnseenChild() { + // GIVEN: Keyguard is not showing, shade is expanded, and a notification is present + keyguardRepository.setKeyguardShowing(false) + whenever(statusBarStateController.isExpanded).thenReturn(true) + runKeyguardCoordinatorTest { + // WHEN: A new notification is posted + val fakeSummary = NotificationEntryBuilder().build() + val fakeChild = + NotificationEntryBuilder() + .setGroup(context, "group") + .setGroupSummary(context, false) + .build() + GroupEntryBuilder().setSummary(fakeSummary).addChild(fakeChild).build() + + collectionListener.onEntryAdded(fakeSummary) + collectionListener.onEntryAdded(fakeChild) + + // WHEN: Keyguard is now showing, both notifications are marked as seen + keyguardRepository.setKeyguardShowing(true) + testScheduler.runCurrent() + + // WHEN: The child notification is now unseen + collectionListener.onEntryUpdated(fakeChild) + + // THEN: The summary is not filtered out, because the child is unseen + assertThat(unseenFilter.shouldFilterOut(fakeSummary, 0L)).isFalse() + } + } + + @Test + fun unseenNotificationIsMarkedAsSeenWhenKeyguardGoesAway() { + // GIVEN: Keyguard is showing, not dozing, unseen notification is present + keyguardRepository.setKeyguardShowing(true) + keyguardRepository.setIsDozing(false) + runKeyguardCoordinatorTest { + val fakeEntry = NotificationEntryBuilder().build() + collectionListener.onEntryAdded(fakeEntry) + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + this.testScheduler, + ) + testScheduler.runCurrent() + + // WHEN: five seconds have passed + testScheduler.advanceTimeBy(5.seconds) + testScheduler.runCurrent() + + // WHEN: Keyguard is no longer showing + keyguardRepository.setKeyguardShowing(false) + kosmos.setTransition( + sceneTransition = Idle(Scenes.Gone), + stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE) + ) + + // WHEN: Keyguard is shown again + keyguardRepository.setKeyguardShowing(true) + kosmos.setTransition( + sceneTransition = Idle(Scenes.Lockscreen), + stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.AOD) + ) + + // THEN: The notification is now recognized as "seen" and is filtered out. + assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue() + } + } + + @Test + fun unseenNotificationIsNotMarkedAsSeenIfShadeNotExpanded() { + // GIVEN: Keyguard is showing, unseen notification is present + keyguardRepository.setKeyguardShowing(true) + runKeyguardCoordinatorTest { + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.GONE, + to = KeyguardState.LOCKSCREEN, + this.testScheduler, + ) + val fakeEntry = NotificationEntryBuilder().build() + collectionListener.onEntryAdded(fakeEntry) + + // WHEN: Keyguard is no longer showing + keyguardRepository.setKeyguardShowing(false) + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + this.testScheduler, + ) + + // WHEN: Keyguard is shown again + keyguardRepository.setKeyguardShowing(true) + testScheduler.runCurrent() + + // THEN: The notification is not recognized as "seen" and is not filtered out. + assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse() + } + } + + @Test + fun unseenNotificationIsNotMarkedAsSeenIfNotOnKeyguardLongEnough() { + // GIVEN: Keyguard is showing, not dozing, unseen notification is present + keyguardRepository.setKeyguardShowing(true) + keyguardRepository.setIsDozing(false) + runKeyguardCoordinatorTest { + kosmos.setTransition( + sceneTransition = Idle(Scenes.Lockscreen), + stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.LOCKSCREEN) + ) + val firstEntry = NotificationEntryBuilder().setId(1).build() + collectionListener.onEntryAdded(firstEntry) + testScheduler.runCurrent() + + // WHEN: one second has passed + testScheduler.advanceTimeBy(1.seconds) + testScheduler.runCurrent() + + // WHEN: another unseen notification is posted + val secondEntry = NotificationEntryBuilder().setId(2).build() + collectionListener.onEntryAdded(secondEntry) + testScheduler.runCurrent() + + // WHEN: four more seconds have passed + testScheduler.advanceTimeBy(4.seconds) + testScheduler.runCurrent() + + // WHEN: the keyguard is no longer showing + keyguardRepository.setKeyguardShowing(false) + kosmos.setTransition( + sceneTransition = Idle(Scenes.Gone), + stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE) + ) + + // WHEN: Keyguard is shown again + keyguardRepository.setKeyguardShowing(true) + kosmos.setTransition( + sceneTransition = Idle(Scenes.Lockscreen), + stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.LOCKSCREEN) + ) + + // THEN: The first notification is considered seen and is filtered out. + assertThat(unseenFilter.shouldFilterOut(firstEntry, 0L)).isTrue() + + // THEN: The second notification is still considered unseen and is not filtered out + assertThat(unseenFilter.shouldFilterOut(secondEntry, 0L)).isFalse() + } + } + + @Test + fun unseenNotificationOnKeyguardNotMarkedAsSeenIfRemovedAfterThreshold() { + // GIVEN: Keyguard is showing, not dozing + keyguardRepository.setKeyguardShowing(true) + keyguardRepository.setIsDozing(false) + runKeyguardCoordinatorTest { + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.GONE, + to = KeyguardState.LOCKSCREEN, + this.testScheduler, + ) + testScheduler.runCurrent() + + // WHEN: a new notification is posted + val entry = NotificationEntryBuilder().setId(1).build() + collectionListener.onEntryAdded(entry) + testScheduler.runCurrent() + + // WHEN: five more seconds have passed + testScheduler.advanceTimeBy(5.seconds) + testScheduler.runCurrent() + + // WHEN: the notification is removed + collectionListener.onEntryRemoved(entry, 0) + testScheduler.runCurrent() + + // WHEN: the notification is re-posted + collectionListener.onEntryAdded(entry) + testScheduler.runCurrent() + + // WHEN: one more second has passed + testScheduler.advanceTimeBy(1.seconds) + testScheduler.runCurrent() + + // WHEN: the keyguard is no longer showing + keyguardRepository.setKeyguardShowing(false) + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + this.testScheduler, + ) + testScheduler.runCurrent() + + // WHEN: Keyguard is shown again + keyguardRepository.setKeyguardShowing(true) + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.GONE, + to = KeyguardState.LOCKSCREEN, + this.testScheduler, + ) + testScheduler.runCurrent() + + // THEN: The notification is considered unseen and is not filtered out. + assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse() + } + } + + @Test + fun unseenNotificationOnKeyguardNotMarkedAsSeenIfRemovedBeforeThreshold() { + // GIVEN: Keyguard is showing, not dozing + keyguardRepository.setKeyguardShowing(true) + keyguardRepository.setIsDozing(false) + runKeyguardCoordinatorTest { + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.GONE, + to = KeyguardState.LOCKSCREEN, + this.testScheduler, + ) + testScheduler.runCurrent() + + // WHEN: a new notification is posted + val entry = NotificationEntryBuilder().setId(1).build() + collectionListener.onEntryAdded(entry) + testScheduler.runCurrent() + + // WHEN: one second has passed + testScheduler.advanceTimeBy(1.seconds) + testScheduler.runCurrent() + + // WHEN: the notification is removed + collectionListener.onEntryRemoved(entry, 0) + testScheduler.runCurrent() + + // WHEN: the notification is re-posted + collectionListener.onEntryAdded(entry) + testScheduler.runCurrent() + + // WHEN: one more second has passed + testScheduler.advanceTimeBy(1.seconds) + testScheduler.runCurrent() + + // WHEN: the keyguard is no longer showing + keyguardRepository.setKeyguardShowing(false) + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + this.testScheduler, + ) + testScheduler.runCurrent() + + // WHEN: Keyguard is shown again + keyguardRepository.setKeyguardShowing(true) + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.GONE, + to = KeyguardState.LOCKSCREEN, + this.testScheduler, + ) + testScheduler.runCurrent() + + // THEN: The notification is considered unseen and is not filtered out. + assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse() + } + } + + @Test + fun unseenNotificationOnKeyguardNotMarkedAsSeenIfUpdatedBeforeThreshold() { + // GIVEN: Keyguard is showing, not dozing + keyguardRepository.setKeyguardShowing(true) + keyguardRepository.setIsDozing(false) + runKeyguardCoordinatorTest { + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.GONE, + to = KeyguardState.LOCKSCREEN, + this.testScheduler, + ) + testScheduler.runCurrent() + + // WHEN: a new notification is posted + val entry = NotificationEntryBuilder().setId(1).build() + collectionListener.onEntryAdded(entry) + testScheduler.runCurrent() + + // WHEN: one second has passed + testScheduler.advanceTimeBy(1.seconds) + testScheduler.runCurrent() + + // WHEN: the notification is updated + collectionListener.onEntryUpdated(entry) + testScheduler.runCurrent() + + // WHEN: four more seconds have passed + testScheduler.advanceTimeBy(4.seconds) + testScheduler.runCurrent() + + // WHEN: the keyguard is no longer showing + keyguardRepository.setKeyguardShowing(false) + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + this.testScheduler, + ) + testScheduler.runCurrent() + + // WHEN: Keyguard is shown again + keyguardRepository.setKeyguardShowing(true) + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.GONE, + to = KeyguardState.LOCKSCREEN, + this.testScheduler, + ) + testScheduler.runCurrent() + + // THEN: The notification is considered unseen and is not filtered out. + assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse() + } + } + + private fun runKeyguardCoordinatorTest( + testBlock: suspend KeyguardCoordinatorTestScope.() -> Unit + ) { + val testDispatcher = UnconfinedTestDispatcher() + val testScope = TestScope(testDispatcher) + val fakeSettings = + FakeSettings().apply { + putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1) + } + val seenNotificationsInteractor = + SeenNotificationsInteractor(ActiveNotificationListRepository()) + val keyguardCoordinator = + OriginalUnseenKeyguardCoordinator( + testDispatcher, + mock<DumpManager>(), + headsUpManager, + keyguardRepository, + kosmos.keyguardTransitionInteractor, + KeyguardCoordinatorLogger(logcatLogBuffer()), + testScope.backgroundScope, + fakeSettings, + seenNotificationsInteractor, + statusBarStateController, + ) + keyguardCoordinator.attach(notifPipeline) + testScope.runTest { + KeyguardCoordinatorTestScope( + keyguardCoordinator, + testScope, + seenNotificationsInteractor, + fakeSettings, + ) + .testBlock() + } + } + + private inner class KeyguardCoordinatorTestScope( + private val keyguardCoordinator: OriginalUnseenKeyguardCoordinator, + private val scope: TestScope, + val seenNotificationsInteractor: SeenNotificationsInteractor, + private val fakeSettings: FakeSettings, + ) : CoroutineScope by scope { + val testScheduler: TestCoroutineScheduler + get() = scope.testScheduler + + val unseenFilter: NotifFilter + get() = keyguardCoordinator.unseenNotifFilter + + val collectionListener: NotifCollectionListener = + argumentCaptor { verify(notifPipeline).addCollectionListener(capture()) }.lastValue + + val onHeadsUpChangedListener: OnHeadsUpChangedListener + get() = argumentCaptor { verify(headsUpManager).addListener(capture()) }.lastValue + + var showOnlyUnseenNotifsOnKeyguardSetting: Boolean + get() = + fakeSettings.getIntForUser( + Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, + UserHandle.USER_CURRENT, + ) == 1 + set(value) { + fakeSettings.putIntForUser( + Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, + if (value) 1 else 2, + UserHandle.USER_CURRENT, + ) + } + } + + companion object { + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return FlagsParameterization.allCombinationsOf().andSceneContainer() + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt index 55c6790d4fb1..b1b2a653fde2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt @@ -16,62 +16,15 @@ package com.android.systemui.statusbar.notification.collection.coordinator -import android.app.NotificationManager -import android.os.UserHandle -import android.provider.Settings -import androidx.annotation.VisibleForTesting -import com.android.systemui.Dumpable -import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.dump.DumpManager -import com.android.systemui.keyguard.data.repository.KeyguardRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.plugins.statusbar.StatusBarStateController -import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.StatusBarState -import com.android.systemui.statusbar.expansionChanges -import com.android.systemui.statusbar.notification.collection.GroupEntry -import com.android.systemui.statusbar.notification.collection.ListEntry import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter -import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter -import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner -import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider -import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider -import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype -import com.android.systemui.statusbar.notification.stack.BUCKET_TOP_ONGOING -import com.android.systemui.statusbar.notification.stack.BUCKET_TOP_UNSEEN -import com.android.systemui.statusbar.policy.HeadsUpManager -import com.android.systemui.statusbar.policy.headsUpEvents -import com.android.systemui.util.asIndenting -import com.android.systemui.util.indentIfPossible -import com.android.systemui.util.settings.SecureSettings -import com.android.systemui.util.settings.SettingsProxyExt.observerFlow -import java.io.PrintWriter import javax.inject.Inject -import kotlin.time.Duration.Companion.seconds -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.conflate -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.launch -import kotlinx.coroutines.yield /** * Filters low priority and privacy-sensitive notifications from the lockscreen, and hides section @@ -82,24 +35,10 @@ import kotlinx.coroutines.yield class KeyguardCoordinator @Inject constructor( - @Background private val bgDispatcher: CoroutineDispatcher, - private val dumpManager: DumpManager, - private val headsUpManager: HeadsUpManager, private val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider, - private val keyguardRepository: KeyguardRepository, - private val keyguardTransitionInteractor: KeyguardTransitionInteractor, - private val logger: KeyguardCoordinatorLogger, - @Application private val scope: CoroutineScope, private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider, - private val secureSettings: SecureSettings, - private val seenNotificationsInteractor: SeenNotificationsInteractor, private val statusBarStateController: StatusBarStateController, -) : Coordinator, Dumpable { - - private val unseenNotifications = mutableSetOf<NotificationEntry>() - private val unseenEntryAdded = MutableSharedFlow<NotificationEntry>(extraBufferCapacity = 1) - private val unseenEntryRemoved = MutableSharedFlow<NotificationEntry>(extraBufferCapacity = 1) - private var unseenFilterEnabled = false +) : Coordinator { override fun attach(pipeline: NotifPipeline) { setupInvalidateNotifListCallbacks() @@ -107,385 +46,14 @@ constructor( pipeline.addFinalizeFilter(notifFilter) keyguardNotificationVisibilityProvider.addOnStateChangedListener(::invalidateListFromFilter) updateSectionHeadersVisibility() - attachUnseenFilter(pipeline) - } - - private fun attachUnseenFilter(pipeline: NotifPipeline) { - if (NotificationMinimalismPrototype.V2.isEnabled) { - pipeline.addPromoter(unseenNotifPromoter) - pipeline.addOnBeforeTransformGroupsListener(::pickOutTopUnseenNotifs) - } - pipeline.addFinalizeFilter(unseenNotifFilter) - pipeline.addCollectionListener(collectionListener) - scope.launch { trackUnseenFilterSettingChanges() } - dumpManager.registerDumpable(this) - } - - private suspend fun trackSeenNotifications() { - // Whether or not keyguard is visible (or occluded). - val isKeyguardPresent: Flow<Boolean> = - keyguardTransitionInteractor - .transitionValue( - scene = Scenes.Gone, - stateWithoutSceneContainer = KeyguardState.GONE, - ) - .map { it == 0f } - .distinctUntilChanged() - .onEach { trackingUnseen -> logger.logTrackingUnseen(trackingUnseen) } - - // Separately track seen notifications while the device is locked, applying once the device - // is unlocked. - val notificationsSeenWhileLocked = mutableSetOf<NotificationEntry>() - - // Use [collectLatest] to cancel any running jobs when [trackingUnseen] changes. - isKeyguardPresent.collectLatest { isKeyguardPresent: Boolean -> - if (isKeyguardPresent) { - // Keyguard is not gone, notifications need to be visible for a certain threshold - // before being marked as seen - trackSeenNotificationsWhileLocked(notificationsSeenWhileLocked) - } else { - // Mark all seen-while-locked notifications as seen for real. - if (notificationsSeenWhileLocked.isNotEmpty()) { - unseenNotifications.removeAll(notificationsSeenWhileLocked) - logger.logAllMarkedSeenOnUnlock( - seenCount = notificationsSeenWhileLocked.size, - remainingUnseenCount = unseenNotifications.size - ) - notificationsSeenWhileLocked.clear() - } - unseenNotifFilter.invalidateList("keyguard no longer showing") - // Keyguard is gone, notifications can be immediately marked as seen when they - // become visible. - trackSeenNotificationsWhileUnlocked() - } - } - } - - /** - * Keep [notificationsSeenWhileLocked] updated to represent which notifications have actually - * been "seen" while the device is on the keyguard. - */ - private suspend fun trackSeenNotificationsWhileLocked( - notificationsSeenWhileLocked: MutableSet<NotificationEntry>, - ) = coroutineScope { - // Remove removed notifications from the set - launch { - unseenEntryRemoved.collect { entry -> - if (notificationsSeenWhileLocked.remove(entry)) { - logger.logRemoveSeenOnLockscreen(entry) - } - } - } - // Use collectLatest so that the timeout delay is cancelled if the device enters doze, and - // is restarted when doze ends. - keyguardRepository.isDozing.collectLatest { isDozing -> - if (!isDozing) { - trackSeenNotificationsWhileLockedAndNotDozing(notificationsSeenWhileLocked) - } - } - } - - /** - * Keep [notificationsSeenWhileLocked] updated to represent which notifications have actually - * been "seen" while the device is on the keyguard and not dozing. Any new and existing unseen - * notifications are not marked as seen until they are visible for the [SEEN_TIMEOUT] duration. - */ - private suspend fun trackSeenNotificationsWhileLockedAndNotDozing( - notificationsSeenWhileLocked: MutableSet<NotificationEntry> - ) = coroutineScope { - // All child tracking jobs will be cancelled automatically when this is cancelled. - val trackingJobsByEntry = mutableMapOf<NotificationEntry, Job>() - - /** - * Wait for the user to spend enough time on the lock screen before removing notification - * from unseen set upon unlock. - */ - suspend fun trackSeenDurationThreshold(entry: NotificationEntry) { - if (notificationsSeenWhileLocked.remove(entry)) { - logger.logResetSeenOnLockscreen(entry) - } - delay(SEEN_TIMEOUT) - notificationsSeenWhileLocked.add(entry) - trackingJobsByEntry.remove(entry) - logger.logSeenOnLockscreen(entry) - } - - /** Stop any unseen tracking when a notification is removed. */ - suspend fun stopTrackingRemovedNotifs(): Nothing = - unseenEntryRemoved.collect { entry -> - trackingJobsByEntry.remove(entry)?.let { - it.cancel() - logger.logStopTrackingLockscreenSeenDuration(entry) - } - } - - /** Start tracking new notifications when they are posted. */ - suspend fun trackNewUnseenNotifs(): Nothing = coroutineScope { - unseenEntryAdded.collect { entry -> - logger.logTrackingLockscreenSeenDuration(entry) - // If this is an update, reset the tracking. - trackingJobsByEntry[entry]?.let { - it.cancel() - logger.logResetSeenOnLockscreen(entry) - } - trackingJobsByEntry[entry] = launch { trackSeenDurationThreshold(entry) } - } - } - - // Start tracking for all notifications that are currently unseen. - logger.logTrackingLockscreenSeenDuration(unseenNotifications) - unseenNotifications.forEach { entry -> - trackingJobsByEntry[entry] = launch { trackSeenDurationThreshold(entry) } - } - - launch { trackNewUnseenNotifs() } - launch { stopTrackingRemovedNotifs() } - } - - // Track "seen" notifications, marking them as such when either shade is expanded or the - // notification becomes heads up. - private suspend fun trackSeenNotificationsWhileUnlocked() { - coroutineScope { - launch { clearUnseenNotificationsWhenShadeIsExpanded() } - launch { markHeadsUpNotificationsAsSeen() } - } - } - - private suspend fun clearUnseenNotificationsWhenShadeIsExpanded() { - statusBarStateController.expansionChanges.collectLatest { isExpanded -> - // Give keyguard events time to propagate, in case this expansion is part of the - // keyguard transition and not the user expanding the shade - yield() - if (isExpanded) { - logger.logShadeExpanded() - unseenNotifications.clear() - } - } - } - - private suspend fun markHeadsUpNotificationsAsSeen() { - headsUpManager.allEntries - .filter { it.isRowPinned } - .forEach { unseenNotifications.remove(it) } - headsUpManager.headsUpEvents.collect { (entry, isHun) -> - if (isHun) { - logger.logUnseenHun(entry.key) - unseenNotifications.remove(entry) - } - } } - private fun unseenFeatureEnabled(): Flow<Boolean> { - if ( - NotificationMinimalismPrototype.V1.isEnabled || - NotificationMinimalismPrototype.V2.isEnabled - ) { - return flowOf(true) - } - return secureSettings - // emit whenever the setting has changed - .observerFlow( - UserHandle.USER_ALL, - Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, - ) - // perform a query immediately - .onStart { emit(Unit) } - // for each change, lookup the new value - .map { - secureSettings.getIntForUser( - name = Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, - def = 0, - userHandle = UserHandle.USER_CURRENT, - ) == 1 - } - // don't emit anything if nothing has changed - .distinctUntilChanged() - // perform lookups on the bg thread pool - .flowOn(bgDispatcher) - // only track the most recent emission, if events are happening faster than they can be - // consumed - .conflate() - } - - private suspend fun trackUnseenFilterSettingChanges() { - unseenFeatureEnabled().collectLatest { setting -> - // update local field and invalidate if necessary - if (setting != unseenFilterEnabled) { - unseenFilterEnabled = setting - unseenNotifFilter.invalidateList("unseen setting changed") - } - // if the setting is enabled, then start tracking and filtering unseen notifications - if (setting) { - trackSeenNotifications() - } - } - } - - private val collectionListener = - object : NotifCollectionListener { - override fun onEntryAdded(entry: NotificationEntry) { - if ( - keyguardRepository.isKeyguardShowing() || !statusBarStateController.isExpanded - ) { - logger.logUnseenAdded(entry.key) - unseenNotifications.add(entry) - unseenEntryAdded.tryEmit(entry) - } - } - - override fun onEntryUpdated(entry: NotificationEntry) { - if ( - keyguardRepository.isKeyguardShowing() || !statusBarStateController.isExpanded - ) { - logger.logUnseenUpdated(entry.key) - unseenNotifications.add(entry) - unseenEntryAdded.tryEmit(entry) - } - } - - override fun onEntryRemoved(entry: NotificationEntry, reason: Int) { - if (unseenNotifications.remove(entry)) { - logger.logUnseenRemoved(entry.key) - unseenEntryRemoved.tryEmit(entry) - } - } - } - - private fun pickOutTopUnseenNotifs(list: List<ListEntry>) { - if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) return - // Only ever elevate a top unseen notification on keyguard, not even locked shade - if (statusBarStateController.state != StatusBarState.KEYGUARD) { - seenNotificationsInteractor.setTopOngoingNotification(null) - seenNotificationsInteractor.setTopUnseenNotification(null) - return - } - // On keyguard pick the top-ranked unseen or ongoing notification to elevate - val nonSummaryEntries: Sequence<NotificationEntry> = - list - .asSequence() - .flatMap { - when (it) { - is NotificationEntry -> listOfNotNull(it) - is GroupEntry -> it.children - else -> error("unhandled type of $it") - } - } - .filter { it.importance >= NotificationManager.IMPORTANCE_DEFAULT } - seenNotificationsInteractor.setTopOngoingNotification( - nonSummaryEntries - .filter { ColorizedFgsCoordinator.isRichOngoing(it) } - .minByOrNull { it.ranking.rank } - ) - seenNotificationsInteractor.setTopUnseenNotification( - nonSummaryEntries - .filter { !ColorizedFgsCoordinator.isRichOngoing(it) && it in unseenNotifications } - .minByOrNull { it.ranking.rank } - ) - } - - @VisibleForTesting - internal val unseenNotifPromoter = - object : NotifPromoter("$TAG-unseen") { - override fun shouldPromoteToTopLevel(child: NotificationEntry): Boolean = - if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) false - else if (!NotificationMinimalismPrototype.V2.ungroupTopUnseen) false - else - seenNotificationsInteractor.isTopOngoingNotification(child) || - seenNotificationsInteractor.isTopUnseenNotification(child) - } - - val topOngoingSectioner = - object : NotifSectioner("TopOngoing", BUCKET_TOP_ONGOING) { - override fun isInSection(entry: ListEntry): Boolean { - if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) return false - return entry.anyEntry { notificationEntry -> - seenNotificationsInteractor.isTopOngoingNotification(notificationEntry) - } - } - } - - val topUnseenSectioner = - object : NotifSectioner("TopUnseen", BUCKET_TOP_UNSEEN) { - override fun isInSection(entry: ListEntry): Boolean { - if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) return false - return entry.anyEntry { notificationEntry -> - seenNotificationsInteractor.isTopUnseenNotification(notificationEntry) - } - } - } - - private fun ListEntry.anyEntry(predicate: (NotificationEntry?) -> Boolean) = - when { - predicate(representativeEntry) -> true - this !is GroupEntry -> false - else -> children.any(predicate) - } - - @VisibleForTesting - internal val unseenNotifFilter = - object : NotifFilter("$TAG-unseen") { - - var hasFilteredAnyNotifs = false - - /** - * Encapsulates a definition of "being on the keyguard". Note that these two definitions - * are wildly different: [StatusBarState.KEYGUARD] is when on the lock screen and does - * not include shade or occluded states, whereas [KeyguardRepository.isKeyguardShowing] - * is any state where the keyguard has not been dismissed, including locked shade and - * occluded lock screen. - * - * Returning false for locked shade and occluded states means that this filter will - * allow seen notifications to appear in the locked shade. - */ - private fun isOnKeyguard(): Boolean = - if (NotificationMinimalismPrototype.V2.isEnabled) { - false // disable this feature under this prototype - } else if ( - NotificationMinimalismPrototype.V1.isEnabled && - NotificationMinimalismPrototype.V1.showOnLockedShade - ) { - statusBarStateController.state == StatusBarState.KEYGUARD - } else { - keyguardRepository.isKeyguardShowing() - } - - override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean = - when { - // Don't apply filter if the setting is disabled - !unseenFilterEnabled -> false - // Don't apply filter if the keyguard isn't currently showing - !isOnKeyguard() -> false - // Don't apply the filter if the notification is unseen - unseenNotifications.contains(entry) -> false - // Don't apply the filter to (non-promoted) group summaries - // - summary will be pruned if necessary, depending on if children are filtered - entry.parent?.summary == entry -> false - // Check that the entry satisfies certain characteristics that would bypass the - // filter - shouldIgnoreUnseenCheck(entry) -> false - else -> true - }.also { hasFiltered -> hasFilteredAnyNotifs = hasFilteredAnyNotifs || hasFiltered } - - override fun onCleanup() { - logger.logProviderHasFilteredOutSeenNotifs(hasFilteredAnyNotifs) - seenNotificationsInteractor.setHasFilteredOutSeenNotifications(hasFilteredAnyNotifs) - hasFilteredAnyNotifs = false - } - } - private val notifFilter: NotifFilter = object : NotifFilter(TAG) { override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean = keyguardNotificationVisibilityProvider.shouldHideNotification(entry) } - private fun shouldIgnoreUnseenCheck(entry: NotificationEntry): Boolean = - when { - entry.isMediaNotification -> true - entry.sbn.isOngoing -> true - else -> false - } - // TODO(b/206118999): merge this class with SensitiveContentCoordinator which also depends on // these same updates private fun setupInvalidateNotifListCallbacks() {} @@ -502,22 +70,7 @@ constructor( sectionHeaderVisibilityProvider.sectionHeadersVisible = showSections } - override fun dump(pw: PrintWriter, args: Array<out String>) = - with(pw.asIndenting()) { - println( - "notificationListInteractor.hasFilteredOutSeenNotifications.value=" + - seenNotificationsInteractor.hasFilteredOutSeenNotifications.value - ) - println("unseen notifications:") - indentIfPossible { - for (notification in unseenNotifications) { - println(notification.key) - } - } - } - companion object { private const val TAG = "KeyguardCoordinator" - private val SEEN_TIMEOUT = 5.seconds } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt index e0389820aedf..99327d1fe116 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt @@ -17,7 +17,11 @@ package com.android.systemui.statusbar.notification.collection.coordinator import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED -import com.android.systemui.statusbar.notification.collection.* +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.NotificationClassificationFlag +import com.android.systemui.statusbar.notification.collection.PipelineDumpable +import com.android.systemui.statusbar.notification.collection.PipelineDumper +import com.android.systemui.statusbar.notification.collection.SortBySectionTimeFlag import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider @@ -42,6 +46,7 @@ constructor( hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator, hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator, keyguardCoordinator: KeyguardCoordinator, + unseenKeyguardCoordinator: OriginalUnseenKeyguardCoordinator, rankingCoordinator: RankingCoordinator, colorizedFgsCoordinator: ColorizedFgsCoordinator, deviceProvisionedCoordinator: DeviceProvisionedCoordinator, @@ -82,6 +87,7 @@ constructor( mCoordinators.add(hideLocallyDismissedNotifsCoordinator) mCoordinators.add(hideNotifsForOtherUsersCoordinator) mCoordinators.add(keyguardCoordinator) + mCoordinators.add(unseenKeyguardCoordinator) mCoordinators.add(rankingCoordinator) mCoordinators.add(colorizedFgsCoordinator) mCoordinators.add(deviceProvisionedCoordinator) @@ -115,11 +121,11 @@ constructor( // Manually add Ordered Sections if (NotificationMinimalismPrototype.V2.isEnabled) { - mOrderedSections.add(keyguardCoordinator.topOngoingSectioner) // Top Ongoing + mOrderedSections.add(unseenKeyguardCoordinator.topOngoingSectioner) // Top Ongoing } mOrderedSections.add(headsUpCoordinator.sectioner) // HeadsUp if (NotificationMinimalismPrototype.V2.isEnabled) { - mOrderedSections.add(keyguardCoordinator.topUnseenSectioner) // Top Unseen + mOrderedSections.add(unseenKeyguardCoordinator.topUnseenSectioner) // Top Unseen } mOrderedSections.add(colorizedFgsCoordinator.sectioner) // ForegroundService if (PriorityPeopleSection.isEnabled) { @@ -131,10 +137,10 @@ constructor( } mOrderedSections.add(rankingCoordinator.alertingSectioner) // Alerting if (NotificationClassificationFlag.isEnabled) { - mOrderedSections.add(bundleCoordinator.newsSectioner); - mOrderedSections.add(bundleCoordinator.socialSectioner); - mOrderedSections.add(bundleCoordinator.recsSectioner); - mOrderedSections.add(bundleCoordinator.promoSectioner); + mOrderedSections.add(bundleCoordinator.newsSectioner) + mOrderedSections.add(bundleCoordinator.socialSectioner) + mOrderedSections.add(bundleCoordinator.recsSectioner) + mOrderedSections.add(bundleCoordinator.promoSectioner) } mOrderedSections.add(rankingCoordinator.silentSectioner) // Silent mOrderedSections.add(rankingCoordinator.minimizedSectioner) // Minimized diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt new file mode 100644 index 000000000000..5dd1663f712f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt @@ -0,0 +1,490 @@ +/* + * 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.notification.collection.coordinator + +import android.annotation.SuppressLint +import android.app.NotificationManager +import android.os.UserHandle +import android.provider.Settings +import androidx.annotation.VisibleForTesting +import com.android.systemui.Dumpable +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dump.DumpManager +import com.android.systemui.keyguard.data.repository.KeyguardRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.statusbar.StatusBarState +import com.android.systemui.statusbar.expansionChanges +import com.android.systemui.statusbar.notification.collection.GroupEntry +import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener +import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor +import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype +import com.android.systemui.statusbar.notification.stack.BUCKET_TOP_ONGOING +import com.android.systemui.statusbar.notification.stack.BUCKET_TOP_UNSEEN +import com.android.systemui.statusbar.policy.HeadsUpManager +import com.android.systemui.statusbar.policy.headsUpEvents +import com.android.systemui.util.asIndenting +import com.android.systemui.util.indentIfPossible +import com.android.systemui.util.settings.SecureSettings +import com.android.systemui.util.settings.SettingsProxyExt.observerFlow +import java.io.PrintWriter +import javax.inject.Inject +import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.launch +import kotlinx.coroutines.yield + +/** + * Filters low priority and privacy-sensitive notifications from the lockscreen, and hides section + * headers on the lockscreen. If enabled, it will also track and hide seen notifications on the + * lockscreen. + */ +@CoordinatorScope +@SuppressLint("SharedFlowCreation") +class OriginalUnseenKeyguardCoordinator +@Inject +constructor( + @Background private val bgDispatcher: CoroutineDispatcher, + private val dumpManager: DumpManager, + private val headsUpManager: HeadsUpManager, + private val keyguardRepository: KeyguardRepository, + private val keyguardTransitionInteractor: KeyguardTransitionInteractor, + private val logger: KeyguardCoordinatorLogger, + @Application private val scope: CoroutineScope, + private val secureSettings: SecureSettings, + private val seenNotificationsInteractor: SeenNotificationsInteractor, + private val statusBarStateController: StatusBarStateController, +) : Coordinator, Dumpable { + + private val unseenNotifications = mutableSetOf<NotificationEntry>() + private val unseenEntryAdded = MutableSharedFlow<NotificationEntry>(extraBufferCapacity = 1) + private val unseenEntryRemoved = MutableSharedFlow<NotificationEntry>(extraBufferCapacity = 1) + private var unseenFilterEnabled = false + + override fun attach(pipeline: NotifPipeline) { + if (NotificationMinimalismPrototype.V2.isEnabled) { + pipeline.addPromoter(unseenNotifPromoter) + pipeline.addOnBeforeTransformGroupsListener(::pickOutTopUnseenNotifs) + } + pipeline.addFinalizeFilter(unseenNotifFilter) + pipeline.addCollectionListener(collectionListener) + scope.launch { trackUnseenFilterSettingChanges() } + dumpManager.registerDumpable(this) + } + + private suspend fun trackSeenNotifications() { + // Whether or not keyguard is visible (or occluded). + val isKeyguardPresentFlow: Flow<Boolean> = + keyguardTransitionInteractor + .transitionValue( + scene = Scenes.Gone, + stateWithoutSceneContainer = KeyguardState.GONE, + ) + .map { it == 0f } + .distinctUntilChanged() + .onEach { trackingUnseen -> logger.logTrackingUnseen(trackingUnseen) } + + // Separately track seen notifications while the device is locked, applying once the device + // is unlocked. + val notificationsSeenWhileLocked = mutableSetOf<NotificationEntry>() + + // Use [collectLatest] to cancel any running jobs when [trackingUnseen] changes. + isKeyguardPresentFlow.collectLatest { isKeyguardPresent: Boolean -> + if (isKeyguardPresent) { + // Keyguard is not gone, notifications need to be visible for a certain threshold + // before being marked as seen + trackSeenNotificationsWhileLocked(notificationsSeenWhileLocked) + } else { + // Mark all seen-while-locked notifications as seen for real. + if (notificationsSeenWhileLocked.isNotEmpty()) { + unseenNotifications.removeAll(notificationsSeenWhileLocked) + logger.logAllMarkedSeenOnUnlock( + seenCount = notificationsSeenWhileLocked.size, + remainingUnseenCount = unseenNotifications.size + ) + notificationsSeenWhileLocked.clear() + } + unseenNotifFilter.invalidateList("keyguard no longer showing") + // Keyguard is gone, notifications can be immediately marked as seen when they + // become visible. + trackSeenNotificationsWhileUnlocked() + } + } + } + + /** + * Keep [notificationsSeenWhileLocked] updated to represent which notifications have actually + * been "seen" while the device is on the keyguard. + */ + private suspend fun trackSeenNotificationsWhileLocked( + notificationsSeenWhileLocked: MutableSet<NotificationEntry>, + ) = coroutineScope { + // Remove removed notifications from the set + launch { + unseenEntryRemoved.collect { entry -> + if (notificationsSeenWhileLocked.remove(entry)) { + logger.logRemoveSeenOnLockscreen(entry) + } + } + } + // Use collectLatest so that the timeout delay is cancelled if the device enters doze, and + // is restarted when doze ends. + keyguardRepository.isDozing.collectLatest { isDozing -> + if (!isDozing) { + trackSeenNotificationsWhileLockedAndNotDozing(notificationsSeenWhileLocked) + } + } + } + + /** + * Keep [notificationsSeenWhileLocked] updated to represent which notifications have actually + * been "seen" while the device is on the keyguard and not dozing. Any new and existing unseen + * notifications are not marked as seen until they are visible for the [SEEN_TIMEOUT] duration. + */ + private suspend fun trackSeenNotificationsWhileLockedAndNotDozing( + notificationsSeenWhileLocked: MutableSet<NotificationEntry> + ) = coroutineScope { + // All child tracking jobs will be cancelled automatically when this is cancelled. + val trackingJobsByEntry = mutableMapOf<NotificationEntry, Job>() + + /** + * Wait for the user to spend enough time on the lock screen before removing notification + * from unseen set upon unlock. + */ + suspend fun trackSeenDurationThreshold(entry: NotificationEntry) { + if (notificationsSeenWhileLocked.remove(entry)) { + logger.logResetSeenOnLockscreen(entry) + } + delay(SEEN_TIMEOUT) + notificationsSeenWhileLocked.add(entry) + trackingJobsByEntry.remove(entry) + logger.logSeenOnLockscreen(entry) + } + + /** Stop any unseen tracking when a notification is removed. */ + suspend fun stopTrackingRemovedNotifs(): Nothing = + unseenEntryRemoved.collect { entry -> + trackingJobsByEntry.remove(entry)?.let { + it.cancel() + logger.logStopTrackingLockscreenSeenDuration(entry) + } + } + + /** Start tracking new notifications when they are posted. */ + suspend fun trackNewUnseenNotifs(): Nothing = coroutineScope { + unseenEntryAdded.collect { entry -> + logger.logTrackingLockscreenSeenDuration(entry) + // If this is an update, reset the tracking. + trackingJobsByEntry[entry]?.let { + it.cancel() + logger.logResetSeenOnLockscreen(entry) + } + trackingJobsByEntry[entry] = launch { trackSeenDurationThreshold(entry) } + } + } + + // Start tracking for all notifications that are currently unseen. + logger.logTrackingLockscreenSeenDuration(unseenNotifications) + unseenNotifications.forEach { entry -> + trackingJobsByEntry[entry] = launch { trackSeenDurationThreshold(entry) } + } + + launch { trackNewUnseenNotifs() } + launch { stopTrackingRemovedNotifs() } + } + + // Track "seen" notifications, marking them as such when either shade is expanded or the + // notification becomes heads up. + private suspend fun trackSeenNotificationsWhileUnlocked() { + coroutineScope { + launch { clearUnseenNotificationsWhenShadeIsExpanded() } + launch { markHeadsUpNotificationsAsSeen() } + } + } + + private suspend fun clearUnseenNotificationsWhenShadeIsExpanded() { + statusBarStateController.expansionChanges.collectLatest { isExpanded -> + // Give keyguard events time to propagate, in case this expansion is part of the + // keyguard transition and not the user expanding the shade + yield() + if (isExpanded) { + logger.logShadeExpanded() + unseenNotifications.clear() + } + } + } + + private suspend fun markHeadsUpNotificationsAsSeen() { + headsUpManager.allEntries + .filter { it.isRowPinned } + .forEach { unseenNotifications.remove(it) } + headsUpManager.headsUpEvents.collect { (entry, isHun) -> + if (isHun) { + logger.logUnseenHun(entry.key) + unseenNotifications.remove(entry) + } + } + } + + private fun unseenFeatureEnabled(): Flow<Boolean> { + if ( + NotificationMinimalismPrototype.V1.isEnabled || + NotificationMinimalismPrototype.V2.isEnabled + ) { + return flowOf(true) + } + return secureSettings + // emit whenever the setting has changed + .observerFlow( + UserHandle.USER_ALL, + Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, + ) + // perform a query immediately + .onStart { emit(Unit) } + // for each change, lookup the new value + .map { + secureSettings.getIntForUser( + name = Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, + def = 0, + userHandle = UserHandle.USER_CURRENT, + ) == 1 + } + // don't emit anything if nothing has changed + .distinctUntilChanged() + // perform lookups on the bg thread pool + .flowOn(bgDispatcher) + // only track the most recent emission, if events are happening faster than they can be + // consumed + .conflate() + } + + private suspend fun trackUnseenFilterSettingChanges() { + unseenFeatureEnabled().collectLatest { setting -> + // update local field and invalidate if necessary + if (setting != unseenFilterEnabled) { + unseenFilterEnabled = setting + unseenNotifFilter.invalidateList("unseen setting changed") + } + // if the setting is enabled, then start tracking and filtering unseen notifications + if (setting) { + trackSeenNotifications() + } + } + } + + private val collectionListener = + object : NotifCollectionListener { + override fun onEntryAdded(entry: NotificationEntry) { + if ( + keyguardRepository.isKeyguardShowing() || !statusBarStateController.isExpanded + ) { + logger.logUnseenAdded(entry.key) + unseenNotifications.add(entry) + unseenEntryAdded.tryEmit(entry) + } + } + + override fun onEntryUpdated(entry: NotificationEntry) { + if ( + keyguardRepository.isKeyguardShowing() || !statusBarStateController.isExpanded + ) { + logger.logUnseenUpdated(entry.key) + unseenNotifications.add(entry) + unseenEntryAdded.tryEmit(entry) + } + } + + override fun onEntryRemoved(entry: NotificationEntry, reason: Int) { + if (unseenNotifications.remove(entry)) { + logger.logUnseenRemoved(entry.key) + unseenEntryRemoved.tryEmit(entry) + } + } + } + + private fun pickOutTopUnseenNotifs(list: List<ListEntry>) { + if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) return + // Only ever elevate a top unseen notification on keyguard, not even locked shade + if (statusBarStateController.state != StatusBarState.KEYGUARD) { + seenNotificationsInteractor.setTopOngoingNotification(null) + seenNotificationsInteractor.setTopUnseenNotification(null) + return + } + // On keyguard pick the top-ranked unseen or ongoing notification to elevate + val nonSummaryEntries: Sequence<NotificationEntry> = + list + .asSequence() + .flatMap { + when (it) { + is NotificationEntry -> listOfNotNull(it) + is GroupEntry -> it.children + else -> error("unhandled type of $it") + } + } + .filter { it.importance >= NotificationManager.IMPORTANCE_DEFAULT } + seenNotificationsInteractor.setTopOngoingNotification( + nonSummaryEntries + .filter { ColorizedFgsCoordinator.isRichOngoing(it) } + .minByOrNull { it.ranking.rank } + ) + seenNotificationsInteractor.setTopUnseenNotification( + nonSummaryEntries + .filter { !ColorizedFgsCoordinator.isRichOngoing(it) && it in unseenNotifications } + .minByOrNull { it.ranking.rank } + ) + } + + @VisibleForTesting + val unseenNotifPromoter = + object : NotifPromoter("$TAG-unseen") { + override fun shouldPromoteToTopLevel(child: NotificationEntry): Boolean = + if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) false + else if (!NotificationMinimalismPrototype.V2.ungroupTopUnseen) false + else + seenNotificationsInteractor.isTopOngoingNotification(child) || + seenNotificationsInteractor.isTopUnseenNotification(child) + } + + val topOngoingSectioner = + object : NotifSectioner("TopOngoing", BUCKET_TOP_ONGOING) { + override fun isInSection(entry: ListEntry): Boolean { + if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) return false + return entry.anyEntry { notificationEntry -> + seenNotificationsInteractor.isTopOngoingNotification(notificationEntry) + } + } + } + + val topUnseenSectioner = + object : NotifSectioner("TopUnseen", BUCKET_TOP_UNSEEN) { + override fun isInSection(entry: ListEntry): Boolean { + if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) return false + return entry.anyEntry { notificationEntry -> + seenNotificationsInteractor.isTopUnseenNotification(notificationEntry) + } + } + } + + private fun ListEntry.anyEntry(predicate: (NotificationEntry?) -> Boolean) = + when { + predicate(representativeEntry) -> true + this !is GroupEntry -> false + else -> children.any(predicate) + } + + @VisibleForTesting + val unseenNotifFilter = + object : NotifFilter("$TAG-unseen") { + + var hasFilteredAnyNotifs = false + + /** + * Encapsulates a definition of "being on the keyguard". Note that these two definitions + * are wildly different: [StatusBarState.KEYGUARD] is when on the lock screen and does + * not include shade or occluded states, whereas [KeyguardRepository.isKeyguardShowing] + * is any state where the keyguard has not been dismissed, including locked shade and + * occluded lock screen. + * + * Returning false for locked shade and occluded states means that this filter will + * allow seen notifications to appear in the locked shade. + */ + private fun isOnKeyguard(): Boolean = + if (NotificationMinimalismPrototype.V2.isEnabled) { + false // disable this feature under this prototype + } else if ( + NotificationMinimalismPrototype.V1.isEnabled && + NotificationMinimalismPrototype.V1.showOnLockedShade + ) { + statusBarStateController.state == StatusBarState.KEYGUARD + } else { + keyguardRepository.isKeyguardShowing() + } + + override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean = + when { + // Don't apply filter if the setting is disabled + !unseenFilterEnabled -> false + // Don't apply filter if the keyguard isn't currently showing + !isOnKeyguard() -> false + // Don't apply the filter if the notification is unseen + unseenNotifications.contains(entry) -> false + // Don't apply the filter to (non-promoted) group summaries + // - summary will be pruned if necessary, depending on if children are filtered + entry.parent?.summary == entry -> false + // Check that the entry satisfies certain characteristics that would bypass the + // filter + shouldIgnoreUnseenCheck(entry) -> false + else -> true + }.also { hasFiltered -> hasFilteredAnyNotifs = hasFilteredAnyNotifs || hasFiltered } + + override fun onCleanup() { + logger.logProviderHasFilteredOutSeenNotifs(hasFilteredAnyNotifs) + seenNotificationsInteractor.setHasFilteredOutSeenNotifications(hasFilteredAnyNotifs) + hasFilteredAnyNotifs = false + } + } + + private fun shouldIgnoreUnseenCheck(entry: NotificationEntry): Boolean = + when { + entry.isMediaNotification -> true + entry.sbn.isOngoing -> true + else -> false + } + + override fun dump(pw: PrintWriter, args: Array<out String>) = + with(pw.asIndenting()) { + println( + "notificationListInteractor.hasFilteredOutSeenNotifications.value=" + + seenNotificationsInteractor.hasFilteredOutSeenNotifications.value + ) + println("unseen notifications:") + indentIfPossible { + for (notification in unseenNotifications) { + println(notification.key) + } + } + } + + companion object { + private const val TAG = "OriginalUnseenKeyguardCoordinator" + private val SEEN_TIMEOUT = 5.seconds + } +} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt index 79933ee3b018..a94ef36bda29 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt @@ -16,6 +16,7 @@ package com.android.keyguard import android.content.BroadcastReceiver +import android.platform.test.annotations.DisableFlags import android.view.View import android.view.ViewTreeObserver import android.widget.FrameLayout @@ -263,9 +264,9 @@ class ClockEventControllerTest : SysuiTestCase() { } @Test + @DisableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun keyguardCallback_visibilityChanged_clockDozeCalled() = runBlocking(IMMEDIATE) { - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) val captor = argumentCaptor<KeyguardUpdateMonitorCallback>() verify(keyguardUpdateMonitor).registerCallback(capture(captor)) diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java index 1d7816848cd0..892375d002c1 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java @@ -29,6 +29,7 @@ import static org.mockito.Mockito.when; import android.database.ContentObserver; import android.os.UserHandle; +import android.platform.test.annotations.DisableFlags; import android.provider.Settings; import android.view.View; @@ -48,11 +49,10 @@ import org.mockito.verification.VerificationMode; @SmallTest @RunWith(AndroidJUnit4.class) +@DisableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchControllerBaseTest { @Test public void testInit_viewAlreadyAttached() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - mController.init(); verifyAttachment(times(1)); @@ -60,8 +60,6 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void testInit_viewNotYetAttached() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - ArgumentCaptor<View.OnAttachStateChangeListener> listenerArgumentCaptor = ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class); @@ -78,16 +76,12 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void testInitSubControllers() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - mController.init(); verify(mKeyguardSliceViewController).init(); } @Test public void testInit_viewDetached() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - ArgumentCaptor<View.OnAttachStateChangeListener> listenerArgumentCaptor = ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class); mController.init(); @@ -101,8 +95,6 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void testPluginPassesStatusBarState() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - ArgumentCaptor<ClockRegistry.ClockChangeListener> listenerArgumentCaptor = ArgumentCaptor.forClass(ClockRegistry.ClockChangeListener.class); @@ -116,8 +108,6 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void testSmartspaceEnabledRemovesKeyguardStatusArea() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - when(mSmartspaceController.isEnabled()).thenReturn(true); mController.init(); @@ -126,8 +116,6 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void onLocaleListChangedRebuildsSmartspaceView() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - when(mSmartspaceController.isEnabled()).thenReturn(true); mController.init(); @@ -138,8 +126,6 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void onLocaleListChanged_rebuildsSmartspaceViews_whenDecouplingEnabled() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - when(mSmartspaceController.isEnabled()).thenReturn(true); when(mSmartspaceController.isDateWeatherDecoupled()).thenReturn(true); mController.init(); @@ -153,8 +139,6 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void testSmartspaceDisabledShowsKeyguardStatusArea() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - when(mSmartspaceController.isEnabled()).thenReturn(false); mController.init(); @@ -163,8 +147,6 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void testRefresh() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - mController.refresh(); verify(mSmartspaceController).requestSmartspaceUpdate(); @@ -172,8 +154,6 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void testChangeToDoubleLineClockSetsSmallClock() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - when(mSecureSettings.getIntForUser(Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, 1, UserHandle.USER_CURRENT)) .thenReturn(0); @@ -197,15 +177,11 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void testGetClock_ForwardsToClock() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - assertEquals(mClockController, mController.getClock()); } @Test public void testGetLargeClockBottom_returnsExpectedValue() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - when(mLargeClockFrame.getVisibility()).thenReturn(View.VISIBLE); when(mLargeClockFrame.getHeight()).thenReturn(100); when(mSmallClockFrame.getHeight()).thenReturn(50); @@ -218,8 +194,6 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void testGetSmallLargeClockBottom_returnsExpectedValue() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - when(mLargeClockFrame.getVisibility()).thenReturn(View.GONE); when(mLargeClockFrame.getHeight()).thenReturn(100); when(mSmallClockFrame.getHeight()).thenReturn(50); @@ -232,16 +206,12 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void testGetClockBottom_nullClock_returnsZero() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - when(mClockEventController.getClock()).thenReturn(null); assertEquals(0, mController.getClockBottom(10)); } @Test public void testChangeLockscreenWeatherEnabledSetsWeatherViewVisible() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - when(mSmartspaceController.isWeatherEnabled()).thenReturn(true); ArgumentCaptor<ContentObserver> observerCaptor = ArgumentCaptor.forClass(ContentObserver.class); @@ -260,8 +230,6 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void testChangeClockDateWeatherEnabled_SetsDateWeatherViewVisibility() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - ArgumentCaptor<ClockRegistry.ClockChangeListener> listenerArgumentCaptor = ArgumentCaptor.forClass(ClockRegistry.ClockChangeListener.class); when(mSmartspaceController.isEnabled()).thenReturn(true); @@ -284,15 +252,11 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void testGetClock_nullClock_returnsNull() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - when(mClockEventController.getClock()).thenReturn(null); assertNull(mController.getClock()); } private void verifyAttachment(VerificationMode times) { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - verify(mClockRegistry, times).registerClockChangeListener( any(ClockRegistry.ClockChangeListener.class)); verify(mClockEventController, times).registerListeners(mView); @@ -300,8 +264,6 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void testSplitShadeEnabledSetToSmartspaceController() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - mController.setSplitShadeEnabled(true); verify(mSmartspaceController, times(1)).setSplitShadeEnabled(true); verify(mSmartspaceController, times(0)).setSplitShadeEnabled(false); @@ -309,8 +271,6 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro @Test public void testSplitShadeDisabledSetToSmartspaceController() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - mController.setSplitShadeEnabled(false); verify(mSmartspaceController, times(1)).setSplitShadeEnabled(false); verify(mSmartspaceController, times(0)).setSplitShadeEnabled(true); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java index 83443bee744e..0bf9d12a09d5 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java @@ -30,6 +30,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.content.Context; +import android.platform.test.annotations.DisableFlags; import android.testing.TestableLooper.RunWithLooper; import android.util.AttributeSet; import android.view.LayoutInflater; @@ -60,6 +61,7 @@ import org.mockito.MockitoAnnotations; // the main thread before acquiring a wake lock. This class is constructed when // the keyguard_clock_switch layout is inflated. @RunWithLooper(setAsMainLooper = true) +@DisableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public class KeyguardClockSwitchTest extends SysuiTestCase { @Mock ViewGroup mMockKeyguardSliceView; @@ -81,8 +83,6 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { @Before public void setUp() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - MockitoAnnotations.initMocks(this); when(mMockKeyguardSliceView.getContext()).thenReturn(mContext); when(mMockKeyguardSliceView.findViewById(R.id.keyguard_status_area)) diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerBaseTest.java index b09357f853b9..c51aa04fc7b6 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerBaseTest.java @@ -151,6 +151,7 @@ public class LegacyLockIconViewControllerBaseTest extends SysuiTestCase { if (!SceneContainerFlag.isEnabled()) { mSetFlagsRule.disableFlags(Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR); + //TODO move this to use @DisableFlags annotation if needed mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt index 693a87761b27..7cc91853a749 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.ui.view.layout.sections import android.graphics.Point +import android.platform.test.annotations.DisableFlags import android.view.WindowManager import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet @@ -106,8 +107,8 @@ class DefaultDeviceEntrySectionTest : SysuiTestCase() { } @Test + @DisableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun addViewsConditionally_migrateFlagOff() { - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR) mSetFlagsRule.disableFlags(AConfigFlags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) val constraintLayout = ConstraintLayout(context, null) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt index 201ee88cdd80..1c99eff0d328 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.ui.view.layout.sections +import android.platform.test.annotations.EnableFlags import android.view.View import android.widget.LinearLayout import androidx.constraintlayout.widget.ConstraintLayout @@ -48,6 +49,7 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidJUnit4::class) @SmallTest +@EnableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) class SmartspaceSectionTest : SysuiTestCase() { private lateinit var underTest: SmartspaceSection @Mock private lateinit var keyguardClockViewModel: KeyguardClockViewModel @@ -70,7 +72,6 @@ class SmartspaceSectionTest : SysuiTestCase() { @Before fun setup() { MockitoAnnotations.initMocks(this) - mSetFlagsRule.enableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) underTest = SmartspaceSection( mContext, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index b80d1a472a72..8a6b68f26f66 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -18,7 +18,6 @@ package com.android.systemui.shade; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; -import static com.android.keyguard.KeyguardClockSwitch.LARGE; import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer; import static com.google.common.truth.Truth.assertThat; @@ -404,7 +403,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mFeatureFlags.set(Flags.QS_USER_DETAIL_SHORTCUT, false); mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR); - mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR); mMainDispatcher = getMainDispatcher(); @@ -801,7 +799,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { .setHeadsUpAppearanceController(mock(HeadsUpAppearanceController.class)); verify(mNotificationStackScrollLayoutController) .setOnEmptySpaceClickListener(mEmptySpaceClickListenerCaptor.capture()); - verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true); reset(mKeyguardStatusViewController); when(mNotificationPanelViewControllerLazy.get()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java index 90e8ea5f34c4..905cc4cd13b2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -92,6 +92,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo * When the Back gesture starts (progress 0%), the scrim will stay at 100% scale (1.0f). */ @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testBackGesture_min_scrimAtMaxScale() { mNotificationPanelViewController.onBackProgressed(0.0f); verify(mScrimController).applyBackScaling(1.0f); @@ -101,6 +102,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo * When the Back gesture is at max (progress 100%), the scrim will be scaled to its minimum. */ @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testBackGesture_max_scrimAtMinScale() { mNotificationPanelViewController.onBackProgressed(1.0f); verify(mScrimController).applyBackScaling( @@ -108,6 +110,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void onNotificationHeightChangeWhileOnKeyguardWillComputeMaxKeyguardNotifications() { mStatusBarStateController.setState(KEYGUARD); ArgumentCaptor<OnHeightChangedListener> captor = @@ -124,6 +127,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void onNotificationHeightChangeWhileInShadeWillNotComputeMaxKeyguardNotifications() { mStatusBarStateController.setState(SHADE); ArgumentCaptor<OnHeightChangedListener> captor = @@ -140,6 +144,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void computeMaxKeyguardNotifications_lockscreenToShade_returnsExistingMax() { when(mAmbientState.getFractionToShade()).thenReturn(0.5f); mNotificationPanelViewController.setMaxDisplayedNotifications(-1); @@ -150,6 +155,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void computeMaxKeyguardNotifications_noTransition_updatesMax() { when(mAmbientState.getFractionToShade()).thenReturn(0f); mNotificationPanelViewController.setMaxDisplayedNotifications(-1); @@ -196,6 +202,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void getVerticalSpaceForLockscreenShelf_useLockIconBottomPadding_returnsShelfHeight() { enableSplitShade(/* enabled= */ false); setBottomPadding(/* stackScrollLayoutBottom= */ 100, @@ -213,6 +220,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void getVerticalSpaceForLockscreenShelf_useIndicationBottomPadding_returnsZero() { enableSplitShade(/* enabled= */ false); setBottomPadding(/* stackScrollLayoutBottom= */ 100, @@ -230,6 +238,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void getVerticalSpaceForLockscreenShelf_useAmbientBottomPadding_returnsZero() { enableSplitShade(/* enabled= */ false); setBottomPadding(/* stackScrollLayoutBottom= */ 100, @@ -247,6 +256,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void getVerticalSpaceForLockscreenShelf_useLockIconPadding_returnsLessThanShelfHeight() { enableSplitShade(/* enabled= */ false); setBottomPadding(/* stackScrollLayoutBottom= */ 100, @@ -264,6 +274,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void getVerticalSpaceForLockscreenShelf_splitShade() { enableSplitShade(/* enabled= */ true); setBottomPadding(/* stackScrollLayoutBottom= */ 100, @@ -281,6 +292,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testSetPanelScrimMinFractionWhenHeadsUpIsDragged() { mNotificationPanelViewController.setHeadsUpDraggingStartingHeight( mNotificationPanelViewController.getMaxPanelHeight() / 2); @@ -288,6 +300,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testSetDozing_notifiesNsslAndStateController() { mNotificationPanelViewController.setDozing(true /* dozing */, false /* animate */); verify(mNotificationStackScrollLayoutController).setDozing(eq(true), eq(false)); @@ -295,6 +308,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testOnDozeAmountChanged_positionClockAndNotificationsUsesUdfpsLocation() { // GIVEN UDFPS is enrolled and we're on the keyguard final Point udfpsLocationCenter = new Point(0, 100); @@ -332,12 +346,14 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testSetExpandedHeight() { mNotificationPanelViewController.setExpandedHeight(200); assertThat((int) mNotificationPanelViewController.getExpandedHeight()).isEqualTo(200); } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testOnTouchEvent_expansionCanBeBlocked() { onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)); onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 200f, 0)); @@ -350,6 +366,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void test_pulsing_onTouchEvent_noTracking() { // GIVEN device is pulsing mNotificationPanelViewController.setPulsing(true); @@ -367,6 +384,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void alternateBouncerVisible_onTouchEvent_notHandled() { mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR); // GIVEN alternate bouncer is visible @@ -385,6 +403,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void test_onTouchEvent_startTracking() { // GIVEN device is NOT pulsing mNotificationPanelViewController.setPulsing(false); @@ -402,9 +421,8 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void onInterceptTouchEvent_nsslMigrationOff_userActivity() { - mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - mTouchHandler.onInterceptTouchEvent(MotionEvent.obtain(0L /* downTime */, 0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */, 0 /* metaState */)); @@ -413,9 +431,8 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @EnableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void onInterceptTouchEvent_nsslMigrationOn_userActivity_not_called() { - mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - mTouchHandler.onInterceptTouchEvent(MotionEvent.obtain(0L /* downTime */, 0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */, 0 /* metaState */)); @@ -424,6 +441,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testOnTouchEvent_expansionResumesAfterBriefTouch() { mFalsingManager.setIsClassifierEnabled(true); mFalsingManager.setIsFalseTouch(false); @@ -460,6 +478,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testA11y_initializeNode() { AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo(); mAccessibilityDelegate.onInitializeAccessibilityNodeInfo(mView, nodeInfo); @@ -473,6 +492,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testA11y_scrollForward() { mAccessibilityDelegate.performAccessibilityAction( mView, @@ -483,6 +503,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testA11y_scrollUp() { mAccessibilityDelegate.performAccessibilityAction( mView, @@ -493,6 +514,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testKeyguardStatusViewInSplitShade_changesConstraintsDependingOnNotifications() { mStatusBarStateController.setState(KEYGUARD); enableSplitShade(/* enabled= */ true); @@ -511,6 +533,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void keyguardStatusView_splitShade_dozing_alwaysDozingOn_isCentered() { when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2); when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true); @@ -523,6 +546,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void keyguardStatusView_splitShade_dozing_alwaysDozingOff_isNotCentered() { when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2); when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true); @@ -535,6 +559,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void keyguardStatusView_splitShade_notDozing_alwaysDozingOn_isNotCentered() { when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2); when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true); @@ -547,6 +572,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void keyguardStatusView_splitShade_pulsing_isNotCentered() { when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2); when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true); @@ -560,6 +586,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void keyguardStatusView_splitShade_notPulsing_isNotCentered() { when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2); when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true); @@ -573,6 +600,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void keyguardStatusView_singleShade_isCentered() { enableSplitShade(/* enabled= */ false); // The conditions below would make the clock NOT be centered on split shade. @@ -587,6 +615,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void keyguardStatusView_willPlayDelayedDoze_isCentered_thenNot() { when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2); when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true); @@ -602,6 +631,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void keyguardStatusView_willPlayDelayedDoze_notifiesKeyguardMediaController() { when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2); when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true); @@ -614,6 +644,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void keyguardStatusView_willPlayDelayedDoze_isCentered_thenStillCenteredIfNoNotifs() { when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0); when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(false); @@ -629,6 +660,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void onKeyguardStatusViewHeightChange_animatesNextTopPaddingChangeForNSSL() { ArgumentCaptor<View.OnLayoutChangeListener> captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener.class); @@ -646,6 +678,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testCanCollapsePanelOnTouch_trueForKeyGuard() { mStatusBarStateController.setState(KEYGUARD); @@ -653,6 +686,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testCanCollapsePanelOnTouch_trueWhenScrolledToBottom() { mStatusBarStateController.setState(SHADE); when(mNotificationStackScrollLayoutController.isScrolledToBottom()).thenReturn(true); @@ -661,6 +695,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testCanCollapsePanelOnTouch_trueWhenInSettings() { mStatusBarStateController.setState(SHADE); when(mQsController.getExpanded()).thenReturn(true); @@ -669,6 +704,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testCanCollapsePanelOnTouch_falseInDualPaneShade() { mStatusBarStateController.setState(SHADE); enableSplitShade(/* enabled= */ true); @@ -695,6 +731,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testCancelSwipeWhileLocked_notifiesKeyguardState() { mStatusBarStateController.setState(KEYGUARD); @@ -707,6 +744,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testSwipe_exactlyToTarget_notifiesNssl() { // No over-expansion mNotificationPanelViewController.setOverExpansion(0f); @@ -722,6 +760,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testRotatingToSplitShadeWithQsExpanded_transitionsToShadeLocked() { mStatusBarStateController.setState(KEYGUARD); when(mQsController.getExpanded()).thenReturn(true); @@ -732,6 +771,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testUnlockedSplitShadeTransitioningToKeyguard_closesQS() { enableSplitShade(true); mStatusBarStateController.setState(SHADE); @@ -741,6 +781,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testLockedSplitShadeTransitioningToKeyguard_closesQS() { enableSplitShade(true); mStatusBarStateController.setState(SHADE_LOCKED); @@ -750,6 +791,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testSwitchesToCorrectClockInSinglePaneShade() { mStatusBarStateController.setState(KEYGUARD); @@ -765,6 +807,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testSwitchesToCorrectClockInSplitShade() { mStatusBarStateController.setState(KEYGUARD); enableSplitShade(/* enabled= */ true); @@ -785,6 +828,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testHasNotifications_switchesToLargeClockWhenEnteringSplitShade() { mStatusBarStateController.setState(KEYGUARD); when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1); @@ -796,6 +840,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testNoNotifications_switchesToLargeClockWhenEnteringSplitShade() { mStatusBarStateController.setState(KEYGUARD); when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0); @@ -807,6 +852,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testHasNotifications_switchesToSmallClockWhenExitingSplitShade() { mStatusBarStateController.setState(KEYGUARD); enableSplitShade(/* enabled= */ true); @@ -820,6 +866,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testNoNotifications_switchesToLargeClockWhenExitingSplitShade() { mStatusBarStateController.setState(KEYGUARD); enableSplitShade(/* enabled= */ true); @@ -833,6 +880,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void clockSize_mediaShowing_inSplitShade_onAod_isLarge() { when(mDozeParameters.getAlwaysOn()).thenReturn(true); mStatusBarStateController.setState(KEYGUARD); @@ -848,6 +896,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void clockSize_mediaShowing_inSplitShade_screenOff_notAod_isSmall() { when(mDozeParameters.getAlwaysOn()).thenReturn(false); mStatusBarStateController.setState(KEYGUARD); @@ -863,6 +912,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void onQsSetExpansionHeightCalled_qsFullyExpandedOnKeyguard_showNSSL() { // GIVEN mStatusBarStateController.setState(KEYGUARD); @@ -883,6 +933,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void onQsSetExpansionHeightCalled_qsFullyExpandedOnKeyguard_hideNSSL() { // GIVEN mStatusBarStateController.setState(KEYGUARD); @@ -904,6 +955,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testSwitchesToBigClockInSplitShadeOnAodAnimateDisabled() { when(mScreenOffAnimationController.shouldAnimateClockChange()).thenReturn(false); mStatusBarStateController.setState(KEYGUARD); @@ -919,6 +971,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void switchesToBigClockInSplitShadeOn_landFlagOn_ForceSmallClock() { when(mScreenOffAnimationController.shouldAnimateClockChange()).thenReturn(false); mStatusBarStateController.setState(KEYGUARD); @@ -938,6 +991,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void switchesToBigClockInSplitShadeOn_landFlagOff_DontForceSmallClock() { when(mScreenOffAnimationController.shouldAnimateClockChange()).thenReturn(false); mStatusBarStateController.setState(KEYGUARD); @@ -957,6 +1011,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testDisplaysSmallClockOnLockscreenInSplitShadeWhenMediaIsPlaying() { mStatusBarStateController.setState(KEYGUARD); enableSplitShade(/* enabled= */ true); @@ -978,6 +1033,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testFoldToAodAnimationCleansupInAnimationEnd() { ArgumentCaptor<Animator.AnimatorListener> animCaptor = ArgumentCaptor.forClass(Animator.AnimatorListener.class); @@ -997,6 +1053,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testExpandWithQsMethodIsUsingLockscreenTransitionController() { enableSplitShade(/* enabled= */ true); mStatusBarStateController.setState(KEYGUARD); @@ -1008,6 +1065,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void setKeyguardStatusBarAlpha_setsAlphaOnKeyguardStatusBarController() { float statusBarAlpha = 0.5f; @@ -1017,6 +1075,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testQsToBeImmediatelyExpandedWhenOpeningPanelInSplitShade() { enableSplitShade(/* enabled= */ true); mShadeExpansionStateManager.updateState(STATE_OPEN); @@ -1030,6 +1089,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testQsNotToBeImmediatelyExpandedWhenGoingFromUnlockedToLocked() { enableSplitShade(/* enabled= */ true); mShadeExpansionStateManager.updateState(STATE_CLOSED); @@ -1042,6 +1102,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testQsImmediateResetsWhenPanelOpensOrCloses() { mShadeExpansionStateManager.updateState(STATE_OPEN); mShadeExpansionStateManager.updateState(STATE_CLOSED); @@ -1049,6 +1110,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testQsExpansionChangedToDefaultWhenRotatingFromOrToSplitShade() { when(mCommandQueue.panelsEnabled()).thenReturn(true); @@ -1065,6 +1127,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testPanelClosedWhenClosingQsInSplitShade() { mShadeExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 1, /* expanded= */ true, /* tracking= */ false); @@ -1078,6 +1141,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void getMaxPanelTransitionDistance_expanding_inSplitShade_returnsSplitShadeFullTransitionDistance() { enableSplitShade(true); mNotificationPanelViewController.expandToQs(); @@ -1088,6 +1152,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void isExpandingOrCollapsing_returnsTrue_whenQsLockscreenDragInProgress() { when(mQsController.getLockscreenShadeDragProgress()).thenReturn(0.5f); assertThat(mNotificationPanelViewController.isExpandingOrCollapsing()).isTrue(); @@ -1095,6 +1160,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void getMaxPanelTransitionDistance_inSplitShade_withHeadsUp_returnsBiggerValue() { enableSplitShade(true); mNotificationPanelViewController.expandToQs(); @@ -1111,6 +1177,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void getMaxPanelTransitionDistance_expandingSplitShade_keyguard_returnsNonSplitShadeValue() { mStatusBarStateController.setState(KEYGUARD); enableSplitShade(true); @@ -1122,6 +1189,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void getMaxPanelTransitionDistance_expanding_notSplitShade_returnsNonSplitShadeValue() { enableSplitShade(false); mNotificationPanelViewController.expandToQs(); @@ -1132,6 +1200,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void onLayoutChange_fullWidth_updatesQSWithFullWithTrue() { setIsFullWidth(true); @@ -1139,6 +1208,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void onLayoutChange_notFullWidth_updatesQSWithFullWithFalse() { setIsFullWidth(false); @@ -1146,6 +1216,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void onLayoutChange_qsNotSet_doesNotCrash() { mQuickSettingsController.setQs(null); @@ -1153,6 +1224,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void onEmptySpaceClicked_notDozingAndOnKeyguard_requestsFaceAuth() { StatusBarStateController.StateListener statusBarStateListener = mNotificationPanelViewController.getStatusBarStateListener(); @@ -1167,8 +1239,8 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @EnableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void nsslFlagEnabled_allowOnlyExternalTouches() { - mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); // This sets the dozing state that is read when onMiddleClicked is eventually invoked. mTouchHandler.onTouch(mock(View.class), mDownMotionEvent); @@ -1179,6 +1251,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void onSplitShadeChanged_duringShadeExpansion_resetsOverScrollState() { // There was a bug where there was left-over overscroll state after going from split shade // to single shade. @@ -1200,6 +1273,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void onSplitShadeChanged_alwaysResetsOverScrollState() { enableSplitShade(true); enableSplitShade(false); @@ -1217,6 +1291,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo * to ensure scrollY can be correctly set to be 0 */ @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void onShadeFlingClosingEnd_mAmbientStateSetClose_thenOnExpansionStopped() { // Given: Shade is expanded mNotificationPanelViewController.notifyExpandingFinished(); @@ -1237,6 +1312,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void onShadeFlingEnd_mExpandImmediateShouldBeReset() { mNotificationPanelViewController.onFlingEnd(false); @@ -1244,6 +1320,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void inUnlockedSplitShade_transitioningMaxTransitionDistance_makesShadeFullyExpanded() { mStatusBarStateController.setState(SHADE); enableSplitShade(true); @@ -1253,6 +1330,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void shadeFullyExpanded_inShadeState() { mStatusBarStateController.setState(SHADE); @@ -1265,6 +1343,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void shadeFullyExpanded_onKeyguard() { mStatusBarStateController.setState(KEYGUARD); @@ -1274,12 +1353,14 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void shadeFullyExpanded_onShadeLocked() { mStatusBarStateController.setState(SHADE_LOCKED); assertThat(mNotificationPanelViewController.isShadeFullyExpanded()).isTrue(); } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void shadeExpanded_whenHasHeight() { int transitionDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance(); mNotificationPanelViewController.setExpandedHeight(transitionDistance); @@ -1287,6 +1368,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void shadeExpanded_whenInstantExpanding() { mNotificationPanelViewController.expand(true); assertThat(mNotificationPanelViewController.isExpanded()).isTrue(); @@ -1300,12 +1382,14 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void shadeExpanded_whenUnlockedOffscreenAnimationRunning() { when(mUnlockedScreenOffAnimationController.isAnimationPlaying()).thenReturn(true); assertThat(mNotificationPanelViewController.isExpanded()).isTrue(); } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void shadeExpanded_whenInputFocusTransferStarted() { when(mCommandQueue.panelsEnabled()).thenReturn(true); @@ -1315,6 +1399,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void shadeNotExpanded_whenInputFocusTransferStartedButPanelsDisabled() { when(mCommandQueue.panelsEnabled()).thenReturn(false); @@ -1324,6 +1409,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void cancelInputFocusTransfer_shadeCollapsed() { when(mCommandQueue.panelsEnabled()).thenReturn(true); mNotificationPanelViewController.startInputFocusTransfer(); @@ -1334,6 +1420,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void finishInputFocusTransfer_shadeFlingingOpen() { when(mCommandQueue.panelsEnabled()).thenReturn(true); mNotificationPanelViewController.startInputFocusTransfer(); @@ -1344,6 +1431,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void getFalsingThreshold_deviceNotInteractive_isQsThreshold() { PowerInteractor.Companion.setAsleepForTest( mPowerInteractor, PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON); @@ -1353,6 +1441,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void getFalsingThreshold_lastWakeNotDueToTouch_isQsThreshold() { PowerInteractor.Companion.setAwakeForTest( mPowerInteractor, PowerManager.WAKE_REASON_POWER_BUTTON); @@ -1362,6 +1451,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void getFalsingThreshold_lastWakeDueToTouch_greaterThanQsThreshold() { PowerInteractor.Companion.setAwakeForTest(mPowerInteractor, PowerManager.WAKE_REASON_TAP); when(mQsController.getFalsingThreshold()).thenReturn(14); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt index e1d92e780c2a..52af907c7b7d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.shade +import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.testing.TestableLooper import android.view.HapticFeedbackConstants @@ -27,6 +28,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.util.CollectionUtils import com.android.keyguard.KeyguardClockSwitch.LARGE +import com.android.systemui.Flags import com.android.systemui.coroutines.collectLastValue import com.android.systemui.res.R import com.android.systemui.statusbar.StatusBarState.KEYGUARD @@ -58,6 +60,7 @@ import org.mockito.Mockito.verify @RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest +@DisableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) class NotificationPanelViewControllerWithCoroutinesTest : NotificationPanelViewControllerBaseTest() { 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 74a299910b18..6f2302a22d7b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -17,6 +17,8 @@ package com.android.systemui.shade import android.content.Context +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.platform.test.annotations.RequiresFlagsDisabled import android.platform.test.flag.junit.FlagsParameterization import android.testing.TestableLooper @@ -31,6 +33,7 @@ import com.android.keyguard.KeyguardSecurityContainerController import com.android.keyguard.LegacyLockIconViewController import com.android.keyguard.dagger.KeyguardBouncerComponent import com.android.systemui.Flags +import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor @@ -398,8 +401,8 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : @Test @DisableSceneContainer + @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun handleDispatchTouchEvent_nsslMigrationOff_userActivity_not_called() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) underTest.setStatusBarViewController(phoneStatusBarViewController) interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) @@ -408,8 +411,8 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : } @Test + @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun handleDispatchTouchEvent_nsslMigrationOn_userActivity() { - enableMigrateClocksFlag() underTest.setStatusBarViewController(phoneStatusBarViewController) interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) @@ -440,6 +443,7 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : } @Test + @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun shouldInterceptTouchEvent_dozing_touchInLockIconArea_touchNotIntercepted() { // GIVEN dozing whenever(sysuiStatusBarStateController.isDozing).thenReturn(true) @@ -452,13 +456,12 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : // AND the lock icon wants the touch whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT)).thenReturn(true) - enableMigrateClocksFlag() - // THEN touch should NOT be intercepted by NotificationShade assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isFalse() } @Test + @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun shouldInterceptTouchEvent_dozing_touchNotInLockIconArea_touchIntercepted() { // GIVEN dozing whenever(sysuiStatusBarStateController.isDozing).thenReturn(true) @@ -471,13 +474,12 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : whenever(quickSettingsController.shouldQuickSettingsIntercept(any(), any(), any())) .thenReturn(false) - enableMigrateClocksFlag() - // THEN touch should be intercepted by NotificationShade assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isTrue() } @Test + @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun shouldInterceptTouchEvent_dozing_touchInStatusBar_touchIntercepted() { // GIVEN dozing whenever(sysuiStatusBarStateController.isDozing).thenReturn(true) @@ -490,13 +492,12 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : whenever(quickSettingsController.shouldQuickSettingsIntercept(any(), any(), any())) .thenReturn(true) - enableMigrateClocksFlag() - // THEN touch should be intercepted by NotificationShade assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isTrue() } @Test + @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun shouldInterceptTouchEvent_dozingAndPulsing_touchIntercepted() { // GIVEN dozing whenever(sysuiStatusBarStateController.isDozing).thenReturn(true) @@ -517,8 +518,6 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : whenever(shadeViewController.handleExternalInterceptTouch(DOWN_EVENT)) .thenReturn(true) - enableMigrateClocksFlag() - // THEN touch should be intercepted by NotificationShade assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isTrue() } @@ -652,19 +651,13 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : } @Test + @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun cancelCurrentTouch_callsDragDownHelper() { - enableMigrateClocksFlag() underTest.cancelCurrentTouch() verify(dragDownHelper).stopDragging() } - private fun enableMigrateClocksFlag() { - if (!Flags.migrateClocksToBlueprint()) { - mSetFlagsRule.enableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - } - } - companion object { private val DOWN_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) private val MOVE_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt index fec7424ac006..ca29dd98a637 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.shade import android.os.SystemClock +import android.platform.test.annotations.DisableFlags import android.testing.TestableLooper.RunWithLooper import android.view.MotionEvent import android.widget.FrameLayout @@ -208,9 +209,9 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { } @Test + @DisableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun testDragDownHelperCalledWhenDraggingDown() = testScope.runTest { - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) whenever(dragDownHelper.isDraggingDown).thenReturn(true) val now = SystemClock.elapsedRealtime() val ev = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP, 0f, 0f, 0 /* meta */) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt index d87b3e23b471..4218be26c58e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt @@ -13,88 +13,57 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) package com.android.systemui.statusbar.notification.collection.coordinator -import android.app.Notification -import android.os.UserHandle -import android.platform.test.flag.junit.FlagsParameterization -import android.provider.Settings +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.flags.andSceneContainer -import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository -import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState -import com.android.systemui.keyguard.shared.model.TransitionStep -import com.android.systemui.kosmos.Kosmos -import com.android.systemui.log.logcatLogBuffer import com.android.systemui.plugins.statusbar.StatusBarStateController -import com.android.systemui.scene.data.repository.Idle -import com.android.systemui.scene.data.repository.setTransition -import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.StatusBarState -import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder import com.android.systemui.statusbar.notification.collection.NotifPipeline -import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder -import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter -import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable -import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider -import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository -import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider -import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow -import com.android.systemui.statusbar.policy.HeadsUpManager -import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.eq -import com.android.systemui.util.mockito.mock -import com.android.systemui.util.mockito.withArgCaptor -import com.android.systemui.util.settings.FakeSettings -import com.google.common.truth.Truth.assertThat import java.util.function.Consumer -import kotlin.time.Duration.Companion.seconds -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestCoroutineScheduler -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.same -import org.mockito.Mockito.anyString import org.mockito.Mockito.clearInvocations -import org.mockito.Mockito.never import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever -import platform.test.runner.parameterized.ParameterizedAndroidJunit4 -import platform.test.runner.parameterized.Parameters +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever @SmallTest -@RunWith(ParameterizedAndroidJunit4::class) -class KeyguardCoordinatorTest(flags: FlagsParameterization) : SysuiTestCase() { +@RunWith(AndroidJUnit4::class) +class KeyguardCoordinatorTest : SysuiTestCase() { - private val kosmos = Kosmos() - - private val headsUpManager: HeadsUpManager = mock() private val keyguardNotifVisibilityProvider: KeyguardNotificationVisibilityProvider = mock() - private val keyguardRepository = FakeKeyguardRepository() - private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository private val notifPipeline: NotifPipeline = mock() private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider = mock() private val statusBarStateController: StatusBarStateController = mock() - init { - mSetFlagsRule.setFlagsParameterization(flags) + private lateinit var onStateChangeListener: Consumer<String> + + @Before + fun setup() { + val keyguardCoordinator = + KeyguardCoordinator( + keyguardNotifVisibilityProvider, + sectionHeaderVisibilityProvider, + statusBarStateController, + ) + keyguardCoordinator.attach(notifPipeline) + onStateChangeListener = + argumentCaptor { + verify(keyguardNotifVisibilityProvider).addOnStateChangedListener(capture()) + } + .lastValue } @Test - fun testSetSectionHeadersVisibleInShade() = runKeyguardCoordinatorTest { + fun testSetSectionHeadersVisibleInShade() { clearInvocations(sectionHeaderVisibilityProvider) whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE) onStateChangeListener.accept("state change") @@ -102,617 +71,10 @@ class KeyguardCoordinatorTest(flags: FlagsParameterization) : SysuiTestCase() { } @Test - fun testSetSectionHeadersNotVisibleOnKeyguard() = runKeyguardCoordinatorTest { + fun testSetSectionHeadersNotVisibleOnKeyguard() { clearInvocations(sectionHeaderVisibilityProvider) whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) onStateChangeListener.accept("state change") verify(sectionHeaderVisibilityProvider).sectionHeadersVisible = eq(false) } - - @Test - fun unseenFilterSuppressesSeenNotifWhileKeyguardShowing() { - // GIVEN: Keyguard is not showing, shade is expanded, and a notification is present - keyguardRepository.setKeyguardShowing(false) - whenever(statusBarStateController.isExpanded).thenReturn(true) - runKeyguardCoordinatorTest { - val fakeEntry = NotificationEntryBuilder().build() - collectionListener.onEntryAdded(fakeEntry) - - // WHEN: The keyguard is now showing - keyguardRepository.setKeyguardShowing(true) - testScheduler.runCurrent() - - // THEN: The notification is recognized as "seen" and is filtered out. - assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue() - - // WHEN: The keyguard goes away - keyguardRepository.setKeyguardShowing(false) - testScheduler.runCurrent() - - // THEN: The notification is shown regardless - assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse() - } - } - - @Test - fun unseenFilterStopsMarkingSeenNotifWhenTransitionToAod() { - // GIVEN: Keyguard is not showing, shade is not expanded, and a notification is present - keyguardRepository.setKeyguardShowing(false) - whenever(statusBarStateController.isExpanded).thenReturn(false) - runKeyguardCoordinatorTest { - val fakeEntry = NotificationEntryBuilder().build() - collectionListener.onEntryAdded(fakeEntry) - - // WHEN: The device transitions to AOD - keyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.GONE, - to = KeyguardState.AOD, - this.testScheduler, - ) - testScheduler.runCurrent() - - // THEN: We are no longer listening for shade expansions - verify(statusBarStateController, never()).addCallback(any()) - } - } - - @Test - fun unseenFilter_headsUpMarkedAsSeen() { - // GIVEN: Keyguard is not showing, shade is not expanded - keyguardRepository.setKeyguardShowing(false) - whenever(statusBarStateController.isExpanded).thenReturn(false) - runKeyguardCoordinatorTest { - kosmos.setTransition( - sceneTransition = Idle(Scenes.Gone), - stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE) - ) - - // WHEN: A notification is posted - val fakeEntry = NotificationEntryBuilder().build() - collectionListener.onEntryAdded(fakeEntry) - - // WHEN: That notification is heads up - onHeadsUpChangedListener.onHeadsUpStateChanged(fakeEntry, /* isHeadsUp= */ true) - testScheduler.runCurrent() - - // WHEN: The keyguard is now showing - keyguardRepository.setKeyguardShowing(true) - kosmos.setTransition( - sceneTransition = Idle(Scenes.Lockscreen), - stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.AOD) - ) - - // THEN: The notification is recognized as "seen" and is filtered out. - assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue() - - // WHEN: The keyguard goes away - keyguardRepository.setKeyguardShowing(false) - kosmos.setTransition( - sceneTransition = Idle(Scenes.Gone), - stateTransition = TransitionStep(KeyguardState.AOD, KeyguardState.GONE) - ) - - // THEN: The notification is shown regardless - assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse() - } - } - - @Test - fun unseenFilterDoesNotSuppressSeenOngoingNotifWhileKeyguardShowing() { - // GIVEN: Keyguard is not showing, shade is expanded, and an ongoing notification is present - keyguardRepository.setKeyguardShowing(false) - whenever(statusBarStateController.isExpanded).thenReturn(true) - runKeyguardCoordinatorTest { - val fakeEntry = - NotificationEntryBuilder() - .setNotification(Notification.Builder(mContext, "id").setOngoing(true).build()) - .build() - collectionListener.onEntryAdded(fakeEntry) - - // WHEN: The keyguard is now showing - keyguardRepository.setKeyguardShowing(true) - testScheduler.runCurrent() - - // THEN: The notification is recognized as "ongoing" and is not filtered out. - assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse() - } - } - - @Test - fun unseenFilterDoesNotSuppressSeenMediaNotifWhileKeyguardShowing() { - // GIVEN: Keyguard is not showing, shade is expanded, and a media notification is present - keyguardRepository.setKeyguardShowing(false) - whenever(statusBarStateController.isExpanded).thenReturn(true) - runKeyguardCoordinatorTest { - val fakeEntry = - NotificationEntryBuilder().build().apply { - row = - mock<ExpandableNotificationRow>().apply { - whenever(isMediaRow).thenReturn(true) - } - } - collectionListener.onEntryAdded(fakeEntry) - - // WHEN: The keyguard is now showing - keyguardRepository.setKeyguardShowing(true) - testScheduler.runCurrent() - - // THEN: The notification is recognized as "media" and is not filtered out. - assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse() - } - } - - @Test - fun unseenFilterUpdatesSeenProviderWhenSuppressing() { - // GIVEN: Keyguard is not showing, shade is expanded, and a notification is present - keyguardRepository.setKeyguardShowing(false) - whenever(statusBarStateController.isExpanded).thenReturn(true) - runKeyguardCoordinatorTest { - val fakeEntry = NotificationEntryBuilder().build() - collectionListener.onEntryAdded(fakeEntry) - - // WHEN: The keyguard is now showing - keyguardRepository.setKeyguardShowing(true) - testScheduler.runCurrent() - - // THEN: The notification is recognized as "seen" and is filtered out. - assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue() - - // WHEN: The filter is cleaned up - unseenFilter.onCleanup() - - // THEN: The SeenNotificationProvider has been updated to reflect the suppression - assertThat(seenNotificationsInteractor.hasFilteredOutSeenNotifications.value).isTrue() - } - } - - @Test - fun unseenFilterInvalidatesWhenSettingChanges() { - // GIVEN: Keyguard is not showing, and shade is expanded - keyguardRepository.setKeyguardShowing(false) - whenever(statusBarStateController.isExpanded).thenReturn(true) - runKeyguardCoordinatorTest { - // GIVEN: A notification is present - val fakeEntry = NotificationEntryBuilder().build() - collectionListener.onEntryAdded(fakeEntry) - - // GIVEN: The setting for filtering unseen notifications is disabled - showOnlyUnseenNotifsOnKeyguardSetting = false - - // GIVEN: The pipeline has registered the unseen filter for invalidation - val invalidationListener: Pluggable.PluggableListener<NotifFilter> = mock() - unseenFilter.setInvalidationListener(invalidationListener) - - // WHEN: The keyguard is now showing - keyguardRepository.setKeyguardShowing(true) - testScheduler.runCurrent() - - // THEN: The notification is not filtered out - assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse() - - // WHEN: The secure setting is changed - showOnlyUnseenNotifsOnKeyguardSetting = true - - // THEN: The pipeline is invalidated - verify(invalidationListener).onPluggableInvalidated(same(unseenFilter), anyString()) - - // THEN: The notification is recognized as "seen" and is filtered out. - assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue() - } - } - - @Test - fun unseenFilterAllowsNewNotif() { - // GIVEN: Keyguard is showing, no notifications present - keyguardRepository.setKeyguardShowing(true) - runKeyguardCoordinatorTest { - // WHEN: A new notification is posted - val fakeEntry = NotificationEntryBuilder().build() - collectionListener.onEntryAdded(fakeEntry) - - // THEN: The notification is recognized as "unseen" and is not filtered out. - assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse() - } - } - - @Test - fun unseenFilterSeenGroupSummaryWithUnseenChild() { - // GIVEN: Keyguard is not showing, shade is expanded, and a notification is present - keyguardRepository.setKeyguardShowing(false) - whenever(statusBarStateController.isExpanded).thenReturn(true) - runKeyguardCoordinatorTest { - // WHEN: A new notification is posted - val fakeSummary = NotificationEntryBuilder().build() - val fakeChild = - NotificationEntryBuilder() - .setGroup(context, "group") - .setGroupSummary(context, false) - .build() - GroupEntryBuilder().setSummary(fakeSummary).addChild(fakeChild).build() - - collectionListener.onEntryAdded(fakeSummary) - collectionListener.onEntryAdded(fakeChild) - - // WHEN: Keyguard is now showing, both notifications are marked as seen - keyguardRepository.setKeyguardShowing(true) - testScheduler.runCurrent() - - // WHEN: The child notification is now unseen - collectionListener.onEntryUpdated(fakeChild) - - // THEN: The summary is not filtered out, because the child is unseen - assertThat(unseenFilter.shouldFilterOut(fakeSummary, 0L)).isFalse() - } - } - - @Test - fun unseenNotificationIsMarkedAsSeenWhenKeyguardGoesAway() { - // GIVEN: Keyguard is showing, not dozing, unseen notification is present - keyguardRepository.setKeyguardShowing(true) - keyguardRepository.setIsDozing(false) - runKeyguardCoordinatorTest { - val fakeEntry = NotificationEntryBuilder().build() - collectionListener.onEntryAdded(fakeEntry) - keyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.AOD, - to = KeyguardState.LOCKSCREEN, - this.testScheduler, - ) - testScheduler.runCurrent() - - // WHEN: five seconds have passed - testScheduler.advanceTimeBy(5.seconds) - testScheduler.runCurrent() - - // WHEN: Keyguard is no longer showing - keyguardRepository.setKeyguardShowing(false) - kosmos.setTransition( - sceneTransition = Idle(Scenes.Gone), - stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE) - ) - - // WHEN: Keyguard is shown again - keyguardRepository.setKeyguardShowing(true) - kosmos.setTransition( - sceneTransition = Idle(Scenes.Lockscreen), - stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.AOD) - ) - - // THEN: The notification is now recognized as "seen" and is filtered out. - assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue() - } - } - - @Test - fun unseenNotificationIsNotMarkedAsSeenIfShadeNotExpanded() { - // GIVEN: Keyguard is showing, unseen notification is present - keyguardRepository.setKeyguardShowing(true) - runKeyguardCoordinatorTest { - keyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.GONE, - to = KeyguardState.LOCKSCREEN, - this.testScheduler, - ) - val fakeEntry = NotificationEntryBuilder().build() - collectionListener.onEntryAdded(fakeEntry) - - // WHEN: Keyguard is no longer showing - keyguardRepository.setKeyguardShowing(false) - keyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.GONE, - this.testScheduler, - ) - - // WHEN: Keyguard is shown again - keyguardRepository.setKeyguardShowing(true) - testScheduler.runCurrent() - - // THEN: The notification is not recognized as "seen" and is not filtered out. - assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse() - } - } - - @Test - fun unseenNotificationIsNotMarkedAsSeenIfNotOnKeyguardLongEnough() { - // GIVEN: Keyguard is showing, not dozing, unseen notification is present - keyguardRepository.setKeyguardShowing(true) - keyguardRepository.setIsDozing(false) - runKeyguardCoordinatorTest { - kosmos.setTransition( - sceneTransition = Idle(Scenes.Lockscreen), - stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.LOCKSCREEN) - ) - val firstEntry = NotificationEntryBuilder().setId(1).build() - collectionListener.onEntryAdded(firstEntry) - testScheduler.runCurrent() - - // WHEN: one second has passed - testScheduler.advanceTimeBy(1.seconds) - testScheduler.runCurrent() - - // WHEN: another unseen notification is posted - val secondEntry = NotificationEntryBuilder().setId(2).build() - collectionListener.onEntryAdded(secondEntry) - testScheduler.runCurrent() - - // WHEN: four more seconds have passed - testScheduler.advanceTimeBy(4.seconds) - testScheduler.runCurrent() - - // WHEN: the keyguard is no longer showing - keyguardRepository.setKeyguardShowing(false) - kosmos.setTransition( - sceneTransition = Idle(Scenes.Gone), - stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE) - ) - - // WHEN: Keyguard is shown again - keyguardRepository.setKeyguardShowing(true) - kosmos.setTransition( - sceneTransition = Idle(Scenes.Lockscreen), - stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.LOCKSCREEN) - ) - - // THEN: The first notification is considered seen and is filtered out. - assertThat(unseenFilter.shouldFilterOut(firstEntry, 0L)).isTrue() - - // THEN: The second notification is still considered unseen and is not filtered out - assertThat(unseenFilter.shouldFilterOut(secondEntry, 0L)).isFalse() - } - } - - @Test - fun unseenNotificationOnKeyguardNotMarkedAsSeenIfRemovedAfterThreshold() { - // GIVEN: Keyguard is showing, not dozing - keyguardRepository.setKeyguardShowing(true) - keyguardRepository.setIsDozing(false) - runKeyguardCoordinatorTest { - keyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.GONE, - to = KeyguardState.LOCKSCREEN, - this.testScheduler, - ) - testScheduler.runCurrent() - - // WHEN: a new notification is posted - val entry = NotificationEntryBuilder().setId(1).build() - collectionListener.onEntryAdded(entry) - testScheduler.runCurrent() - - // WHEN: five more seconds have passed - testScheduler.advanceTimeBy(5.seconds) - testScheduler.runCurrent() - - // WHEN: the notification is removed - collectionListener.onEntryRemoved(entry, 0) - testScheduler.runCurrent() - - // WHEN: the notification is re-posted - collectionListener.onEntryAdded(entry) - testScheduler.runCurrent() - - // WHEN: one more second has passed - testScheduler.advanceTimeBy(1.seconds) - testScheduler.runCurrent() - - // WHEN: the keyguard is no longer showing - keyguardRepository.setKeyguardShowing(false) - keyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.GONE, - this.testScheduler, - ) - testScheduler.runCurrent() - - // WHEN: Keyguard is shown again - keyguardRepository.setKeyguardShowing(true) - keyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.GONE, - to = KeyguardState.LOCKSCREEN, - this.testScheduler, - ) - testScheduler.runCurrent() - - // THEN: The notification is considered unseen and is not filtered out. - assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse() - } - } - - @Test - fun unseenNotificationOnKeyguardNotMarkedAsSeenIfRemovedBeforeThreshold() { - // GIVEN: Keyguard is showing, not dozing - keyguardRepository.setKeyguardShowing(true) - keyguardRepository.setIsDozing(false) - runKeyguardCoordinatorTest { - keyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.GONE, - to = KeyguardState.LOCKSCREEN, - this.testScheduler, - ) - testScheduler.runCurrent() - - // WHEN: a new notification is posted - val entry = NotificationEntryBuilder().setId(1).build() - collectionListener.onEntryAdded(entry) - testScheduler.runCurrent() - - // WHEN: one second has passed - testScheduler.advanceTimeBy(1.seconds) - testScheduler.runCurrent() - - // WHEN: the notification is removed - collectionListener.onEntryRemoved(entry, 0) - testScheduler.runCurrent() - - // WHEN: the notification is re-posted - collectionListener.onEntryAdded(entry) - testScheduler.runCurrent() - - // WHEN: one more second has passed - testScheduler.advanceTimeBy(1.seconds) - testScheduler.runCurrent() - - // WHEN: the keyguard is no longer showing - keyguardRepository.setKeyguardShowing(false) - keyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.GONE, - this.testScheduler, - ) - testScheduler.runCurrent() - - // WHEN: Keyguard is shown again - keyguardRepository.setKeyguardShowing(true) - keyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.GONE, - to = KeyguardState.LOCKSCREEN, - this.testScheduler, - ) - testScheduler.runCurrent() - - // THEN: The notification is considered unseen and is not filtered out. - assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse() - } - } - - @Test - fun unseenNotificationOnKeyguardNotMarkedAsSeenIfUpdatedBeforeThreshold() { - // GIVEN: Keyguard is showing, not dozing - keyguardRepository.setKeyguardShowing(true) - keyguardRepository.setIsDozing(false) - runKeyguardCoordinatorTest { - keyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.GONE, - to = KeyguardState.LOCKSCREEN, - this.testScheduler, - ) - testScheduler.runCurrent() - - // WHEN: a new notification is posted - val entry = NotificationEntryBuilder().setId(1).build() - collectionListener.onEntryAdded(entry) - testScheduler.runCurrent() - - // WHEN: one second has passed - testScheduler.advanceTimeBy(1.seconds) - testScheduler.runCurrent() - - // WHEN: the notification is updated - collectionListener.onEntryUpdated(entry) - testScheduler.runCurrent() - - // WHEN: four more seconds have passed - testScheduler.advanceTimeBy(4.seconds) - testScheduler.runCurrent() - - // WHEN: the keyguard is no longer showing - keyguardRepository.setKeyguardShowing(false) - keyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.GONE, - this.testScheduler, - ) - testScheduler.runCurrent() - - // WHEN: Keyguard is shown again - keyguardRepository.setKeyguardShowing(true) - keyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.GONE, - to = KeyguardState.LOCKSCREEN, - this.testScheduler, - ) - testScheduler.runCurrent() - - // THEN: The notification is considered unseen and is not filtered out. - assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse() - } - } - - private fun runKeyguardCoordinatorTest( - testBlock: suspend KeyguardCoordinatorTestScope.() -> Unit - ) { - val testDispatcher = UnconfinedTestDispatcher() - val testScope = TestScope(testDispatcher) - val fakeSettings = - FakeSettings().apply { - putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1) - } - val seenNotificationsInteractor = - SeenNotificationsInteractor(ActiveNotificationListRepository()) - val keyguardCoordinator = - KeyguardCoordinator( - testDispatcher, - mock<DumpManager>(), - headsUpManager, - keyguardNotifVisibilityProvider, - keyguardRepository, - kosmos.keyguardTransitionInteractor, - KeyguardCoordinatorLogger(logcatLogBuffer()), - testScope.backgroundScope, - sectionHeaderVisibilityProvider, - fakeSettings, - seenNotificationsInteractor, - statusBarStateController, - ) - keyguardCoordinator.attach(notifPipeline) - testScope.runTest(dispatchTimeoutMs = 1.seconds.inWholeMilliseconds) { - KeyguardCoordinatorTestScope( - keyguardCoordinator, - testScope, - seenNotificationsInteractor, - fakeSettings, - ) - .testBlock() - } - } - - private inner class KeyguardCoordinatorTestScope( - private val keyguardCoordinator: KeyguardCoordinator, - private val scope: TestScope, - val seenNotificationsInteractor: SeenNotificationsInteractor, - private val fakeSettings: FakeSettings, - ) : CoroutineScope by scope { - val testScheduler: TestCoroutineScheduler - get() = scope.testScheduler - - val onStateChangeListener: Consumer<String> = withArgCaptor { - verify(keyguardNotifVisibilityProvider).addOnStateChangedListener(capture()) - } - - val unseenFilter: NotifFilter - get() = keyguardCoordinator.unseenNotifFilter - - val collectionListener: NotifCollectionListener = withArgCaptor { - verify(notifPipeline).addCollectionListener(capture()) - } - - val onHeadsUpChangedListener: OnHeadsUpChangedListener - get() = withArgCaptor { verify(headsUpManager).addListener(capture()) } - - val statusBarStateListener: StatusBarStateController.StateListener - get() = withArgCaptor { verify(statusBarStateController).addCallback(capture()) } - - var showOnlyUnseenNotifsOnKeyguardSetting: Boolean - get() = - fakeSettings.getIntForUser( - Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, - UserHandle.USER_CURRENT, - ) == 1 - set(value) { - fakeSettings.putIntForUser( - Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, - if (value) 1 else 2, - UserHandle.USER_CURRENT, - ) - } - } - - companion object { - @JvmStatic - @Parameters(name = "{0}") - fun getParams(): List<FlagsParameterization> { - return FlagsParameterization.allCombinationsOf().andSceneContainer() - } - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java index fea0e72fe577..8dfbb37f8189 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java @@ -118,8 +118,8 @@ public class LegacyNotificationIconAreaControllerImplTest extends SysuiTestCase } @Test + @DisableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void testAppearResetsTranslation() { - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); mController.setupAodIcons(mAodIcons); when(mDozeParameters.shouldControlScreenOff()).thenReturn(false); mController.appearAodIcons(); diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 092ee16f3342..67985efcd7bc 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -102,7 +102,6 @@ import android.util.StatsEvent; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; import com.android.internal.os.BinderCallsStats; import com.android.internal.os.Clock; @@ -1192,7 +1191,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub .setMinConsumedPowerThreshold(minConsumedPowerThreshold) .build(); bus = getBatteryUsageStats(List.of(query)).get(0); - return new StatsPerUidLogger(new FrameworkStatsLogger()).logStats(bus, data); + return StatsPerUidLogger.logStats(bus, data); } default: throw new UnsupportedOperationException("Unknown tagId=" + atomTag); @@ -1205,35 +1204,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub } } - public static class FrameworkStatsLogger { - /** - * Wrapper for the FrameworkStatsLog.buildStatsEvent method that makes it easier - * for mocking. - */ - @VisibleForTesting - public StatsEvent buildStatsEvent(long sessionStartTs, long sessionEndTs, - long sessionDuration, int sessionDischargePercentage, long sessionDischargeDuration, - int uid, @BatteryConsumer.ProcessState int processState, long timeInStateMillis, - String powerComponentName, float totalConsumedPowerMah, float powerComponentMah, - long powerComponentDurationMillis) { - return FrameworkStatsLog.buildStatsEvent( - FrameworkStatsLog.BATTERY_USAGE_STATS_PER_UID, - sessionStartTs, - sessionEndTs, - sessionDuration, - sessionDischargePercentage, - sessionDischargeDuration, - uid, - processState, - timeInStateMillis, - powerComponentName, - totalConsumedPowerMah, - powerComponentMah, - powerComponentDurationMillis); - } - } - - public static class StatsPerUidLogger { + private static class StatsPerUidLogger { private static final int STATSD_METRIC_MAX_DIMENSIONS_COUNT = 3000; @@ -1253,18 +1224,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub long dischargeDuration) {} ; - private final FrameworkStatsLogger mFrameworkStatsLogger; - - public StatsPerUidLogger(FrameworkStatsLogger frameworkStatsLogger) { - mFrameworkStatsLogger = frameworkStatsLogger; - } - - /** - * Generates StatsEvents for the supplied battery usage stats and adds them to - * the supplied list. - */ - @VisibleForTesting - public int logStats(BatteryUsageStats bus, List<StatsEvent> data) { + static int logStats(BatteryUsageStats bus, List<StatsEvent> data) { final SessionInfo sessionInfo = new SessionInfo( bus.getStatsStartTimestamp(), @@ -1380,7 +1340,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub return StatsManager.PULL_SUCCESS; } - private boolean addStatsForPredefinedComponent( + private static boolean addStatsForPredefinedComponent( List<StatsEvent> data, SessionInfo sessionInfo, int uid, @@ -1420,7 +1380,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub powerComponentDurationMillis); } - private boolean addStatsForCustomComponent( + private static boolean addStatsForCustomComponent( List<StatsEvent> data, SessionInfo sessionInfo, int uid, @@ -1462,7 +1422,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub * Returns true on success and false if reached max atoms capacity and no more atoms should * be added */ - private boolean addStatsAtom( + private static boolean addStatsAtom( List<StatsEvent> data, SessionInfo sessionInfo, int uid, @@ -1472,7 +1432,9 @@ public final class BatteryStatsService extends IBatteryStats.Stub float totalConsumedPowerMah, float powerComponentMah, long powerComponentDurationMillis) { - data.add(mFrameworkStatsLogger.buildStatsEvent( + data.add( + FrameworkStatsLog.buildStatsEvent( + FrameworkStatsLog.BATTERY_USAGE_STATS_PER_UID, sessionInfo.startTs(), sessionInfo.endTs(), sessionInfo.duration(), diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 326ed69fa1dc..25b9228d3b37 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -1871,7 +1871,10 @@ public class AudioDeviceBroker { synchronized (mBluetoothAudioStateLock) { reapplyAudioHalBluetoothState(); } - mBtHelper.onAudioServerDiedRestoreA2dp(); + final int forceForMedia = getBluetoothA2dpEnabled() + ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP; + setForceUse_Async( + AudioSystem.FOR_MEDIA, forceForMedia, "MSG_RESTORE_DEVICES"); updateCommunicationRoute("MSG_RESTORE_DEVICES"); } } diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index 02aa6f52ba8a..ca69f31adb35 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -1757,6 +1757,15 @@ public class AudioDeviceInventory { if (AudioService.DEBUG_DEVICES) { Slog.i(TAG, "deviceInfo:" + di + " is(already)Connected:" + isConnected); } + // Do not report an error in case of redundant connect or disconnect request + // as this can cause a state mismatch between BtHelper and AudioDeviceInventory + if (connect == isConnected) { + Log.i(TAG, "handleDeviceConnection() deviceInfo=" + di + " is already " + + (connect ? "" : "dis") + "connected"); + mmi.set(MediaMetrics.Property.STATE, connect + ? MediaMetrics.Value.CONNECT : MediaMetrics.Value.DISCONNECT).record(); + return true; + } if (connect && !isConnected) { final int res; if (isForTesting) { diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java index 57bffa7e5b40..ce92dfbcc1c8 100644 --- a/services/core/java/com/android/server/audio/BtHelper.java +++ b/services/core/java/com/android/server/audio/BtHelper.java @@ -83,39 +83,53 @@ public class BtHelper { } // BluetoothHeadset API to control SCO connection + @GuardedBy("BtHelper.this") private @Nullable BluetoothHeadset mBluetoothHeadset; // Bluetooth headset device + @GuardedBy("mDeviceBroker.mDeviceStateLock") private @Nullable BluetoothDevice mBluetoothHeadsetDevice; + + @GuardedBy("mDeviceBroker.mDeviceStateLock") private final Map<BluetoothDevice, AudioDeviceAttributes> mResolvedScoAudioDevices = new HashMap<>(); + @GuardedBy("BtHelper.this") private @Nullable BluetoothHearingAid mHearingAid = null; + @GuardedBy("BtHelper.this") private @Nullable BluetoothLeAudio mLeAudio = null; + @GuardedBy("BtHelper.this") private @Nullable BluetoothLeAudioCodecConfig mLeAudioCodecConfig; // Reference to BluetoothA2dp to query for AbsoluteVolume. + @GuardedBy("BtHelper.this") private @Nullable BluetoothA2dp mA2dp = null; + @GuardedBy("BtHelper.this") private @Nullable BluetoothCodecConfig mA2dpCodecConfig; + @GuardedBy("BtHelper.this") private @AudioSystem.AudioFormatNativeEnumForBtCodec int mLeAudioBroadcastCodec = AudioSystem.AUDIO_FORMAT_DEFAULT; // If absolute volume is supported in AVRCP device + @GuardedBy("mDeviceBroker.mDeviceStateLock") private boolean mAvrcpAbsVolSupported = false; // Current connection state indicated by bluetooth headset + @GuardedBy("mDeviceBroker.mDeviceStateLock") private int mScoConnectionState; // Indicate if SCO audio connection is currently active and if the initiator is // audio service (internal) or bluetooth headset (external) + @GuardedBy("mDeviceBroker.mDeviceStateLock") private int mScoAudioState; // Indicates the mode used for SCO audio connection. The mode is virtual call if the request // originated from an app targeting an API version before JB MR2 and raw audio after that. + @GuardedBy("mDeviceBroker.mDeviceStateLock") private int mScoAudioMode; // SCO audio state is not active @@ -210,7 +224,7 @@ public class BtHelper { //---------------------------------------------------------------------- // Interface for AudioDeviceBroker - // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock + @GuardedBy("mDeviceBroker.mDeviceStateLock") /*package*/ synchronized void onSystemReady() { mScoConnectionState = android.media.AudioManager.SCO_AUDIO_STATE_ERROR; resetBluetoothSco(); @@ -238,17 +252,13 @@ public class BtHelper { } } - /*package*/ synchronized void onAudioServerDiedRestoreA2dp() { - final int forMed = mDeviceBroker.getBluetoothA2dpEnabled() - ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP; - mDeviceBroker.setForceUse_Async(AudioSystem.FOR_MEDIA, forMed, "onAudioServerDied()"); - } - - /*package*/ synchronized void setAvrcpAbsoluteVolumeSupported(boolean supported) { + @GuardedBy("mDeviceBroker.mDeviceStateLock") + /*package*/ void setAvrcpAbsoluteVolumeSupported(boolean supported) { mAvrcpAbsVolSupported = supported; Log.i(TAG, "setAvrcpAbsoluteVolumeSupported supported=" + supported); } + @GuardedBy("mDeviceBroker.mDeviceStateLock") /*package*/ synchronized void setAvrcpAbsoluteVolumeIndex(int index) { if (mA2dp == null) { if (AudioService.DEBUG_VOL) { @@ -371,7 +381,7 @@ public class BtHelper { return codecAndChanged; } - // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock + @GuardedBy("mDeviceBroker.mDeviceStateLock") /*package*/ synchronized void onReceiveBtEvent(Intent intent) { final String action = intent.getAction(); @@ -393,12 +403,12 @@ public class BtHelper { } /** - * Exclusively called from AudioDeviceBroker (with mSetModeLock held) + * Exclusively called from AudioDeviceBroker (with mDeviceStateLock held) * when handling MSG_L_RECEIVED_BT_EVENT in {@link #onReceiveBtEvent(Intent)} * as part of the serialization of the communication route selection */ - @GuardedBy("BtHelper.this") - private void onScoAudioStateChanged(int state) { + @GuardedBy("mDeviceBroker.mDeviceStateLock") + private synchronized void onScoAudioStateChanged(int state) { boolean broadcast = false; int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR; Log.i(TAG, "onScoAudioStateChanged state: " + state @@ -414,12 +424,14 @@ public class BtHelper { broadcast = true; } if (!mDeviceBroker.isScoManagedByAudio()) { - mDeviceBroker.setBluetoothScoOn(true, "BtHelper.onScoAudioStateChanged"); + mDeviceBroker.setBluetoothScoOn( + true, "BtHelper.onScoAudioStateChanged, state: " + state); } break; case BluetoothHeadset.STATE_AUDIO_DISCONNECTED: if (!mDeviceBroker.isScoManagedByAudio()) { - mDeviceBroker.setBluetoothScoOn(false, "BtHelper.onScoAudioStateChanged"); + mDeviceBroker.setBluetoothScoOn( + false, "BtHelper.onScoAudioStateChanged, state: " + state); } scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED; // There are two cases where we want to immediately reconnect audio: @@ -466,6 +478,7 @@ public class BtHelper { * * @return false if SCO isn't connected */ + @GuardedBy("mDeviceBroker.mDeviceStateLock") /*package*/ synchronized boolean isBluetoothScoOn() { if (mBluetoothHeadset == null || mBluetoothHeadsetDevice == null) { return false; @@ -479,19 +492,20 @@ public class BtHelper { return false; } - /*package*/ synchronized boolean isBluetoothScoRequestedInternally() { + @GuardedBy("mDeviceBroker.mDeviceStateLock") + /*package*/ boolean isBluetoothScoRequestedInternally() { return mScoAudioState == SCO_STATE_ACTIVE_INTERNAL || mScoAudioState == SCO_STATE_ACTIVATE_REQ; } - // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock + @GuardedBy("mDeviceBroker.mDeviceStateLock") /*package*/ synchronized boolean startBluetoothSco(int scoAudioMode, @NonNull String eventSource) { AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(eventSource)); return requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode); } - // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock + @GuardedBy("mDeviceBroker.mDeviceStateLock") /*package*/ synchronized boolean stopBluetoothSco(@NonNull String eventSource) { AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(eventSource)); return requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, SCO_MODE_VIRTUAL_CALL); @@ -551,7 +565,8 @@ public class BtHelper { } } - /*package*/ synchronized void onBroadcastScoConnectionState(int state) { + @GuardedBy("mDeviceBroker.mDeviceStateLock") + /*package*/ void onBroadcastScoConnectionState(int state) { if (state == mScoConnectionState) { return; } @@ -563,8 +578,8 @@ public class BtHelper { mScoConnectionState = state; } - // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock - /*package*/ synchronized void resetBluetoothSco() { + @GuardedBy("mDeviceBroker.mDeviceStateLock") + /*package*/ void resetBluetoothSco() { mScoAudioState = SCO_STATE_INACTIVE; broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); mDeviceBroker.clearA2dpSuspended(false /* internalOnly */); @@ -572,7 +587,7 @@ public class BtHelper { mDeviceBroker.setBluetoothScoOn(false, "resetBluetoothSco"); } - // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock + @GuardedBy("mDeviceBroker.mDeviceStateLock") /*package*/ synchronized void onBtProfileDisconnected(int profile) { AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( "BT profile " + BluetoothProfile.getProfileName(profile) @@ -634,9 +649,10 @@ public class BtHelper { } } + @GuardedBy("BtHelper.this") MyLeAudioCallback mLeAudioCallback = null; - // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock + @GuardedBy("mDeviceBroker.mDeviceStateLock") /*package*/ synchronized void onBtProfileConnected(int profile, BluetoothProfile proxy) { AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( "BT profile " + BluetoothProfile.getProfileName(profile) + " connected to proxy " @@ -773,8 +789,8 @@ public class BtHelper { } } - // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock - private void onHeadsetProfileConnected(@NonNull BluetoothHeadset headset) { + @GuardedBy("mDeviceBroker.mDeviceStateLock") + private synchronized void onHeadsetProfileConnected(@NonNull BluetoothHeadset headset) { // Discard timeout message mDeviceBroker.handleCancelFailureToConnectToBtHeadsetService(); mBluetoothHeadset = headset; @@ -830,6 +846,7 @@ public class BtHelper { mDeviceBroker.postBroadcastScoConnectionState(state); } + @GuardedBy("mDeviceBroker.mDeviceStateLock") @Nullable AudioDeviceAttributes getHeadsetAudioDevice() { if (mBluetoothHeadsetDevice == null) { return null; @@ -837,6 +854,7 @@ public class BtHelper { return getHeadsetAudioDevice(mBluetoothHeadsetDevice); } + @GuardedBy("mDeviceBroker.mDeviceStateLock") private @NonNull AudioDeviceAttributes getHeadsetAudioDevice(BluetoothDevice btDevice) { AudioDeviceAttributes deviceAttr = mResolvedScoAudioDevices.get(btDevice); if (deviceAttr != null) { @@ -876,30 +894,44 @@ public class BtHelper { return new AudioDeviceAttributes(nativeType, address, name); } + @GuardedBy("mDeviceBroker.mDeviceStateLock") private boolean handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive) { if (btDevice == null) { return true; } - int inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET; - AudioDeviceAttributes audioDevice = btHeadsetDeviceToAudioDevice(btDevice); boolean result = false; + AudioDeviceAttributes audioDevice = null; // Only used if isActive is true + String address = btDevice.getAddress(); + String name = getName(btDevice); + // Handle output device if (isActive) { - result |= mDeviceBroker.handleDeviceConnection(audioDevice, isActive, btDevice); + audioDevice = btHeadsetDeviceToAudioDevice(btDevice); + result = mDeviceBroker.handleDeviceConnection( + audioDevice, true /*connect*/, btDevice); } else { - int[] outDeviceTypes = { + AudioDeviceAttributes ada = mResolvedScoAudioDevices.get(btDevice); + if (ada != null) { + result = mDeviceBroker.handleDeviceConnection( + ada, false /*connect*/, btDevice); + } else { + // Disconnect all possible audio device types if the disconnected device type is + // unknown + int[] outDeviceTypes = { AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET, AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT - }; - for (int outDeviceType : outDeviceTypes) { - result |= mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes( - outDeviceType, audioDevice.getAddress(), audioDevice.getName()), - isActive, btDevice); + }; + for (int outDeviceType : outDeviceTypes) { + result |= mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes( + outDeviceType, address, name), false /*connect*/, btDevice); + } } } + // Handle input device + int inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET; // handleDeviceConnection() && result to make sure the method get executed result = mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes( - inDevice, audioDevice.getAddress(), audioDevice.getName()), + inDevice, address, name), isActive, btDevice) && result; if (result) { if (isActive) { @@ -916,8 +948,8 @@ public class BtHelper { return btDevice == null ? "(null)" : btDevice.getAnonymizedAddress(); } - // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock - /*package */ synchronized void onSetBtScoActiveDevice(BluetoothDevice btDevice) { + @GuardedBy("mDeviceBroker.mDeviceStateLock") + /*package */ void onSetBtScoActiveDevice(BluetoothDevice btDevice) { Log.i(TAG, "onSetBtScoActiveDevice: " + getAnonymizedAddress(mBluetoothHeadsetDevice) + " -> " + getAnonymizedAddress(btDevice)); final BluetoothDevice previousActiveDevice = mBluetoothHeadsetDevice; @@ -987,10 +1019,8 @@ public class BtHelper { //---------------------------------------------------------------------- - // @GuardedBy("mDeviceBroker.mSetModeLock") - // @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock") - @GuardedBy("BtHelper.this") - private boolean requestScoState(int state, int scoAudioMode) { + @GuardedBy("mDeviceBroker.mDeviceStateLock") + private synchronized boolean requestScoState(int state, int scoAudioMode) { checkScoAudioState(); if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) { // Make sure that the state transitions to CONNECTING even if we cannot initiate @@ -1154,13 +1184,14 @@ public class BtHelper { } } - private void checkScoAudioState() { + @GuardedBy("mDeviceBroker.mDeviceStateLock") + private synchronized void checkScoAudioState() { try { if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null && mScoAudioState == SCO_STATE_INACTIVE && mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice) - != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { + != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; } } catch (Exception e) { @@ -1184,7 +1215,7 @@ public class BtHelper { return result; } - /*package*/ int getLeAudioDeviceGroupId(BluetoothDevice device) { + /*package*/ synchronized int getLeAudioDeviceGroupId(BluetoothDevice device) { if (mLeAudio == null || device == null) { return BluetoothLeAudio.GROUP_ID_INVALID; } @@ -1197,7 +1228,7 @@ public class BtHelper { * @return A List of Pair(String main_address, String identity_address). Note that the * addresses returned by BluetoothDevice can be null. */ - /*package*/ List<Pair<String, String>> getLeAudioGroupAddresses(int groupId) { + /*package*/ synchronized List<Pair<String, String>> getLeAudioGroupAddresses(int groupId) { List<Pair<String, String>> addresses = new ArrayList<>(); BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); if (adapter == null || mLeAudio == null) { diff --git a/services/tests/powerstatstests/Android.bp b/services/tests/powerstatstests/Android.bp index b2a5b02c49e1..f2b4136c51ed 100644 --- a/services/tests/powerstatstests/Android.bp +++ b/services/tests/powerstatstests/Android.bp @@ -59,7 +59,6 @@ android_ravenwood_test { name: "PowerStatsTestsRavenwood", static_libs: [ "services.core", - "platformprotosnano", "coretests-aidl", "ravenwood-junit", "truth", |