1 /*
<lambda>null2  * Copyright (C) 2024 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.launcher3.icons.mono
18 
19 import android.annotation.TargetApi
20 import android.content.Context
21 import android.graphics.Bitmap
22 import android.graphics.Bitmap.Config.ALPHA_8
23 import android.graphics.Bitmap.Config.HARDWARE
24 import android.graphics.BlendMode.SRC_IN
25 import android.graphics.BlendModeColorFilter
26 import android.graphics.Canvas
27 import android.graphics.Color
28 import android.graphics.drawable.AdaptiveIconDrawable
29 import android.graphics.drawable.BitmapDrawable
30 import android.graphics.drawable.ColorDrawable
31 import android.graphics.drawable.Drawable
32 import android.graphics.drawable.InsetDrawable
33 import android.os.Build
34 import com.android.launcher3.Flags
35 import com.android.launcher3.icons.BaseIconFactory
36 import com.android.launcher3.icons.BitmapInfo
37 import com.android.launcher3.icons.IconThemeController
38 import com.android.launcher3.icons.MonochromeIconFactory
39 import com.android.launcher3.icons.ThemedBitmap
40 import com.android.launcher3.icons.mono.ThemedIconDrawable.Companion.getColors
41 import java.nio.ByteBuffer
42 
43 @TargetApi(Build.VERSION_CODES.TIRAMISU)
44 class MonoIconThemeController : IconThemeController {
45 
46     override fun createThemedBitmap(
47         icon: AdaptiveIconDrawable,
48         info: BitmapInfo,
49         factory: BaseIconFactory,
50     ): ThemedBitmap? {
51         val mono = getMonochromeDrawable(icon, info)
52         if (mono != null) {
53             val scale =
54                 factory.normalizer.getScale(
55                     AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null),
56                     null,
57                     null,
58                     null,
59                 )
60             return MonoThemedBitmap(
61                 factory.createIconBitmap(mono, scale, BaseIconFactory.MODE_ALPHA),
62                 factory.whiteShadowLayer,
63             )
64         }
65         return null
66     }
67 
68     /**
69      * Returns a monochromatic version of the given drawable or null, if it is not supported
70      *
71      * @param base the original icon
72      */
73     private fun getMonochromeDrawable(base: AdaptiveIconDrawable, info: BitmapInfo): Drawable? {
74         val mono = base.monochrome
75         if (mono != null) {
76             return ClippedMonoDrawable(mono)
77         }
78         if (Flags.forceMonochromeAppIcons()) {
79             return MonochromeIconFactory(info.icon.width).wrap(base)
80         }
81         return null
82     }
83 
84     override fun decode(
85         data: ByteArray,
86         info: BitmapInfo,
87         factory: BaseIconFactory,
88     ): ThemedBitmap? {
89         val icon = info.icon
90         if (data.size != icon.height * icon.width) return null
91 
92         var monoBitmap = Bitmap.createBitmap(icon.width, icon.height, ALPHA_8)
93         monoBitmap.copyPixelsFromBuffer(ByteBuffer.wrap(data))
94 
95         val hwMonoBitmap = monoBitmap.copy(HARDWARE, false /*isMutable*/)
96         if (hwMonoBitmap != null) {
97             monoBitmap.recycle()
98             monoBitmap = hwMonoBitmap
99         }
100         return MonoThemedBitmap(monoBitmap, factory.whiteShadowLayer)
101     }
102 
103     override fun createThemedAdaptiveIcon(
104         context: Context,
105         originalIcon: AdaptiveIconDrawable,
106         info: BitmapInfo?,
107     ): AdaptiveIconDrawable? {
108         val colors = getColors(context)
109         originalIcon.mutate()
110         var monoDrawable = originalIcon.monochrome?.apply { setTint(colors[1]) }
111 
112         if (monoDrawable == null) {
113             info?.themedBitmap?.let { themedBitmap ->
114                 if (themedBitmap is MonoThemedBitmap) {
115                     // Inject a previously generated monochrome icon
116                     // Use BitmapDrawable instead of FastBitmapDrawable so that the colorState is
117                     // preserved in constantState
118                     // Inset the drawable according to the AdaptiveIconDrawable layers
119                     monoDrawable =
120                         InsetDrawable(
121                             BitmapDrawable(themedBitmap.mono).apply {
122                                 colorFilter = BlendModeColorFilter(colors[1], SRC_IN)
123                             },
124                             AdaptiveIconDrawable.getExtraInsetFraction() / 2,
125                         )
126                 }
127             }
128         }
129 
130         return monoDrawable?.let { AdaptiveIconDrawable(ColorDrawable(colors[0]), it) }
131     }
132 
133     class ClippedMonoDrawable(base: Drawable?) :
134         InsetDrawable(base, -AdaptiveIconDrawable.getExtraInsetFraction()) {
135         private val mCrop = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null)
136 
137         override fun draw(canvas: Canvas) {
138             mCrop.bounds = bounds
139             val saveCount = canvas.save()
140             canvas.clipPath(mCrop.iconMask)
141             super.draw(canvas)
142             canvas.restoreToCount(saveCount)
143         }
144     }
145 }
146