Add perf tracing for several key operations in TaskView lifecycle.

Fix: 402622741
Test: Manually looking at perf traces
Flag: EXEMPT - tracing only
Change-Id: If42a30d83dd72ebfe4074fedd7bdefcd102881d6
This commit is contained in:
Uwais Ashraf
2025-03-12 11:02:00 +00:00
parent b615b9bdaf
commit d4b187b997
4 changed files with 256 additions and 203 deletions
@@ -28,6 +28,7 @@ import android.window.TransitionInfo
import androidx.annotation.BinderThread
import androidx.annotation.UiThread
import androidx.annotation.VisibleForTesting
import com.android.app.tracing.traceSection
import com.android.internal.jank.Cuj
import com.android.launcher3.Flags.enableAltTabKqsOnConnectedDisplays
import com.android.launcher3.Flags.enableLargeDesktopWindowingTile
@@ -143,35 +144,38 @@ constructor(
* completion (returns false).
*/
@UiThread
private fun processNextCommand() {
val command: CommandInfo =
commandQueue.firstOrNull()
?: run {
Log.d(TAG, "no pending commands to be executed.")
return
}
command.status = CommandStatus.PROCESSING
Log.d(TAG, "executing command: $command")
if (enableOverviewCommandHelperTimeout()) {
coroutineScope.launch(dispatcherProvider.main) {
withTimeout(QUEUE_WAIT_DURATION_IN_MS) {
executeCommandSuspended(command)
ensureActive()
onCommandFinished(command)
}
private fun processNextCommand() =
traceSection("OverviewCommandHelper.processNextCommand") {
val command: CommandInfo? = commandQueue.firstOrNull()
if (command == null) {
Log.d(TAG, "no pending commands to be executed.")
return@traceSection
}
} else {
val result = executeCommand(command, onCallbackResult = { onCommandFinished(command) })
Log.d(TAG, "command executed: $command with result: $result")
if (result) {
onCommandFinished(command)
command.status = CommandStatus.PROCESSING
Log.d(TAG, "executing command: $command")
if (enableOverviewCommandHelperTimeout()) {
coroutineScope.launch(dispatcherProvider.main) {
traceSection("OverviewCommandHelper.executeCommandWithTimeout") {
withTimeout(QUEUE_WAIT_DURATION_IN_MS) {
executeCommandSuspended(command)
ensureActive()
onCommandFinished(command)
}
}
}
} else {
Log.d(TAG, "waiting for command callback: $command")
val result =
executeCommand(command, onCallbackResult = { onCommandFinished(command) })
Log.d(TAG, "command executed: $command with result: $result")
if (result) {
onCommandFinished(command)
} else {
Log.d(TAG, "waiting for command callback: $command")
}
}
}
}
/**
* Executes the task and returns true if next task can be executed. If false, then the next task
@@ -17,6 +17,8 @@
package com.android.quickstep.views;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.os.Trace.traceBegin;
import static android.os.Trace.traceEnd;
import static android.view.Surface.ROTATION_0;
import static android.view.View.MeasureSpec.EXACTLY;
import static android.view.View.MeasureSpec.makeMeasureSpec;
@@ -104,6 +106,7 @@ import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
import android.os.VibrationEffect;
import android.text.Layout;
@@ -139,6 +142,7 @@ import androidx.annotation.UiThread;
import androidx.core.graphics.ColorUtils;
import androidx.dynamicanimation.animation.SpringAnimation;
import com.android.app.tracing.TraceUtilsKt;
import com.android.internal.jank.Cuj;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BaseActivity.MultiWindowModeChangedListener;
@@ -1292,6 +1296,7 @@ public abstract class RecentsView<
@Override
public void onViewRemoved(View child) {
traceBegin(Trace.TRACE_TAG_APP, "RecentsView.onViewRemoved");
super.onViewRemoved(child);
// Clear the task data for the removed child if it was visible unless:
// - It's the initial taskview for entering split screen, we only pretend to dismiss the
@@ -1303,6 +1308,7 @@ public abstract class RecentsView<
clearAndRecycleTaskView((TaskView) child);
}
}
traceEnd(Trace.TRACE_TAG_APP);
}
private void clearAndRecycleTaskView(TaskView taskView) {
@@ -1321,6 +1327,7 @@ public abstract class RecentsView<
@Override
public void onViewAdded(View child) {
traceBegin(Trace.TRACE_TAG_APP, "RecentsView.onViewAdded");
super.onViewAdded(child);
if (child instanceof TaskView) {
mTaskViewCount++;
@@ -1331,6 +1338,7 @@ public abstract class RecentsView<
child.setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_LTR : View.LAYOUT_DIRECTION_RTL);
mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, false);
updateEmptyMessage();
traceEnd(Trace.TRACE_TAG_APP);
}
@Override
@@ -1927,6 +1935,9 @@ public abstract class RecentsView<
return;
}
// Start here to avoid early returns and empty cases which have special logic
traceBegin(Trace.TRACE_TAG_APP, "RecentsView.applyLoadPlan");
TaskView currentTaskView = getTaskViewAt(mCurrentPage);
int[] currentTaskIds = null;
// Track the current DesktopTaskView through [deskId] as a desk can be empty without any
@@ -1972,8 +1983,9 @@ public abstract class RecentsView<
// TaskIds will no longer be valid after remove and re-add, clearing mTopRowIdSet.
mAnyTaskHasBeenDismissed = false;
mTopRowIdSet.clear();
traceBegin(Trace.TRACE_TAG_APP, "RecentsView.applyLoadPlan.removeAllViews");
removeAllViews();
traceEnd(Trace.TRACE_TAG_APP);
// If we are entering Overview as a result of initiating a split from somewhere else
// (e.g. split from Home), we need to make sure the staged app is not drawn as a thumbnail.
int stagedTaskIdToBeRemoved;
@@ -2000,6 +2012,7 @@ public abstract class RecentsView<
// Add `mAddDesktopButton` as the first child.
addView(mAddDesktopButton);
}
traceBegin(Trace.TRACE_TAG_APP, "RecentsView.applyLoadPlan.forLoop");
// Add views as children based on whether it's grouped or single task. Looping through
// taskGroups backwards populates the thumbnail grid from least recent to most recent.
@@ -2019,8 +2032,11 @@ public abstract class RecentsView<
// If we need to remove half of a pair of tasks, force a TaskView with Type.SINGLE
// to be a temporary container for the remaining task.
traceBegin(Trace.TRACE_TAG_APP, "RecentsView.applyLoadPlan.forLoop.createTaskView");
TaskView taskView = getTaskViewFromPool(
containsStagedTask ? TaskViewType.SINGLE : groupTask.taskViewType);
traceEnd(Trace.TRACE_TAG_APP);
traceBegin(Trace.TRACE_TAG_APP, "RecentsView.applyLoadPlan.forLoop.bind");
if (taskView instanceof GroupedTaskView groupedTaskView) {
var splitTask = (SplitTask) groupTask;
groupedTaskView.bind(splitTask.getTopLeftTask(),
@@ -2038,13 +2054,18 @@ public abstract class RecentsView<
taskView.bind(((SingleTask) groupTask).getTask(), mOrientationState,
mTaskOverlayFactory);
}
traceEnd(Trace.TRACE_TAG_APP);
traceBegin(Trace.TRACE_TAG_APP, "RecentsView.applyLoadPlan.forLoop.addTaskView");
addView(taskView);
traceEnd(Trace.TRACE_TAG_APP);
// enables instance filtering if the feature flag for it is on
if (FeatureFlags.ENABLE_MULTI_INSTANCE.get()) {
taskView.setUpShowAllInstancesListener();
}
}
// For loop end trace
traceEnd(Trace.TRACE_TAG_APP);
addView(mClearAllButton);
@@ -2064,8 +2085,10 @@ public abstract class RecentsView<
setFocusedTaskViewId(
newFocusedTaskView != null ? newFocusedTaskView.getTaskViewId() : INVALID_TASK_ID);
traceBegin(Trace.TRACE_TAG_APP, "RecentsView.applyLoadPlan.layouts");
updateTaskSize();
updateChildTaskOrientations();
traceEnd(Trace.TRACE_TAG_APP);
TaskView newRunningTaskView = mUtils.getDesktopTaskViewForDeskId(runningTaskViewDeskId);
if (newRunningTaskView == null) {
@@ -2120,6 +2143,7 @@ public abstract class RecentsView<
});
}
traceBegin(Trace.TRACE_TAG_APP, "RecentsView.applyLoadPlan.cleanupStates");
if (mIgnoreResetTaskId != INVALID_TASK_ID &&
getTaskViewByTaskId(mIgnoreResetTaskId) != ignoreResetTaskView) {
// If the taskView mapping is changing, do not preserve the visuals. Since we are
@@ -2134,6 +2158,10 @@ public abstract class RecentsView<
if (isPageScrollsInitialized()) {
onPageScrollsInitialized();
}
traceEnd(Trace.TRACE_TAG_APP);
// applyLoadPlan end trace
traceEnd(Trace.TRACE_TAG_APP);
}
private boolean isModal() {
@@ -21,6 +21,7 @@ import android.graphics.Matrix
import android.util.Log
import android.view.View
import android.view.View.OnClickListener
import com.android.app.tracing.traceSection
import com.android.launcher3.Flags.enableRefactorTaskThumbnail
import com.android.launcher3.model.data.TaskViewItemInfo
import com.android.launcher3.util.SplitConfigurationOptions
@@ -100,26 +101,28 @@ class TaskContainer(
val itemInfo: TaskViewItemInfo
get() = TaskViewItemInfo(taskView, this)
fun bind() {
digitalWellBeingToast?.bind(task, taskView, snapshotView, stagePosition)
if (!enableRefactorTaskThumbnail()) {
thumbnailViewDeprecated.bind(task, overlay, taskView)
fun bind() =
traceSection("TaskContainer.bind") {
digitalWellBeingToast?.bind(task, taskView, snapshotView, stagePosition)
if (!enableRefactorTaskThumbnail()) {
thumbnailViewDeprecated.bind(task, overlay, taskView)
}
}
}
fun destroy() {
digitalWellBeingToast?.destroy()
taskContentView.scaleX = 1f
taskContentView.scaleY = 1f
overlay.reset()
if (enableRefactorTaskThumbnail()) {
isThumbnailValid = false
thumbnailData = null
thumbnailView.onRecycle()
} else {
thumbnailViewDeprecated.setShowSplashForSplitSelection(false)
fun destroy() =
traceSection("TaskContainer.destroy") {
digitalWellBeingToast?.destroy()
taskContentView.scaleX = 1f
taskContentView.scaleY = 1f
overlay.reset()
if (enableRefactorTaskThumbnail()) {
isThumbnailValid = false
thumbnailData = null
thumbnailView.onRecycle()
} else {
thumbnailViewDeprecated.setShowSplashForSplitSelection(false)
}
}
}
fun setOverlayEnabled(enabled: Boolean) {
if (!enableRefactorTaskThumbnail()) {
@@ -137,23 +140,24 @@ class TaskContainer(
}
}
fun refreshOverlay(thumbnailPosition: ThumbnailPosition?) {
this.thumbnailPosition = thumbnailPosition
when {
!overlayEnabledStatus -> overlay.reset()
thumbnailPosition == null -> {
Log.e(TAG, "Thumbnail position was null during overlay refresh", Exception())
overlay.reset()
fun refreshOverlay(thumbnailPosition: ThumbnailPosition?) =
traceSection("TaskContainer.refreshOverlay") {
this.thumbnailPosition = thumbnailPosition
when {
!overlayEnabledStatus -> overlay.reset()
thumbnailPosition == null -> {
Log.e(TAG, "Thumbnail position was null during overlay refresh", Exception())
overlay.reset()
}
else ->
overlay.initOverlay(
task,
thumbnailData?.thumbnail,
thumbnailPosition.matrix,
thumbnailPosition.isRotated,
)
}
else ->
overlay.initOverlay(
task,
thumbnailData?.thumbnail,
thumbnailPosition.matrix,
thumbnailPosition.isRotated,
)
}
}
fun addChildForAccessibility(outChildren: ArrayList<View>) {
addAccessibleChildToList(iconView.asView(), outChildren)
@@ -168,15 +172,16 @@ class TaskContainer(
liveTile: Boolean,
hasHeader: Boolean,
clickCloseListener: OnClickListener?,
) {
taskContentView.setState(
TaskUiStateMapper.toTaskHeaderState(state, hasHeader, clickCloseListener),
TaskUiStateMapper.toTaskThumbnailUiState(state, liveTile),
state?.taskId,
)
thumbnailData = if (state is TaskData.Data) state.thumbnailData else null
overlay.setThumbnailState(thumbnailData)
}
) =
traceSection("TaskContainer.setState") {
taskContentView.setState(
TaskUiStateMapper.toTaskHeaderState(state, hasHeader, clickCloseListener),
TaskUiStateMapper.toTaskThumbnailUiState(state, liveTile),
state?.taskId,
)
thumbnailData = if (state is TaskData.Data) state.thumbnailData else null
overlay.setThumbnailState(thumbnailData)
}
fun updateTintAmount(tintAmount: Float) {
thumbnailView.updateTintAmount(tintAmount)
@@ -42,6 +42,7 @@ import androidx.annotation.IntDef
import androidx.annotation.VisibleForTesting
import androidx.core.view.updateLayoutParams
import com.android.app.animation.Interpolators
import com.android.app.tracing.traceSection
import com.android.launcher3.Flags.enableCursorHoverStates
import com.android.launcher3.Flags.enableDesktopExplodedView
import com.android.launcher3.Flags.enableGridOnlyOverview
@@ -733,61 +734,63 @@ constructor(
?.inflate()
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
if (enableRefactorTaskThumbnail()) {
// The TaskView lifecycle is starts the ViewModel during onBind, and cleans it in
// onRecycle. So it should be initialized at this point. TaskView Lifecycle:
// `bind` -> `onBind` -> onAttachedToWindow() -> onDetachFromWindow -> onRecycle
coroutineJobs +=
coroutineScope.launch(dispatcherProvider.main) {
viewModel!!.state.collectLatest(::updateTaskViewState)
}
}
}
private fun updateTaskViewState(state: TaskTileUiState) {
sysUiStatusNavFlags = state.sysUiStatusNavFlags
// Updating containers
val mapOfTasks = state.tasks.associateBy { it.taskId }
taskContainers.forEach { container ->
val taskId = container.task.key.id
val containerState = mapOfTasks[taskId]
val shouldHaveHeader = (type == TaskViewType.DESKTOP) && enableDesktopExplodedView()
container.setState(
state = containerState,
liveTile = state.isLiveTile,
hasHeader = shouldHaveHeader,
clickCloseListener =
if (shouldHaveHeader) {
{
// Update the layout UI to remove this task from the layout grid, and
// remove the task from ActivityManager afterwards.
recentsView?.dismissTask(
taskId,
/* animate= */ true,
/* removeTask= */ true,
)
}
} else {
null
},
)
updateThumbnailValidity(container)
val thumbnailPosition =
updateThumbnailMatrix(
container = container,
width = container.thumbnailView.width,
height = container.thumbnailView.height,
)
container.setOverlayEnabled(state.taskOverlayEnabled, thumbnailPosition)
if (enableOverviewIconMenu()) {
setIconState(container, containerState)
override fun onAttachedToWindow() =
traceSection("TaskView.onAttachedToWindow") {
super.onAttachedToWindow()
if (enableRefactorTaskThumbnail()) {
// The TaskView lifecycle is starts the ViewModel during onBind, and cleans it in
// onRecycle. So it should be initialized at this point. TaskView Lifecycle:
// `bind` -> `onBind` -> onAttachedToWindow() -> onDetachFromWindow -> onRecycle
coroutineJobs +=
coroutineScope.launch(dispatcherProvider.main) {
viewModel!!.state.collectLatest(::updateTaskViewState)
}
}
}
private fun updateTaskViewState(state: TaskTileUiState) =
traceSection("TaskView.updateTaskViewState") {
sysUiStatusNavFlags = state.sysUiStatusNavFlags
// Updating containers
val mapOfTasks = state.tasks.associateBy { it.taskId }
taskContainers.forEach { container ->
val taskId = container.task.key.id
val containerState = mapOfTasks[taskId]
val shouldHaveHeader = (type == TaskViewType.DESKTOP) && enableDesktopExplodedView()
container.setState(
state = containerState,
liveTile = state.isLiveTile,
hasHeader = shouldHaveHeader,
clickCloseListener =
if (shouldHaveHeader) {
{
// Update the layout UI to remove this task from the layout grid,
// and remove the task from ActivityManager afterwards.
recentsView?.dismissTask(
taskId,
/* animate= */ true,
/* removeTask= */ true,
)
}
} else {
null
},
)
updateThumbnailValidity(container)
val thumbnailPosition =
updateThumbnailMatrix(
container = container,
width = container.thumbnailView.width,
height = container.thumbnailView.height,
)
container.setOverlayEnabled(state.taskOverlayEnabled, thumbnailPosition)
if (enableOverviewIconMenu()) {
setIconState(container, containerState)
}
}
}
}
private fun updateThumbnailValidity(container: TaskContainer) {
container.isThumbnailValid =
@@ -814,27 +817,33 @@ constructor(
container: TaskContainer,
width: Int,
height: Int,
): ThumbnailPosition? {
val thumbnailPosition =
viewModel?.getThumbnailPosition(container.thumbnailData, width, height, isLayoutRtl)
?: return null
container.updateThumbnailMatrix(thumbnailPosition.matrix)
return thumbnailPosition
}
): ThumbnailPosition? =
traceSection("TaskView.updateThumbnailMatrix") {
val thumbnailPosition =
viewModel?.getThumbnailPosition(container.thumbnailData, width, height, isLayoutRtl)
?: return null
container.updateThumbnailMatrix(thumbnailPosition.matrix)
return thumbnailPosition
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
if (enableRefactorTaskThumbnail()) {
// The jobs are being cancelled in the background thread. So we make a copy of the list
// to prevent cleaning a new job that might be added to this list during onAttach
// or another moment in the lifecycle.
val coroutineJobsToCancel = coroutineJobs.toList()
coroutineJobs.clear()
coroutineScope.launch(dispatcherProvider.background) {
coroutineJobsToCancel.forEach { it.cancel("TaskView detaching from window") }
override fun onDetachedFromWindow() =
traceSection("TaskView.onDetachedFromWindow") {
super.onDetachedFromWindow()
if (enableRefactorTaskThumbnail()) {
// The jobs are being cancelled in the background thread. So we make a copy of the
// list to prevent cleaning a new job that might be added to this list during
// onAttach or another moment in the lifecycle.
val coroutineJobsToCancel = coroutineJobs.toList()
coroutineJobs.clear()
coroutineScope.launch(dispatcherProvider.background) {
traceSection("TaskView.onDetachedFromWindow.cancellingJobs") {
coroutineJobsToCancel.forEach {
it.cancel("TaskView detaching from window")
}
}
}
}
}
}
/** Updates this task view to the given {@param task}. */
open fun bind(
@@ -860,37 +869,40 @@ constructor(
onBind(orientedState)
}
protected open fun onBind(orientedState: RecentsOrientedState) {
if (enableRefactorTaskThumbnail()) {
val scopeId = context
Log.d(TAG, "onBind $scopeId ${orientedState.containerInterface}")
viewModel =
TaskViewModel(
taskViewType = type,
recentsViewData = RecentsDependencies.get(scopeId),
getTaskUseCase = RecentsDependencies.get(scopeId),
getSysUiStatusNavFlagsUseCase = RecentsDependencies.get(scopeId),
isThumbnailValidUseCase = RecentsDependencies.get(scopeId),
getThumbnailPositionUseCase = RecentsDependencies.get(scopeId),
dispatcherProvider = RecentsDependencies.get(scopeId),
)
.apply { bind(*taskIds) }
}
taskContainers.forEach { container ->
container.bind()
if (enableRefactorTaskThumbnail()) {
container.taskContentView.cornerRadius =
thumbnailFullscreenParams.currentCornerRadius
container.taskContentView.doOnSizeChange { width, height ->
updateThumbnailValidity(container)
val thumbnailPosition = updateThumbnailMatrix(container, width, height)
container.refreshOverlay(thumbnailPosition)
protected open fun onBind(orientedState: RecentsOrientedState) =
traceSection("TaskView.onBind") {
traceSection("TaskView.onBind.createViewModel") {
if (enableRefactorTaskThumbnail()) {
val scopeId = context
Log.d(TAG, "onBind $scopeId ${orientedState.containerInterface}")
viewModel =
TaskViewModel(
taskViewType = type,
recentsViewData = RecentsDependencies.get(scopeId),
getTaskUseCase = RecentsDependencies.get(scopeId),
getSysUiStatusNavFlagsUseCase = RecentsDependencies.get(scopeId),
isThumbnailValidUseCase = RecentsDependencies.get(scopeId),
getThumbnailPositionUseCase = RecentsDependencies.get(scopeId),
dispatcherProvider = RecentsDependencies.get(scopeId),
)
.apply { bind(*taskIds) }
}
}
taskContainers.forEach { container ->
container.bind()
if (enableRefactorTaskThumbnail()) {
container.taskContentView.cornerRadius =
thumbnailFullscreenParams.currentCornerRadius
container.taskContentView.doOnSizeChange { width, height ->
updateThumbnailValidity(container)
val thumbnailPosition = updateThumbnailMatrix(container, width, height)
container.refreshOverlay(thumbnailPosition)
}
}
}
setOrientationState(orientedState)
}
setOrientationState(orientedState)
}
private fun applyThumbnailSplashAlpha() {
val alpha = getSplashAlphaProgress()
@@ -916,22 +928,23 @@ constructor(
@IdRes digitalWellbeingBannerId: Int,
@StagePosition stagePosition: Int,
taskOverlayFactory: TaskOverlayFactory,
): TaskContainer {
val iconView = findViewById<View>(iconViewId) as TaskViewIcon
val taskContentView = findViewById<TaskContentView>(taskContentViewId)
return TaskContainer(
this,
task,
taskContentView,
taskContentView.findViewById(thumbnailViewId),
iconView,
TransformingTouchDelegate(iconView.asView()),
stagePosition,
findViewById(digitalWellbeingBannerId)!!,
findViewById(showWindowViewId)!!,
taskOverlayFactory,
)
}
): TaskContainer =
traceSection("TaskView.createTaskContainer") {
val iconView = findViewById<View>(iconViewId) as TaskViewIcon
val taskContentView = findViewById<TaskContentView>(taskContentViewId)
return TaskContainer(
this,
task,
taskContentView,
taskContentView.findViewById(thumbnailViewId),
iconView,
TransformingTouchDelegate(iconView.asView()),
stagePosition,
findViewById(digitalWellbeingBannerId)!!,
findViewById(showWindowViewId)!!,
taskOverlayFactory,
)
}
fun containsMultipleTasks() = taskContainers.size > 1
@@ -944,11 +957,12 @@ constructor(
/** Check if given `taskId` is tracked in this view */
fun containsTaskId(taskId: Int) = getTaskContainerById(taskId) != null
open fun setOrientationState(orientationState: RecentsOrientedState) {
this.orientedState = orientationState
taskContainers.forEach { it.iconView.setIconOrientation(orientationState, isGridTask) }
setThumbnailOrientation(orientationState)
}
open fun setOrientationState(orientationState: RecentsOrientedState) =
traceSection("TaskView.setOrientationState") {
this.orientedState = orientationState
taskContainers.forEach { it.iconView.setIconOrientation(orientationState, isGridTask) }
setThumbnailOrientation(orientationState)
}
protected open fun setThumbnailOrientation(orientationState: RecentsOrientedState) {
taskContainers.forEach {
@@ -1095,25 +1109,27 @@ constructor(
protected open fun needsUpdate(@TaskDataChanges dataChange: Int, @TaskDataChanges flag: Int) =
(dataChange and flag) == flag
protected open fun cancelPendingLoadTasks() {
pendingThumbnailLoadRequests.forEach { it.cancel() }
pendingThumbnailLoadRequests.clear()
pendingIconLoadRequests.forEach { it.cancel() }
pendingIconLoadRequests.clear()
}
protected open fun cancelPendingLoadTasks() =
traceSection("TaskView.cancelPendingLoadTasks") {
pendingThumbnailLoadRequests.forEach { it.cancel() }
pendingThumbnailLoadRequests.clear()
pendingIconLoadRequests.forEach { it.cancel() }
pendingIconLoadRequests.clear()
}
protected open fun setIconState(container: TaskContainer, state: TaskData?) {
if (enableOverviewIconMenu()) {
if (state is TaskData.Data) {
setIcon(container.iconView, state.icon)
container.iconView.setText(state.title)
container.digitalWellBeingToast?.initialize()
} else {
setIcon(container.iconView, null)
container.iconView.setText(null)
protected open fun setIconState(container: TaskContainer, state: TaskData?) =
traceSection("TaskView.setIconState") {
if (enableOverviewIconMenu()) {
if (state is TaskData.Data) {
setIcon(container.iconView, state.icon)
container.iconView.setText(state.title)
container.digitalWellBeingToast?.initialize()
} else {
setIcon(container.iconView, null)
container.iconView.setText(null)
}
}
}
}
protected open fun onIconLoaded(taskContainer: TaskContainer) {
setIcon(taskContainer.iconView, taskContainer.task.icon)