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

Commit bda9a719 authored by chelseahao's avatar chelseahao
Browse files

Use autoOn new api.

Test: atest -c com.android.systemui.qs.tiles.dialog.bluetooth
Bug: b/316822488 b/316985153
Flag: NA
Change-Id: Ie620946259850603bc3e06e607f38e7498fdfd27
parent a372e4b6
Loading
Loading
Loading
Loading
+5 −14
Original line number Diff line number Diff line
@@ -19,8 +19,6 @@ package com.android.systemui.qs.tiles.dialog.bluetooth
import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map

/** Interactor class responsible for interacting with the Bluetooth Auto-On feature. */
@SysUISingleton
@@ -30,14 +28,10 @@ constructor(
    private val bluetoothAutoOnRepository: BluetoothAutoOnRepository,
) {

    val isEnabled = bluetoothAutoOnRepository.isAutoOn.map { it == ENABLED }.distinctUntilChanged()
    val isEnabled = bluetoothAutoOnRepository.isAutoOn

    /**
     * Checks if the auto on value is present in the repository.
     *
     * @return `true` if a value is present (i.e, the feature is enabled by the Bluetooth server).
     */
    suspend fun isValuePresent(): Boolean = bluetoothAutoOnRepository.isValuePresent()
    /** Checks if the auto on feature is supported. */
    suspend fun isAutoOnSupported(): Boolean = bluetoothAutoOnRepository.isAutoOnSupported()

    /**
     * Sets enabled or disabled based on the provided value.
@@ -45,17 +39,14 @@ constructor(
     * @param value `true` to enable the feature, `false` to disable it.
     */
    suspend fun setEnabled(value: Boolean) {
        if (!isValuePresent()) {
        if (!isAutoOnSupported()) {
            Log.e(TAG, "Trying to set toggle value while feature not available.")
        } else {
            val newValue = if (value) ENABLED else DISABLED
            bluetoothAutoOnRepository.setAutoOn(newValue)
            bluetoothAutoOnRepository.setAutoOn(value)
        }
    }

    companion object {
        private const val TAG = "BluetoothAutoOnInteractor"
        const val DISABLED = 0
        const val ENABLED = 1
    }
}
+76 −49
Original line number Diff line number Diff line
@@ -16,22 +16,23 @@

package com.android.systemui.qs.tiles.dialog.bluetooth

import android.bluetooth.BluetoothAdapter
import android.util.Log
import com.android.settingslib.bluetooth.BluetoothCallback
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
@@ -44,61 +45,87 @@ import kotlinx.coroutines.withContext
class BluetoothAutoOnRepository
@Inject
constructor(
    private val secureSettings: SecureSettings,
    private val userRepository: UserRepository,
    localBluetoothManager: LocalBluetoothManager?,
    private val bluetoothAdapter: BluetoothAdapter?,
    @Application private val coroutineScope: CoroutineScope,
    @Background private val backgroundDispatcher: CoroutineDispatcher,
) {

    // Flow representing the auto on setting value for the current user
    @OptIn(ExperimentalCoroutinesApi::class)
    internal val isAutoOn: StateFlow<Int> =
        userRepository.selectedUserInfo
            .flatMapLatest { userInfo ->
                secureSettings
                    .observerFlow(userInfo.id, SETTING_NAME)
                    .onStart { emit(Unit) }
                    .map { secureSettings.getIntForUser(SETTING_NAME, UNSET, userInfo.id) }
    // Flow representing the auto on state for the current user
    internal val isAutoOn: Flow<Boolean> =
        localBluetoothManager?.eventManager?.let { eventManager ->
            conflatedCallbackFlow {
                    val listener =
                        object : BluetoothCallback {
                            override fun onAutoOnStateChanged(autoOnState: Int) {
                                super.onAutoOnStateChanged(autoOnState)
                                if (
                                    autoOnState == BluetoothAdapter.AUTO_ON_STATE_ENABLED ||
                                        autoOnState == BluetoothAdapter.AUTO_ON_STATE_DISABLED
                                ) {
                                    trySendWithFailureLogging(
                                        autoOnState == BluetoothAdapter.AUTO_ON_STATE_ENABLED,
                                        TAG,
                                        "onAutoOnStateChanged"
                                    )
                                }
                            }
                        }
            .distinctUntilChanged()
                    eventManager.registerCallback(listener)
                    awaitClose { eventManager.unregisterCallback(listener) }
                }
                .onStart { emit(isAutoOnEnabled()) }
                .flowOn(backgroundDispatcher)
                .stateIn(
                    coroutineScope,
                    SharingStarted.WhileSubscribed(replayExpirationMillis = 0),
                UNSET
                    initialValue = false
                )
        }
            ?: flowOf(false)

    /**
     * Checks if the auto on setting value is ever set for the current user.
     * Checks if the auto on feature is supported for the current user.
     *
     * @return `true` if the setting value is not UNSET, `false` otherwise.
     * @throws Exception if an error occurs while checking auto-on support.
     */
    suspend fun isValuePresent(): Boolean =
    suspend fun isAutoOnSupported(): Boolean =
        withContext(backgroundDispatcher) {
            secureSettings.getIntForUser(
                SETTING_NAME,
                UNSET,
                userRepository.getSelectedUserInfo().id
            ) != UNSET
            try {
                bluetoothAdapter?.isAutoOnSupported ?: false
            } catch (e: Exception) {
                // Server could throw TimeoutException, InterruptedException or ExecutionException
                Log.e(TAG, "Error calling isAutoOnSupported", e)
                false
            }
        }

    /**
     * Sets the Bluetooth Auto-On setting value for the current user.
     *
     * @param value The new setting value to be applied.
     */
    suspend fun setAutoOn(value: Int) {
    /** Sets the Bluetooth Auto-On for the current user. */
    suspend fun setAutoOn(value: Boolean) {
        withContext(backgroundDispatcher) {
            secureSettings.putIntForUser(
                SETTING_NAME,
                value,
                userRepository.getSelectedUserInfo().id
            )
            try {
                bluetoothAdapter?.setAutoOnEnabled(value)
            } catch (e: Exception) {
                // Server could throw IllegalStateException, TimeoutException, InterruptedException
                // or ExecutionException
                Log.e(TAG, "Error calling setAutoOnEnabled", e)
            }
        }
    }

    private suspend fun isAutoOnEnabled() =
        withContext(backgroundDispatcher) {
            try {
                bluetoothAdapter?.isAutoOnEnabled ?: false
            } catch (e: Exception) {
                // Server could throw IllegalStateException, TimeoutException, InterruptedException
                // or ExecutionException
                Log.e(TAG, "Error calling isAutoOnEnabled", e)
                false
            }
        }

    companion object {
        const val SETTING_NAME = "bluetooth_automatic_turn_on"
        const val UNSET = -1
    private companion object {
        const val TAG = "BluetoothAutoOnRepository"
    }
}
+1 −2
Original line number Diff line number Diff line
@@ -29,7 +29,6 @@ import androidx.annotation.StringRes
import androidx.annotation.VisibleForTesting
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.logging.UiEventLogger
import com.android.settingslib.flags.Flags.bluetoothQsTileDialogAutoOnToggle
import com.android.systemui.Prefs
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
@@ -277,7 +276,7 @@ constructor(

    @VisibleForTesting
    internal suspend fun isAutoOnToggleFeatureAvailable() =
        bluetoothQsTileDialogAutoOnToggle() && bluetoothAutoOnInteractor.isValuePresent()
        bluetoothAutoOnInteractor.isAutoOnSupported()

    companion object {
        private const val INTERACTION_JANK_TAG = "bluetooth_tile_dialog"
+30 −32
Original line number Diff line number Diff line
@@ -16,22 +16,25 @@

package com.android.systemui.qs.tiles.dialog.bluetooth

import android.content.pm.UserInfo
import android.bluetooth.BluetoothAdapter
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import kotlin.test.Test
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.Mock
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule

@@ -41,8 +44,17 @@ class BluetoothAutoOnInteractorTest : SysuiTestCase() {
    @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
    private val testDispatcher = StandardTestDispatcher()
    private val testScope = TestScope(testDispatcher)
    private var secureSettings: FakeSettings = FakeSettings()
    private val userRepository: FakeUserRepository = FakeUserRepository()
    private val bluetoothAdapter =
        mock<BluetoothAdapter> {
            var autoOn = false
            whenever(isAutoOnEnabled).thenAnswer { autoOn }

            whenever(setAutoOnEnabled(anyBoolean())).thenAnswer { invocation ->
                autoOn = invocation.getArgument(0) as Boolean
                autoOn
            }
        }
    @Mock private lateinit var localBluetoothManager: LocalBluetoothManager
    private lateinit var bluetoothAutoOnInteractor: BluetoothAutoOnInteractor

    @Before
@@ -50,49 +62,35 @@ class BluetoothAutoOnInteractorTest : SysuiTestCase() {
        bluetoothAutoOnInteractor =
            BluetoothAutoOnInteractor(
                BluetoothAutoOnRepository(
                    secureSettings,
                    userRepository,
                    localBluetoothManager,
                    bluetoothAdapter,
                    testScope.backgroundScope,
                    testDispatcher
                    testDispatcher,
                )
            )
    }

    @Test
    fun testSet_bluetoothAutoOnUnset_doNothing() {
    fun testSetEnabled_bluetoothAutoOnUnsupported_doNothing() {
        testScope.runTest {
            bluetoothAutoOnInteractor.setEnabled(true)

            val actualValue by collectLastValue(bluetoothAutoOnInteractor.isEnabled)
            whenever(bluetoothAdapter.isAutoOnSupported).thenReturn(false)

            bluetoothAutoOnInteractor.setEnabled(true)
            runCurrent()

            Truth.assertThat(actualValue).isEqualTo(false)
            assertFalse(bluetoothAdapter.isAutoOnEnabled)
        }
    }

    @Test
    fun testSet_bluetoothAutoOnSet_setNewValue() {
    fun testSetEnabled_bluetoothAutoOnSupported_setNewValue() {
        testScope.runTest {
            userRepository.setUserInfos(listOf(SYSTEM_USER))
            secureSettings.putIntForUser(
                BluetoothAutoOnRepository.SETTING_NAME,
                BluetoothAutoOnInteractor.DISABLED,
                SYSTEM_USER_ID
            )
            bluetoothAutoOnInteractor.setEnabled(true)

            val actualValue by collectLastValue(bluetoothAutoOnInteractor.isEnabled)
            whenever(bluetoothAdapter.isAutoOnSupported).thenReturn(true)

            bluetoothAutoOnInteractor.setEnabled(true)
            runCurrent()

            Truth.assertThat(actualValue).isEqualTo(true)
        }
            assertTrue(bluetoothAdapter.isAutoOnEnabled)
        }

    companion object {
        private const val SYSTEM_USER_ID = 0
        private val SYSTEM_USER =
            UserInfo(/* id= */ SYSTEM_USER_ID, /* name= */ "system user", /* flags= */ 0)
    }
}
+22 −51
Original line number Diff line number Diff line
@@ -16,18 +16,14 @@

package com.android.systemui.qs.tiles.dialog.bluetooth

import android.content.pm.UserInfo
import android.os.UserHandle
import android.bluetooth.BluetoothAdapter
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.settingslib.bluetooth.BluetoothEventManager
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothAutoOnInteractor.Companion.DISABLED
import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothAutoOnInteractor.Companion.ENABLED
import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothAutoOnRepository.Companion.SETTING_NAME
import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothAutoOnRepository.Companion.UNSET
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
@@ -37,6 +33,7 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule

@@ -46,83 +43,57 @@ class BluetoothAutoOnRepositoryTest : SysuiTestCase() {
    @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
    private val testDispatcher = StandardTestDispatcher()
    private val testScope = TestScope(testDispatcher)
    private var secureSettings: FakeSettings = FakeSettings()
    private val userRepository: FakeUserRepository = FakeUserRepository()
    @Mock private lateinit var bluetoothAdapter: BluetoothAdapter
    @Mock private lateinit var localBluetoothManager: LocalBluetoothManager
    @Mock private lateinit var eventManager: BluetoothEventManager

    private lateinit var bluetoothAutoOnRepository: BluetoothAutoOnRepository

    @Before
    fun setUp() {
        whenever(localBluetoothManager.eventManager).thenReturn(eventManager)
        bluetoothAutoOnRepository =
            BluetoothAutoOnRepository(
                secureSettings,
                userRepository,
                localBluetoothManager,
                bluetoothAdapter,
                testScope.backgroundScope,
                testDispatcher
                testDispatcher,
            )

        userRepository.setUserInfos(listOf(SECONDARY_USER, SYSTEM_USER))
    }

    @Test
    fun testGetValue_valueUnset() {
    fun testIsAutoOn_returnFalse() {
        testScope.runTest {
            userRepository.setSelectedUserInfo(SYSTEM_USER)
            whenever(bluetoothAdapter.isAutoOnEnabled).thenReturn(false)
            val actualValue by collectLastValue(bluetoothAutoOnRepository.isAutoOn)

            runCurrent()

            assertThat(actualValue).isEqualTo(UNSET)
            assertThat(bluetoothAutoOnRepository.isValuePresent()).isFalse()
            assertThat(actualValue).isEqualTo(false)
        }
    }

    @Test
    fun testGetValue_valueFalse() {
    fun testIsAutoOn_returnTrue() {
        testScope.runTest {
            userRepository.setSelectedUserInfo(SYSTEM_USER)
            whenever(bluetoothAdapter.isAutoOnEnabled).thenReturn(true)
            val actualValue by collectLastValue(bluetoothAutoOnRepository.isAutoOn)

            secureSettings.putIntForUser(SETTING_NAME, DISABLED, UserHandle.USER_SYSTEM)
            runCurrent()

            assertThat(actualValue).isEqualTo(DISABLED)
            assertThat(actualValue).isEqualTo(true)
        }
    }

    @Test
    fun testGetValue_valueTrue() {
    fun testIsAutoOnSupported_returnTrue() {
        testScope.runTest {
            userRepository.setSelectedUserInfo(SYSTEM_USER)
            val actualValue by collectLastValue(bluetoothAutoOnRepository.isAutoOn)
            whenever(bluetoothAdapter.isAutoOnSupported).thenReturn(true)
            val actualValue = bluetoothAutoOnRepository.isAutoOnSupported()

            secureSettings.putIntForUser(SETTING_NAME, ENABLED, UserHandle.USER_SYSTEM)
            runCurrent()

            assertThat(actualValue).isEqualTo(ENABLED)
            assertThat(actualValue).isEqualTo(true)
        }
    }

    @Test
    fun testGetValue_valueTrue_secondaryUser_returnTrue() {
        testScope.runTest {
            userRepository.setSelectedUserInfo(SECONDARY_USER)
            val actualValue by collectLastValue(bluetoothAutoOnRepository.isAutoOn)

            secureSettings.putIntForUser(SETTING_NAME, DISABLED, SYSTEM_USER_ID)
            secureSettings.putIntForUser(SETTING_NAME, ENABLED, SECONDARY_USER_ID)
            runCurrent()

            assertThat(actualValue).isEqualTo(ENABLED)
        }
    }

    companion object {
        private const val SYSTEM_USER_ID = 0
        private const val SECONDARY_USER_ID = 1
        private val SYSTEM_USER =
            UserInfo(/* id= */ SYSTEM_USER_ID, /* name= */ "system user", /* flags= */ 0)
        private val SECONDARY_USER =
            UserInfo(/* id= */ SECONDARY_USER_ID, /* name= */ "secondary user", /* flags= */ 0)
    }
}
Loading