1 /*
2  * Copyright (C) 2022 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.systemui.statusbar.notification.stack
17 
18 import android.animation.AnimatorListenerAdapter
19 import android.content.Context
20 import android.content.res.Configuration
21 import android.graphics.Canvas
22 import android.graphics.Path
23 import android.graphics.RectF
24 import android.util.AttributeSet
25 import android.util.Log
26 import com.android.systemui.Flags
27 import com.android.systemui.res.R
28 import com.android.systemui.statusbar.notification.row.ExpandableView
29 
30 /** Root view to insert Lock screen media controls into the notification stack. */
31 class MediaContainerView(context: Context, attrs: AttributeSet?) : ExpandableView(context, attrs) {
32 
33     override var clipHeight = 0
34     var cornerRadius = 0f
35     var clipRect = RectF()
36     var clipPath = Path()
37 
38     init {
39         setWillNotDraw(false) // Run onDraw after invalidate.
40         updateResources()
41     }
42 
onConfigurationChangednull43     override fun onConfigurationChanged(newConfig: Configuration?) {
44         super.onConfigurationChanged(newConfig)
45         updateResources()
46     }
47 
updateResourcesnull48     private fun updateResources() {
49         cornerRadius =
50             context.resources.getDimensionPixelSize(R.dimen.notification_corner_radius).toFloat()
51     }
52 
updateClippingnull53     public override fun updateClipping() {
54         if (clipHeight != actualHeight) {
55             clipHeight = actualHeight
56         }
57         invalidate()
58     }
59 
onDrawnull60     override fun onDraw(canvas: Canvas) {
61         super.onDraw(canvas)
62 
63         val bounds = canvas.clipBounds
64         bounds.bottom = clipHeight
65         clipRect.set(bounds)
66 
67         clipPath.reset()
68         clipPath.addRoundRect(clipRect, cornerRadius, cornerRadius, Path.Direction.CW)
69         canvas.clipPath(clipPath)
70     }
71 
performRemoveAnimationnull72     override fun performRemoveAnimation(
73             duration: Long,
74             delay: Long,
75             translationDirection: Float,
76             isHeadsUpAnimation: Boolean,
77             onStartedRunnable: Runnable?,
78             onFinishedRunnable: Runnable?,
79             animationListener: AnimatorListenerAdapter?,
80             clipSide: ClipSide
81     ): Long {
82         return 0
83     }
84 
performAddAnimationnull85     override fun performAddAnimation(
86         delay: Long,
87         duration: Long,
88         isHeadsUpAppear: Boolean,
89         onEnd: Runnable?
90     ) {
91         // No animation, it doesn't need it, this would be local
92     }
93 
setVisibilitynull94     override fun setVisibility(visibility: Int) {
95         if (Flags.bindKeyguardMediaVisibility()) {
96             if (isVisibilityValid(visibility)) {
97                 super.setVisibility(visibility)
98             }
99         } else {
100             super.setVisibility(visibility)
101         }
102 
103         assertMediaContainerVisibility(visibility)
104     }
105 
106     /**
107      * visibility should be aligned with MediaContainerView visibility on the keyguard.
108      */
isVisibilityValidnull109     private fun isVisibilityValid(visibility: Int): Boolean {
110         val currentViewState = viewState as? MediaContainerViewState ?: return true
111         val shouldBeGone = !currentViewState.shouldBeVisible
112         return if (shouldBeGone) visibility == GONE else visibility != GONE
113     }
114 
115     /**
116      * b/298213983
117      * MediaContainerView's visibility is changed to VISIBLE when it should be GONE.
118      * This method check this state and logs.
119      */
assertMediaContainerVisibilitynull120     private fun assertMediaContainerVisibility(visibility: Int) {
121         val currentViewState = viewState
122 
123         if (currentViewState is MediaContainerViewState) {
124             if (!currentViewState.shouldBeVisible && visibility == VISIBLE) {
125                 Log.wtf("MediaContainerView", "MediaContainerView should be GONE " +
126                         "but its visibility changed to VISIBLE")
127             }
128         }
129     }
130 
setKeyguardVisibilitynull131     fun setKeyguardVisibility(isVisible: Boolean) {
132         val currentViewState = viewState
133         if (currentViewState is MediaContainerViewState) {
134             currentViewState.shouldBeVisible = isVisible
135         }
136 
137         visibility = if (isVisible) VISIBLE else GONE
138     }
139 
createExpandableViewStatenull140     override fun createExpandableViewState(): ExpandableViewState = MediaContainerViewState()
141 
142     class MediaContainerViewState : ExpandableViewState() {
143         var shouldBeVisible: Boolean = false
144 
145         override fun copyFrom(viewState: ViewState) {
146             super.copyFrom(viewState)
147             if (viewState is MediaContainerViewState) {
148                 shouldBeVisible = viewState.shouldBeVisible
149             }
150         }
151     }
152 }
153