/* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.launcher3.taskbar import android.animation.AnimatorSet import android.graphics.Canvas import android.view.View import android.view.ViewTreeObserver import android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION import android.view.WindowManager import android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY import com.android.launcher3.util.DisplayController import com.android.launcher3.views.BaseDragLayer import com.android.systemui.animation.ViewRootSync import java.io.PrintWriter private const val TASKBAR_ICONS_FADE_DURATION = 300L private const val STASHED_HANDLE_FADE_DURATION = 180L private const val TEMP_BACKGROUND_WINDOW_TITLE = "VoiceInteractionTaskbarBackground" /** * Controls Taskbar behavior while Voice Interaction Window (assistant) is showing. Specifically: * - We always hide the taskbar icons or stashed handle, whichever is currently showing. * - For persistent taskbar, we also move the taskbar background to a new window/layer * (TYPE_APPLICATION_OVERLAY) which is behind the assistant. * - For transient taskbar, we hide the real taskbar background (if it's showing). */ class VoiceInteractionWindowController(val context: TaskbarActivityContext) : TaskbarControllers.LoggableTaskbarController, TaskbarControllers.BackgroundRendererController { private val isSeparateBackgroundEnabled = !DisplayController.isTransientTaskbar(context) private val taskbarBackgroundRenderer = TaskbarBackgroundRenderer(context) private val nonTouchableInsetsComputer = ViewTreeObserver.OnComputeInternalInsetsListener { it.touchableRegion.setEmpty() it.setTouchableInsets(TOUCHABLE_INSETS_REGION) } // Initialized in init. private lateinit var controllers: TaskbarControllers // Only initialized if isSeparateBackgroundEnabled private var separateWindowForTaskbarBackground: BaseDragLayer? = null private var separateWindowLayoutParams: WindowManager.LayoutParams? = null private var isVoiceInteractionWindowVisible: Boolean = false private var pendingAttachedToWindowListener: View.OnAttachStateChangeListener? = null fun init(controllers: TaskbarControllers) { this.controllers = controllers if (!isSeparateBackgroundEnabled) { return } separateWindowForTaskbarBackground = object : BaseDragLayer(context, null, 0) { override fun recreateControllers() { mControllers = emptyArray() } override fun draw(canvas: Canvas) { super.draw(canvas) if (controllers.taskbarStashController.isTaskbarVisibleAndNotStashing) { taskbarBackgroundRenderer.draw(canvas) } } override fun onAttachedToWindow() { super.onAttachedToWindow() viewTreeObserver.addOnComputeInternalInsetsListener(nonTouchableInsetsComputer) } override fun onDetachedFromWindow() { super.onDetachedFromWindow() viewTreeObserver.removeOnComputeInternalInsetsListener( nonTouchableInsetsComputer ) } } separateWindowForTaskbarBackground?.recreateControllers() separateWindowForTaskbarBackground?.setWillNotDraw(false) separateWindowLayoutParams = context.createDefaultWindowLayoutParams( TYPE_APPLICATION_OVERLAY, TEMP_BACKGROUND_WINDOW_TITLE ) separateWindowLayoutParams?.isSystemApplicationOverlay = true } fun onDestroy() { setIsVoiceInteractionWindowVisible(visible = false, skipAnim = true) separateWindowForTaskbarBackground?.removeOnAttachStateChangeListener( pendingAttachedToWindowListener ) } fun setIsVoiceInteractionWindowVisible(visible: Boolean, skipAnim: Boolean) { if (isVoiceInteractionWindowVisible == visible) { return } isVoiceInteractionWindowVisible = visible // Fade out taskbar icons and stashed handle. val taskbarIconAlpha = if (isVoiceInteractionWindowVisible) 0f else 1f val fadeTaskbarIcons = controllers.taskbarViewController.taskbarIconAlpha .get(TaskbarViewController.ALPHA_INDEX_ASSISTANT_INVOKED) .animateToValue(taskbarIconAlpha) .setDuration(TASKBAR_ICONS_FADE_DURATION) val fadeStashedHandle = controllers.stashedHandleViewController.stashedHandleAlpha .get(StashedHandleViewController.ALPHA_INDEX_ASSISTANT_INVOKED) .animateToValue(taskbarIconAlpha) .setDuration(STASHED_HANDLE_FADE_DURATION) val animSet = AnimatorSet() animSet.play(fadeTaskbarIcons) animSet.play(fadeStashedHandle) if (!isSeparateBackgroundEnabled) { val fadeTaskbarBackground = controllers.taskbarDragLayerController.assistantBgTaskbar .animateToValue(taskbarIconAlpha) .setDuration(TASKBAR_ICONS_FADE_DURATION) animSet.play(fadeTaskbarBackground) } animSet.start() if (skipAnim) { animSet.end() } if (isSeparateBackgroundEnabled) { moveTaskbarBackgroundToAppropriateLayer(skipAnim) } } /** * Either: * * Hides the TaskbarDragLayer background and creates a new window to draw just that background. * * OR * * Removes the temporary window and show the TaskbarDragLayer background again. */ private fun moveTaskbarBackgroundToAppropriateLayer(skipAnim: Boolean) { val moveToLowerLayer = isVoiceInteractionWindowVisible val onWindowsSynchronized = if (moveToLowerLayer) { // First add the temporary window, then hide the overlapping taskbar background. context.addWindowView( separateWindowForTaskbarBackground, separateWindowLayoutParams ); { controllers.taskbarDragLayerController.setIsBackgroundDrawnElsewhere(true) } } else { // First reapply the original taskbar background, then remove the temporary window. controllers.taskbarDragLayerController.setIsBackgroundDrawnElsewhere(false); { context.removeWindowView(separateWindowForTaskbarBackground) } } if (skipAnim) { onWindowsSynchronized() } else { separateWindowForTaskbarBackground?.runWhenAttachedToWindow { ViewRootSync.synchronizeNextDraw( separateWindowForTaskbarBackground!!, context.dragLayer, onWindowsSynchronized ) } } } private fun View.runWhenAttachedToWindow(onAttachedToWindow: () -> Unit) { if (isAttachedToWindow) { onAttachedToWindow() return } removeOnAttachStateChangeListener(pendingAttachedToWindowListener) pendingAttachedToWindowListener = object : View.OnAttachStateChangeListener { override fun onViewAttachedToWindow(v: View) { onAttachedToWindow() removeOnAttachStateChangeListener(this) pendingAttachedToWindowListener = null } override fun onViewDetachedFromWindow(v: View) {} } addOnAttachStateChangeListener(pendingAttachedToWindowListener) } override fun setCornerRoundness(cornerRoundness: Float) { if (!isSeparateBackgroundEnabled) { return } taskbarBackgroundRenderer.setCornerRoundness(cornerRoundness) separateWindowForTaskbarBackground?.invalidate() } override fun dumpLogs(prefix: String, pw: PrintWriter) { pw.println(prefix + "VoiceInteractionWindowController:") pw.println("$prefix\tisSeparateBackgroundEnabled=$isSeparateBackgroundEnabled") pw.println("$prefix\tisVoiceInteractionWindowVisible=$isVoiceInteractionWindowVisible") pw.println( "$prefix\tisSeparateTaskbarBackgroundAttachedToWindow=" + "${separateWindowForTaskbarBackground?.isAttachedToWindow}" ) } }