1 /* 2 * Copyright (C) 2021 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.egg.widget 18 19 import android.app.PendingIntent 20 import android.appwidget.AppWidgetManager 21 import android.appwidget.AppWidgetProvider 22 import android.content.ComponentName 23 import android.content.Context 24 import android.content.Intent 25 import android.os.Bundle 26 import android.util.Log 27 import android.util.SizeF 28 import android.widget.RemoteViews 29 30 import com.android.egg.R 31 32 /** 33 * A homescreen widget to explore the current dynamic system theme. 34 */ 35 class PaintChipsWidget : AppWidgetProvider() { 36 override fun onUpdate( 37 context: Context, 38 appWidgetManager: AppWidgetManager, 39 appWidgetIds: IntArray 40 ) { 41 for (appWidgetId in appWidgetIds) { 42 updateAppWidget(context, appWidgetManager, appWidgetId) 43 } 44 } 45 46 override fun onAppWidgetOptionsChanged( 47 context: Context, 48 appWidgetManager: AppWidgetManager, 49 appWidgetId: Int, 50 newOptions: Bundle? 51 ) { 52 // Log.v(TAG, "onAppWidgetOptionsChanged: id=${appWidgetId}") 53 updateAppWidget(context, appWidgetManager, appWidgetId) 54 super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions) 55 } 56 } 57 58 const val TAG = "PaintChips" 59 60 val SHADE_NUMBERS = intArrayOf(0, 10, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000) 61 62 val COLORS_NEUTRAL1 = intArrayOf( 63 android.R.color.system_neutral1_0, 64 android.R.color.system_neutral1_10, 65 android.R.color.system_neutral1_50, 66 android.R.color.system_neutral1_100, 67 android.R.color.system_neutral1_200, 68 android.R.color.system_neutral1_300, 69 android.R.color.system_neutral1_400, 70 android.R.color.system_neutral1_500, 71 android.R.color.system_neutral1_600, 72 android.R.color.system_neutral1_700, 73 android.R.color.system_neutral1_800, 74 android.R.color.system_neutral1_900, 75 android.R.color.system_neutral1_1000 76 ) 77 78 val COLORS_NEUTRAL2 = intArrayOf( 79 android.R.color.system_neutral2_0, 80 android.R.color.system_neutral2_10, 81 android.R.color.system_neutral2_50, 82 android.R.color.system_neutral2_100, 83 android.R.color.system_neutral2_200, 84 android.R.color.system_neutral2_300, 85 android.R.color.system_neutral2_400, 86 android.R.color.system_neutral2_500, 87 android.R.color.system_neutral2_600, 88 android.R.color.system_neutral2_700, 89 android.R.color.system_neutral2_800, 90 android.R.color.system_neutral2_900, 91 android.R.color.system_neutral2_1000 92 ) 93 94 var COLORS_ACCENT1 = intArrayOf( 95 android.R.color.system_accent1_0, 96 android.R.color.system_accent1_10, 97 android.R.color.system_accent1_50, 98 android.R.color.system_accent1_100, 99 android.R.color.system_accent1_200, 100 android.R.color.system_accent1_300, 101 android.R.color.system_accent1_400, 102 android.R.color.system_accent1_500, 103 android.R.color.system_accent1_600, 104 android.R.color.system_accent1_700, 105 android.R.color.system_accent1_800, 106 android.R.color.system_accent1_900, 107 android.R.color.system_accent1_1000 108 ) 109 110 var COLORS_ACCENT2 = intArrayOf( 111 android.R.color.system_accent2_0, 112 android.R.color.system_accent2_10, 113 android.R.color.system_accent2_50, 114 android.R.color.system_accent2_100, 115 android.R.color.system_accent2_200, 116 android.R.color.system_accent2_300, 117 android.R.color.system_accent2_400, 118 android.R.color.system_accent2_500, 119 android.R.color.system_accent2_600, 120 android.R.color.system_accent2_700, 121 android.R.color.system_accent2_800, 122 android.R.color.system_accent2_900, 123 android.R.color.system_accent2_1000 124 ) 125 126 var COLORS_ACCENT3 = intArrayOf( 127 android.R.color.system_accent3_0, 128 android.R.color.system_accent3_10, 129 android.R.color.system_accent3_50, 130 android.R.color.system_accent3_100, 131 android.R.color.system_accent3_200, 132 android.R.color.system_accent3_300, 133 android.R.color.system_accent3_400, 134 android.R.color.system_accent3_500, 135 android.R.color.system_accent3_600, 136 android.R.color.system_accent3_700, 137 android.R.color.system_accent3_800, 138 android.R.color.system_accent3_900, 139 android.R.color.system_accent3_1000 140 ) 141 142 var COLOR_NAMES = arrayOf( 143 "N1", "N2", "A1", "A2", "A3" 144 ) 145 146 var COLORS = arrayOf( 147 COLORS_NEUTRAL1, 148 COLORS_NEUTRAL2, 149 COLORS_ACCENT1, 150 COLORS_ACCENT2, 151 COLORS_ACCENT3 152 ) 153 154 internal fun updateAppWidget( 155 context: Context, 156 appWidgetManager: AppWidgetManager, 157 appWidgetId: Int 158 ) { 159 // val opts = appWidgetManager.getAppWidgetOptions(appWidgetId) 160 // Log.v(TAG, "requested sizes=${opts[OPTION_APPWIDGET_SIZES]}") 161 162 val allSizes = mapOf( 163 SizeF(50f, 50f) 164 to buildWidget(context, 1, 1, ClickBehavior.LAUNCH), 165 SizeF(100f, 50f) 166 to buildWidget(context, 1, 2, ClickBehavior.LAUNCH), 167 SizeF(150f, 50f) 168 to buildWidget(context, 1, 3, ClickBehavior.LAUNCH), 169 SizeF(200f, 50f) 170 to buildWidget(context, 1, 4, ClickBehavior.LAUNCH), 171 SizeF(250f, 50f) 172 to buildWidget(context, 1, 5, ClickBehavior.LAUNCH), 173 174 SizeF(50f, 120f) 175 to buildWidget(context, 3, 1, ClickBehavior.LAUNCH), 176 SizeF(100f, 120f) 177 to buildWidget(context, 3, 2, ClickBehavior.LAUNCH), 178 SizeF(150f, 120f) 179 to buildWidget(context, 3, 3, ClickBehavior.LAUNCH), 180 SizeF(200f, 120f) 181 to buildWidget(context, 3, 4, ClickBehavior.LAUNCH), 182 SizeF(250f, 120f) 183 to buildWidget(context, 3, 5, ClickBehavior.LAUNCH), 184 185 SizeF(50f, 250f) 186 to buildWidget(context, 5, 1, ClickBehavior.LAUNCH), 187 SizeF(100f, 250f) 188 to buildWidget(context, 5, 2, ClickBehavior.LAUNCH), 189 SizeF(150f, 250f) 190 to buildWidget(context, 5, 3, ClickBehavior.LAUNCH), 191 SizeF(200f, 250f) 192 to buildWidget(context, 5, 4, ClickBehavior.LAUNCH), 193 SizeF(250f, 250f) 194 to buildWidget(context, 5, 5, ClickBehavior.LAUNCH), 195 196 SizeF(300f, 300f) 197 to buildWidget(context, SHADE_NUMBERS.size, COLORS.size, ClickBehavior.LAUNCH) 198 ) 199 200 // Instruct the widget manager to update the widget 201 appWidgetManager.updateAppWidget(appWidgetId, RemoteViews(allSizes)) 202 } 203 204 fun buildFullWidget(context: Context, clickable: ClickBehavior): RemoteViews { 205 return buildWidget(context, SHADE_NUMBERS.size, COLORS.size, clickable) 206 } 207 208 fun buildWidget(context: Context, numShades: Int, numColors: Int, clickable: ClickBehavior): 209 RemoteViews { 210 val grid = RemoteViews(context.packageName, R.layout.paint_chips_grid) 211 212 // shouldn't be necessary but sometimes the RV instructions get played twice in launcher. 213 grid.removeAllViews(R.id.paint_grid) 214 215 grid.setInt(R.id.paint_grid, "setRowCount", numShades) 216 grid.setInt(R.id.paint_grid, "setColumnCount", numColors) 217 218 Log.v(TAG, "building widget: shade rows=$numShades, color columns=$numColors") 219 220 COLORS.forEachIndexed colorLoop@{ i, colorlist -> 221 when (colorlist) { 222 COLORS_NEUTRAL1 -> if (numColors < 2) return@colorLoop 223 COLORS_NEUTRAL2 -> if (numColors < 4) return@colorLoop 224 COLORS_ACCENT2 -> if (numColors < 3) return@colorLoop 225 COLORS_ACCENT3 -> if (numColors < 5) return@colorLoop 226 else -> {} // always do ACCENT1 227 } 228 colorlist.forEachIndexed shadeLoop@{ j, resId -> 229 when (SHADE_NUMBERS[j]) { 230 500 -> {} 231 300, 700 -> if (numShades < 3) return@shadeLoop 232 100, 900 -> if (numShades < 5) return@shadeLoop 233 else -> if (numShades < SHADE_NUMBERS.size) return@shadeLoop 234 } 235 val cell = RemoteViews(context.packageName, R.layout.paint_chip) 236 cell.setTextViewText(R.id.chip, "${COLOR_NAMES[i]}-${SHADE_NUMBERS[j]}") 237 val textColor = if (SHADE_NUMBERS[j] > 500) 238 colorlist[0] 239 else colorlist[colorlist.size - 1] 240 cell.setTextColor(R.id.chip, context.getColor(textColor)) 241 cell.setColorStateList(R.id.chip, "setBackgroundTintList", resId) 242 val text = """ 243 ${COLOR_NAMES[i]}-${SHADE_NUMBERS[j]} (@${ 244 context.resources.getResourceName(resId) }) 245 currently: #${ String.format("%06x", context.getColor(resId) and 0xFFFFFF) } 246 """.trimIndent() 247 when (clickable) { 248 ClickBehavior.SHARE -> cell.setOnClickPendingIntent( 249 R.id.chip, 250 makeTextSharePendingIntent(context, text) 251 ) 252 ClickBehavior.LAUNCH -> cell.setOnClickPendingIntent( 253 R.id.chip, 254 makeActivityLaunchPendingIntent(context) 255 ) 256 ClickBehavior.NONE -> { } 257 } 258 grid.addView(R.id.paint_grid, cell) 259 } 260 } 261 262 return grid 263 } 264 265 enum class ClickBehavior { 266 NONE, 267 SHARE, 268 LAUNCH 269 } 270 271 fun makeTextSharePendingIntent(context: Context, text: String): PendingIntent { 272 val shareIntent: Intent = Intent().apply { 273 action = Intent.ACTION_SEND 274 putExtra(Intent.EXTRA_TEXT, text) 275 type = "text/plain" 276 } 277 278 val chooserIntent = Intent.createChooser(shareIntent, null).apply { 279 identifier = text // incredible quality-of-life improvement, thanks framework team 280 } 281 282 return PendingIntent.getActivity(context, 0, chooserIntent, 283 PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT) 284 } 285 286 fun makeActivityLaunchPendingIntent(context: Context): PendingIntent { 287 return PendingIntent.getActivity(context, 0, 288 Intent().apply { 289 component = ComponentName(context, PaintChipsActivity::class.java) 290 action = Intent.ACTION_MAIN 291 }, 292 PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT) 293 } 294