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

Commit 57927ea7 authored by Behnam Heydarshahi's avatar Behnam Heydarshahi Committed by Android (Google) Code Review
Browse files

Merge "Migrate LocationTile" into main

parents ca8fc5aa 17512e0d
Loading
Loading
Loading
Loading
+92 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.qs.tiles.impl.location.domain

import android.graphics.drawable.Drawable
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel
import com.android.systemui.qs.tiles.impl.location.qsLocationTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.res.R
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth
import junit.framework.Assert
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidJUnit4::class)
class LocationTileMapperTest : SysuiTestCase() {
    private val kosmos = Kosmos()
    private val qsTileConfig = kosmos.qsLocationTileConfig
    private val mapper by lazy { LocationTileMapper(context) }

    @Test
    fun mapsDisabledDataToInactiveState() {
        val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(false))

        val actualActivationState = tileState.activationState
        Assert.assertEquals(QSTileState.ActivationState.INACTIVE, actualActivationState)
    }

    @Test
    fun mapsEnabledDataToActiveState() {
        val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(true))

        val actualActivationState = tileState.activationState
        Assert.assertEquals(QSTileState.ActivationState.ACTIVE, actualActivationState)
    }

    @Test
    fun mapsEnabledDataToOnIconState() {
        val fakeDrawable = mock<Drawable>()
        context.orCreateTestableResources.addOverride(R.drawable.qs_location_icon_on, fakeDrawable)
        val expectedIcon = Icon.Loaded(fakeDrawable, null)

        val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(true))

        val actualIcon = tileState.icon()
        Truth.assertThat(actualIcon).isEqualTo(expectedIcon)
    }

    @Test
    fun mapsDisabledDataToOffIconState() {
        val fakeDrawable = mock<Drawable>()
        context.orCreateTestableResources.addOverride(R.drawable.qs_location_icon_off, fakeDrawable)
        val expectedIcon = Icon.Loaded(fakeDrawable, null)

        val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(false))

        val actualIcon = tileState.icon()
        Truth.assertThat(actualIcon).isEqualTo(expectedIcon)
    }

    @Test
    fun supportsClickAndLongClickActions() {
        val dontCare = true

        val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(dontCare))

        val supportedActions = tileState.supportedActions
        Truth.assertThat(supportedActions)
            .containsExactly(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
    }
}
+83 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.qs.tiles.impl.location.interactor

import android.os.UserHandle
import android.testing.LeakCheck
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.coroutines.collectValues
import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
import com.android.systemui.qs.tiles.impl.location.domain.interactor.LocationTileDataInteractor
import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel
import com.android.systemui.utils.leaks.FakeLocationController
import com.google.common.truth.Truth
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class LocationTileDataInteractorTest : SysuiTestCase() {
    private lateinit var controller: FakeLocationController
    private lateinit var underTest: LocationTileDataInteractor

    @Before
    fun setup() {
        controller = FakeLocationController(LeakCheck())
        underTest = LocationTileDataInteractor(controller)
    }

    @Test
    fun isAvailableRegardlessOfController() = runTest {
        controller.setLocationEnabled(false)

        runCurrent()
        val availability by collectLastValue(underTest.availability(TEST_USER))

        Truth.assertThat(availability).isTrue()
    }

    @Test
    fun dataMatchesController() = runTest {
        controller.setLocationEnabled(false)
        val flowValues: List<LocationTileModel> by
            collectValues(underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)))

        runCurrent()
        controller.setLocationEnabled(true)
        runCurrent()
        controller.setLocationEnabled(false)
        runCurrent()

        Truth.assertThat(flowValues.size).isEqualTo(3)
        Truth.assertThat(flowValues.map { it.isEnabled })
            .containsExactly(false, true, false)
            .inOrder()
    }

    private companion object {
        val TEST_USER = UserHandle.of(1)!!
    }
}
+101 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.qs.tiles.impl.location.interactor

import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
import com.android.systemui.qs.tiles.base.actions.intentInputs
import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.click
import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.longClick
import com.android.systemui.qs.tiles.impl.location.domain.interactor.LocationTileUserActionInteractor
import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel
import com.android.systemui.statusbar.phone.FakeKeyguardStateController
import com.android.systemui.statusbar.policy.LocationController
import com.google.common.truth.Truth.assertThat
import kotlin.coroutines.EmptyCoroutineContext
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.MockitoAnnotations

@SmallTest
@RunWith(AndroidJUnit4::class)
class LocationTileUserActionInteractorTest : SysuiTestCase() {

    private val qsTileIntentUserActionHandler = FakeQSTileIntentUserInputHandler()
    private val keyguardController = FakeKeyguardStateController()

    private lateinit var underTest: LocationTileUserActionInteractor

    @Mock private lateinit var locationController: LocationController
    @Mock private lateinit var activityStarter: ActivityStarter

    @Before
    fun setup() {
        MockitoAnnotations.initMocks(this)
        val kosmos = Kosmos()
        underTest =
            LocationTileUserActionInteractor(
                EmptyCoroutineContext,
                kosmos.testScope,
                locationController,
                qsTileIntentUserActionHandler,
                activityStarter,
                keyguardController,
            )
    }

    @Test
    fun handleClickToEnable() = runTest {
        val stateBeforeClick = false

        underTest.handleInput(click(LocationTileModel(stateBeforeClick)))

        Mockito.verify(locationController).setLocationEnabled(!stateBeforeClick)
    }

    @Test
    fun handleClickToDisable() = runTest {
        val stateBeforeClick = true

        underTest.handleInput(click(LocationTileModel(stateBeforeClick)))

        Mockito.verify(locationController).setLocationEnabled(!stateBeforeClick)
    }

    @Test
    fun handleLongClick() = runTest {
        val dontCare = true

        underTest.handleInput(longClick(LocationTileModel(dontCare)))

        assertThat(qsTileIntentUserActionHandler.handledInputs).hasSize(1)
        val intentInput = qsTileIntentUserActionHandler.intentInputs.last()
        val actualIntentAction = intentInput.intent.action
        val expectedIntentAction = Settings.ACTION_LOCATION_SOURCE_SETTINGS
        assertThat(actualIntentAction).isEqualTo(expectedIntentAction)
    }
}
+60 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.qs.tiles.impl.location.domain

import android.content.Context
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.res.R
import javax.inject.Inject

/** Maps [LocationTileModel] to [QSTileState]. */
class LocationTileMapper @Inject constructor(private val context: Context) :
    QSTileDataToStateMapper<LocationTileModel> {

    override fun map(config: QSTileConfig, data: LocationTileModel): QSTileState =
        QSTileState.build(context, config.uiConfig) {
            val icon =
                Icon.Loaded(
                    context.resources.getDrawable(
                        if (data.isEnabled) {
                            R.drawable.qs_location_icon_on
                        } else {
                            R.drawable.qs_location_icon_off
                        }
                    ),
                    contentDescription = null
                )
            this.icon = { icon }

            this.label = context.resources.getString(R.string.quick_settings_location_label)

            if (data.isEnabled) {
                activationState = QSTileState.ActivationState.ACTIVE
                secondaryLabel = context.resources.getStringArray(R.array.tile_states_location)[2]
            } else {
                activationState = QSTileState.ActivationState.INACTIVE
                secondaryLabel = context.resources.getStringArray(R.array.tile_states_location)[1]
            }
            contentDescription = label
            supportedActions =
                setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
        }
}
+56 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.qs.tiles.impl.location.domain.interactor

import android.os.UserHandle
import com.android.systemui.common.coroutine.ConflatedCallbackFlow
import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel
import com.android.systemui.statusbar.policy.LocationController
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf

/** Observes location state changes providing the [LocationTileModel]. */
class LocationTileDataInteractor
@Inject
constructor(
    private val locationController: LocationController,
) : QSTileDataInteractor<LocationTileModel> {

    override fun tileData(
        user: UserHandle,
        triggers: Flow<DataUpdateTrigger>
    ): Flow<LocationTileModel> =
        ConflatedCallbackFlow.conflatedCallbackFlow {
            val initialValue = locationController.isLocationEnabled
            trySend(LocationTileModel(initialValue))

            val callback =
                object : LocationController.LocationChangeCallback {
                    override fun onLocationSettingsChanged(locationEnabled: Boolean) {
                        trySend(LocationTileModel(locationEnabled))
                    }
                }
            locationController.addCallback(callback)
            awaitClose { locationController.removeCallback(callback) }
        }

    override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true)
}
Loading