• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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.bubbles
17 
18 import android.content.Context
19 import android.graphics.Color
20 import android.graphics.Rect
21 import android.graphics.drawable.ColorDrawable
22 import android.view.Gravity
23 import android.view.LayoutInflater
24 import android.view.View
25 import android.view.ViewGroup
26 import android.widget.Button
27 import android.widget.LinearLayout
28 import com.android.internal.R.color.system_neutral1_900
29 import com.android.wm.shell.R
30 import com.android.wm.shell.shared.TypefaceUtils
31 import com.android.wm.shell.shared.animation.Interpolators
32 
33 /**
34  * User education view to highlight the manage button that allows a user to configure the settings
35  * for the bubble. Shown only the first time a user expands a bubble.
36  */
37 class ManageEducationView(
38     context: Context,
39     private val positioner: BubblePositioner
40 ) : LinearLayout(context) {
41 
42     companion object {
43         const val PREF_MANAGED_EDUCATION: String = "HasSeenBubblesManageOnboarding"
44         private const val ANIMATE_DURATION: Long = 200
45     }
46 
<lambda>null47     private val manageView by lazy { requireViewById<ViewGroup>(R.id.manage_education_view) }
<lambda>null48     private val manageButton by lazy { requireViewById<Button>(R.id.manage_button) }
<lambda>null49     private val gotItButton by lazy { requireViewById<Button>(R.id.got_it) }
50 
51     private var isHiding = false
52     private var realManageButtonRect = Rect()
53     private var bubbleExpandedView: BubbleExpandedView? = null
54 
55     init {
56         LayoutInflater.from(context).inflate(R.layout.bubbles_manage_button_education, this)
57         TypefaceUtils.setTypeface(findViewById(R.id.user_education_title),
58             TypefaceUtils.FontFamily.GSF_HEADLINE_SMALL_EMPHASIZED)
59         TypefaceUtils.setTypeface(findViewById(R.id.user_education_description),
60             TypefaceUtils.FontFamily.GSF_BODY_MEDIUM)
61         TypefaceUtils.setTypeface(manageButton, TypefaceUtils.FontFamily.GSF_LABEL_LARGE_EMPHASIZED)
62         TypefaceUtils.setTypeface(gotItButton, TypefaceUtils.FontFamily.GSF_LABEL_LARGE_EMPHASIZED)
63         visibility = View.GONE
64         elevation = resources.getDimensionPixelSize(R.dimen.bubble_elevation).toFloat()
65 
66         // BubbleStackView forces LTR by default
67         // since most of Bubble UI direction depends on positioning by the user.
68         // This view actually lays out differently in RTL, so we set layout LOCALE here.
69         layoutDirection = View.LAYOUT_DIRECTION_LOCALE
70     }
71 
setLayoutDirectionnull72     override fun setLayoutDirection(layoutDirection: Int) {
73         super.setLayoutDirection(layoutDirection)
74         setDrawableDirection(layoutDirection == LAYOUT_DIRECTION_LTR)
75     }
76 
onFinishInflatenull77     override fun onFinishInflate() {
78         super.onFinishInflate()
79         layoutDirection = resources.configuration.layoutDirection
80     }
81 
setButtonColornull82     private fun setButtonColor() {
83         val typedArray =
84             mContext.obtainStyledAttributes(
85                 intArrayOf(com.android.internal.R.attr.colorAccentPrimary)
86             )
87         val buttonColor = typedArray.getColor(0 /* index */, Color.TRANSPARENT)
88         typedArray.recycle()
89 
90         manageButton.setTextColor(mContext.getColor(system_neutral1_900))
91         manageButton.setBackgroundDrawable(ColorDrawable(buttonColor))
92         gotItButton.setBackgroundDrawable(ColorDrawable(buttonColor))
93     }
94 
setDrawableDirectionnull95     private fun setDrawableDirection(isOnLeft: Boolean) {
96         manageView.setBackgroundResource(
97             if (isOnLeft) R.drawable.bubble_stack_user_education_bg
98             else R.drawable.bubble_stack_user_education_bg_rtl
99         )
100     }
101 
102     /**
103      * If necessary, toggles the user education view for the manage button. This is shown when the
104      * bubble stack is expanded for the first time.
105      *
106      * @param expandedView the expandedView the user education is shown on top of.
107      * @param isStackOnLeft the bubble stack position on the screen
108      */
shownull109     fun show(expandedView: BubbleExpandedView, isStackOnLeft: Boolean) {
110         setButtonColor()
111         if (visibility == VISIBLE) return
112 
113         bubbleExpandedView = expandedView
114         expandedView.taskView?.setObscuredTouchRect(Rect(positioner.screenRect))
115 
116         alpha = 0f
117         visibility = View.VISIBLE
118         expandedView.getManageButtonBoundsOnScreen(realManageButtonRect)
119         layoutManageView(realManageButtonRect, expandedView.manageButtonMargin, isStackOnLeft)
120 
121         post {
122             manageButton.setOnClickListener {
123                 hide()
124                 expandedView.requireViewById<View>(R.id.manage_button).performClick()
125             }
126             gotItButton.setOnClickListener { hide() }
127             setOnClickListener { hide() }
128 
129             val offsetViewBounds = Rect()
130             manageButton.getDrawingRect(offsetViewBounds)
131             manageView.offsetDescendantRectToMyCoords(manageButton, offsetViewBounds)
132             translationY = (realManageButtonRect.top - offsetViewBounds.top).toFloat()
133             bringToFront()
134             animate()
135                 .setDuration(ANIMATE_DURATION)
136                 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
137                 .alpha(1f)
138         }
139         updateManageEducationSeen()
140     }
141 
142     /**
143      * On tablet the user education is aligned to the left or to right side depending on where the
144      * stack is positioned when collapsed. On phone the user education follows the layout direction.
145      *
146      * @param manageButtonRect the manage button rect on the screen
147      * @param manageButtonMargin the manage button margin
148      * @param isStackOnLeft the bubble stack position on the screen
149      */
layoutManageViewnull150     private fun layoutManageView(
151         manageButtonRect: Rect,
152         manageButtonMargin: Int,
153         isStackOnLeft: Boolean
154     ) {
155         val isLTR = resources.configuration.layoutDirection == LAYOUT_DIRECTION_LTR
156         val isPinnedLeft = if (positioner.isLargeScreen) isStackOnLeft else isLTR
157         val paddingHorizontal =
158             resources.getDimensionPixelSize(R.dimen.bubble_user_education_padding_horizontal)
159 
160         // The user education view background image direction
161         setDrawableDirection(isPinnedLeft)
162 
163         // The user education view layout gravity
164         gravity = if (isPinnedLeft) Gravity.LEFT else Gravity.RIGHT
165 
166         // The user education view width
167         manageView.layoutParams.width =
168             when {
169                 // Left-to-Right direction and the education is on the right side
170                 isLTR && !isPinnedLeft ->
171                     positioner.screenRect.right -
172                         (manageButtonRect.left - manageButtonMargin - paddingHorizontal)
173                 // Right-to-Left direction and the education is on the left side
174                 !isLTR && isPinnedLeft ->
175                     manageButtonRect.right + manageButtonMargin + paddingHorizontal
176                 // Large screen and the education position matches the layout direction
177                 positioner.isLargeScreen -> ViewGroup.LayoutParams.WRAP_CONTENT
178                 // Small screen, landscape orientation
179                 positioner.isLandscape ->
180                     resources.getDimensionPixelSize(R.dimen.bubbles_user_education_width)
181                 // Otherwise
182                 else -> ViewGroup.LayoutParams.MATCH_PARENT
183             }
184 
185         // The user education view margin on the opposite side of where it's pinned
186         (manageView.layoutParams as MarginLayoutParams).apply {
187             val edgeMargin =
188                 resources.getDimensionPixelSize(R.dimen.bubble_user_education_margin_horizontal)
189             leftMargin = if (isPinnedLeft) 0 else edgeMargin
190             rightMargin = if (isPinnedLeft) edgeMargin else 0
191         }
192 
193         // The user education view padding
194         manageView.apply {
195             val paddingLeft =
196                 if (isLTR && isPinnedLeft) {
197                     // Offset on the left to align with the manage button
198                     manageButtonRect.left - manageButtonMargin
199                 } else {
200                     // Use default padding
201                     paddingHorizontal
202                 }
203             val paddingRight =
204                 if (!isLTR && !isPinnedLeft) {
205                     // Offset on the right to align with the manage button
206                     positioner.screenRect.right - manageButtonRect.right - manageButtonMargin
207                 } else {
208                     // Use default padding
209                     paddingHorizontal
210                 }
211             setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom)
212         }
213     }
214 
hidenull215     fun hide() {
216         bubbleExpandedView?.taskView?.setObscuredTouchRect(null)
217         if (visibility != VISIBLE || isHiding) return
218 
219         animate()
220             .withStartAction { isHiding = true }
221             .alpha(0f)
222             .setDuration(ANIMATE_DURATION)
223             .withEndAction {
224                 isHiding = false
225                 visibility = GONE
226             }
227     }
228 
updateManageEducationSeennull229     private fun updateManageEducationSeen() {
230         context
231             .getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
232             .edit()
233             .putBoolean(PREF_MANAGED_EDUCATION, true)
234             .apply()
235     }
236 }
237