Implement remote home controls data source
Implements a remote data source for the information home controls dreams
needs, which will query the remote service in SYSTEM_USER.
Flag: com.android.systemui.home_controls_dream_hsum
Bug: com.android.systemui.home_controls_dream_hsum
Test: atest HomeControlsRemoteProxyTest
Test: atest RemoteHomeControlsDataSourceDelegatorTest
Change-Id: I97c5b455debfce0499daf78b1510df946c771f60
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/service/HomeControlsRemoteProxyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/service/HomeControlsRemoteProxyTest.kt
new file mode 100644
index 0000000..e57776f
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/service/HomeControlsRemoteProxyTest.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.homecontrols.service
+
+import android.content.ComponentName
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dreams.homecontrols.shared.model.HomeControlsComponentInfo
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class HomeControlsRemoteProxyTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private val fakeBinder = kosmos.fakeHomeControlsRemoteBinder
+
+ private val underTest by lazy { kosmos.homeControlsRemoteProxy }
+
+ @Test
+ fun testRegistersOnlyWhileSubscribed() =
+ testScope.runTest {
+ assertThat(fakeBinder.callbacks).isEmpty()
+
+ val job = launch { underTest.componentInfo.collect {} }
+ runCurrent()
+ assertThat(fakeBinder.callbacks).hasSize(1)
+
+ job.cancel()
+ runCurrent()
+ assertThat(fakeBinder.callbacks).isEmpty()
+ }
+
+ @Test
+ fun testEmitsOnCallback() =
+ testScope.runTest {
+ val componentInfo by collectLastValue(underTest.componentInfo)
+ assertThat(componentInfo).isNull()
+
+ fakeBinder.notifyCallbacks(TEST_COMPONENT, allowTrivialControlsOnLockscreen = true)
+ assertThat(componentInfo)
+ .isEqualTo(
+ HomeControlsComponentInfo(
+ TEST_COMPONENT,
+ allowTrivialControlsOnLockscreen = true,
+ )
+ )
+ }
+
+ @Test
+ fun testOnlyRegistersSingleCallbackForMultipleSubscribers() =
+ testScope.runTest {
+ assertThat(fakeBinder.callbacks).isEmpty()
+
+ // 2 collectors
+ val job = launch {
+ launch { underTest.componentInfo.collect {} }
+ launch { underTest.componentInfo.collect {} }
+ }
+ runCurrent()
+ assertThat(fakeBinder.callbacks).hasSize(1)
+ job.cancel()
+ }
+
+ private companion object {
+ val TEST_COMPONENT = ComponentName("pkg.test", "class.test")
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/service/RemoteHomeControlsDataSourceDelegatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/service/RemoteHomeControlsDataSourceDelegatorTest.kt
new file mode 100644
index 0000000..4002175
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/service/RemoteHomeControlsDataSourceDelegatorTest.kt
@@ -0,0 +1,128 @@
+/*
+ * 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.dreams.homecontrols.service
+
+import android.content.ComponentName
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dreams.homecontrols.dagger.HomeControlsRemoteServiceComponent
+import com.android.systemui.dreams.homecontrols.shared.model.HomeControlsComponentInfo
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.service.ObservableServiceConnection
+import com.android.systemui.util.service.PersistentConnectionManager
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.stub
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class RemoteHomeControlsDataSourceDelegatorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private val proxy = kosmos.homeControlsRemoteProxy
+ private val fakeBinder = kosmos.fakeHomeControlsRemoteBinder
+
+ private val callbackCaptor =
+ argumentCaptor<ObservableServiceConnection.Callback<HomeControlsRemoteProxy>>()
+
+ private val connectionManager =
+ mock<PersistentConnectionManager<HomeControlsRemoteProxy>> {
+ on { start() } doAnswer { simulateConnect() }
+ on { stop() } doAnswer { simulateDisconnect() }
+ }
+ private val serviceComponent =
+ mock<HomeControlsRemoteServiceComponent> {
+ on { connectionManager } doReturn connectionManager
+ }
+
+ private val underTest by lazy { kosmos.remoteHomeControlsDataSourceDelegator }
+
+ @Before
+ fun setUp() {
+ kosmos.homeControlsRemoteServiceFactory =
+ mock<HomeControlsRemoteServiceComponent.Factory>().stub {
+ on { create(callbackCaptor.capture()) } doReturn serviceComponent
+ }
+ }
+
+ @Test
+ fun testQueriesComponentInfoFromBinder() =
+ testScope.runTest {
+ assertThat(fakeBinder.callbacks).isEmpty()
+
+ val componentInfo by collectLastValue(underTest.componentInfo)
+
+ assertThat(componentInfo).isNull()
+ assertThat(fakeBinder.callbacks).hasSize(1)
+
+ fakeBinder.notifyCallbacks(TEST_COMPONENT, allowTrivialControlsOnLockscreen = true)
+ assertThat(componentInfo)
+ .isEqualTo(
+ HomeControlsComponentInfo(
+ TEST_COMPONENT,
+ allowTrivialControlsOnLockscreen = true,
+ )
+ )
+ }
+
+ @Test
+ fun testOnlyConnectToServiceOnSubscription() =
+ testScope.runTest {
+ verify(connectionManager, never()).start()
+
+ val job = launch { underTest.componentInfo.collect {} }
+ runCurrent()
+ verify(connectionManager, times(1)).start()
+ verify(connectionManager, never()).stop()
+
+ job.cancel()
+ runCurrent()
+ verify(connectionManager, times(1)).start()
+ verify(connectionManager, times(1)).stop()
+ }
+
+ private fun simulateConnect() {
+ callbackCaptor.lastValue.onConnected(mock(), proxy)
+ }
+
+ private fun simulateDisconnect() {
+ callbackCaptor.lastValue.onDisconnected(mock(), 0)
+ }
+
+ private companion object {
+ val TEST_COMPONENT = ComponentName("pkg.test", "class.test")
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/dagger/HomeControlsDataSourceModule.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/dagger/HomeControlsDataSourceModule.kt
new file mode 100644
index 0000000..3a2791f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/dagger/HomeControlsDataSourceModule.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.dreams.homecontrols.dagger
+
+import com.android.systemui.Flags.homeControlsDreamHsum
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dreams.homecontrols.service.RemoteHomeControlsDataSourceDelegator
+import com.android.systemui.dreams.homecontrols.shared.model.HomeControlsDataSource
+import com.android.systemui.dreams.homecontrols.system.LocalHomeControlsDataSourceDelegator
+import dagger.Lazy
+import dagger.Module
+import dagger.Provides
+
+@Module
+interface HomeControlsDataSourceModule {
+ companion object {
+ @Provides
+ @SysUISingleton
+ fun providesHomeControlsDataSource(
+ localSource: Lazy<LocalHomeControlsDataSourceDelegator>,
+ remoteSource: Lazy<RemoteHomeControlsDataSourceDelegator>,
+ ): HomeControlsDataSource {
+ return if (homeControlsDreamHsum()) {
+ remoteSource.get()
+ } else {
+ localSource.get()
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/dagger/HomeControlsRemoteServiceComponent.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/dagger/HomeControlsRemoteServiceComponent.kt
new file mode 100644
index 0000000..500d15e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/dagger/HomeControlsRemoteServiceComponent.kt
@@ -0,0 +1,116 @@
+/*
+ * 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.dreams.homecontrols.dagger
+
+import android.content.Context
+import android.content.Intent
+import android.os.IBinder
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dreams.homecontrols.service.HomeControlsRemoteProxy
+import com.android.systemui.dreams.homecontrols.shared.IHomeControlsRemoteProxy
+import com.android.systemui.dreams.homecontrols.system.HomeControlsRemoteService
+import com.android.systemui.util.service.ObservableServiceConnection
+import com.android.systemui.util.service.Observer
+import com.android.systemui.util.service.PersistentConnectionManager
+import com.android.systemui.util.service.dagger.ObservableServiceModule
+import dagger.BindsInstance
+import dagger.Module
+import dagger.Provides
+import dagger.Subcomponent
+import javax.inject.Named
+
+/**
+ * This component is responsible for generating the connection to the home controls remote service
+ * which runs in the SYSTEM_USER context and provides the data needed to run the home controls dream
+ * in the foreground user context.
+ */
+@Subcomponent(
+ modules =
+ [
+ ObservableServiceModule::class,
+ HomeControlsRemoteServiceComponent.HomeControlsRemoteServiceModule::class,
+ ]
+)
+interface HomeControlsRemoteServiceComponent {
+ /** Creates a [HomeControlsRemoteServiceComponent]. */
+ @Subcomponent.Factory
+ interface Factory {
+ fun create(
+ @BindsInstance callback: ObservableServiceConnection.Callback<HomeControlsRemoteProxy>
+ ): HomeControlsRemoteServiceComponent
+ }
+
+ /** A [PersistentConnectionManager] pointing to the home controls remote service. */
+ val connectionManager: PersistentConnectionManager<HomeControlsRemoteProxy>
+
+ /** Scoped module providing specific components for the [ObservableServiceConnection]. */
+ @Module
+ interface HomeControlsRemoteServiceModule {
+ companion object {
+ @Provides
+ @Named(ObservableServiceModule.SERVICE_CONNECTION)
+ fun providesConnection(
+ connection: ObservableServiceConnection<HomeControlsRemoteProxy>,
+ callback: ObservableServiceConnection.Callback<HomeControlsRemoteProxy>,
+ ): ObservableServiceConnection<HomeControlsRemoteProxy> {
+ connection.addCallback(callback)
+ return connection
+ }
+
+ /** Provides the wrapper around the home controls remote binder */
+ @Provides
+ fun providesTransformer(
+ factory: HomeControlsRemoteProxy.Factory
+ ): ObservableServiceConnection.ServiceTransformer<HomeControlsRemoteProxy> {
+ return ObservableServiceConnection.ServiceTransformer { service: IBinder ->
+ factory.create(IHomeControlsRemoteProxy.Stub.asInterface(service))
+ }
+ }
+
+ /** Provides the intent to connect to [HomeControlsRemoteService] */
+ @Provides
+ fun providesIntent(@Application context: Context): Intent {
+ return Intent(context, HomeControlsRemoteService::class.java)
+ }
+
+ /** Provides no-op [Observer] since the remote service is in the same package */
+ @Provides
+ @Named(ObservableServiceModule.OBSERVER)
+ fun providesObserver(): Observer {
+ return object : Observer {
+ override fun addCallback(callback: Observer.Callback?) {
+ // no-op, do nothing
+ }
+
+ override fun removeCallback(callback: Observer.Callback?) {
+ // no-op, do nothing
+ }
+ }
+ }
+
+ /**
+ * Provides a name that will be used by [PersistentConnectionManager] when logging
+ * state.
+ */
+ @Provides
+ @Named(ObservableServiceModule.DUMPSYS_NAME)
+ fun providesDumpsysName(): String {
+ return "HomeControlsRemoteService"
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/HomeControlsRemoteProxy.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/HomeControlsRemoteProxy.kt
new file mode 100644
index 0000000..2bcfea8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/HomeControlsRemoteProxy.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.dreams.homecontrols.service
+
+import android.content.ComponentName
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dreams.homecontrols.shared.IHomeControlsRemoteProxy
+import com.android.systemui.dreams.homecontrols.shared.IOnControlsSettingsChangeListener
+import com.android.systemui.dreams.homecontrols.shared.model.HomeControlsComponentInfo
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.util.kotlin.FlowDumperImpl
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.stateIn
+
+/** Class to wrap [IHomeControlsRemoteProxy], which exposes the current user's home controls info */
+class HomeControlsRemoteProxy
+@AssistedInject
+constructor(
+ @Background bgScope: CoroutineScope,
+ dumpManager: DumpManager,
+ @Assisted private val proxy: IHomeControlsRemoteProxy,
+) : FlowDumperImpl(dumpManager) {
+
+ private companion object {
+ const val TAG = "HomeControlsRemoteProxy"
+ }
+
+ val componentInfo: Flow<HomeControlsComponentInfo> =
+ conflatedCallbackFlow {
+ val listener =
+ object : IOnControlsSettingsChangeListener.Stub() {
+ override fun onControlsSettingsChanged(
+ panelComponent: ComponentName?,
+ allowTrivialControlsOnLockscreen: Boolean,
+ ) {
+ trySendWithFailureLogging(
+ HomeControlsComponentInfo(
+ panelComponent,
+ allowTrivialControlsOnLockscreen,
+ ),
+ TAG,
+ )
+ }
+ }
+ proxy.registerListenerForCurrentUser(listener)
+ awaitClose { proxy.unregisterListenerForCurrentUser(listener) }
+ }
+ .distinctUntilChanged()
+ .stateIn(bgScope, SharingStarted.WhileSubscribed(), null)
+ .dumpValue("componentInfo")
+ .filterNotNull()
+
+ @AssistedFactory
+ interface Factory {
+ fun create(proxy: IHomeControlsRemoteProxy): HomeControlsRemoteProxy
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/RemoteHomeControlsDataSourceDelegator.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/RemoteHomeControlsDataSourceDelegator.kt
new file mode 100644
index 0000000..b14903d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/RemoteHomeControlsDataSourceDelegator.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.dreams.homecontrols.service
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dreams.DreamLogger
+import com.android.systemui.dreams.homecontrols.dagger.HomeControlsRemoteServiceComponent
+import com.android.systemui.dreams.homecontrols.shared.model.HomeControlsComponentInfo
+import com.android.systemui.dreams.homecontrols.shared.model.HomeControlsDataSource
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.dagger.DreamLog
+import com.android.systemui.util.kotlin.FlowDumperImpl
+import com.android.systemui.util.service.ObservableServiceConnection
+import com.android.systemui.util.service.PersistentConnectionManager
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.dropWhile
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+
+/**
+ * Queries a remote service for [HomeControlsComponentInfo] necessary to show the home controls
+ * dream.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class RemoteHomeControlsDataSourceDelegator
+@Inject
+constructor(
+ @Background bgScope: CoroutineScope,
+ serviceFactory: HomeControlsRemoteServiceComponent.Factory,
+ @DreamLog logBuffer: LogBuffer,
+ dumpManager: DumpManager,
+) : HomeControlsDataSource, FlowDumperImpl(dumpManager) {
+ private val logger = DreamLogger(logBuffer, TAG)
+
+ private val connectionManager: PersistentConnectionManager<HomeControlsRemoteProxy> by lazy {
+ serviceFactory.create(callback).connectionManager
+ }
+
+ private val proxyState =
+ MutableStateFlow<HomeControlsRemoteProxy?>(null)
+ .apply {
+ subscriptionCount
+ .map { it > 0 }
+ .dropWhile { !it }
+ .distinctUntilChanged()
+ .onEach { active ->
+ logger.d({ "Remote service connection active: $bool1" }) { bool1 = active }
+ if (active) {
+ connectionManager.start()
+ } else {
+ connectionManager.stop()
+ }
+ }
+ .launchIn(bgScope)
+ }
+ .dumpValue("proxyState")
+
+ private val callback: ObservableServiceConnection.Callback<HomeControlsRemoteProxy> =
+ object : ObservableServiceConnection.Callback<HomeControlsRemoteProxy> {
+ override fun onConnected(
+ connection: ObservableServiceConnection<HomeControlsRemoteProxy>?,
+ proxy: HomeControlsRemoteProxy,
+ ) {
+ logger.d("Service connected")
+ proxyState.value = proxy
+ }
+
+ override fun onDisconnected(
+ connection: ObservableServiceConnection<HomeControlsRemoteProxy>?,
+ reason: Int,
+ ) {
+ logger.d({ "Service disconnected with reason $int1" }) { int1 = reason }
+ proxyState.value = null
+ }
+ }
+
+ override val componentInfo: Flow<HomeControlsComponentInfo> =
+ proxyState
+ .filterNotNull()
+ .flatMapLatest { proxy: HomeControlsRemoteProxy -> proxy.componentInfo }
+ .dumpWhileCollecting("componentInfo")
+
+ private companion object {
+ const val TAG = "HomeControlsRemoteDataSourceDelegator"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/shared/model/HomeControlsComponentInfo.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/shared/model/HomeControlsComponentInfo.kt
new file mode 100644
index 0000000..b9e5080
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/shared/model/HomeControlsComponentInfo.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.dreams.homecontrols.shared.model
+
+import android.content.ComponentName
+
+data class HomeControlsComponentInfo(
+ val componentName: ComponentName?,
+ val allowTrivialControlsOnLockscreen: Boolean,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/shared/model/HomeControlsDataSource.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/shared/model/HomeControlsDataSource.kt
new file mode 100644
index 0000000..8187c54
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/shared/model/HomeControlsDataSource.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.dreams.homecontrols.shared.model
+
+import kotlinx.coroutines.flow.Flow
+
+/** Source of data for home controls dream to get the necessary information it needs. */
+interface HomeControlsDataSource {
+ val componentInfo: Flow<HomeControlsComponentInfo>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/system/LocalHomeControlsDataSourceDelegator.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/system/LocalHomeControlsDataSourceDelegator.kt
new file mode 100644
index 0000000..ca255fd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/system/LocalHomeControlsDataSourceDelegator.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.dreams.homecontrols.system
+
+import com.android.systemui.controls.settings.ControlsSettingsRepository
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dreams.homecontrols.shared.model.HomeControlsComponentInfo
+import com.android.systemui.dreams.homecontrols.shared.model.HomeControlsDataSource
+import com.android.systemui.dreams.homecontrols.system.domain.interactor.HomeControlsComponentInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+
+/**
+ * Queries local data sources for the [HomeControlsComponentInfo] necessary to show the home
+ * controls dream.
+ */
+@SysUISingleton
+class LocalHomeControlsDataSourceDelegator
+@Inject
+constructor(
+ homeControlsComponentInteractor: HomeControlsComponentInteractor,
+ controlsSettingsRepository: ControlsSettingsRepository,
+) : HomeControlsDataSource {
+ override val componentInfo: Flow<HomeControlsComponentInfo> =
+ combine(
+ homeControlsComponentInteractor.panelComponent,
+ controlsSettingsRepository.allowActionOnTrivialControlsInLockscreen,
+ ) { panelComponent, allowActionOnTrivialControlsInLockscreen ->
+ HomeControlsComponentInfo(panelComponent, allowActionOnTrivialControlsInLockscreen)
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/homecontrols/service/HomeControlsRemoteProxyKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/homecontrols/service/HomeControlsRemoteProxyKosmos.kt
new file mode 100644
index 0000000..58ebbf4
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/homecontrols/service/HomeControlsRemoteProxyKosmos.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.dreams.homecontrols.service
+
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+
+val Kosmos.homeControlsRemoteProxy by
+ Kosmos.Fixture {
+ HomeControlsRemoteProxy(
+ bgScope = applicationCoroutineScope,
+ proxy = fakeHomeControlsRemoteBinder,
+ dumpManager = dumpManager,
+ )
+ }
+
+val Kosmos.fakeHomeControlsRemoteBinder by
+ Kosmos.Fixture<FakeIHomeControlsRemoteProxyBinder> { FakeIHomeControlsRemoteProxyBinder() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/homecontrols/service/RemoteHomeControlsDataSourceDelegatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/homecontrols/service/RemoteHomeControlsDataSourceDelegatorKosmos.kt
new file mode 100644
index 0000000..c85c888
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/homecontrols/service/RemoteHomeControlsDataSourceDelegatorKosmos.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.dreams.homecontrols.service
+
+import com.android.systemui.dreams.homecontrols.dagger.HomeControlsRemoteServiceComponent
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.log.logcatLogBuffer
+import org.mockito.kotlin.mock
+
+val Kosmos.remoteHomeControlsDataSourceDelegator by
+ Kosmos.Fixture {
+ RemoteHomeControlsDataSourceDelegator(
+ bgScope = applicationCoroutineScope,
+ serviceFactory = homeControlsRemoteServiceFactory,
+ logBuffer = logcatLogBuffer("HomeControlsDreamInteractor"),
+ dumpManager = dumpManager,
+ )
+ }
+
+var Kosmos.homeControlsRemoteServiceFactory by
+ Kosmos.Fixture<HomeControlsRemoteServiceComponent.Factory> { mock() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/homecontrols/shared/model/FakeHomeControlsDataSource.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/homecontrols/shared/model/FakeHomeControlsDataSource.kt
new file mode 100644
index 0000000..f8ea022
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/homecontrols/shared/model/FakeHomeControlsDataSource.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.dreams.homecontrols.shared.model
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.filterNotNull
+
+class FakeHomeControlsDataSource : HomeControlsDataSource {
+
+ private val _componentInfo = MutableStateFlow<HomeControlsComponentInfo?>(null)
+
+ override val componentInfo: Flow<HomeControlsComponentInfo>
+ get() = _componentInfo.filterNotNull()
+
+ fun setComponentInfo(info: HomeControlsComponentInfo) {
+ _componentInfo.value = info
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/homecontrols/shared/model/HomeControlsDataSourceKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/homecontrols/shared/model/HomeControlsDataSourceKosmos.kt
new file mode 100644
index 0000000..942216b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/homecontrols/shared/model/HomeControlsDataSourceKosmos.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.dreams.homecontrols.shared.model
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.homeControlsDataSource by
+ Kosmos.Fixture<HomeControlsDataSource> { fakeHomeControlsDataSource }
+
+val Kosmos.fakeHomeControlsDataSource by
+ Kosmos.Fixture<FakeHomeControlsDataSource> { FakeHomeControlsDataSource() }