1 /* <lambda>null2 * Copyright (C) 2024 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.wm.shell.windowdecor 17 18 import android.animation.AnimatorSet 19 import android.animation.ObjectAnimator 20 import android.animation.ValueAnimator 21 import android.annotation.DrawableRes 22 import android.content.Context 23 import android.content.res.ColorStateList 24 import android.graphics.Color 25 import android.graphics.drawable.Drawable 26 import android.util.AttributeSet 27 import android.view.LayoutInflater 28 import android.view.View 29 import android.view.ViewStub 30 import android.widget.FrameLayout 31 import android.widget.ImageButton 32 import android.widget.ProgressBar 33 import android.window.DesktopModeFlags 34 import androidx.core.animation.doOnEnd 35 import androidx.core.animation.doOnStart 36 import androidx.core.content.ContextCompat 37 import com.android.wm.shell.R 38 39 private const val OPEN_MAXIMIZE_MENU_DELAY_ON_HOVER_MS = 350 40 private const val MAX_DRAWABLE_ALPHA = 255 41 42 class MaximizeButtonView(context: Context, attrs: AttributeSet) : FrameLayout(context, attrs) { 43 lateinit var onHoverAnimationFinishedListener: () -> Unit 44 private val hoverProgressAnimatorSet = AnimatorSet() 45 var hoverDisabled = false 46 47 private lateinit var stubProgressBarContainer: ViewStub 48 private val maximizeWindow: ImageButton 49 private val progressBar: ProgressBar by lazy { 50 (stubProgressBarContainer.inflate() as FrameLayout) 51 .requireViewById(R.id.progress_bar) 52 } 53 54 init { 55 LayoutInflater.from(context).inflate(R.layout.maximize_menu_button, this, true) 56 57 stubProgressBarContainer = requireViewById(R.id.stub_progress_bar_container) 58 maximizeWindow = requireViewById(R.id.maximize_window) 59 } 60 61 fun startHoverAnimation() { 62 if (hoverDisabled) return 63 if (hoverProgressAnimatorSet.isRunning) { 64 cancelHoverAnimation() 65 } 66 67 maximizeWindow.background.alpha = 0 68 69 hoverProgressAnimatorSet.playSequentially( 70 ValueAnimator.ofInt(0, MAX_DRAWABLE_ALPHA) 71 .setDuration(50) 72 .apply { 73 addUpdateListener { 74 maximizeWindow.background.alpha = animatedValue as Int 75 } 76 }, 77 ObjectAnimator.ofInt(progressBar, "progress", 100) 78 .setDuration(OPEN_MAXIMIZE_MENU_DELAY_ON_HOVER_MS.toLong()) 79 .apply { 80 doOnStart { 81 progressBar.setProgress(0, false) 82 progressBar.visibility = View.VISIBLE 83 } 84 doOnEnd { 85 progressBar.visibility = View.INVISIBLE 86 onHoverAnimationFinishedListener() 87 } 88 } 89 ) 90 hoverProgressAnimatorSet.start() 91 } 92 93 fun cancelHoverAnimation() { 94 hoverProgressAnimatorSet.childAnimations.forEach { it.removeAllListeners() } 95 hoverProgressAnimatorSet.cancel() 96 progressBar.visibility = View.INVISIBLE 97 } 98 99 /** 100 * Set the color tints of the maximize button views. 101 * 102 * @param darkMode whether the app's theme is in dark mode. 103 * @param iconForegroundColor the color tint to use for the maximize icon to match the rest of 104 * the App Header icons 105 * @param baseForegroundColor the base foreground color tint used by the App Header, used to style 106 * views within this button using the same base color but with different opacities. 107 */ 108 fun setAnimationTints( 109 darkMode: Boolean, 110 iconForegroundColor: ColorStateList? = null, 111 baseForegroundColor: Int? = null, 112 backgroundDrawable: Drawable? = null 113 ) { 114 if (DesktopModeFlags.ENABLE_THEMED_APP_HEADERS.isTrue()) { 115 requireNotNull(iconForegroundColor) { "Icon foreground color must be non-null" } 116 requireNotNull(baseForegroundColor) { "Base foreground color must be non-null" } 117 requireNotNull(backgroundDrawable) { "Background drawable must be non-null" } 118 maximizeWindow.imageTintList = iconForegroundColor 119 maximizeWindow.background = backgroundDrawable 120 stubProgressBarContainer.setOnInflateListener { _, inflated -> 121 val progressBar = (inflated as FrameLayout) 122 .requireViewById(R.id.progress_bar) as ProgressBar 123 progressBar.progressTintList = ColorStateList.valueOf(baseForegroundColor) 124 .withAlpha(OPACITY_15) 125 progressBar.progressBackgroundTintList = ColorStateList.valueOf(Color.TRANSPARENT) 126 } 127 } else { 128 val progressTint = if (darkMode) { 129 ColorStateList.valueOf( 130 resources.getColor(R.color.desktop_mode_maximize_menu_progress_dark)) 131 } else { 132 ColorStateList.valueOf( 133 resources.getColor(R.color.desktop_mode_maximize_menu_progress_light)) 134 } 135 val backgroundTint = if (darkMode) { 136 ContextCompat.getColorStateList(context, 137 R.color.desktop_mode_caption_button_color_selector_dark) 138 } else { 139 ContextCompat.getColorStateList(context, 140 R.color.desktop_mode_caption_button_color_selector_light) 141 } 142 stubProgressBarContainer.setOnInflateListener { _, inflated -> 143 val progressBar = (inflated as FrameLayout) 144 .requireViewById(R.id.progress_bar) as ProgressBar 145 progressBar.progressTintList = progressTint 146 } 147 maximizeWindow.background?.setTintList(backgroundTint) 148 } 149 } 150 151 /** Set the drawable resource to use for the maximize button. */ 152 fun setIcon(@DrawableRes icon: Int) { 153 maximizeWindow.setImageResource(icon) 154 } 155 156 companion object { 157 private const val OPACITY_15 = 38 158 } 159 } 160