From 1ce7abb6fbf6cd9523e11328fa5b53a840bfca52 Mon Sep 17 00:00:00 2001 From: Shamali P Date: Tue, 15 Apr 2025 16:20:53 +0000 Subject: [PATCH] Add a ui state for the app timer toast and add a mapper function for it Bug: 405359383 Flag: com.android.launcher3.enable_refactor_digital_wellbeing_toast Test: Unit test for mapper Change-Id: I35271ff33c8610d79674438fb18a3d851eb3d2d3 --- .../recents/ui/mapper/TaskUiStateMapper.kt | 36 +++++++ .../task/apptimer/TaskAppTimerUiState.kt | 50 ++++++++++ .../ui/mapper/TaskUiStateMapperTest.kt | 98 ++++++++++++++++++- 3 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 quickstep/src/com/android/quickstep/task/apptimer/TaskAppTimerUiState.kt diff --git a/quickstep/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapper.kt b/quickstep/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapper.kt index 7425d361ee..e035015070 100644 --- a/quickstep/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapper.kt +++ b/quickstep/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapper.kt @@ -18,7 +18,10 @@ package com.android.quickstep.recents.ui.mapper import android.view.View.OnClickListener import com.android.launcher3.Flags.enableDesktopExplodedView +import com.android.launcher3.R +import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT import com.android.quickstep.recents.ui.viewmodel.TaskData +import com.android.quickstep.task.apptimer.TaskAppTimerUiState import com.android.quickstep.task.thumbnail.TaskHeaderUiState import com.android.quickstep.task.thumbnail.TaskThumbnailUiState import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly @@ -105,4 +108,37 @@ object TaskUiStateMapper { taskData.icon != null && taskData.titleDescription != null && clickCloseListener != null + + /** + * Converts a [TaskData] object into a [TaskAppTimerUiState] for displaying an app timer toast + * + * @property taskData The [TaskData] to convert. Can be null or a specific sub-class. + * @property stagePosition the position of this task when shown as a group + * @return a [TaskAppTimerUiState] representing state for the information displayed in the app + * timer toast. + */ + fun toTaskAppTimerUiState( + canShowAppTimer: Boolean, + stagePosition: Int, + taskData: TaskData?, + ): TaskAppTimerUiState = + when { + taskData !is TaskData.Data -> TaskAppTimerUiState.Uninitialized + + !canShowAppTimer || taskData.remainingAppTimerDuration == null -> + TaskAppTimerUiState.NoTimer(taskDescription = taskData.titleDescription) + + else -> + TaskAppTimerUiState.Timer( + taskDescription = taskData.titleDescription, + timeRemaining = taskData.remainingAppTimerDuration, + taskPackageName = taskData.packageName, + accessibilityActionId = + if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) { + R.id.action_digital_wellbeing_bottom_right + } else { + R.id.action_digital_wellbeing_top_left + }, + ) + } } diff --git a/quickstep/src/com/android/quickstep/task/apptimer/TaskAppTimerUiState.kt b/quickstep/src/com/android/quickstep/task/apptimer/TaskAppTimerUiState.kt new file mode 100644 index 0000000000..08c92db044 --- /dev/null +++ b/quickstep/src/com/android/quickstep/task/apptimer/TaskAppTimerUiState.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2025 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.quickstep.task.apptimer + +import androidx.annotation.IdRes +import java.time.Duration + +/** + * UI state of the digital wellbeing app timer toast + * + * @property taskDescription description of the task for which the timer is being displayed. + */ +sealed class TaskAppTimerUiState(open val taskDescription: String?) { + /** Timer information not available in UI. */ + data object Uninitialized : TaskAppTimerUiState(null) + + /** No timer information to display */ + data class NoTimer(override val taskDescription: String?) : + TaskAppTimerUiState(taskDescription) + + /** + * Represents the UI state necessary to show an app timer on a task + * + * @property timeRemaining time remaining on the app timer for the application. + * @property taskDescription description of the task for which the timer is being displayed. + * @property taskPackageName package name for of the top component for the task's app. + * @property accessibilityActionId action id to use for tap like accessibility actions on this + * timer. + */ + data class Timer( + val timeRemaining: Duration, + override val taskDescription: String?, + val taskPackageName: String, + @IdRes val accessibilityActionId: Int, + ) : TaskAppTimerUiState(taskDescription) +} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapperTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapperTest.kt index e55d8e53f2..db78449b2f 100644 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapperTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapperTest.kt @@ -26,7 +26,11 @@ import android.view.Surface import android.view.View import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.launcher3.Flags +import com.android.launcher3.R +import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT +import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT import com.android.quickstep.recents.ui.viewmodel.TaskData +import com.android.quickstep.task.apptimer.TaskAppTimerUiState import com.android.quickstep.task.thumbnail.TaskHeaderUiState import com.android.quickstep.task.thumbnail.TaskThumbnailUiState import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile @@ -189,6 +193,96 @@ class TaskUiStateMapperTest { assertThat(result).isEqualTo(expected) } + @Test + fun toTaskAppTimer_nullTaskData_returnsUninitialized() { + val result = + TaskUiStateMapper.toTaskAppTimerUiState( + canShowAppTimer = true, + stagePosition = STAGE_POSITION_DEFAULT, + taskData = null, + ) + + val expected = TaskAppTimerUiState.Uninitialized + assertThat(result).isEqualTo(expected) + } + + @Test + fun toTaskAppTimer_noTaskData_returnsUninitialized() { + val result = + TaskUiStateMapper.toTaskAppTimerUiState( + canShowAppTimer = true, + stagePosition = STAGE_POSITION_DEFAULT, + taskData = TaskData.NoData(TASK_ID), + ) + + val expected = TaskAppTimerUiState.Uninitialized + assertThat(result).isEqualTo(expected) + } + + @Test + fun toTaskAppTimer_canShowAppTimerFalse_returnsNoTimer() { + val result = + TaskUiStateMapper.toTaskAppTimerUiState( + canShowAppTimer = false, + stagePosition = STAGE_POSITION_DEFAULT, + taskData = TASK_DATA, + ) + + val expected = TaskAppTimerUiState.NoTimer(taskDescription = TASK_TITLE_DESCRIPTION) + assertThat(result).isEqualTo(expected) + } + + @Test + fun toTaskAppTimer_timerNullAndCanShow_returnsNoTimer() { + val result = + TaskUiStateMapper.toTaskAppTimerUiState( + canShowAppTimer = false, + stagePosition = STAGE_POSITION_DEFAULT, + taskData = TASK_DATA.copy(remainingAppTimerDuration = null), + ) + + val expected = TaskAppTimerUiState.NoTimer(taskDescription = TASK_TITLE_DESCRIPTION) + assertThat(result).isEqualTo(expected) + } + + @Test + fun toTaskAppTimer_timerPresentAndCanShow_returnsTimer() { + val result = + TaskUiStateMapper.toTaskAppTimerUiState( + canShowAppTimer = true, + stagePosition = STAGE_POSITION_DEFAULT, + taskData = TASK_DATA.copy(remainingAppTimerDuration = TASK_APP_TIMER_DURATION), + ) + + val expected = + TaskAppTimerUiState.Timer( + timeRemaining = TASK_APP_TIMER_DURATION, + taskDescription = TASK_DATA.titleDescription, + taskPackageName = TASK_DATA.packageName, + accessibilityActionId = R.id.action_digital_wellbeing_top_left, + ) + assertThat(result).isEqualTo(expected) + } + + @Test + fun toTaskAppTimer_stagePositionBottomOrRight_returnsTimerWithCorrectActionId() { + val result = + TaskUiStateMapper.toTaskAppTimerUiState( + canShowAppTimer = true, + stagePosition = STAGE_POSITION_BOTTOM_OR_RIGHT, + taskData = TASK_DATA.copy(remainingAppTimerDuration = TASK_APP_TIMER_DURATION), + ) + + val expected = + TaskAppTimerUiState.Timer( + timeRemaining = TASK_APP_TIMER_DURATION, + taskDescription = TASK_DATA.titleDescription, + taskPackageName = TASK_DATA.packageName, + accessibilityActionId = R.id.action_digital_wellbeing_bottom_right, + ) + assertThat(result).isEqualTo(expected) + } + private companion object { const val TASK_TITLE_DESCRIPTION = "Title Description 1" var TASK_ID = 1 @@ -198,6 +292,8 @@ class TaskUiStateMapperTest { val TASK_THUMBNAIL_DATA = ThumbnailData(thumbnail = TASK_THUMBNAIL, rotation = Surface.ROTATION_0) val TASK_BACKGROUND_COLOR = Color.rgb(1, 2, 3) + val TASK_APP_TIMER_DURATION: Duration = Duration.ofMillis(30) + val STAGE_POSITION_DEFAULT = STAGE_POSITION_TOP_OR_LEFT val TASK_DATA = TaskData.Data( TASK_ID, @@ -209,7 +305,7 @@ class TaskUiStateMapperTest { backgroundColor = TASK_BACKGROUND_COLOR, isLocked = false, isLiveTile = false, - remainingAppTimerDuration = Duration.ofMillis(30), + remainingAppTimerDuration = TASK_APP_TIMER_DURATION, ) } }