1 /*
2  * Copyright 2023 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.systemui.scene.ui.view
18 
19 import android.view.Gravity
20 import android.view.View
21 import android.view.ViewGroup
22 import android.widget.FrameLayout
23 import androidx.activity.OnBackPressedDispatcher
24 import androidx.activity.OnBackPressedDispatcherOwner
25 import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
26 import androidx.core.view.isVisible
27 import androidx.lifecycle.Lifecycle
28 import androidx.lifecycle.lifecycleScope
29 import androidx.lifecycle.repeatOnLifecycle
30 import com.android.systemui.R
31 import com.android.systemui.compose.ComposeFacade
32 import com.android.systemui.lifecycle.repeatWhenAttached
33 import com.android.systemui.scene.shared.model.Scene
34 import com.android.systemui.scene.shared.model.SceneContainerConfig
35 import com.android.systemui.scene.shared.model.SceneKey
36 import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
37 import java.time.Instant
38 import kotlinx.coroutines.launch
39 
40 object SceneWindowRootViewBinder {
41 
42     /** Binds between the view and view-model pertaining to a specific scene container. */
43     fun bind(
44         view: ViewGroup,
45         viewModel: SceneContainerViewModel,
46         containerConfig: SceneContainerConfig,
47         scenes: Set<Scene>,
48         onVisibilityChangedInternal: (isVisible: Boolean) -> Unit,
49     ) {
50         val unsortedSceneByKey: Map<SceneKey, Scene> = scenes.associateBy { scene -> scene.key }
51         val sortedSceneByKey: Map<SceneKey, Scene> = buildMap {
52             containerConfig.sceneKeys.forEach { sceneKey ->
53                 val scene =
54                     checkNotNull(unsortedSceneByKey[sceneKey]) {
55                         "Scene not found for key \"$sceneKey\"!"
56                     }
57 
58                 put(sceneKey, scene)
59             }
60         }
61 
62         view.repeatWhenAttached {
63             lifecycleScope.launch {
64                 repeatOnLifecycle(Lifecycle.State.CREATED) {
65                     view.setViewTreeOnBackPressedDispatcherOwner(
66                         object : OnBackPressedDispatcherOwner {
67                             override val onBackPressedDispatcher =
68                                 OnBackPressedDispatcher().apply {
69                                     setOnBackInvokedDispatcher(
70                                         view.viewRootImpl.onBackInvokedDispatcher
71                                     )
72                                 }
73 
74                             override val lifecycle: Lifecycle = this@repeatWhenAttached.lifecycle
75                         }
76                     )
77 
78                     view.addView(
79                         ComposeFacade.createSceneContainerView(
80                             context = view.context,
81                             viewModel = viewModel,
82                             sceneByKey = sortedSceneByKey,
83                         )
84                     )
85 
86                     val legacyView = view.requireViewById<View>(R.id.legacy_window_root)
87                     view.addView(createVisibilityToggleView(legacyView))
88 
89                     launch {
90                         viewModel.isVisible.collect { isVisible ->
91                             onVisibilityChangedInternal(isVisible)
92                         }
93                     }
94                 }
95 
96                 // Here when destroyed.
97                 view.removeAllViews()
98             }
99         }
100     }
101 
102     private var clickCount = 0
103     private var lastClick = Instant.now()
104 
105     /**
106      * A temporary UI to toggle on/off the visibility of the given [otherView]. It is toggled by
107      * tapping 5 times in quick succession on the device camera (top center).
108      */
109     // TODO(b/291321285): Remove this when the Flexiglass UI is mature enough to turn off legacy
110     //  SysUI altogether.
111     private fun createVisibilityToggleView(otherView: View): View {
112         val toggleView = View(otherView.context)
113         toggleView.layoutParams = FrameLayout.LayoutParams(200, 200, Gravity.CENTER_HORIZONTAL)
114         toggleView.setOnClickListener {
115             val now = Instant.now()
116             clickCount = if (now.minusSeconds(2) > lastClick) 1 else clickCount + 1
117             if (clickCount == 5) {
118                 otherView.isVisible = !otherView.isVisible
119                 clickCount = 0
120             }
121             lastClick = now
122         }
123         return toggleView
124     }
125 }
126