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