diff options
11 files changed, 709 insertions, 0 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt new file mode 100644 index 000000000000..2b744ac8398a --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt @@ -0,0 +1,115 @@ +/* + * 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 + ) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileDataInteractorTest.kt new file mode 100644 index 000000000000..990d74728052 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileDataInteractorTest.kt @@ -0,0 +1,131 @@ +/* + * 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)!! + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractorTest.kt new file mode 100644 index 000000000000..e44c8493244c --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractorTest.kt @@ -0,0 +1,82 @@ +/* + * 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) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt new file mode 100644 index 000000000000..63865777e14f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt @@ -0,0 +1,63 @@ +/* + * 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) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileDataInteractor.kt new file mode 100644 index 000000000000..51cd501c0c80 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileDataInteractor.kt @@ -0,0 +1,57 @@ +/* + * 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) +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractor.kt new file mode 100644 index 000000000000..afca57c75788 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractor.kt @@ -0,0 +1,67 @@ +/* + * 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.content.Intent +import android.provider.AlarmClock +import com.android.internal.jank.InteractionJankMonitor +import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.qs.tiles.base.interactor.QSTileInput +import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.alarm.domain.model.AlarmTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction +import javax.inject.Inject + +/** Handles alarm tile clicks. */ +class AlarmTileUserActionInteractor +@Inject +constructor( + private val activityStarter: ActivityStarter, +) : QSTileUserActionInteractor<AlarmTileModel> { + override suspend fun handleInput(input: QSTileInput<AlarmTileModel>): Unit = + with(input) { + when (action) { + is QSTileUserAction.Click -> { + val animationController = + action.view?.let { + ActivityLaunchAnimator.Controller.fromView( + it, + InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE + ) + } + if ( + data is AlarmTileModel.NextAlarmSet && + data.alarmClockInfo.showIntent != null + ) { + val pendingIndent = data.alarmClockInfo.showIntent + activityStarter.postStartActivityDismissingKeyguard( + pendingIndent, + animationController + ) + } else { + activityStarter.postStartActivityDismissingKeyguard( + Intent(AlarmClock.ACTION_SHOW_ALARMS), + 0, + animationController + ) + } + } + is QSTileUserAction.LongClick -> {} + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/model/AlarmTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/model/AlarmTileModel.kt new file mode 100644 index 000000000000..7647d7cf3194 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/model/AlarmTileModel.kt @@ -0,0 +1,28 @@ +/* + * 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.model + +import android.app.AlarmManager + +/** Alarm tile model */ +sealed interface AlarmTileModel { + data object NoAlarmSet : AlarmTileModel + data class NextAlarmSet( + val is24HourFormat: Boolean, + val alarmClockInfo: AlarmManager.AlarmClockInfo + ) : AlarmTileModel +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt index 75ae16ebab31..0f2da2d09633 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt @@ -26,6 +26,10 @@ import com.android.systemui.qs.tiles.MicrophoneToggleTile import com.android.systemui.qs.tiles.UiModeNightTile import com.android.systemui.qs.tiles.WorkModeTile import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory +import com.android.systemui.qs.tiles.impl.alarm.domain.AlarmTileMapper +import com.android.systemui.qs.tiles.impl.alarm.domain.interactor.AlarmTileDataInteractor +import com.android.systemui.qs.tiles.impl.alarm.domain.interactor.AlarmTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.alarm.domain.model.AlarmTileModel import com.android.systemui.qs.tiles.impl.flashlight.domain.FlashlightMapper import com.android.systemui.qs.tiles.impl.flashlight.domain.interactor.FlashlightTileDataInteractor import com.android.systemui.qs.tiles.impl.flashlight.domain.interactor.FlashlightTileUserActionInteractor @@ -59,6 +63,7 @@ interface PolicyModule { companion object { const val FLASHLIGHT_TILE_SPEC = "flashlight" const val LOCATION_TILE_SPEC = "location" + const val ALARM_TILE_SPEC = "alarm" /** Inject flashlight config */ @Provides @@ -123,6 +128,38 @@ interface PolicyModule { stateInteractor, mapper, ) + + /** Inject alarm config */ + @Provides + @IntoMap + @StringKey(ALARM_TILE_SPEC) + fun provideAlarmTileConfig(uiEventLogger: QsEventLogger): QSTileConfig = + QSTileConfig( + tileSpec = TileSpec.create(ALARM_TILE_SPEC), + uiConfig = + QSTileUIConfig.Resource( + iconRes = R.drawable.ic_alarm, + labelRes = R.string.status_bar_alarm, + ), + instanceId = uiEventLogger.getNewInstanceId(), + ) + + /** Inject AlarmTile into tileViewModelMap in QSModule */ + @Provides + @IntoMap + @StringKey(ALARM_TILE_SPEC) + fun provideAlarmTileViewModel( + factory: QSTileViewModelFactory.Static<AlarmTileModel>, + mapper: AlarmTileMapper, + stateInteractor: AlarmTileDataInteractor, + userActionInteractor: AlarmTileUserActionInteractor + ): QSTileViewModel = + factory.create( + TileSpec.create(ALARM_TILE_SPEC), + userActionInteractor, + stateInteractor, + mapper, + ) } /** Inject FlashlightTile into tileMap in QSModule */ diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/alarm/AlarmTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/alarm/AlarmTileKosmos.kt new file mode 100644 index 000000000000..2fa92c75b917 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/alarm/AlarmTileKosmos.kt @@ -0,0 +1,24 @@ +/* + * 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 + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.qsEventLogger +import com.android.systemui.statusbar.policy.PolicyModule + +val Kosmos.qsAlarmTileConfig by + Kosmos.Fixture { PolicyModule.provideAlarmTileConfig(qsEventLogger) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt new file mode 100644 index 000000000000..9d0faca94fb4 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt @@ -0,0 +1,76 @@ +/* + * 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.custom + +import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject.Companion.assertThat +import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject.Companion.states +import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.assertThat +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.google.common.truth.FailureMetadata +import com.google.common.truth.Subject +import com.google.common.truth.Subject.Factory +import com.google.common.truth.Truth + +/** + * [QSTileState]-specific extension for [Truth]. Use [assertThat] or [states] to get an instance of + * this subject. + */ +class QSTileStateSubject +private constructor(failureMetadata: FailureMetadata, subject: QSTileState?) : + Subject(failureMetadata, subject) { + + private val actual: QSTileState? = subject + + /** Asserts if the [QSTileState] fields are the same. */ + fun isEqualTo(other: QSTileState?) { + if (actual == null) { + check("other").that(other).isNull() + return + } else { + check("other").that(other).isNotNull() + other ?: return + } + check("icon").that(actual.icon()).isEqualTo(other.icon()) + check("label").that(actual.label).isEqualTo(other.label) + check("activationState").that(actual.activationState).isEqualTo(other.activationState) + check("secondaryLabel").that(actual.secondaryLabel).isEqualTo(other.secondaryLabel) + check("label").that(actual.supportedActions).isEqualTo(other.supportedActions) + check("contentDescription") + .that(actual.contentDescription) + .isEqualTo(other.contentDescription) + check("stateDescription").that(actual.stateDescription).isEqualTo(other.stateDescription) + check("sideViewIcon").that(actual.sideViewIcon).isEqualTo(other.sideViewIcon) + check("enabledState").that(actual.enabledState).isEqualTo(other.enabledState) + check("expandedAccessibilityClassName") + .that(actual.expandedAccessibilityClassName) + .isEqualTo(other.expandedAccessibilityClassName) + } + + companion object { + + /** Returns a factory to be used with [Truth.assertAbout]. */ + fun states(): Factory<QSTileStateSubject, QSTileState?> { + return Factory { failureMetadata: FailureMetadata, subject: QSTileState? -> + QSTileStateSubject(failureMetadata, subject) + } + } + + /** Shortcut for `Truth.assertAbout(states()).that(state)`. */ + fun assertThat(state: QSTileState?): QSTileStateSubject = + Truth.assertAbout(states()).that(state) + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java index 5ae8e22c06ee..377e97ca0f9a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java @@ -14,15 +14,44 @@ package com.android.systemui.utils.leaks; +import android.app.AlarmManager; import android.testing.LeakCheck; import com.android.systemui.statusbar.policy.NextAlarmController; import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback; +import java.util.ArrayList; +import java.util.List; + public class FakeNextAlarmController extends BaseLeakChecker<NextAlarmChangeCallback> implements NextAlarmController { + private AlarmManager.AlarmClockInfo mNextAlarm = null; + private List<NextAlarmChangeCallback> mCallbacks = new ArrayList<>(); + public FakeNextAlarmController(LeakCheck test) { super(test, "alarm"); } + + /** + * Helper method for setting the next alarm + */ + public void setNextAlarm(AlarmManager.AlarmClockInfo nextAlarm) { + this.mNextAlarm = nextAlarm; + for (var callback: mCallbacks) { + callback.onNextAlarmChanged(nextAlarm); + } + } + + @Override + public void addCallback(NextAlarmChangeCallback listener) { + mCallbacks.add(listener); + listener.onNextAlarmChanged(mNextAlarm); + } + + @Override + public void removeCallback(NextAlarmChangeCallback listener) { + mCallbacks.remove(listener); + } + } |