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