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