Adjust Mute Quick Affordance to not make binder calls on main thread

Bug: 267256662
Test: MuteQuickAffordanceConfigTest.kt
Test: MuteQuickAffordanceCoreStartableTest.kt
Change-Id: I147e42df06c6e4e566524ddb2dfe84c76312540b
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt
index d085db9..da91572 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt
@@ -27,16 +27,24 @@
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.RingerModeTracker
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.distinctUntilChanged
+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.withContext
 import javax.inject.Inject
 
 @SysUISingleton
@@ -46,6 +54,9 @@
         private val userFileManager: UserFileManager,
         private val ringerModeTracker: RingerModeTracker,
         private val audioManager: AudioManager,
+        @Application private val coroutineScope: CoroutineScope,
+        @Main private val mainDispatcher: CoroutineDispatcher,
+        @Background private val backgroundDispatcher: CoroutineDispatcher,
 ) : KeyguardQuickAffordanceConfig {
 
     private var previousNonSilentMode: Int = DEFAULT_LAST_NON_SILENT_VALUE
@@ -58,7 +69,7 @@
 
     override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
         ringerModeTracker.ringerModeInternal.asFlow()
-            .onStart { emit(getLastNonSilentRingerMode()) }
+            .onStart { getLastNonSilentRingerMode() }
             .distinctUntilChanged()
             .onEach { mode ->
                 // only remember last non-SILENT ringer mode
@@ -87,54 +98,60 @@
                     activationState,
                 )
             }
+            .flowOn(backgroundDispatcher)
 
     override fun onTriggered(
         expandable: Expandable?
     ): KeyguardQuickAffordanceConfig.OnTriggeredResult {
-        val newRingerMode: Int
-        val currentRingerMode =
-                ringerModeTracker.ringerModeInternal.value ?: DEFAULT_LAST_NON_SILENT_VALUE
-        if (currentRingerMode == AudioManager.RINGER_MODE_SILENT) {
-            newRingerMode = previousNonSilentMode
-        } else {
-            previousNonSilentMode = currentRingerMode
-            newRingerMode = AudioManager.RINGER_MODE_SILENT
-        }
+        coroutineScope.launch(backgroundDispatcher) {
+            val newRingerMode: Int
+            val currentRingerMode = audioManager.ringerModeInternal
+            if (currentRingerMode == AudioManager.RINGER_MODE_SILENT) {
+                newRingerMode = previousNonSilentMode
+            } else {
+                previousNonSilentMode = currentRingerMode
+                newRingerMode = AudioManager.RINGER_MODE_SILENT
+            }
 
-        if (currentRingerMode != newRingerMode) {
-            audioManager.ringerModeInternal = newRingerMode
+            if (currentRingerMode != newRingerMode) {
+                audioManager.ringerModeInternal = newRingerMode
+            }
         }
         return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
     }
 
     override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState =
-        if (audioManager.isVolumeFixed) {
-            KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
-        } else {
-            KeyguardQuickAffordanceConfig.PickerScreenState.Default()
+        withContext(backgroundDispatcher) {
+            if (audioManager.isVolumeFixed) {
+                KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
+            } else {
+                KeyguardQuickAffordanceConfig.PickerScreenState.Default()
+            }
         }
 
     /**
      * Gets the last non-silent ringer mode from shared-preferences if it exists. This is
      *  cached by [MuteQuickAffordanceCoreStartable] while this affordance is selected
      */
-    private fun getLastNonSilentRingerMode(): Int =
-        userFileManager.getSharedPreferences(
-            MUTE_QUICK_AFFORDANCE_PREFS_FILE_NAME,
-            Context.MODE_PRIVATE,
-            userTracker.userId
-        ).getInt(
-            LAST_NON_SILENT_RINGER_MODE_KEY,
-            ringerModeTracker.ringerModeInternal.value ?: DEFAULT_LAST_NON_SILENT_VALUE
-        )
+    private suspend fun getLastNonSilentRingerMode(): Int =
+        withContext(backgroundDispatcher) {
+            userFileManager.getSharedPreferences(
+                    MUTE_QUICK_AFFORDANCE_PREFS_FILE_NAME,
+                    Context.MODE_PRIVATE,
+                    userTracker.userId
+            ).getInt(
+                    LAST_NON_SILENT_RINGER_MODE_KEY,
+                    ringerModeTracker.ringerModeInternal.value ?: DEFAULT_LAST_NON_SILENT_VALUE
+            )
+        }
 
     private fun <T> LiveData<T>.asFlow(): Flow<T?> =
-            conflatedCallbackFlow {
-                val observer = Observer { value: T -> trySend(value) }
-                observeForever(observer)
-                send(value)
-                awaitClose { removeObserver(observer) }
-            }
+        conflatedCallbackFlow {
+            val observer = Observer { value: T -> trySend(value) }
+            observeForever(observer)
+            send(value)
+            awaitClose { removeObserver(observer) }
+        }.flowOn(mainDispatcher)
 
     companion object {
         const val LAST_NON_SILENT_RINGER_MODE_KEY = "key_last_non_silent_ringer_mode"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartable.kt
index 12a6310..cd0805e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartable.kt
@@ -23,15 +23,18 @@
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.RingerModeTracker
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
 import javax.inject.Inject
 
 /**
@@ -45,6 +48,7 @@
     private val userFileManager: UserFileManager,
     private val keyguardQuickAffordanceRepository: KeyguardQuickAffordanceRepository,
     @Application private val coroutineScope: CoroutineScope,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
 ) : CoreStartable {
 
     private val observer = Observer(this::updateLastNonSilentRingerMode)
@@ -72,15 +76,17 @@
     }
 
     private fun updateLastNonSilentRingerMode(lastRingerMode: Int) {
-        if (AudioManager.RINGER_MODE_SILENT != lastRingerMode) {
-            userFileManager.getSharedPreferences(
-                MuteQuickAffordanceConfig.MUTE_QUICK_AFFORDANCE_PREFS_FILE_NAME,
-                Context.MODE_PRIVATE,
-                userTracker.userId
-            )
-            .edit()
-            .putInt(MuteQuickAffordanceConfig.LAST_NON_SILENT_RINGER_MODE_KEY, lastRingerMode)
-            .apply()
+        coroutineScope.launch(backgroundDispatcher) {
+            if (AudioManager.RINGER_MODE_SILENT != lastRingerMode) {
+                userFileManager.getSharedPreferences(
+                        MuteQuickAffordanceConfig.MUTE_QUICK_AFFORDANCE_PREFS_FILE_NAME,
+                        Context.MODE_PRIVATE,
+                        userTracker.userId
+                )
+                .edit()
+                .putInt(MuteQuickAffordanceConfig.LAST_NON_SILENT_RINGER_MODE_KEY, lastRingerMode)
+                .apply()
+            }
         }
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt
index a3740d8..925c06f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt
@@ -19,7 +19,6 @@
 
 import android.content.Context
 import android.media.AudioManager
-import androidx.lifecycle.LiveData
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.settings.UserFileManager
@@ -27,10 +26,12 @@
 import com.android.systemui.util.RingerModeTracker
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestDispatcher
 import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Assert.assertEquals
 import org.junit.Before
@@ -57,13 +58,15 @@
     @Mock
     private lateinit var userFileManager: UserFileManager
 
+    private lateinit var testDispatcher: TestDispatcher
     private lateinit var testScope: TestScope
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        testScope = TestScope()
+        testDispatcher = StandardTestDispatcher()
+        testScope = TestScope(testDispatcher)
 
         whenever(userTracker.userContext).thenReturn(context)
         whenever(userFileManager.getSharedPreferences(any(), any(), any()))
@@ -74,7 +77,10 @@
                 userTracker,
                 userFileManager,
                 ringerModeTracker,
-                audioManager
+                audioManager,
+                testScope.backgroundScope,
+                testDispatcher,
+                testDispatcher,
         )
     }
 
@@ -103,17 +109,16 @@
     }
 
     @Test
-    fun `triggered - state was previously NORMAL - currently SILENT - move to previous state`() {
+    fun `triggered - state was previously NORMAL - currently SILENT - move to previous state`() = testScope.runTest {
         //given
         val ringerModeCapture = argumentCaptor<Int>()
-        val ringerModeInternal = mock<LiveData<Int>>()
-        whenever(ringerModeTracker.ringerModeInternal).thenReturn(ringerModeInternal)
-        whenever(ringerModeInternal.value).thenReturn(AudioManager.RINGER_MODE_NORMAL)
+        whenever(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_NORMAL)
         underTest.onTriggered(null)
-        whenever(ringerModeInternal.value).thenReturn(AudioManager.RINGER_MODE_SILENT)
+        whenever(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_SILENT)
 
         //when
         val result = underTest.onTriggered(null)
+        runCurrent()
         verify(audioManager, times(2)).ringerModeInternal = ringerModeCapture.capture()
 
         //then
@@ -122,15 +127,14 @@
     }
 
     @Test
-    fun `triggered - state is not SILENT - move to SILENT ringer`() {
+    fun `triggered - state is not SILENT - move to SILENT ringer`() = testScope.runTest {
         //given
         val ringerModeCapture = argumentCaptor<Int>()
-        val ringerModeInternal = mock<LiveData<Int>>()
-        whenever(ringerModeTracker.ringerModeInternal).thenReturn(ringerModeInternal)
-        whenever(ringerModeInternal.value).thenReturn(AudioManager.RINGER_MODE_NORMAL)
+        whenever(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_NORMAL)
 
         //when
         val result = underTest.onTriggered(null)
+        runCurrent()
         verify(audioManager).ringerModeInternal = ringerModeCapture.capture()
 
         //then
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt
index 26601b63..34f3ed8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt
@@ -37,6 +37,8 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.cancelChildren
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestDispatcher
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
@@ -67,6 +69,7 @@
     @Mock
     private lateinit var keyguardQuickAffordanceRepository: KeyguardQuickAffordanceRepository
 
+    private lateinit var testDispatcher: TestDispatcher
     private lateinit var testScope: TestScope
 
     private lateinit var underTest: MuteQuickAffordanceCoreStartable
@@ -83,7 +86,8 @@
         val emission = MutableStateFlow(mapOf("testQuickAffordanceKey" to listOf(config)))
         whenever(keyguardQuickAffordanceRepository.selections).thenReturn(emission)
 
-        testScope = TestScope()
+        testDispatcher = StandardTestDispatcher()
+        testScope = TestScope(testDispatcher)
 
         underTest = MuteQuickAffordanceCoreStartable(
             featureFlags,
@@ -91,7 +95,8 @@
             ringerModeTracker,
             userFileManager,
             keyguardQuickAffordanceRepository,
-            testScope,
+            testScope.backgroundScope,
+            testDispatcher,
         )
     }
 
@@ -158,6 +163,7 @@
         runCurrent()
         verify(ringerModeInternal).observeForever(observerCaptor.capture())
         observerCaptor.value.onChanged(newRingerMode)
+        runCurrent()
         val result = sharedPrefs.getInt("key_last_non_silent_ringer_mode", -1)
 
         //then