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"