Merge "Move binder calls to the background threads" into main
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiver.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiver.kt
index c3b1a7c..aeea8cb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiver.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiver.kt
@@ -24,6 +24,7 @@
import android.util.Log
import com.android.settingslib.volume.shared.model.AudioManagerEvent
import com.android.settingslib.volume.shared.model.AudioStream
+import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.SharedFlow
@@ -31,6 +32,7 @@
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.launch
@@ -44,6 +46,7 @@
class AudioManagerEventsReceiverImpl(
private val context: Context,
coroutineScope: CoroutineScope,
+ backgroundCoroutineContext: CoroutineContext,
) : AudioManagerEventsReceiver {
private val allActions: Collection<String>
@@ -79,6 +82,7 @@
.filterNotNull()
.filter { intent -> allActions.contains(intent.action) }
.mapNotNull { it.toAudioManagerEvent() }
+ .flowOn(backgroundCoroutineContext)
.shareIn(coroutineScope, SharingStarted.WhileSubscribed())
private fun Intent.toAudioManagerEvent(): AudioManagerEvent? {
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiverTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiverTest.kt
index 35ee828..58a09fb 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiverTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiverTest.kt
@@ -60,7 +60,12 @@
fun setup() {
MockitoAnnotations.initMocks(this)
- underTest = AudioManagerEventsReceiverImpl(context, testScope.backgroundScope)
+ underTest =
+ AudioManagerEventsReceiverImpl(
+ context,
+ testScope.backgroundScope,
+ testScope.testScheduler,
+ )
}
@Test
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/CaptioningRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/CaptioningRepository.kt
index 5414b62..39fd44a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/CaptioningRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/CaptioningRepository.kt
@@ -63,6 +63,7 @@
private val captioningManager: StateFlow<CaptioningManager?> =
userRepository.selectedUser
.map { userScopedCaptioningManagerProvider.forUser(it.userInfo.userHandle) }
+ .flowOn(backgroundCoroutineContext)
.stateIn(coroutineScope, SharingStarted.WhileSubscribed(), null)
override val captioningModel: StateFlow<CaptioningModel?> =
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
index 617aaa7..d5b8597 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
@@ -53,7 +53,9 @@
fun provideAudioManagerIntentsReceiver(
@Application context: Context,
@Application coroutineScope: CoroutineScope,
- ): AudioManagerEventsReceiver = AudioManagerEventsReceiverImpl(context, coroutineScope)
+ @Background coroutineContext: CoroutineContext,
+ ): AudioManagerEventsReceiver =
+ AudioManagerEventsReceiverImpl(context, coroutineScope, coroutineContext)
@Provides
@SysUISingleton
@@ -82,7 +84,7 @@
localBluetoothManager: LocalBluetoothManager?,
@Application coroutineScope: CoroutineScope,
@Background coroutineContext: CoroutineContext,
- volumeLogger: VolumeLogger
+ volumeLogger: VolumeLogger,
): AudioSharingRepository =
if (Flags.enableLeAudioSharing() && localBluetoothManager != null) {
AudioSharingRepositoryImpl(
@@ -90,7 +92,7 @@
localBluetoothManager,
coroutineScope,
coroutineContext,
- volumeLogger
+ volumeLogger,
)
} else {
AudioSharingRepositoryEmptyImpl()
@@ -111,8 +113,7 @@
@Provides
@SysUISingleton
- fun provideAudioSystemRepository(
- @Application context: Context,
- ): AudioSystemRepository = AudioSystemRepositoryImpl(context)
+ fun provideAudioSystemRepository(@Application context: Context): AudioSystemRepository =
+ AudioSystemRepositoryImpl(context)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt
index dacd6c7..b9f47d7 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt
@@ -22,15 +22,17 @@
import android.media.session.PlaybackState
import android.os.Bundle
import android.os.Handler
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaControllerChangeModel
import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.channels.ProducerScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
-import com.android.app.tracing.coroutines.launchTraced as launch
+import kotlinx.coroutines.flow.flowOn
interface MediaControllerInteractor {
@@ -43,14 +45,16 @@
@Inject
constructor(
@Background private val backgroundHandler: Handler,
+ @Background private val backgroundCoroutineContext: CoroutineContext,
) : MediaControllerInteractor {
override fun stateChanges(mediaController: MediaController): Flow<MediaControllerChangeModel> {
return conflatedCallbackFlow {
- val callback = MediaControllerCallbackProducer(this)
- mediaController.registerCallback(callback, backgroundHandler)
- awaitClose { mediaController.unregisterCallback(callback) }
- }
+ val callback = MediaControllerCallbackProducer(this)
+ mediaController.registerCallback(callback, backgroundHandler)
+ awaitClose { mediaController.unregisterCallback(callback) }
+ }
+ .flowOn(backgroundCoroutineContext)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
index aa07cfd..b3848a6 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
@@ -20,10 +20,12 @@
import android.media.VolumeProvider
import android.media.session.MediaController
import android.util.Log
+import androidx.annotation.WorkerThread
import com.android.settingslib.media.MediaDevice
import com.android.settingslib.volume.data.repository.LocalMediaRepository
import com.android.settingslib.volume.data.repository.MediaControllerRepository
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.concurrency.Execution
import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory
import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSessions
import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
@@ -62,6 +64,7 @@
@Background private val backgroundCoroutineContext: CoroutineContext,
mediaControllerRepository: MediaControllerRepository,
private val mediaControllerInteractor: MediaControllerInteractor,
+ private val execution: Execution,
) {
private val activeMediaControllers: Flow<MediaControllers> =
@@ -82,9 +85,10 @@
.map {
MediaDeviceSessions(
local = it.local?.mediaDeviceSession(),
- remote = it.remote?.mediaDeviceSession()
+ remote = it.remote?.mediaDeviceSession(),
)
}
+ .flowOn(backgroundCoroutineContext)
.stateIn(coroutineScope, SharingStarted.Eagerly, MediaDeviceSessions(null, null))
/** Returns the default [MediaDeviceSession] from [activeMediaDeviceSessions] */
@@ -115,55 +119,43 @@
val currentConnectedDevice: Flow<MediaDevice?> =
localMediaRepository.flatMapLatest { it.currentConnectedDevice }.distinctUntilChanged()
- private suspend fun getApplicationLabel(packageName: String): CharSequence? {
- return try {
- withContext(backgroundCoroutineContext) {
- val appInfo =
- packageManager.getApplicationInfo(
- packageName,
- PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_ANY_USER
- )
- appInfo.loadLabel(packageManager)
- }
- } catch (e: PackageManager.NameNotFoundException) {
- Log.e(TAG, "Unable to find info for package: $packageName")
- null
- }
- }
-
/** Finds local and remote media controllers. */
- private fun getMediaControllers(
- controllers: Collection<MediaController>,
- ): MediaControllers {
- var localController: MediaController? = null
- var remoteController: MediaController? = null
- val remoteMediaSessions: MutableSet<String> = mutableSetOf()
- for (controller in controllers) {
- val playbackInfo: MediaController.PlaybackInfo = controller.playbackInfo ?: continue
- when (playbackInfo.playbackType) {
- MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE -> {
- // MediaController can't be local if there is a remote one for the same package
- if (localController?.packageName.equals(controller.packageName)) {
- localController = null
+ private suspend fun getMediaControllers(
+ controllers: Collection<MediaController>
+ ): MediaControllers =
+ withContext(backgroundCoroutineContext) {
+ var localController: MediaController? = null
+ var remoteController: MediaController? = null
+ val remoteMediaSessions: MutableSet<String> = mutableSetOf()
+ for (controller in controllers) {
+ val playbackInfo: MediaController.PlaybackInfo = controller.playbackInfo ?: continue
+ when (playbackInfo.playbackType) {
+ MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE -> {
+ // MediaController can't be local if there is a remote one for the same
+ // package
+ if (localController?.packageName.equals(controller.packageName)) {
+ localController = null
+ }
+ if (!remoteMediaSessions.contains(controller.packageName)) {
+ remoteMediaSessions.add(controller.packageName)
+ remoteController = chooseController(remoteController, controller)
+ }
}
- if (!remoteMediaSessions.contains(controller.packageName)) {
- remoteMediaSessions.add(controller.packageName)
- remoteController = chooseController(remoteController, controller)
+ MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL -> {
+ if (controller.packageName in remoteMediaSessions) continue
+ localController = chooseController(localController, controller)
}
}
- MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL -> {
- if (controller.packageName in remoteMediaSessions) continue
- localController = chooseController(localController, controller)
- }
}
+ MediaControllers(local = localController, remote = remoteController)
}
- return MediaControllers(local = localController, remote = remoteController)
- }
+ @WorkerThread
private fun chooseController(
currentController: MediaController?,
newController: MediaController,
): MediaController {
+ require(!execution.isMainThread())
if (currentController == null) {
return newController
}
@@ -175,12 +167,26 @@
return currentController
}
- private suspend fun MediaController.mediaDeviceSession(): MediaDeviceSession? {
+ @WorkerThread
+ private fun MediaController.mediaDeviceSession(): MediaDeviceSession? {
+ require(!execution.isMainThread())
+ val applicationLabel =
+ try {
+ packageManager
+ .getApplicationInfo(
+ packageName,
+ PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_ANY_USER,
+ )
+ .loadLabel(packageManager)
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.e(TAG, "Unable to find info for package: $packageName")
+ null
+ } ?: return null
return MediaDeviceSession(
packageName = packageName,
sessionToken = sessionToken,
canAdjustVolume = playbackInfo.volumeControl != VolumeProvider.VOLUME_CONTROL_FIXED,
- appLabel = getApplicationLabel(packageName) ?: return null
+ appLabel = applicationLabel,
)
}
@@ -195,10 +201,7 @@
.onStart { emit(this@stateChanges) }
}
- private data class MediaControllers(
- val local: MediaController?,
- val remote: MediaController?,
- )
+ private data class MediaControllers(val local: MediaController?, val remote: MediaController?)
private companion object {
const val TAG = "MediaOutputInteractor"
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/ExecutionKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/ExecutionKosmos.kt
new file mode 100644
index 0000000..bf66cb6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/ExecutionKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.concurrency
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeExecution: FakeExecution by
+ Kosmos.Fixture { FakeExecution().apply { simulateMainThread = false } }
+var Kosmos.execution: Execution by Kosmos.Fixture { fakeExecution }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
index 1b58582..ed5322e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
@@ -21,6 +21,7 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.mediaOutputDialogManager
+import com.android.systemui.util.concurrency.execution
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
@@ -53,6 +54,7 @@
testScope.testScheduler,
mediaControllerRepository,
mediaControllerInteractor,
+ execution,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractorKosmos.kt
index 652b3ea..fdeb8ce 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractorKosmos.kt
@@ -19,6 +19,7 @@
import android.os.Handler
import android.os.looper
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
var Kosmos.mediaControllerInteractor: MediaControllerInteractor by
- Kosmos.Fixture { MediaControllerInteractorImpl(Handler(looper)) }
+ Kosmos.Fixture { MediaControllerInteractorImpl(Handler(looper), testScope.testScheduler) }