• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.Path
29 import android.graphics.Rect
30 import android.graphics.drawable.AdaptiveIconDrawable
31 import android.graphics.drawable.BitmapDrawable
32 import android.graphics.drawable.ColorDrawable
33 import android.graphics.drawable.Drawable
34 import android.graphics.drawable.InsetDrawable
35 import android.os.Build
36 import com.android.launcher3.Flags
37 import com.android.launcher3.icons.BaseIconFactory
38 import com.android.launcher3.icons.BaseIconFactory.MODE_ALPHA
39 import com.android.launcher3.icons.BitmapInfo
40 import com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR
41 import com.android.launcher3.icons.IconThemeController
42 import com.android.launcher3.icons.MonochromeIconFactory
43 import com.android.launcher3.icons.SourceHint
44 import com.android.launcher3.icons.ThemedBitmap
45 import java.nio.ByteBuffer
46 
47 @TargetApi(Build.VERSION_CODES.TIRAMISU)
48 class MonoIconThemeController(
49     private val colorProvider: (Context) -> IntArray = ThemedIconDrawable.Companion::getColors
50 ) : IconThemeController {
51 
52     override val themeID = "with-theme"
53 
54     override fun createThemedBitmap(
55         icon: AdaptiveIconDrawable,
56         info: BitmapInfo,
57         factory: BaseIconFactory,
58         sourceHint: SourceHint?,
59     ): ThemedBitmap? {
60         val mono =
61             getMonochromeDrawable(
62                 icon,
63                 info,
64                 factory.getShapePath(icon, Rect(0, 0, info.icon.width, info.icon.height)),
65                 factory.iconScale,
66                 sourceHint?.isFileDrawable ?: false,
67                 factory.shouldForceThemeIcon(),
68             )
69         if (mono != null) {
70             return MonoThemedBitmap(
71                 factory.createIconBitmap(mono, ICON_VISIBLE_AREA_FACTOR, MODE_ALPHA),
72                 factory.whiteShadowLayer,
73                 colorProvider,
74             )
75         }
76         return null
77     }
78 
79     /**
80      * Returns a monochromatic version of the given drawable or null, if it is not supported
81      *
82      * @param base the original icon
83      */
84     private fun getMonochromeDrawable(
85         base: AdaptiveIconDrawable,
86         info: BitmapInfo,
87         shapePath: Path,
88         iconScale: Float,
89         isFileDrawable: Boolean,
90         shouldForceThemeIcon: Boolean,
91     ): Drawable? {
92         val mono = base.monochrome
93         if (mono != null) {
94             return ClippedMonoDrawable(mono, shapePath, iconScale)
95         }
96         if (Flags.forceMonochromeAppIcons() && shouldForceThemeIcon && !isFileDrawable) {
97             return MonochromeIconFactory(info.icon.width).wrap(base, shapePath, iconScale)
98         }
99         return null
100     }
101 
102     override fun decode(
103         data: ByteArray,
104         info: BitmapInfo,
105         factory: BaseIconFactory,
106         sourceHint: SourceHint,
107     ): ThemedBitmap? {
108         val icon = info.icon
109         if (data.size != icon.height * icon.width) return null
110 
111         var monoBitmap = Bitmap.createBitmap(icon.width, icon.height, ALPHA_8)
112         monoBitmap.copyPixelsFromBuffer(ByteBuffer.wrap(data))
113 
114         val hwMonoBitmap = monoBitmap.copy(HARDWARE, false /*isMutable*/)
115         if (hwMonoBitmap != null) {
116             monoBitmap.recycle()
117             monoBitmap = hwMonoBitmap
118         }
119         return MonoThemedBitmap(monoBitmap, factory.whiteShadowLayer, colorProvider)
120     }
121 
122     override fun createThemedAdaptiveIcon(
123         context: Context,
124         originalIcon: AdaptiveIconDrawable,
125         info: BitmapInfo?,
126     ): AdaptiveIconDrawable? {
127         val colors = colorProvider(context)
128         originalIcon.mutate()
129         var monoDrawable = originalIcon.monochrome?.apply { setTint(colors[1]) }
130 
131         if (monoDrawable == null) {
132             info?.themedBitmap?.let { themedBitmap ->
133                 if (themedBitmap is MonoThemedBitmap) {
134                     // Inject a previously generated monochrome icon
135                     // Use BitmapDrawable instead of FastBitmapDrawable so that the colorState is
136                     // preserved in constantState
137                     // Inset the drawable according to the AdaptiveIconDrawable layers
138                     monoDrawable =
139                         InsetDrawable(
140                             BitmapDrawable(themedBitmap.mono).apply {
141                                 colorFilter = BlendModeColorFilter(colors[1], SRC_IN)
142                             },
143                             AdaptiveIconDrawable.getExtraInsetFraction() / 2,
144                         )
145                 }
146             }
147         }
148 
149         return monoDrawable?.let { AdaptiveIconDrawable(ColorDrawable(colors[0]), it) }
150     }
151 
152     class ClippedMonoDrawable(
153         base: Drawable?,
154         private val shapePath: Path,
155         private val iconScale: Float,
156     ) : InsetDrawable(base, -AdaptiveIconDrawable.getExtraInsetFraction()) {
157         // TODO(b/399666950): remove this after launcher icon shapes is fully enabled
158         private val mCrop = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null)
159 
160         override fun draw(canvas: Canvas) {
161             mCrop.bounds = bounds
162             val saveCount = canvas.save()
163             if (Flags.enableLauncherIconShapes()) {
164                 canvas.clipPath(shapePath)
165                 canvas.scale(iconScale, iconScale, bounds.width() / 2f, bounds.height() / 2f)
166             } else {
167                 canvas.clipPath(mCrop.iconMask)
168             }
169             super.draw(canvas)
170             canvas.restoreToCount(saveCount)
171         }
172     }
173 }
174