• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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