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.LayoutInflater
23 import android.view.View
24 import android.view.ViewGroup
25 import android.widget.Button
26 import android.widget.LinearLayout
27 import com.android.internal.R.color.system_neutral1_900
28 import com.android.wm.shell.R
29 import com.android.wm.shell.animation.Interpolators
30 
31 /**
32  * User education view to highlight the manage button that allows a user to configure the settings
33  * for the bubble. Shown only the first time a user expands a bubble.
34  */
35 class ManageEducationView constructor(context: Context, positioner: BubblePositioner)
36     : LinearLayout(context) {
37 
38     private val TAG = if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "ManageEducationView"
39         else BubbleDebugConfig.TAG_BUBBLES
40 
41     private val ANIMATE_DURATION: Long = 200
42 
43     private val positioner: BubblePositioner = positioner
44     private val manageView by lazy { requireViewById<ViewGroup>(R.id.manage_education_view) }
45     private val manageButton by lazy { requireViewById<Button>(R.id.manage_button) }
46     private val gotItButton by lazy { requireViewById<Button>(R.id.got_it) }
47 
48     private var isHiding = false
49     private var realManageButtonRect = Rect()
50     private var bubbleExpandedView: BubbleExpandedView? = null
51 
52     init {
53         LayoutInflater.from(context).inflate(R.layout.bubbles_manage_button_education, this)
54         visibility = View.GONE
55         elevation = resources.getDimensionPixelSize(R.dimen.bubble_elevation).toFloat()
56 
57         // BubbleStackView forces LTR by default
58         // since most of Bubble UI direction depends on positioning by the user.
59         // This view actually lays out differently in RTL, so we set layout LOCALE here.
60         layoutDirection = View.LAYOUT_DIRECTION_LOCALE
61     }
62 
63     override fun setLayoutDirection(layoutDirection: Int) {
64         super.setLayoutDirection(layoutDirection)
65         setDrawableDirection()
66     }
67 
68     override fun onFinishInflate() {
69         super.onFinishInflate()
70         layoutDirection = resources.configuration.layoutDirection
71     }
72 
73     private fun setButtonColor() {
74         val typedArray = mContext.obtainStyledAttributes(intArrayOf(
75                 com.android.internal.R.attr.colorAccentPrimary))
76         val buttonColor = typedArray.getColor(0 /* index */, Color.TRANSPARENT)
77         typedArray.recycle()
78 
79         manageButton.setTextColor(mContext.getColor(system_neutral1_900))
80         manageButton.setBackgroundDrawable(ColorDrawable(buttonColor))
81         gotItButton.setBackgroundDrawable(ColorDrawable(buttonColor))
82     }
83 
84     private fun setDrawableDirection() {
85         manageView.setBackgroundResource(
86             if (resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL)
87                 R.drawable.bubble_stack_user_education_bg_rtl
88             else R.drawable.bubble_stack_user_education_bg)
89     }
90 
91     /**
92      * If necessary, toggles the user education view for the manage button. This is shown when the
93      * bubble stack is expanded for the first time.
94      *
95      * @param expandedView the expandedView the user education is shown on top of.
96      */
97     fun show(expandedView: BubbleExpandedView) {
98         setButtonColor()
99         if (visibility == VISIBLE) return
100 
101         bubbleExpandedView = expandedView
102         expandedView.taskView?.setObscuredTouchRect(Rect(positioner.screenRect))
103 
104         layoutParams.width = if (positioner.isLargeScreen || positioner.isLandscape)
105             context.resources.getDimensionPixelSize(R.dimen.bubbles_user_education_width)
106         else ViewGroup.LayoutParams.MATCH_PARENT
107 
108         alpha = 0f
109         visibility = View.VISIBLE
110         expandedView.getManageButtonBoundsOnScreen(realManageButtonRect)
111         val isRTL = mContext.resources.configuration.layoutDirection == LAYOUT_DIRECTION_RTL
112         if (isRTL) {
113             val rightPadding = positioner.screenRect.right - realManageButtonRect.right -
114                     expandedView.manageButtonMargin
115             manageView.setPadding(manageView.paddingLeft, manageView.paddingTop,
116                     rightPadding, manageView.paddingBottom)
117         } else {
118             manageView.setPadding(realManageButtonRect.left - expandedView.manageButtonMargin,
119             manageView.paddingTop, manageView.paddingRight, manageView.paddingBottom)
120         }
121         post {
122             manageButton
123                 .setOnClickListener {
124                     hide()
125                     expandedView.requireViewById<View>(R.id.manage_button).performClick()
126                 }
127             gotItButton.setOnClickListener { hide() }
128             setOnClickListener { hide() }
129 
130             val offsetViewBounds = Rect()
131             manageButton.getDrawingRect(offsetViewBounds)
132             manageView.offsetDescendantRectToMyCoords(manageButton, offsetViewBounds)
133             if (isRTL && (positioner.isLargeScreen || positioner.isLandscape)) {
134                 translationX = (positioner.screenRect.right - width).toFloat()
135             } else {
136                 translationX = 0f
137             }
138             translationY = (realManageButtonRect.top - offsetViewBounds.top).toFloat()
139             bringToFront()
140             animate()
141                 .setDuration(ANIMATE_DURATION)
142                 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
143                 .alpha(1f)
144         }
145         setShouldShow(false)
146     }
147 
148     fun hide() {
149         bubbleExpandedView?.taskView?.setObscuredTouchRect(null)
150         if (visibility != VISIBLE || isHiding) return
151 
152         animate()
153             .withStartAction { isHiding = true }
154             .alpha(0f)
155             .setDuration(ANIMATE_DURATION)
156             .withEndAction {
157                 isHiding = false
158                 visibility = GONE
159             }
160     }
161 
162     private fun setShouldShow(shouldShow: Boolean) {
163         context.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
164                 .edit().putBoolean(PREF_MANAGED_EDUCATION, !shouldShow).apply()
165     }
166 }
167 
168 const val PREF_MANAGED_EDUCATION: String = "HasSeenBubblesManageOnboarding"