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