1 /*
<lambda>null2  * Copyright (C) 2023 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.permissioncontroller.permission.ui.wear.elements
18 
19 import android.graphics.drawable.Animatable
20 import android.graphics.drawable.ColorDrawable
21 import android.graphics.drawable.Drawable
22 import android.os.Build
23 import android.os.Handler
24 import android.os.Looper
25 import android.view.View
26 import androidx.compose.runtime.Composable
27 import androidx.compose.runtime.RememberObserver
28 import androidx.compose.runtime.getValue
29 import androidx.compose.runtime.mutableStateOf
30 import androidx.compose.runtime.remember
31 import androidx.compose.runtime.setValue
32 import androidx.compose.ui.geometry.Size
33 import androidx.compose.ui.graphics.Color
34 import androidx.compose.ui.graphics.ColorFilter
35 import androidx.compose.ui.graphics.asAndroidColorFilter
36 import androidx.compose.ui.graphics.drawscope.DrawScope
37 import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
38 import androidx.compose.ui.graphics.nativeCanvas
39 import androidx.compose.ui.graphics.painter.ColorPainter
40 import androidx.compose.ui.graphics.painter.Painter
41 import androidx.compose.ui.graphics.withSave
42 import androidx.compose.ui.unit.LayoutDirection
43 import kotlin.math.roundToInt
44 
45 private val MAIN_HANDLER by lazy(LazyThreadSafetyMode.NONE) { Handler(Looper.getMainLooper()) }
46 
47 /**
48  * A [Painter] which draws an Android [Drawable] and supports [Animatable] drawables. Instances
49  * should be remembered to be able to start and stop [Animatable] animations.
50  *
51  * Instances are usually retrieved from [rememberDrawablePainter].
52  */
53 class DrawablePainter(val drawable: Drawable) : Painter(), RememberObserver {
54     private var drawInvalidateTick by mutableStateOf(0)
55     private var drawableIntrinsicSize by mutableStateOf(drawable.intrinsicSize)
56 
<lambda>null57     private val callback: Drawable.Callback by lazy {
58         object : Drawable.Callback {
59             override fun invalidateDrawable(d: Drawable) {
60                 // Update the tick so that we get re-drawn
61                 drawInvalidateTick++
62                 // Update our intrinsic size too
63                 drawableIntrinsicSize = drawable.intrinsicSize
64             }
65 
66             override fun scheduleDrawable(d: Drawable, what: Runnable, time: Long) {
67                 MAIN_HANDLER.postAtTime(what, time)
68             }
69 
70             override fun unscheduleDrawable(d: Drawable, what: Runnable) {
71                 MAIN_HANDLER.removeCallbacks(what)
72             }
73         }
74     }
75 
76     init {
77         if (drawable.intrinsicWidth >= 0 && drawable.intrinsicHeight >= 0) {
78             // Update the drawable's bounds to match the intrinsic size
79             drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
80         }
81     }
82 
onRememberednull83     override fun onRemembered() {
84         drawable.callback = callback
85         drawable.setVisible(true, true)
86         if (drawable is Animatable) drawable.start()
87     }
88 
onAbandonednull89     override fun onAbandoned() = onForgotten()
90 
91     override fun onForgotten() {
92         if (drawable is Animatable) drawable.stop()
93         drawable.setVisible(false, false)
94         drawable.callback = null
95     }
96 
applyAlphanull97     override fun applyAlpha(alpha: Float): Boolean {
98         drawable.alpha = (alpha * 255).roundToInt().coerceIn(0, 255)
99         return true
100     }
101 
applyColorFilternull102     override fun applyColorFilter(colorFilter: ColorFilter?): Boolean {
103         drawable.colorFilter = colorFilter?.asAndroidColorFilter()
104         return true
105     }
106 
applyLayoutDirectionnull107     override fun applyLayoutDirection(layoutDirection: LayoutDirection): Boolean {
108         if (Build.VERSION.SDK_INT >= 23) {
109             return drawable.setLayoutDirection(
110                 when (layoutDirection) {
111                     LayoutDirection.Ltr -> View.LAYOUT_DIRECTION_LTR
112                     LayoutDirection.Rtl -> View.LAYOUT_DIRECTION_RTL
113                 }
114             )
115         }
116         return false
117     }
118 
119     override val intrinsicSize: Size
120         get() = drawableIntrinsicSize
121 
onDrawnull122     override fun DrawScope.onDraw() {
123         drawIntoCanvas { canvas ->
124             // Reading this ensures that we invalidate when invalidateDrawable() is called
125             drawInvalidateTick
126 
127             // Update the Drawable's bounds
128             drawable.setBounds(0, 0, size.width.roundToInt(), size.height.roundToInt())
129 
130             canvas.withSave { drawable.draw(canvas.nativeCanvas) }
131         }
132     }
133 }
134 
135 /**
136  * Remembers [Drawable] wrapped up as a [Painter]. This function attempts to un-wrap the drawable
137  * contents and use Compose primitives where possible.
138  *
139  * If the provided [drawable] is `null`, an empty no-op painter is returned.
140  *
141  * This function tries to dispatch lifecycle events to [drawable] as much as possible from within
142  * Compose.
143  */
144 @Composable
rememberDrawablePainternull145 fun rememberDrawablePainter(drawable: Drawable?): Painter =
146     remember(drawable) {
147         when (drawable) {
148             null -> EmptyPainter
149             is ColorDrawable -> ColorPainter(Color(drawable.color))
150             // Since the DrawablePainter will be remembered and it implements RememberObserver, it
151             // will receive the necessary events
152             else -> DrawablePainter(drawable.mutate())
153         }
154     }
155 
156 private val Drawable.intrinsicSize: Size
157     get() =
158         when {
159             // Only return a finite size if the drawable has an intrinsic size
160             intrinsicWidth >= 0 && intrinsicHeight >= 0 -> {
161                 Size(width = intrinsicWidth.toFloat(), height = intrinsicHeight.toFloat())
162             }
163             else -> Size.Unspecified
164         }
165 
166 internal object EmptyPainter : Painter() {
167     override val intrinsicSize: Size
168         get() = Size.Unspecified
onDrawnull169     override fun DrawScope.onDraw() {}
170 }
171