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