Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d4473d8f authored by Lucas Silva's avatar Lucas Silva Committed by Android (Google) Code Review
Browse files

Merge changes from topic "370691405" into main

* changes:
  Update HomeControlsDreamService to support HSUM
  Implement remote home controls data source
  Remove unnecessary logic from HomeControlsComponentInteractor
  Ensure home control dream is enabled in user context
  Define remote service for home controls
  Define bugfix flag for home controls HSUM support
parents bc092a5a bad11c8a
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -1137,5 +1137,11 @@
                android:name="android.service.dream"
                android:resource="@xml/home_controls_dream_metadata" />
        </service>

        <service android:name="com.android.systemui.dreams.homecontrols.system.HomeControlsRemoteService"
            android:singleUser="true"
            android:exported="false"
            />

    </application>
</manifest>
+10 −0
Original line number Diff line number Diff line
@@ -1614,6 +1614,16 @@ flag {
  }
}

flag {
  name: "home_controls_dream_hsum"
  namespace: "systemui"
  description: "Enables the home controls dream in HSUM"
  bug: "370691405"
  metadata {
    purpose: PURPOSE_BUGFIX
  }
}

flag {
   name: "only_show_media_stream_slider_in_single_volume_mode"
   namespace: "systemui"
+63 −35
Original line number Diff line number Diff line
@@ -16,25 +16,37 @@
package com.android.systemui.dreams.homecontrols

import android.app.Activity
import android.content.ComponentName
import android.content.Intent
import android.os.powerManager
import android.service.controls.ControlsProviderService.CONTROLS_SURFACE_ACTIVITY_PANEL
import android.service.controls.ControlsProviderService.CONTROLS_SURFACE_DREAM
import android.service.controls.ControlsProviderService.EXTRA_CONTROLS_SURFACE
import android.service.dreams.DreamService
import android.window.TaskFragmentInfo
import androidx.lifecycle.testing.TestLifecycleOwner
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.settings.FakeControlsSettingsRepository
import com.android.systemui.dreams.homecontrols.service.TaskFragmentComponent
import com.android.systemui.dreams.homecontrols.shared.model.HomeControlsComponentInfo
import com.android.systemui.dreams.homecontrols.shared.model.fakeHomeControlsDataSource
import com.android.systemui.dreams.homecontrols.shared.model.homeControlsDataSource
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.testKosmos
import com.android.systemui.util.time.fakeSystemClock
import com.android.systemui.util.wakelock.WakeLockFake
import com.google.common.truth.Truth.assertThat
import java.util.Optional
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -47,7 +59,6 @@ import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever

@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -62,13 +73,18 @@ class HomeControlsDreamServiceTest : SysuiTestCase() {
        WakeLockFake.Builder(context).apply { setWakeLock(fakeWakeLock) }
    }

    private val lifecycleOwner = TestLifecycleOwner(coroutineDispatcher = kosmos.testDispatcher)

    private val taskFragmentComponent = mock<TaskFragmentComponent>()
    private val activity = mock<Activity>()
    private val onCreateCallback = argumentCaptor<(TaskFragmentInfo) -> Unit>()
    private val onInfoChangedCallback = argumentCaptor<(TaskFragmentInfo) -> Unit>()
    private val hideCallback = argumentCaptor<() -> Unit>()
    private val dreamServiceDelegate =
        mock<DreamServiceDelegate> { on { getActivity(any()) } doReturn activity }
    private var dreamService =
        mock<DreamService> {
            on { activity } doReturn activity
            on { redirectWake } doReturn false
        }

    private val taskFragmentComponentFactory =
        mock<TaskFragmentComponent.Factory> {
@@ -82,12 +98,32 @@ class HomeControlsDreamServiceTest : SysuiTestCase() {
            } doReturn taskFragmentComponent
        }

    private val underTest: HomeControlsDreamService by lazy { buildService() }
    private val underTest: HomeControlsDreamServiceImpl by lazy {
        with(kosmos) {
            HomeControlsDreamServiceImpl(
                taskFragmentFactory = taskFragmentComponentFactory,
                wakeLockBuilder = fakeWakeLockBuilder,
                powerManager = powerManager,
                systemClock = fakeSystemClock,
                dataSource = homeControlsDataSource,
                logBuffer = logcatLogBuffer("HomeControlsDreamServiceTest"),
                service = dreamService,
                lifecycleOwner = lifecycleOwner,
            )
        }
    }

    @Before
    fun setup() {
        whenever(kosmos.controlsComponent.getControlsListingController())
            .thenReturn(Optional.of(kosmos.controlsListingController))
        Dispatchers.setMain(kosmos.testDispatcher)
        kosmos.fakeHomeControlsDataSource.setComponentInfo(
            HomeControlsComponentInfo(PANEL_COMPONENT, true)
        )
    }

    @After
    fun tearDown() {
        Dispatchers.resetMain()
    }

    @Test
@@ -108,13 +144,10 @@ class HomeControlsDreamServiceTest : SysuiTestCase() {
    @Test
    fun testNotCreatingTaskFragmentComponentWhenActivityIsNull() =
        testScope.runTest {
            val serviceWithNullActivity =
                buildService(
                    mock<DreamServiceDelegate> { on { getActivity(underTest) } doReturn null }
                )

            serviceWithNullActivity.onAttachedToWindow()
            dreamService = mock<DreamService> { on { activity } doReturn null }
            underTest.onAttachedToWindow()
            verify(taskFragmentComponentFactory, never()).create(any(), any(), any(), any())
            verify(dreamService).finish()
        }

    @Test
@@ -137,9 +170,9 @@ class HomeControlsDreamServiceTest : SysuiTestCase() {
    @Test
    fun testFinishesDreamWithoutRestartingActivityWhenNotRedirectingWakes() =
        testScope.runTest {
            whenever(dreamServiceDelegate.redirectWake(any())).thenReturn(false)
            underTest.onAttachedToWindow()
            onCreateCallback.firstValue.invoke(mock<TaskFragmentInfo>())
            runCurrent()
            verify(taskFragmentComponent, times(1)).startActivityInTaskFragment(intentMatcher())

            // Task fragment becomes empty
@@ -149,16 +182,21 @@ class HomeControlsDreamServiceTest : SysuiTestCase() {
            advanceUntilIdle()
            // Dream is finished and activity is not restarted
            verify(taskFragmentComponent, times(1)).startActivityInTaskFragment(intentMatcher())
            verify(dreamServiceDelegate, never()).wakeUp(any())
            verify(dreamServiceDelegate).finish(any())
            verify(dreamService, never()).wakeUp()
            verify(dreamService).finish()
        }

    @Test
    fun testRestartsActivityWhenRedirectingWakes() =
        testScope.runTest {
            whenever(dreamServiceDelegate.redirectWake(any())).thenReturn(true)
            dreamService =
                mock<DreamService> {
                    on { activity } doReturn activity
                    on { redirectWake } doReturn true
                }
            underTest.onAttachedToWindow()
            onCreateCallback.firstValue.invoke(mock<TaskFragmentInfo>())
            runCurrent()
            verify(taskFragmentComponent, times(1)).startActivityInTaskFragment(intentMatcher())

            // Task fragment becomes empty
@@ -166,30 +204,20 @@ class HomeControlsDreamServiceTest : SysuiTestCase() {
                mock<TaskFragmentInfo> { on { isEmpty } doReturn true }
            )
            advanceUntilIdle()

            // Activity is restarted instead of finishing the dream.
            verify(taskFragmentComponent, times(2)).startActivityInTaskFragment(intentMatcher())
            verify(dreamServiceDelegate).wakeUp(any())
            verify(dreamServiceDelegate, never()).finish(any())
            verify(dreamService).wakeUp()
            verify(dreamService, never()).finish()
        }

    private fun intentMatcher() =
        argThat<Intent> {
            getIntExtra(EXTRA_CONTROLS_SURFACE, CONTROLS_SURFACE_ACTIVITY_PANEL) ==
                CONTROLS_SURFACE_DREAM
                CONTROLS_SURFACE_DREAM && component == PANEL_COMPONENT
        }

    private fun buildService(
        activityProvider: DreamServiceDelegate = dreamServiceDelegate
    ): HomeControlsDreamService =
        with(kosmos) {
            return HomeControlsDreamService(
                controlsSettingsRepository = FakeControlsSettingsRepository(),
                taskFragmentFactory = taskFragmentComponentFactory,
                homeControlsComponentInteractor = homeControlsComponentInteractor,
                wakeLockBuilder = fakeWakeLockBuilder,
                dreamServiceDelegate = activityProvider,
                bgDispatcher = testDispatcher,
                logBuffer = logcatLogBuffer("HomeControlsDreamServiceTest")
            )
    private companion object {
        val PANEL_COMPONENT = ComponentName("test.pkg", "test.panel")
    }
}
+16 −10
Original line number Diff line number Diff line
@@ -34,9 +34,14 @@ import com.android.systemui.controls.panels.AuthorizedPanelsRepository
import com.android.systemui.controls.panels.SelectedComponentRepository
import com.android.systemui.controls.panels.authorizedPanelsRepository
import com.android.systemui.controls.panels.selectedComponentRepository
import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor
import com.android.systemui.dreams.homecontrols.system.HomeControlsDreamStartable
import com.android.systemui.dreams.homecontrols.system.domain.interactor.HomeControlsComponentInteractor
import com.android.systemui.dreams.homecontrols.system.domain.interactor.controlsComponent
import com.android.systemui.dreams.homecontrols.system.domain.interactor.controlsListingController
import com.android.systemui.dreams.homecontrols.system.domain.interactor.homeControlsComponentInteractor
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testScope
import com.android.systemui.settings.userTracker
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.repository.fakeUserRepository
@@ -96,8 +101,9 @@ class HomeControlsDreamStartableTest : SysuiTestCase() {
            HomeControlsDreamStartable(
                mContext,
                packageManager,
                kosmos.userTracker,
                homeControlsComponentInteractor,
                kosmos.applicationCoroutineScope
                kosmos.applicationCoroutineScope,
            )
    }

@@ -113,7 +119,7 @@ class HomeControlsDreamStartableTest : SysuiTestCase() {
                .setComponentEnabledSetting(
                    eq(componentName),
                    eq(PackageManager.COMPONENT_ENABLED_STATE_ENABLED),
                    eq(PackageManager.DONT_KILL_APP)
                    eq(PackageManager.DONT_KILL_APP),
                )
        }

@@ -128,7 +134,7 @@ class HomeControlsDreamStartableTest : SysuiTestCase() {
                .setComponentEnabledSetting(
                    eq(componentName),
                    eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED),
                    eq(PackageManager.DONT_KILL_APP)
                    eq(PackageManager.DONT_KILL_APP),
                )
        }

@@ -143,14 +149,14 @@ class HomeControlsDreamStartableTest : SysuiTestCase() {
                .setComponentEnabledSetting(
                    eq(componentName),
                    eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED),
                    eq(PackageManager.DONT_KILL_APP)
                    eq(PackageManager.DONT_KILL_APP),
                )
        }

    private fun ControlsServiceInfo(
        componentName: ComponentName,
        label: CharSequence,
        hasPanel: Boolean
        hasPanel: Boolean,
    ): ControlsServiceInfo {
        val serviceInfo =
            ServiceInfo().apply {
@@ -165,7 +171,7 @@ class HomeControlsDreamStartableTest : SysuiTestCase() {
        context: Context,
        serviceInfo: ServiceInfo,
        private val label: CharSequence,
        hasPanel: Boolean
        hasPanel: Boolean,
    ) : ControlsServiceInfo(context, serviceInfo) {

        init {
@@ -185,7 +191,7 @@ class HomeControlsDreamStartableTest : SysuiTestCase() {
            UserInfo(
                /* id= */ PRIMARY_USER_ID,
                /* name= */ "primary user",
                /* flags= */ UserInfo.FLAG_PRIMARY
                /* flags= */ UserInfo.FLAG_PRIMARY,
            )
        private const val TEST_PACKAGE_PANEL = "pkg.panel"
        private val TEST_COMPONENT_PANEL = ComponentName(TEST_PACKAGE_PANEL, "service")
@@ -193,13 +199,13 @@ class HomeControlsDreamStartableTest : SysuiTestCase() {
            SelectedComponentRepository.SelectedComponent(
                TEST_PACKAGE_PANEL,
                TEST_COMPONENT_PANEL,
                true
                true,
            )
        private val TEST_SELECTED_COMPONENT_NON_PANEL =
            SelectedComponentRepository.SelectedComponent(
                TEST_PACKAGE_PANEL,
                TEST_COMPONENT_PANEL,
                false
                false,
            )
    }
}
+95 −0
Original line number Diff line number Diff line
/*
 * 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")
    }
}
Loading