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.os.Bundle
19 import android.view.View
20 import android.view.View.GONE
21 import android.view.View.VISIBLE
22 import android.view.ViewGroup.LayoutParams.MATCH_PARENT
23 import android.view.ViewGroup.MarginLayoutParams
24 import android.view.accessibility.AccessibilityEvent
25 import android.view.accessibility.AccessibilityNodeInfo
26 import androidx.annotation.IntDef
27 import androidx.annotation.LayoutRes
28 import androidx.core.view.updateLayoutParams
29 import com.airbnb.lottie.LottieAnimationView
30 import com.android.launcher3.R
31 import com.android.launcher3.Utilities
32 import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_EDU_OPEN
33 import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
34 import com.android.launcher3.util.DisplayController
35 import com.android.launcher3.util.OnboardingPrefs.TASKBAR_EDU_TOOLTIP_STEP
36 import com.android.quickstep.util.LottieAnimationColorUtils
37 import java.io.PrintWriter
38
39 /** First EDU step for swiping up to show transient Taskbar. */
40 const val TOOLTIP_STEP_SWIPE = 0
41 /** Second EDU step for explaining Taskbar functionality when unstashed. */
42 const val TOOLTIP_STEP_FEATURES = 1
43 /**
44 * EDU is completed.
45 *
46 * This value should match the maximum count for [TASKBAR_EDU_TOOLTIP_STEP].
47 */
48 const val TOOLTIP_STEP_NONE = 2
49
50 /** Current step in the tooltip EDU flow. */
51 @Retention(AnnotationRetention.SOURCE)
52 @IntDef(TOOLTIP_STEP_SWIPE, TOOLTIP_STEP_FEATURES, TOOLTIP_STEP_NONE)
53 annotation class TaskbarEduTooltipStep
54
55 /** Controls stepping through the Taskbar tooltip EDU. */
56 class TaskbarEduTooltipController(val activityContext: TaskbarActivityContext) :
57 LoggableTaskbarController {
58
59 private val isTooltipEnabled: Boolean
60 get() = !Utilities.isRunningInTestHarness()
61 private val isOpen: Boolean
62 get() = tooltip?.isOpen ?: false
63 val isBeforeTooltipFeaturesStep: Boolean
64 get() = isTooltipEnabled && tooltipStep <= TOOLTIP_STEP_FEATURES
65 private lateinit var controllers: TaskbarControllers
66
67 @TaskbarEduTooltipStep
68 var tooltipStep: Int
69 get() {
70 return activityContext.onboardingPrefs?.getCount(TASKBAR_EDU_TOOLTIP_STEP)
71 ?: TOOLTIP_STEP_NONE
72 }
73 private set(step) {
74 activityContext.onboardingPrefs?.setEventCount(step, TASKBAR_EDU_TOOLTIP_STEP)
75 }
76
77 private var tooltip: TaskbarEduTooltip? = null
78
initnull79 fun init(controllers: TaskbarControllers) {
80 this.controllers = controllers
81 }
82
83 /** Shows swipe EDU tooltip if it is the current [tooltipStep]. */
maybeShowSwipeEdunull84 fun maybeShowSwipeEdu() {
85 if (
86 !isTooltipEnabled ||
87 !DisplayController.isTransientTaskbar(activityContext) ||
88 tooltipStep > TOOLTIP_STEP_SWIPE
89 ) {
90 return
91 }
92
93 tooltipStep = TOOLTIP_STEP_FEATURES
94 inflateTooltip(R.layout.taskbar_edu_swipe)
95 tooltip?.run {
96 findViewById<LottieAnimationView>(R.id.swipe_animation).supportLightTheme()
97 show()
98 }
99 }
100
101 /**
102 * Shows feature EDU tooltip if this step has not been seen.
103 *
104 * If [TOOLTIP_STEP_SWIPE] has not been seen at this point, the first step is skipped because a
105 * swipe up is necessary to show this step.
106 */
maybeShowFeaturesEdunull107 fun maybeShowFeaturesEdu() {
108 if (!isTooltipEnabled || tooltipStep > TOOLTIP_STEP_FEATURES) {
109 return
110 }
111
112 tooltipStep = TOOLTIP_STEP_NONE
113 inflateTooltip(R.layout.taskbar_edu_features)
114 tooltip?.run {
115 val splitscreenAnim = findViewById<LottieAnimationView>(R.id.splitscreen_animation)
116 val suggestionsAnim = findViewById<LottieAnimationView>(R.id.suggestions_animation)
117 val settingsAnim = findViewById<LottieAnimationView>(R.id.settings_animation)
118 val settingsEdu = findViewById<View>(R.id.settings_edu)
119 splitscreenAnim.supportLightTheme()
120 suggestionsAnim.supportLightTheme()
121 settingsAnim.supportLightTheme()
122 if (DisplayController.isTransientTaskbar(activityContext)) {
123 splitscreenAnim.setAnimation(R.raw.taskbar_edu_splitscreen_transient)
124 suggestionsAnim.setAnimation(R.raw.taskbar_edu_suggestions_transient)
125 settingsEdu.visibility = GONE
126 } else {
127 splitscreenAnim.setAnimation(R.raw.taskbar_edu_splitscreen_persistent)
128 suggestionsAnim.setAnimation(R.raw.taskbar_edu_suggestions_persistent)
129 settingsEdu.visibility = VISIBLE
130 }
131
132 // Set up layout parameters.
133 content.updateLayoutParams { width = MATCH_PARENT }
134 updateLayoutParams<MarginLayoutParams> {
135 if (DisplayController.isTransientTaskbar(activityContext)) {
136 width =
137 resources.getDimensionPixelSize(
138 R.dimen.taskbar_edu_features_tooltip_width_transient
139 )
140 bottomMargin += activityContext.deviceProfile.taskbarHeight
141 } else {
142 width =
143 resources.getDimensionPixelSize(
144 R.dimen.taskbar_edu_features_tooltip_width_persistent
145 )
146 }
147 }
148
149 findViewById<View>(R.id.done_button)?.setOnClickListener { hide() }
150 show()
151 }
152 }
153
154 /** Closes the current [tooltip]. */
hidenull155 fun hide() = tooltip?.close(true)
156
157 /** Initializes [tooltip] with content from [contentResId]. */
158 private fun inflateTooltip(@LayoutRes contentResId: Int) {
159 val overlayContext = controllers.taskbarOverlayController.requestWindow()
160 val tooltip =
161 overlayContext.layoutInflater.inflate(
162 R.layout.taskbar_edu_tooltip,
163 overlayContext.dragLayer,
164 false
165 ) as TaskbarEduTooltip
166
167 controllers.taskbarAutohideSuspendController.updateFlag(
168 FLAG_AUTOHIDE_SUSPEND_EDU_OPEN,
169 true
170 )
171
172 tooltip.onCloseCallback = {
173 this.tooltip = null
174 controllers.taskbarAutohideSuspendController.updateFlag(
175 FLAG_AUTOHIDE_SUSPEND_EDU_OPEN,
176 false
177 )
178 controllers.taskbarStashController.updateAndAnimateTransientTaskbar(true)
179 }
180 tooltip.accessibilityDelegate = createAccessibilityDelegate()
181
182 overlayContext.layoutInflater.inflate(contentResId, tooltip.content, true)
183 this.tooltip = tooltip
184 }
185
createAccessibilityDelegatenull186 private fun createAccessibilityDelegate() =
187 object : View.AccessibilityDelegate() {
188 override fun performAccessibilityAction(
189 host: View?,
190 action: Int,
191 args: Bundle?
192 ): Boolean {
193 if (action == R.id.close) {
194 hide()
195 return true
196 }
197 return super.performAccessibilityAction(host, action, args)
198 }
199
200 override fun onPopulateAccessibilityEvent(host: View?, event: AccessibilityEvent?) {
201 super.onPopulateAccessibilityEvent(host, event)
202 if (event?.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
203 event.text?.add(host?.context?.getText(R.string.taskbar_edu_a11y_title))
204 }
205 }
206
207 override fun onInitializeAccessibilityNodeInfo(
208 host: View?,
209 info: AccessibilityNodeInfo?
210 ) {
211 super.onInitializeAccessibilityNodeInfo(host, info)
212 info?.addAction(
213 AccessibilityNodeInfo.AccessibilityAction(
214 R.id.close,
215 host?.context?.getText(R.string.taskbar_edu_close)
216 )
217 )
218 }
219 }
220
dumpLogsnull221 override fun dumpLogs(prefix: String?, pw: PrintWriter?) {
222 pw?.println(prefix + "TaskbarEduTooltipController:")
223 pw?.println("$prefix\tisTooltipEnabled=$isTooltipEnabled")
224 pw?.println("$prefix\tisOpen=$isOpen")
225 pw?.println("$prefix\ttooltipStep=$tooltipStep")
226 }
227 }
228
229 /**
230 * Maps colors in the dark-themed Lottie assets to their light-themed equivalents.
231 *
232 * For instance, `".blue100" to R.color.lottie_blue400` means objects that are material blue100 in
233 * dark theme should be changed to material blue400 in light theme.
234 */
235 private val DARK_TO_LIGHT_COLORS =
236 mapOf(
237 ".blue100" to R.color.lottie_blue400,
238 ".blue400" to R.color.lottie_blue600,
239 ".green100" to R.color.lottie_green400,
240 ".green400" to R.color.lottie_green600,
241 ".grey300" to R.color.lottie_grey600,
242 ".grey400" to R.color.lottie_grey700,
243 ".grey800" to R.color.lottie_grey200,
244 ".red400" to R.color.lottie_red600,
245 ".yellow100" to R.color.lottie_yellow400,
246 ".yellow400" to R.color.lottie_yellow600,
247 )
248
LottieAnimationViewnull249 private fun LottieAnimationView.supportLightTheme() {
250 if (Utilities.isDarkTheme(context)) {
251 return
252 }
253
254 LottieAnimationColorUtils.updateColors(this, DARK_TO_LIGHT_COLORS, context.theme)
255 }
256