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