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