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