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