1 /*
<lambda>null2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.quickstep.task.util
18 
19 import android.util.Log
20 import android.view.View.OnLayoutChangeListener
21 import com.android.quickstep.TaskOverlayFactory
22 import com.android.quickstep.recents.di.RecentsDependencies
23 import com.android.quickstep.recents.di.get
24 import com.android.quickstep.task.thumbnail.TaskOverlayUiState
25 import com.android.quickstep.task.thumbnail.TaskOverlayUiState.Disabled
26 import com.android.quickstep.task.thumbnail.TaskOverlayUiState.Enabled
27 import com.android.quickstep.task.viewmodel.TaskOverlayViewModel
28 import com.android.systemui.shared.recents.model.Task
29 import kotlinx.coroutines.CoroutineName
30 import kotlinx.coroutines.CoroutineScope
31 import kotlinx.coroutines.Dispatchers
32 import kotlinx.coroutines.SupervisorJob
33 import kotlinx.coroutines.cancel
34 import kotlinx.coroutines.flow.launchIn
35 import kotlinx.coroutines.flow.onEach
36 
37 /**
38  * Helper for [TaskOverlayFactory.TaskOverlay] to interact with [TaskOverlayViewModel], this helper
39  * should merge with [TaskOverlayFactory.TaskOverlay] when it's migrated to MVVM.
40  */
41 class TaskOverlayHelper(val task: Task, val overlay: TaskOverlayFactory.TaskOverlay<*>) {
42     private lateinit var overlayInitializedScope: CoroutineScope
43     private var uiState: TaskOverlayUiState = Disabled
44 
45     private lateinit var viewModel: TaskOverlayViewModel
46 
47     // TODO(b/331753115): TaskOverlay should listen for state changes and react.
48     val enabledState: Enabled
49         get() = uiState as Enabled
50 
51     private val snapshotLayoutChangeListener = OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
52         (uiState as? Enabled)?.let { initOverlay(it) }
53     }
54 
55     fun getThumbnailMatrix() = getThumbnailPositionState().matrix
56 
57     private fun getThumbnailPositionState() =
58         viewModel.getThumbnailPositionState(
59             overlay.snapshotView.width,
60             overlay.snapshotView.height,
61             overlay.snapshotView.isLayoutRtl,
62         )
63 
64     fun init() {
65         overlayInitializedScope =
66             CoroutineScope(SupervisorJob() + Dispatchers.Main + CoroutineName("TaskOverlayHelper"))
67         viewModel =
68             TaskOverlayViewModel(
69                 task = task,
70                 recentsViewData = RecentsDependencies.get(),
71                 getThumbnailPositionUseCase = RecentsDependencies.get(),
72                 recentTasksRepository = RecentsDependencies.get(),
73             )
74         viewModel.overlayState
75             .onEach {
76                 uiState = it
77                 if (it is Enabled) {
78                     initOverlay(it)
79                 } else {
80                     reset()
81                 }
82             }
83             .launchIn(overlayInitializedScope)
84         overlay.snapshotView.addOnLayoutChangeListener(snapshotLayoutChangeListener)
85     }
86 
87     private fun initOverlay(enabledState: Enabled) {
88         if (DEBUG) {
89             Log.d(TAG, "initOverlay - taskId: ${task.key.id}, thumbnail: ${enabledState.thumbnail}")
90         }
91         with(getThumbnailPositionState()) {
92             overlay.initOverlay(task, enabledState.thumbnail, matrix, isRotated)
93         }
94     }
95 
96     private fun reset() {
97         if (DEBUG) {
98             Log.d(TAG, "reset - taskId: ${task.key.id}")
99         }
100         overlay.reset()
101     }
102 
103     fun destroy() {
104         overlayInitializedScope.cancel()
105         uiState = Disabled
106         overlay.snapshotView.removeOnLayoutChangeListener(snapshotLayoutChangeListener)
107         reset()
108     }
109 
110     companion object {
111         private const val TAG = "TaskOverlayHelper"
112         private const val DEBUG = false
113     }
114 }
115