1 /* 2 * Copyright (C) 2021 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.systemui.statusbar.events 18 19 import android.animation.ValueAnimator 20 import android.content.Context 21 import android.view.Gravity 22 import android.view.LayoutInflater 23 import android.view.View 24 import android.view.ViewGroup.LayoutParams.MATCH_PARENT 25 import android.view.ViewGroup.LayoutParams.WRAP_CONTENT 26 import android.widget.FrameLayout 27 import com.android.systemui.R 28 import com.android.systemui.statusbar.phone.StatusBarLocationPublisher 29 import com.android.systemui.statusbar.window.StatusBarWindowController 30 import javax.inject.Inject 31 32 /** 33 * Controls the view for system event animations. 34 */ 35 class SystemEventChipAnimationController @Inject constructor( 36 private val context: Context, 37 private val statusBarWindowController: StatusBarWindowController, 38 private val locationPublisher: StatusBarLocationPublisher 39 ) : SystemStatusChipAnimationCallback { 40 var showPersistentDot = false 41 set(value) { 42 field = value 43 statusBarWindowController.setForceStatusBarVisible(value) 44 maybeUpdateShowDot() 45 } 46 47 private lateinit var animationWindowView: FrameLayout 48 private lateinit var animationDotView: View 49 private var currentAnimatedView: View? = null 50 51 // TODO: move to dagger 52 private var initialized = false 53 54 override fun onChipAnimationStart( 55 viewCreator: (context: Context) -> View, 56 @SystemAnimationState state: Int 57 ) { 58 if (!initialized) init() 59 60 if (state == ANIMATING_IN) { 61 currentAnimatedView = viewCreator(context) 62 animationWindowView.addView(currentAnimatedView, layoutParamsDefault()) 63 64 // We are animating IN; chip comes in from View.END 65 currentAnimatedView?.apply { 66 val translation = width.toFloat() 67 translationX = if (isLayoutRtl) -translation else translation 68 alpha = 0f 69 visibility = View.VISIBLE 70 setPadding(locationPublisher.marginLeft, 0, locationPublisher.marginRight, 0) 71 } 72 } else { 73 // We are animating away 74 currentAnimatedView?.apply { 75 translationX = 0f 76 alpha = 1f 77 } 78 } 79 } 80 81 override fun onChipAnimationEnd(@SystemAnimationState state: Int) { 82 if (state == ANIMATING_IN) { 83 // Finished animating in 84 currentAnimatedView?.apply { 85 translationX = 0f 86 alpha = 1f 87 } 88 } else { 89 // Finished animating away 90 currentAnimatedView?.apply { 91 visibility = View.INVISIBLE 92 } 93 animationWindowView.removeView(currentAnimatedView) 94 } 95 } 96 97 override fun onChipAnimationUpdate( 98 animator: ValueAnimator, 99 @SystemAnimationState state: Int 100 ) { 101 // Alpha is parameterized 0,1, and translation from (width, 0) 102 currentAnimatedView?.apply { 103 val amt = animator.animatedValue as Float 104 105 alpha = amt 106 107 val w = width 108 val translation = (1 - amt) * w 109 translationX = if (isLayoutRtl) -translation else translation 110 } 111 } 112 113 private fun maybeUpdateShowDot() { 114 if (!initialized) return 115 if (!showPersistentDot && currentAnimatedView == null) { 116 animationDotView.visibility = View.INVISIBLE 117 } 118 } 119 120 private fun init() { 121 initialized = true 122 animationWindowView = LayoutInflater.from(context) 123 .inflate(R.layout.system_event_animation_window, null) as FrameLayout 124 animationDotView = animationWindowView.findViewById(R.id.dot_view) 125 val lp = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) 126 lp.gravity = Gravity.END or Gravity.CENTER_VERTICAL 127 statusBarWindowController.addViewToWindow(animationWindowView, lp) 128 } 129 130 private fun start() = if (animationWindowView.isLayoutRtl) right() else left() 131 private fun right() = locationPublisher.marginRight 132 private fun left() = locationPublisher.marginLeft 133 134 private fun layoutParamsDefault(): FrameLayout.LayoutParams = 135 FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).also { 136 it.gravity = Gravity.END or Gravity.CENTER_VERTICAL 137 it.marginStart = start() 138 } 139 } 140