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 17 package com.android.wm.shell.bubbles 18 19 import android.animation.ObjectAnimator 20 import android.content.Context 21 import android.graphics.Color 22 import android.graphics.drawable.GradientDrawable 23 import android.util.IntProperty 24 import android.view.Gravity 25 import android.view.View 26 import android.view.ViewGroup 27 import android.view.WindowInsets 28 import android.view.WindowManager 29 import android.widget.FrameLayout 30 import androidx.dynamicanimation.animation.DynamicAnimation 31 import androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY 32 import androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW 33 import com.android.wm.shell.R 34 import com.android.wm.shell.animation.PhysicsAnimator 35 import com.android.wm.shell.common.DismissCircleView 36 37 /* 38 * View that handles interactions between DismissCircleView and BubbleStackView. 39 */ 40 class DismissView(context: Context) : FrameLayout(context) { 41 42 var circle = DismissCircleView(context) 43 var isShowing = false 44 var targetSizeResId: Int 45 46 private val animator = PhysicsAnimator.getInstance(circle) 47 private val spring = PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY) 48 private val DISMISS_SCRIM_FADE_MS = 200L 49 private var wm: WindowManager = 50 context.getSystemService(Context.WINDOW_SERVICE) as WindowManager 51 private var gradientDrawable = createGradient() 52 53 private val GRADIENT_ALPHA: IntProperty<GradientDrawable> = 54 object : IntProperty<GradientDrawable>("alpha") { setValuenull55 override fun setValue(d: GradientDrawable, percent: Int) { 56 d.alpha = percent 57 } getnull58 override fun get(d: GradientDrawable): Int { 59 return d.alpha 60 } 61 } 62 63 init { 64 setLayoutParams(LayoutParams( 65 ViewGroup.LayoutParams.MATCH_PARENT, 66 resources.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height), 67 Gravity.BOTTOM)) 68 updatePadding() 69 setClipToPadding(false) 70 setClipChildren(false) 71 setVisibility(View.INVISIBLE) 72 setBackgroundDrawable(gradientDrawable) 73 74 targetSizeResId = R.dimen.dismiss_circle_size 75 val targetSize: Int = resources.getDimensionPixelSize(targetSizeResId) 76 addView(circle, LayoutParams(targetSize, targetSize, 77 Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL)) 78 // start with circle offscreen so it's animated up 79 circle.setTranslationY(resources.getDimensionPixelSize( 80 R.dimen.floating_dismiss_gradient_height).toFloat()) 81 } 82 83 /** 84 * Animates this view in. 85 */ shownull86 fun show() { 87 if (isShowing) return 88 isShowing = true 89 setVisibility(View.VISIBLE) 90 val alphaAnim = ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA, 91 gradientDrawable.alpha, 255) 92 alphaAnim.setDuration(DISMISS_SCRIM_FADE_MS) 93 alphaAnim.start() 94 95 animator.cancel() 96 animator 97 .spring(DynamicAnimation.TRANSLATION_Y, 0f, spring) 98 .start() 99 } 100 101 /** 102 * Animates this view out, as well as the circle that encircles the bubbles, if they 103 * were dragged into the target and encircled. 104 */ hidenull105 fun hide() { 106 if (!isShowing) return 107 isShowing = false 108 val alphaAnim = ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA, 109 gradientDrawable.alpha, 0) 110 alphaAnim.setDuration(DISMISS_SCRIM_FADE_MS) 111 alphaAnim.start() 112 animator 113 .spring(DynamicAnimation.TRANSLATION_Y, height.toFloat(), 114 spring) 115 .withEndActions({ setVisibility(View.INVISIBLE) }) 116 .start() 117 } 118 119 /** 120 * Cancels the animator for the dismiss target. 121 */ cancelAnimatorsnull122 fun cancelAnimators() { 123 animator.cancel() 124 } 125 updateResourcesnull126 fun updateResources() { 127 updatePadding() 128 layoutParams.height = resources.getDimensionPixelSize( 129 R.dimen.floating_dismiss_gradient_height) 130 131 val targetSize = resources.getDimensionPixelSize(targetSizeResId) 132 circle.layoutParams.width = targetSize 133 circle.layoutParams.height = targetSize 134 circle.requestLayout() 135 } 136 createGradientnull137 private fun createGradient(): GradientDrawable { 138 val gradientColor = context.resources.getColor(android.R.color.system_neutral1_900) 139 val alpha = 0.7f * 255 140 val gradientColorWithAlpha = Color.argb(alpha.toInt(), 141 Color.red(gradientColor), 142 Color.green(gradientColor), 143 Color.blue(gradientColor)) 144 val gd = GradientDrawable( 145 GradientDrawable.Orientation.BOTTOM_TOP, 146 intArrayOf(gradientColorWithAlpha, Color.TRANSPARENT)) 147 gd.setAlpha(0) 148 return gd 149 } 150 updatePaddingnull151 private fun updatePadding() { 152 val insets: WindowInsets = wm.getCurrentWindowMetrics().getWindowInsets() 153 val navInset = insets.getInsetsIgnoringVisibility( 154 WindowInsets.Type.navigationBars()) 155 setPadding(0, 0, 0, navInset.bottom + 156 resources.getDimensionPixelSize(R.dimen.floating_dismiss_bottom_margin)) 157 } 158 } 159