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.PointF 21 import android.view.LayoutInflater 22 import android.view.View 23 import android.widget.LinearLayout 24 import android.widget.TextView 25 import com.android.internal.util.ContrastColorUtil 26 import com.android.wm.shell.R 27 import com.android.wm.shell.animation.Interpolators 28 29 /** 30 * User education view to highlight the collapsed stack of bubbles. 31 * Shown only the first time a user taps the stack. 32 */ 33 class StackEducationView constructor(context: Context) : LinearLayout(context) { 34 35 private val TAG = if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "BubbleStackEducationView" 36 else BubbleDebugConfig.TAG_BUBBLES 37 38 private val ANIMATE_DURATION: Long = 200 39 private val ANIMATE_DURATION_SHORT: Long = 40 40 <lambda>null41 private val view by lazy { findViewById<View>(R.id.stack_education_layout) } <lambda>null42 private val titleTextView by lazy { findViewById<TextView>(R.id.stack_education_title) } <lambda>null43 private val descTextView by lazy { findViewById<TextView>(R.id.stack_education_description) } 44 45 private var isHiding = false 46 47 init { 48 LayoutInflater.from(context).inflate(R.layout.bubble_stack_user_education, this) 49 50 visibility = View.GONE 51 elevation = resources.getDimensionPixelSize(R.dimen.bubble_elevation).toFloat() 52 53 // BubbleStackView forces LTR by default 54 // since most of Bubble UI direction depends on positioning by the user. 55 // This view actually lays out differently in RTL, so we set layout LOCALE here. 56 layoutDirection = View.LAYOUT_DIRECTION_LOCALE 57 } 58 setLayoutDirectionnull59 override fun setLayoutDirection(layoutDirection: Int) { 60 super.setLayoutDirection(layoutDirection) 61 setDrawableDirection() 62 } 63 onFinishInflatenull64 override fun onFinishInflate() { 65 super.onFinishInflate() 66 layoutDirection = resources.configuration.layoutDirection 67 setTextColor() 68 } 69 setTextColornull70 private fun setTextColor() { 71 val ta = mContext.obtainStyledAttributes(intArrayOf(android.R.attr.colorAccent, 72 android.R.attr.textColorPrimaryInverse)) 73 val bgColor = ta.getColor(0 /* index */, Color.BLACK) 74 var textColor = ta.getColor(1 /* index */, Color.WHITE) 75 ta.recycle() 76 textColor = ContrastColorUtil.ensureTextContrast(textColor, bgColor, true) 77 titleTextView.setTextColor(textColor) 78 descTextView.setTextColor(textColor) 79 } 80 setDrawableDirectionnull81 private fun setDrawableDirection() { 82 view.setBackgroundResource( 83 if (resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR) 84 R.drawable.bubble_stack_user_education_bg 85 else R.drawable.bubble_stack_user_education_bg_rtl) 86 } 87 88 /** 89 * If necessary, shows the user education view for the bubble stack. This appears the first 90 * time a user taps on a bubble. 91 * 92 * @return true if user education was shown, false otherwise. 93 */ shownull94 fun show(stackPosition: PointF): Boolean { 95 if (visibility == VISIBLE) return false 96 97 setAlpha(0f) 98 setVisibility(View.VISIBLE) 99 post { 100 with(view) { 101 val bubbleSize = context.resources.getDimensionPixelSize( 102 R.dimen.bubble_size) 103 translationY = stackPosition.y + bubbleSize / 2 - getHeight() / 2 104 } 105 animate() 106 .setDuration(ANIMATE_DURATION) 107 .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) 108 .alpha(1f) 109 } 110 setShouldShow(false) 111 return true 112 } 113 114 /** 115 * If necessary, hides the stack education view. 116 * 117 * @param fromExpansion if true this indicates the hide is happening due to the bubble being 118 * expanded, false if due to a touch outside of the bubble stack. 119 */ hidenull120 fun hide(fromExpansion: Boolean) { 121 if (visibility != VISIBLE || isHiding) return 122 123 animate() 124 .alpha(0f) 125 .setDuration(if (fromExpansion) ANIMATE_DURATION_SHORT else ANIMATE_DURATION) 126 .withEndAction { visibility = GONE } 127 } 128 setShouldShownull129 private fun setShouldShow(shouldShow: Boolean) { 130 context.getSharedPreferences(context.packageName, Context.MODE_PRIVATE) 131 .edit().putBoolean(PREF_STACK_EDUCATION, !shouldShow).apply() 132 } 133 } 134 135 const val PREF_STACK_EDUCATION: String = "HasSeenBubblesOnboarding"