1 /*
2  * Copyright (C) 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 package com.android.launcher3.taskbar
17 
18 import android.animation.AnimatorSet
19 import android.graphics.Canvas
20 import android.view.View
21 import android.view.ViewTreeObserver
22 import android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION
23 import android.view.WindowManager
24 import android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
25 import com.android.launcher3.util.DisplayController
26 import com.android.launcher3.views.BaseDragLayer
27 import com.android.systemui.animation.ViewRootSync
28 import java.io.PrintWriter
29 
30 private const val TASKBAR_ICONS_FADE_DURATION = 300L
31 private const val STASHED_HANDLE_FADE_DURATION = 180L
32 private const val TEMP_BACKGROUND_WINDOW_TITLE = "VoiceInteractionTaskbarBackground"
33 
34 /**
35  * Controls Taskbar behavior while Voice Interaction Window (assistant) is showing. Specifically:
36  * - We always hide the taskbar icons or stashed handle, whichever is currently showing.
37  * - For persistent taskbar, we also move the taskbar background to a new window/layer
38  *   (TYPE_APPLICATION_OVERLAY) which is behind the assistant.
39  * - For transient taskbar, we hide the real taskbar background (if it's showing).
40  */
41 class VoiceInteractionWindowController(val context: TaskbarActivityContext) :
42     TaskbarControllers.LoggableTaskbarController, TaskbarControllers.BackgroundRendererController {
43 
44     private val isSeparateBackgroundEnabled = !DisplayController.isTransientTaskbar(context)
45     private val taskbarBackgroundRenderer = TaskbarBackgroundRenderer(context)
46     private val nonTouchableInsetsComputer =
<lambda>null47         ViewTreeObserver.OnComputeInternalInsetsListener {
48             it.touchableRegion.setEmpty()
49             it.setTouchableInsets(TOUCHABLE_INSETS_REGION)
50         }
51 
52     // Initialized in init.
53     private lateinit var controllers: TaskbarControllers
54     // Only initialized if isSeparateBackgroundEnabled
55     private var separateWindowForTaskbarBackground: BaseDragLayer<TaskbarActivityContext>? = null
56     private var separateWindowLayoutParams: WindowManager.LayoutParams? = null
57 
58     private var isVoiceInteractionWindowVisible: Boolean = false
59     private var pendingAttachedToWindowListener: View.OnAttachStateChangeListener? = null
60 
initnull61     fun init(controllers: TaskbarControllers) {
62         this.controllers = controllers
63 
64         if (!isSeparateBackgroundEnabled) {
65             return
66         }
67 
68         separateWindowForTaskbarBackground =
69             object : BaseDragLayer<TaskbarActivityContext>(context, null, 0) {
70                 override fun recreateControllers() {
71                     mControllers = emptyArray()
72                 }
73 
74                 override fun draw(canvas: Canvas) {
75                     super.draw(canvas)
76                     if (controllers.taskbarStashController.isTaskbarVisibleAndNotStashing) {
77                         taskbarBackgroundRenderer.draw(canvas)
78                     }
79                 }
80 
81                 override fun onAttachedToWindow() {
82                     super.onAttachedToWindow()
83                     viewTreeObserver.addOnComputeInternalInsetsListener(nonTouchableInsetsComputer)
84                 }
85 
86                 override fun onDetachedFromWindow() {
87                     super.onDetachedFromWindow()
88                     viewTreeObserver.removeOnComputeInternalInsetsListener(
89                         nonTouchableInsetsComputer
90                     )
91                 }
92             }
93         separateWindowForTaskbarBackground?.recreateControllers()
94         separateWindowForTaskbarBackground?.setWillNotDraw(false)
95 
96         separateWindowLayoutParams =
97             context.createDefaultWindowLayoutParams(
98                 TYPE_APPLICATION_OVERLAY,
99                 TEMP_BACKGROUND_WINDOW_TITLE
100             )
101         separateWindowLayoutParams?.isSystemApplicationOverlay = true
102     }
103 
onDestroynull104     fun onDestroy() {
105         setIsVoiceInteractionWindowVisible(visible = false, skipAnim = true)
106         separateWindowForTaskbarBackground?.removeOnAttachStateChangeListener(
107             pendingAttachedToWindowListener
108         )
109     }
110 
setIsVoiceInteractionWindowVisiblenull111     fun setIsVoiceInteractionWindowVisible(visible: Boolean, skipAnim: Boolean) {
112         if (isVoiceInteractionWindowVisible == visible) {
113             return
114         }
115         isVoiceInteractionWindowVisible = visible
116 
117         // Fade out taskbar icons and stashed handle.
118         val taskbarIconAlpha = if (isVoiceInteractionWindowVisible) 0f else 1f
119         val fadeTaskbarIcons =
120             controllers.taskbarViewController.taskbarIconAlpha
121                 .get(TaskbarViewController.ALPHA_INDEX_ASSISTANT_INVOKED)
122                 .animateToValue(taskbarIconAlpha)
123                 .setDuration(TASKBAR_ICONS_FADE_DURATION)
124         val fadeStashedHandle =
125             controllers.stashedHandleViewController.stashedHandleAlpha
126                 .get(StashedHandleViewController.ALPHA_INDEX_ASSISTANT_INVOKED)
127                 .animateToValue(taskbarIconAlpha)
128                 .setDuration(STASHED_HANDLE_FADE_DURATION)
129         val animSet = AnimatorSet()
130         animSet.play(fadeTaskbarIcons)
131         animSet.play(fadeStashedHandle)
132         if (!isSeparateBackgroundEnabled) {
133             val fadeTaskbarBackground =
134                 controllers.taskbarDragLayerController.assistantBgTaskbar
135                     .animateToValue(taskbarIconAlpha)
136                     .setDuration(TASKBAR_ICONS_FADE_DURATION)
137             animSet.play(fadeTaskbarBackground)
138         }
139         animSet.start()
140         if (skipAnim) {
141             animSet.end()
142         }
143 
144         if (isSeparateBackgroundEnabled) {
145             moveTaskbarBackgroundToAppropriateLayer(skipAnim)
146         }
147     }
148 
149     /**
150      * Either:
151      *
152      * Hides the TaskbarDragLayer background and creates a new window to draw just that background.
153      *
154      * OR
155      *
156      * Removes the temporary window and show the TaskbarDragLayer background again.
157      */
moveTaskbarBackgroundToAppropriateLayernull158     private fun moveTaskbarBackgroundToAppropriateLayer(skipAnim: Boolean) {
159         val moveToLowerLayer = isVoiceInteractionWindowVisible
160         val onWindowsSynchronized =
161             if (moveToLowerLayer) {
162                 // First add the temporary window, then hide the overlapping taskbar background.
163                 context.addWindowView(
164                     separateWindowForTaskbarBackground,
165                     separateWindowLayoutParams
166                 );
167                 { controllers.taskbarDragLayerController.setIsBackgroundDrawnElsewhere(true) }
168             } else {
169                 // First reapply the original taskbar background, then remove the temporary window.
170                 controllers.taskbarDragLayerController.setIsBackgroundDrawnElsewhere(false);
171                 { context.removeWindowView(separateWindowForTaskbarBackground) }
172             }
173 
174         if (skipAnim) {
175             onWindowsSynchronized()
176         } else {
177             separateWindowForTaskbarBackground?.runWhenAttachedToWindow {
178                 ViewRootSync.synchronizeNextDraw(
179                     separateWindowForTaskbarBackground!!,
180                     context.dragLayer,
181                     onWindowsSynchronized
182                 )
183             }
184         }
185     }
186 
runWhenAttachedToWindownull187     private fun View.runWhenAttachedToWindow(onAttachedToWindow: () -> Unit) {
188         if (isAttachedToWindow) {
189             onAttachedToWindow()
190             return
191         }
192         removeOnAttachStateChangeListener(pendingAttachedToWindowListener)
193         pendingAttachedToWindowListener =
194             object : View.OnAttachStateChangeListener {
195                 override fun onViewAttachedToWindow(v: View) {
196                     onAttachedToWindow()
197                     removeOnAttachStateChangeListener(this)
198                     pendingAttachedToWindowListener = null
199                 }
200 
201                 override fun onViewDetachedFromWindow(v: View) {}
202             }
203         addOnAttachStateChangeListener(pendingAttachedToWindowListener)
204     }
205 
setCornerRoundnessnull206     override fun setCornerRoundness(cornerRoundness: Float) {
207         if (!isSeparateBackgroundEnabled) {
208             return
209         }
210         taskbarBackgroundRenderer.setCornerRoundness(cornerRoundness)
211         separateWindowForTaskbarBackground?.invalidate()
212     }
213 
dumpLogsnull214     override fun dumpLogs(prefix: String, pw: PrintWriter) {
215         pw.println(prefix + "VoiceInteractionWindowController:")
216         pw.println("$prefix\tisSeparateBackgroundEnabled=$isSeparateBackgroundEnabled")
217         pw.println("$prefix\tisVoiceInteractionWindowVisible=$isVoiceInteractionWindowVisible")
218         pw.println(
219             "$prefix\tisSeparateTaskbarBackgroundAttachedToWindow=" +
220                 "${separateWindowForTaskbarBackground?.isAttachedToWindow}"
221         )
222     }
223 }
224