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