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

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

Merge "Always start dream underneath hub" into main

parents 2153fff8 9a554c3c
Loading
Loading
Loading
Loading
+16 −0
Original line number Original line Diff line number Diff line
@@ -184,6 +184,22 @@ public class DreamManager {
        }
        }
    }
    }


    /**
     * Whether dreaming can start given user settings and the current dock/charge state.
     *
     * @hide
     */
    @UserHandleAware
    @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE)
    public boolean canStartDreaming(boolean isScreenOn) {
        try {
            return mService.canStartDreaming(isScreenOn);
        } catch (RemoteException e) {
            e.rethrowFromSystemServer();
        }
        return false;
    }

    /**
    /**
     * Returns whether the device is Dreaming.
     * Returns whether the device is Dreaming.
     *
     *
+2 −0
Original line number Original line Diff line number Diff line
@@ -38,6 +38,8 @@ interface IDreamManager {
    boolean isDreaming();
    boolean isDreaming();
    @UnsupportedAppUsage
    @UnsupportedAppUsage
    boolean isDreamingOrInPreview();
    boolean isDreamingOrInPreview();
    @UnsupportedAppUsage
    boolean canStartDreaming(boolean isScreenOn);
    void finishSelf(in IBinder token, boolean immediate);
    void finishSelf(in IBinder token, boolean immediate);
    void startDozing(in IBinder token, int screenState, int screenBrightness);
    void startDozing(in IBinder token, int screenState, int screenBrightness);
    void stopDozing(in IBinder token);
    void stopDozing(in IBinder token);
+132 −0
Original line number Original line 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.communal

import android.service.dream.dreamManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.power.shared.model.ScreenPowerState
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.whenever
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
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.Mockito.never
import org.mockito.Mockito.verify

@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class CommunalDreamStartableTest : SysuiTestCase() {
    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope

    private lateinit var underTest: CommunalDreamStartable

    private val dreamManager by lazy { kosmos.dreamManager }
    private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
    private val powerRepository by lazy { kosmos.fakePowerRepository }

    @Before
    fun setUp() {
        underTest =
            CommunalDreamStartable(
                    powerInteractor = kosmos.powerInteractor,
                    keyguardInteractor = kosmos.keyguardInteractor,
                    keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
                    dreamManager = dreamManager,
                    bgScope = kosmos.applicationCoroutineScope,
                )
                .apply { start() }
    }

    @Test
    fun startDreamWhenTransitioningToHub() =
        testScope.runTest {
            keyguardRepository.setDreaming(false)
            powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
            whenever(dreamManager.canStartDreaming(/* isScreenOn = */ true)).thenReturn(true)
            runCurrent()

            verify(dreamManager, never()).startDream()

            transition(from = KeyguardState.DREAMING, to = KeyguardState.GLANCEABLE_HUB)

            verify(dreamManager).startDream()
        }

    @Test
    fun shouldNotStartDreamWhenIneligibleToDream() =
        testScope.runTest {
            keyguardRepository.setDreaming(false)
            powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
            // Not eligible to dream
            whenever(dreamManager.canStartDreaming(/* isScreenOn = */ true)).thenReturn(false)
            transition(from = KeyguardState.DREAMING, to = KeyguardState.GLANCEABLE_HUB)

            verify(dreamManager, never()).startDream()
        }

    @Test
    fun shouldNotStartDreamIfAlreadyDreaming() =
        testScope.runTest {
            keyguardRepository.setDreaming(true)
            powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
            whenever(dreamManager.canStartDreaming(/* isScreenOn = */ true)).thenReturn(true)
            transition(from = KeyguardState.DREAMING, to = KeyguardState.GLANCEABLE_HUB)

            verify(dreamManager, never()).startDream()
        }

    @Test
    fun shouldNotStartDreamForInvalidTransition() =
        testScope.runTest {
            keyguardRepository.setDreaming(true)
            powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
            whenever(dreamManager.canStartDreaming(/* isScreenOn = */ true)).thenReturn(true)

            // Verify we do not trigger dreaming for any other state besides glanceable hub
            for (state in KeyguardState.entries) {
                if (state == KeyguardState.GLANCEABLE_HUB) continue
                transition(from = KeyguardState.GLANCEABLE_HUB, to = state)
                verify(dreamManager, never()).startDream()
            }
        }

    private suspend fun TestScope.transition(from: KeyguardState, to: KeyguardState) {
        kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
            from = from,
            to = to,
            testScope = this
        )
        runCurrent()
    }
}
+70 −0
Original line number Original line 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.communal

import android.annotation.SuppressLint
import android.app.DreamManager
import com.android.systemui.CoreStartable
import com.android.systemui.Flags.communalHub
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.util.kotlin.Utils.Companion.sample
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach

/**
 * A [CoreStartable] responsible for automatically starting the dream when the communal hub is
 * shown, to support the user swiping away the hub to enter the dream.
 */
@SysUISingleton
class CommunalDreamStartable
@Inject
constructor(
    private val powerInteractor: PowerInteractor,
    private val keyguardInteractor: KeyguardInteractor,
    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
    private val dreamManager: DreamManager,
    @Background private val bgScope: CoroutineScope,
) : CoreStartable {
    @SuppressLint("MissingPermission")
    override fun start() {
        if (!communalHub()) {
            return
        }

        // Restart the dream underneath the hub in order to support the ability to swipe
        // away the hub to enter the dream.
        keyguardTransitionInteractor.finishedKeyguardState
            .sample(powerInteractor.isAwake, keyguardInteractor.isDreaming)
            .onEach { (finishedState, isAwake, dreaming) ->
                if (
                    finishedState == KeyguardState.GLANCEABLE_HUB &&
                        !dreaming &&
                        dreamManager.canStartDreaming(isAwake)
                ) {
                    dreamManager.startDream()
                }
            }
            .launchIn(bgScope)
    }
}
+6 −0
Original line number Original line Diff line number Diff line
@@ -24,6 +24,7 @@ import com.android.systemui.accessibility.Magnification
import com.android.systemui.back.domain.interactor.BackActionInteractor
import com.android.systemui.back.domain.interactor.BackActionInteractor
import com.android.systemui.biometrics.BiometricNotificationService
import com.android.systemui.biometrics.BiometricNotificationService
import com.android.systemui.clipboardoverlay.ClipboardListener
import com.android.systemui.clipboardoverlay.ClipboardListener
import com.android.systemui.communal.CommunalDreamStartable
import com.android.systemui.communal.CommunalSceneStartable
import com.android.systemui.communal.CommunalSceneStartable
import com.android.systemui.communal.log.CommunalLoggerStartable
import com.android.systemui.communal.log.CommunalLoggerStartable
import com.android.systemui.communal.widgets.CommunalAppWidgetHostStartable
import com.android.systemui.communal.widgets.CommunalAppWidgetHostStartable
@@ -326,6 +327,11 @@ abstract class SystemUICoreStartableModule {
    @ClassKey(CommunalSceneStartable::class)
    @ClassKey(CommunalSceneStartable::class)
    abstract fun bindCommunalSceneStartable(impl: CommunalSceneStartable): CoreStartable
    abstract fun bindCommunalSceneStartable(impl: CommunalSceneStartable): CoreStartable


    @Binds
    @IntoMap
    @ClassKey(CommunalDreamStartable::class)
    abstract fun bindCommunalDreamStartable(impl: CommunalDreamStartable): CoreStartable

    @Binds
    @Binds
    @IntoMap
    @IntoMap
    @ClassKey(CommunalAppWidgetHostStartable::class)
    @ClassKey(CommunalAppWidgetHostStartable::class)
Loading