Loading packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt 0 → 100644 +115 −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.alarm.domain import android.app.AlarmManager import android.widget.Switch 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.alarm.domain.model.AlarmTileModel import com.android.systemui.qs.tiles.impl.alarm.qsAlarmTileConfig import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.res.R import java.time.Instant import java.time.LocalDateTime import java.util.TimeZone import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class AlarmTileMapperTest : SysuiTestCase() { private val kosmos = Kosmos() private val alarmTileConfig = kosmos.qsAlarmTileConfig // Using lazy (versus =) to make sure we override the right context -- see b/311612168 private val mapper by lazy { AlarmTileMapper(context.orCreateTestableResources.resources) } @Test fun notAlarmSet() { val inputModel = AlarmTileModel.NoAlarmSet val outputState = mapper.map(alarmTileConfig, inputModel) val expectedState = createAlarmTileState( QSTileState.ActivationState.INACTIVE, context.getString(R.string.qs_alarm_tile_no_alarm) ) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) } @Test fun nextAlarmSet24HourFormat() { val triggerTime = 1L val inputModel = AlarmTileModel.NextAlarmSet(true, AlarmManager.AlarmClockInfo(triggerTime, null)) val outputState = mapper.map(alarmTileConfig, inputModel) val localDateTime = LocalDateTime.ofInstant( Instant.ofEpochMilli(triggerTime), TimeZone.getDefault().toZoneId() ) val expectedSecondaryLabel = AlarmTileMapper.formatter24Hour.format(localDateTime) val expectedState = createAlarmTileState(QSTileState.ActivationState.ACTIVE, expectedSecondaryLabel) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) } @Test fun nextAlarmSet12HourFormat() { val triggerTime = 1L val inputModel = AlarmTileModel.NextAlarmSet(false, AlarmManager.AlarmClockInfo(triggerTime, null)) val outputState = mapper.map(alarmTileConfig, inputModel) val localDateTime = LocalDateTime.ofInstant( Instant.ofEpochMilli(triggerTime), TimeZone.getDefault().toZoneId() ) val expectedSecondaryLabel = AlarmTileMapper.formatter12Hour.format(localDateTime) val expectedState = createAlarmTileState(QSTileState.ActivationState.ACTIVE, expectedSecondaryLabel) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) } private fun createAlarmTileState( activationState: QSTileState.ActivationState, secondaryLabel: String ): QSTileState { val label = context.getString(R.string.status_bar_alarm) return QSTileState( { Icon.Resource(R.drawable.ic_alarm, null) }, label, activationState, secondaryLabel, setOf(QSTileState.UserAction.CLICK), label, null, QSTileState.SideViewIcon.None, QSTileState.EnabledState.ENABLED, Switch::class.qualifiedName ) } } packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileDataInteractorTest.kt 0 → 100644 +131 −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.alarm.domain.interactor import android.app.AlarmManager import android.app.PendingIntent 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.alarm.domain.model.AlarmTileModel import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.DateFormatUtil import com.android.systemui.utils.leaks.FakeNextAlarmController import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.toCollection 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 AlarmTileDataInteractorTest : SysuiTestCase() { private lateinit var dateFormatUtil: DateFormatUtil private val nextAlarmController = FakeNextAlarmController(LeakCheck()) private lateinit var underTest: AlarmTileDataInteractor @Before fun setup() { dateFormatUtil = mock<DateFormatUtil>() underTest = AlarmTileDataInteractor(nextAlarmController, dateFormatUtil) } @Test fun alarmTriggerTimeDataMatchesTheController() = runTest { val expectedTriggerTime = 1L val alarmInfo = AlarmManager.AlarmClockInfo(expectedTriggerTime, mock<PendingIntent>()) val dataList: List<AlarmTileModel> by collectValues(underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest))) runCurrent() nextAlarmController.setNextAlarm(alarmInfo) runCurrent() nextAlarmController.setNextAlarm(null) runCurrent() assertThat(dataList).hasSize(3) assertThat(dataList[0]).isInstanceOf(AlarmTileModel.NoAlarmSet::class.java) assertThat(dataList[1]).isInstanceOf(AlarmTileModel.NextAlarmSet::class.java) val actualAlarmClockInfo = (dataList[1] as AlarmTileModel.NextAlarmSet).alarmClockInfo assertThat(actualAlarmClockInfo).isNotNull() val actualTriggerTime = actualAlarmClockInfo.triggerTime assertThat(actualTriggerTime).isEqualTo(expectedTriggerTime) assertThat(dataList[2]).isInstanceOf(AlarmTileModel.NoAlarmSet::class.java) } @Test fun dateFormatUtil24HourDataMatchesController() = runTest { val expectedValue = true whenever(dateFormatUtil.is24HourFormat).thenReturn(expectedValue) val alarmInfo = AlarmManager.AlarmClockInfo(1L, mock<PendingIntent>()) nextAlarmController.setNextAlarm(alarmInfo) val model by collectLastValue( underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)) ) runCurrent() assertThat(model).isNotNull() assertThat(model).isInstanceOf(AlarmTileModel.NextAlarmSet::class.java) val actualValue = (model as AlarmTileModel.NextAlarmSet).is24HourFormat assertThat(actualValue).isEqualTo(expectedValue) } @Test fun dateFormatUtil12HourDataMatchesController() = runTest { val expectedValue = false whenever(dateFormatUtil.is24HourFormat).thenReturn(expectedValue) val alarmInfo = AlarmManager.AlarmClockInfo(1L, mock<PendingIntent>()) nextAlarmController.setNextAlarm(alarmInfo) val model by collectLastValue( underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)) ) runCurrent() assertThat(model).isNotNull() assertThat(model).isInstanceOf(AlarmTileModel.NextAlarmSet::class.java) val actualValue = (model as AlarmTileModel.NextAlarmSet).is24HourFormat assertThat(actualValue).isEqualTo(expectedValue) } @Test fun alwaysAvailable() = runTest { val availability = underTest.availability(TEST_USER).toCollection(mutableListOf()) assertThat(availability).hasSize(1) assertThat(availability.last()).isTrue() } private companion object { val TEST_USER = UserHandle.of(1)!! } } packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractorTest.kt 0 → 100644 +82 −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.alarm.domain.interactor import android.app.AlarmManager.AlarmClockInfo import android.app.PendingIntent import android.content.Intent import android.provider.AlarmClock import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.plugins.ActivityStarter import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.click import com.android.systemui.qs.tiles.impl.alarm.domain.model.AlarmTileModel import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.nullable import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Mockito.verify @SmallTest @RunWith(AndroidJUnit4::class) class AlarmTileUserActionInteractorTest : SysuiTestCase() { private lateinit var activityStarter: ActivityStarter private lateinit var intentCaptor: ArgumentCaptor<Intent> private lateinit var pendingIntentCaptor: ArgumentCaptor<PendingIntent> lateinit var underTest: AlarmTileUserActionInteractor @Before fun setup() { activityStarter = mock<ActivityStarter>() intentCaptor = ArgumentCaptor.forClass(Intent::class.java) pendingIntentCaptor = ArgumentCaptor.forClass(PendingIntent::class.java) underTest = AlarmTileUserActionInteractor(activityStarter) } @Test fun handleClickWithDefaultIntent() = runTest { val alarmInfo = AlarmClockInfo(1L, null) val inputModel = AlarmTileModel.NextAlarmSet(true, alarmInfo) underTest.handleInput(click(inputModel)) verify(activityStarter) .postStartActivityDismissingKeyguard(capture(intentCaptor), eq(0), nullable()) assertThat(intentCaptor.value.action).isEqualTo(AlarmClock.ACTION_SHOW_ALARMS) } @Test fun handleClickWithPendingIntent() = runTest { val expectedIntent: PendingIntent = mock<PendingIntent>() val alarmInfo = AlarmClockInfo(1L, expectedIntent) val inputModel = AlarmTileModel.NextAlarmSet(true, alarmInfo) underTest.handleInput(click(inputModel)) verify(activityStarter) .postStartActivityDismissingKeyguard(capture(pendingIntentCaptor), nullable()) assertThat(pendingIntentCaptor.value).isEqualTo(expectedIntent) } } packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt 0 → 100644 +63 −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.alarm.domain import android.content.res.Resources import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper import com.android.systemui.qs.tiles.impl.alarm.domain.model.AlarmTileModel import com.android.systemui.qs.tiles.viewmodel.QSTileConfig import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.res.R import java.time.Instant import java.time.LocalDateTime import java.time.format.DateTimeFormatter import java.util.TimeZone import javax.inject.Inject /** Maps [AlarmTileModel] to [QSTileState]. */ class AlarmTileMapper @Inject constructor(@Main private val resources: Resources) : QSTileDataToStateMapper<AlarmTileModel> { companion object { val formatter12Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("E hh:mm a") val formatter24Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("E HH:mm") } override fun map(config: QSTileConfig, data: AlarmTileModel): QSTileState = QSTileState.build(resources, config.uiConfig) { when (data) { is AlarmTileModel.NextAlarmSet -> { activationState = QSTileState.ActivationState.ACTIVE val localDateTime = LocalDateTime.ofInstant( Instant.ofEpochMilli(data.alarmClockInfo.triggerTime), TimeZone.getDefault().toZoneId() ) secondaryLabel = if (data.is24HourFormat) formatter24Hour.format(localDateTime) else formatter12Hour.format(localDateTime) } is AlarmTileModel.NoAlarmSet -> { activationState = QSTileState.ActivationState.INACTIVE secondaryLabel = resources.getString(R.string.qs_alarm_tile_no_alarm) } } contentDescription = label supportedActions = setOf(QSTileState.UserAction.CLICK) } } packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileDataInteractor.kt 0 → 100644 +57 −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.alarm.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.alarm.domain.model.AlarmTileModel import com.android.systemui.statusbar.policy.NextAlarmController import com.android.systemui.util.time.DateFormatUtil import javax.inject.Inject import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf /** Observes alarm state changes providing the [AlarmTileModel]. */ class AlarmTileDataInteractor @Inject constructor( private val alarmController: NextAlarmController, private val dateFormatUtil: DateFormatUtil ) : QSTileDataInteractor<AlarmTileModel> { override fun tileData( user: UserHandle, triggers: Flow<DataUpdateTrigger> ): Flow<AlarmTileModel> = ConflatedCallbackFlow.conflatedCallbackFlow { val alarmCallback = NextAlarmController.NextAlarmChangeCallback { val model = if (it == null) AlarmTileModel.NoAlarmSet else AlarmTileModel.NextAlarmSet(dateFormatUtil.is24HourFormat, it) trySend(model) } alarmController.addCallback(alarmCallback) awaitClose { alarmController.removeCallback(alarmCallback) } } override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true) } Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt 0 → 100644 +115 −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.alarm.domain import android.app.AlarmManager import android.widget.Switch 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.alarm.domain.model.AlarmTileModel import com.android.systemui.qs.tiles.impl.alarm.qsAlarmTileConfig import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.res.R import java.time.Instant import java.time.LocalDateTime import java.util.TimeZone import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class AlarmTileMapperTest : SysuiTestCase() { private val kosmos = Kosmos() private val alarmTileConfig = kosmos.qsAlarmTileConfig // Using lazy (versus =) to make sure we override the right context -- see b/311612168 private val mapper by lazy { AlarmTileMapper(context.orCreateTestableResources.resources) } @Test fun notAlarmSet() { val inputModel = AlarmTileModel.NoAlarmSet val outputState = mapper.map(alarmTileConfig, inputModel) val expectedState = createAlarmTileState( QSTileState.ActivationState.INACTIVE, context.getString(R.string.qs_alarm_tile_no_alarm) ) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) } @Test fun nextAlarmSet24HourFormat() { val triggerTime = 1L val inputModel = AlarmTileModel.NextAlarmSet(true, AlarmManager.AlarmClockInfo(triggerTime, null)) val outputState = mapper.map(alarmTileConfig, inputModel) val localDateTime = LocalDateTime.ofInstant( Instant.ofEpochMilli(triggerTime), TimeZone.getDefault().toZoneId() ) val expectedSecondaryLabel = AlarmTileMapper.formatter24Hour.format(localDateTime) val expectedState = createAlarmTileState(QSTileState.ActivationState.ACTIVE, expectedSecondaryLabel) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) } @Test fun nextAlarmSet12HourFormat() { val triggerTime = 1L val inputModel = AlarmTileModel.NextAlarmSet(false, AlarmManager.AlarmClockInfo(triggerTime, null)) val outputState = mapper.map(alarmTileConfig, inputModel) val localDateTime = LocalDateTime.ofInstant( Instant.ofEpochMilli(triggerTime), TimeZone.getDefault().toZoneId() ) val expectedSecondaryLabel = AlarmTileMapper.formatter12Hour.format(localDateTime) val expectedState = createAlarmTileState(QSTileState.ActivationState.ACTIVE, expectedSecondaryLabel) QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) } private fun createAlarmTileState( activationState: QSTileState.ActivationState, secondaryLabel: String ): QSTileState { val label = context.getString(R.string.status_bar_alarm) return QSTileState( { Icon.Resource(R.drawable.ic_alarm, null) }, label, activationState, secondaryLabel, setOf(QSTileState.UserAction.CLICK), label, null, QSTileState.SideViewIcon.None, QSTileState.EnabledState.ENABLED, Switch::class.qualifiedName ) } }
packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileDataInteractorTest.kt 0 → 100644 +131 −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.alarm.domain.interactor import android.app.AlarmManager import android.app.PendingIntent 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.alarm.domain.model.AlarmTileModel import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.DateFormatUtil import com.android.systemui.utils.leaks.FakeNextAlarmController import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.toCollection 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 AlarmTileDataInteractorTest : SysuiTestCase() { private lateinit var dateFormatUtil: DateFormatUtil private val nextAlarmController = FakeNextAlarmController(LeakCheck()) private lateinit var underTest: AlarmTileDataInteractor @Before fun setup() { dateFormatUtil = mock<DateFormatUtil>() underTest = AlarmTileDataInteractor(nextAlarmController, dateFormatUtil) } @Test fun alarmTriggerTimeDataMatchesTheController() = runTest { val expectedTriggerTime = 1L val alarmInfo = AlarmManager.AlarmClockInfo(expectedTriggerTime, mock<PendingIntent>()) val dataList: List<AlarmTileModel> by collectValues(underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest))) runCurrent() nextAlarmController.setNextAlarm(alarmInfo) runCurrent() nextAlarmController.setNextAlarm(null) runCurrent() assertThat(dataList).hasSize(3) assertThat(dataList[0]).isInstanceOf(AlarmTileModel.NoAlarmSet::class.java) assertThat(dataList[1]).isInstanceOf(AlarmTileModel.NextAlarmSet::class.java) val actualAlarmClockInfo = (dataList[1] as AlarmTileModel.NextAlarmSet).alarmClockInfo assertThat(actualAlarmClockInfo).isNotNull() val actualTriggerTime = actualAlarmClockInfo.triggerTime assertThat(actualTriggerTime).isEqualTo(expectedTriggerTime) assertThat(dataList[2]).isInstanceOf(AlarmTileModel.NoAlarmSet::class.java) } @Test fun dateFormatUtil24HourDataMatchesController() = runTest { val expectedValue = true whenever(dateFormatUtil.is24HourFormat).thenReturn(expectedValue) val alarmInfo = AlarmManager.AlarmClockInfo(1L, mock<PendingIntent>()) nextAlarmController.setNextAlarm(alarmInfo) val model by collectLastValue( underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)) ) runCurrent() assertThat(model).isNotNull() assertThat(model).isInstanceOf(AlarmTileModel.NextAlarmSet::class.java) val actualValue = (model as AlarmTileModel.NextAlarmSet).is24HourFormat assertThat(actualValue).isEqualTo(expectedValue) } @Test fun dateFormatUtil12HourDataMatchesController() = runTest { val expectedValue = false whenever(dateFormatUtil.is24HourFormat).thenReturn(expectedValue) val alarmInfo = AlarmManager.AlarmClockInfo(1L, mock<PendingIntent>()) nextAlarmController.setNextAlarm(alarmInfo) val model by collectLastValue( underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)) ) runCurrent() assertThat(model).isNotNull() assertThat(model).isInstanceOf(AlarmTileModel.NextAlarmSet::class.java) val actualValue = (model as AlarmTileModel.NextAlarmSet).is24HourFormat assertThat(actualValue).isEqualTo(expectedValue) } @Test fun alwaysAvailable() = runTest { val availability = underTest.availability(TEST_USER).toCollection(mutableListOf()) assertThat(availability).hasSize(1) assertThat(availability.last()).isTrue() } private companion object { val TEST_USER = UserHandle.of(1)!! } }
packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractorTest.kt 0 → 100644 +82 −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.alarm.domain.interactor import android.app.AlarmManager.AlarmClockInfo import android.app.PendingIntent import android.content.Intent import android.provider.AlarmClock import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.plugins.ActivityStarter import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.click import com.android.systemui.qs.tiles.impl.alarm.domain.model.AlarmTileModel import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.nullable import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Mockito.verify @SmallTest @RunWith(AndroidJUnit4::class) class AlarmTileUserActionInteractorTest : SysuiTestCase() { private lateinit var activityStarter: ActivityStarter private lateinit var intentCaptor: ArgumentCaptor<Intent> private lateinit var pendingIntentCaptor: ArgumentCaptor<PendingIntent> lateinit var underTest: AlarmTileUserActionInteractor @Before fun setup() { activityStarter = mock<ActivityStarter>() intentCaptor = ArgumentCaptor.forClass(Intent::class.java) pendingIntentCaptor = ArgumentCaptor.forClass(PendingIntent::class.java) underTest = AlarmTileUserActionInteractor(activityStarter) } @Test fun handleClickWithDefaultIntent() = runTest { val alarmInfo = AlarmClockInfo(1L, null) val inputModel = AlarmTileModel.NextAlarmSet(true, alarmInfo) underTest.handleInput(click(inputModel)) verify(activityStarter) .postStartActivityDismissingKeyguard(capture(intentCaptor), eq(0), nullable()) assertThat(intentCaptor.value.action).isEqualTo(AlarmClock.ACTION_SHOW_ALARMS) } @Test fun handleClickWithPendingIntent() = runTest { val expectedIntent: PendingIntent = mock<PendingIntent>() val alarmInfo = AlarmClockInfo(1L, expectedIntent) val inputModel = AlarmTileModel.NextAlarmSet(true, alarmInfo) underTest.handleInput(click(inputModel)) verify(activityStarter) .postStartActivityDismissingKeyguard(capture(pendingIntentCaptor), nullable()) assertThat(pendingIntentCaptor.value).isEqualTo(expectedIntent) } }
packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt 0 → 100644 +63 −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.alarm.domain import android.content.res.Resources import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper import com.android.systemui.qs.tiles.impl.alarm.domain.model.AlarmTileModel import com.android.systemui.qs.tiles.viewmodel.QSTileConfig import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.res.R import java.time.Instant import java.time.LocalDateTime import java.time.format.DateTimeFormatter import java.util.TimeZone import javax.inject.Inject /** Maps [AlarmTileModel] to [QSTileState]. */ class AlarmTileMapper @Inject constructor(@Main private val resources: Resources) : QSTileDataToStateMapper<AlarmTileModel> { companion object { val formatter12Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("E hh:mm a") val formatter24Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("E HH:mm") } override fun map(config: QSTileConfig, data: AlarmTileModel): QSTileState = QSTileState.build(resources, config.uiConfig) { when (data) { is AlarmTileModel.NextAlarmSet -> { activationState = QSTileState.ActivationState.ACTIVE val localDateTime = LocalDateTime.ofInstant( Instant.ofEpochMilli(data.alarmClockInfo.triggerTime), TimeZone.getDefault().toZoneId() ) secondaryLabel = if (data.is24HourFormat) formatter24Hour.format(localDateTime) else formatter12Hour.format(localDateTime) } is AlarmTileModel.NoAlarmSet -> { activationState = QSTileState.ActivationState.INACTIVE secondaryLabel = resources.getString(R.string.qs_alarm_tile_no_alarm) } } contentDescription = label supportedActions = setOf(QSTileState.UserAction.CLICK) } }
packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileDataInteractor.kt 0 → 100644 +57 −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.alarm.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.alarm.domain.model.AlarmTileModel import com.android.systemui.statusbar.policy.NextAlarmController import com.android.systemui.util.time.DateFormatUtil import javax.inject.Inject import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf /** Observes alarm state changes providing the [AlarmTileModel]. */ class AlarmTileDataInteractor @Inject constructor( private val alarmController: NextAlarmController, private val dateFormatUtil: DateFormatUtil ) : QSTileDataInteractor<AlarmTileModel> { override fun tileData( user: UserHandle, triggers: Flow<DataUpdateTrigger> ): Flow<AlarmTileModel> = ConflatedCallbackFlow.conflatedCallbackFlow { val alarmCallback = NextAlarmController.NextAlarmChangeCallback { val model = if (it == null) AlarmTileModel.NoAlarmSet else AlarmTileModel.NextAlarmSet(dateFormatUtil.is24HourFormat, it) trySend(model) } alarmController.addCallback(alarmCallback) awaitClose { alarmController.removeCallback(alarmCallback) } } override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true) }