• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.animation.ValueAnimator
20 import android.content.Context
21 import android.provider.Settings
22 import android.provider.Settings.Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING
23 import android.util.AttributeSet
24 import android.view.MotionEvent
25 import android.view.MotionEvent.ACTION_DOWN
26 import android.view.View
27 import android.view.ViewGroup
28 import android.view.animation.Interpolator
29 import com.android.launcher3.AbstractFloatingView
30 import com.android.launcher3.R
31 import com.android.launcher3.anim.AnimatorListeners
32 import com.android.launcher3.popup.RoundedArrowDrawable
33 import com.android.launcher3.util.Themes
34 import com.android.launcher3.views.ActivityContext
35 import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
36 import com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE
37 import com.android.systemui.animation.Interpolators.STANDARD
38 
39 private const val ENTER_DURATION_MS = 300L
40 private const val EXIT_DURATION_MS = 150L
41 
42 /** Floating tooltip for Taskbar education. */
43 class TaskbarEduTooltip
44 @JvmOverloads
45 constructor(
46     context: Context,
47     attrs: AttributeSet? = null,
48     defStyleAttr: Int = 0,
49 ) : AbstractFloatingView(context, attrs, defStyleAttr) {
50 
51     private val activityContext: ActivityContext = ActivityContext.lookupContext(context)
52 
53     private val backgroundColor =
54         Themes.getAttrColor(context, com.android.internal.R.attr.colorSurface)
55 
56     private val tooltipCornerRadius = Themes.getDialogCornerRadius(context)
57     private val arrowWidth = resources.getDimension(R.dimen.popup_arrow_width)
58     private val arrowHeight = resources.getDimension(R.dimen.popup_arrow_height)
59     private val arrowPointRadius = resources.getDimension(R.dimen.popup_arrow_corner_radius)
60 
61     private val enterYDelta = resources.getDimension(R.dimen.taskbar_edu_tooltip_enter_y_delta)
62     private val exitYDelta = resources.getDimension(R.dimen.taskbar_edu_tooltip_exit_y_delta)
63 
64     /** Container where the tooltip's body should be inflated. */
65     lateinit var content: ViewGroup
66         private set
67     private lateinit var arrow: View
68 
69     /** Callback invoked when the tooltip is being closed. */
<lambda>null70     var onCloseCallback: () -> Unit = {}
71     private var openCloseAnimator: AnimatorSet? = null
72 
73     /** Animates the tooltip into view. */
shownull74     fun show() {
75         if (isOpen) {
76             return
77         }
78         mIsOpen = true
79         activityContext.dragLayer.addView(this)
80         openCloseAnimator = createOpenCloseAnimator(isOpening = true).apply { start() }
81     }
82 
onFinishInflatenull83     override fun onFinishInflate() {
84         super.onFinishInflate()
85 
86         content = findViewById(R.id.content)
87         arrow = findViewById(R.id.arrow)
88         arrow.background =
89             RoundedArrowDrawable(
90                 arrowWidth,
91                 arrowHeight,
92                 arrowPointRadius,
93                 tooltipCornerRadius,
94                 measuredWidth.toFloat(),
95                 measuredHeight.toFloat(),
96                 (measuredWidth - arrowWidth) / 2, // arrowOffsetX
97                 0f, // arrowOffsetY
98                 false, // isPointingUp
99                 true, // leftAligned
100                 backgroundColor,
101             )
102     }
103 
handleClosenull104     override fun handleClose(animate: Boolean) {
105         if (!isOpen) {
106             return
107         }
108 
109         onCloseCallback()
110         if (!animate) {
111             return closeComplete()
112         }
113 
114         openCloseAnimator?.cancel()
115         openCloseAnimator = createOpenCloseAnimator(isOpening = false)
116         openCloseAnimator?.addListener(AnimatorListeners.forEndCallback(this::closeComplete))
117         openCloseAnimator?.start()
118     }
119 
isOfTypenull120     override fun isOfType(type: Int): Boolean = type and TYPE_TASKBAR_EDUCATION_DIALOG != 0
121 
122     override fun onControllerInterceptTouchEvent(ev: MotionEvent?): Boolean {
123         if (ev?.action == ACTION_DOWN && !activityContext.dragLayer.isEventOverView(this, ev)) {
124             close(true)
125         }
126         return false
127     }
128 
onDetachedFromWindownull129     override fun onDetachedFromWindow() {
130         super.onDetachedFromWindow()
131         Settings.Secure.putInt(mContext.contentResolver, LAUNCHER_TASKBAR_EDUCATION_SHOWING, 0)
132     }
133 
closeCompletenull134     private fun closeComplete() {
135         openCloseAnimator?.cancel()
136         openCloseAnimator = null
137         mIsOpen = false
138         activityContext.dragLayer.removeView(this)
139     }
140 
createOpenCloseAnimatornull141     private fun createOpenCloseAnimator(isOpening: Boolean): AnimatorSet {
142         val duration: Long
143         val alphaValues: FloatArray
144         val translateYValues: FloatArray
145         val fadeInterpolator: Interpolator
146         val translateYInterpolator: Interpolator
147 
148         if (isOpening) {
149             duration = ENTER_DURATION_MS
150             alphaValues = floatArrayOf(0f, 1f)
151             translateYValues = floatArrayOf(enterYDelta, 0f)
152             fadeInterpolator = STANDARD
153             translateYInterpolator = EMPHASIZED_DECELERATE
154         } else {
155             duration = EXIT_DURATION_MS
156             alphaValues = floatArrayOf(1f, 0f)
157             translateYValues = floatArrayOf(0f, exitYDelta)
158             fadeInterpolator = EMPHASIZED_ACCELERATE
159             translateYInterpolator = EMPHASIZED_ACCELERATE
160         }
161 
162         val fade =
163             ValueAnimator.ofFloat(*alphaValues).apply {
164                 interpolator = fadeInterpolator
165                 addUpdateListener {
166                     val alpha = it.animatedValue as Float
167                     content.alpha = alpha
168                     arrow.alpha = alpha
169                 }
170             }
171 
172         val translateY =
173             ValueAnimator.ofFloat(*translateYValues).apply {
174                 interpolator = translateYInterpolator
175                 addUpdateListener {
176                     val translationY = it.animatedValue as Float
177                     content.translationY = translationY
178                     arrow.translationY = translationY
179                 }
180             }
181 
182         return AnimatorSet().apply {
183             this.duration = duration
184             playTogether(fade, translateY)
185         }
186     }
187 }
188