Merge pull request #2714 from Philoul/wear/new_custom_watchface

Wear CWF : Fix potential crash (phone side) + reduce complexity (SonarLint)
This commit is contained in:
Milos Kozak 2023-08-28 22:53:27 +02:00 committed by GitHub
commit 29cf0ea63d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 83 additions and 75 deletions

View file

@ -260,13 +260,13 @@ class ZipWatchfaceFormat {
} }
// Valid CWF file must contains a valid json file with a name within metadata and a custom watchface image // Valid CWF file must contains a valid json file with a name within metadata and a custom watchface image
if (metadata.containsKey(CwfMetadataKey.CWF_NAME) && drawableDatas.containsKey(CwfDrawableFileMap.CUSTOM_WATCHFACE)) return if (metadata.containsKey(CwfMetadataKey.CWF_NAME) && drawableDatas.containsKey(CwfDrawableFileMap.CUSTOM_WATCHFACE))
return CwfData(json.toString(4), metadata, drawableDatas) CwfData(json.toString(4), metadata, drawableDatas)
else else
return null null
} catch (e: Exception) { } catch (e: Exception) {
return null return null // mainly IOException
} }
} }
@ -292,6 +292,7 @@ class ZipWatchfaceFormat {
zipOutputStream.close() zipOutputStream.close()
outputStream.close() outputStream.close()
} catch (_: Exception) { } catch (_: Exception) {
// Ignore file
} }
} }
@ -299,13 +300,9 @@ class ZipWatchfaceFormat {
fun loadMetadata(contents: JSONObject): CwfMetadataMap { fun loadMetadata(contents: JSONObject): CwfMetadataMap {
val metadata: CwfMetadataMap = mutableMapOf() val metadata: CwfMetadataMap = mutableMapOf()
if (contents.has(JsonKeys.METADATA.key)) { contents.optJSONObject(JsonKeys.METADATA.key)?.also { jsonObject -> // optJSONObject doesn't throw Exception
val meta = contents.getJSONObject(JsonKeys.METADATA.key) for (key in jsonObject.keys()) {
for (key in meta.keys()) { CwfMetadataKey.fromKey(key)?.let { metadata[it] = jsonObject.optString(key) } // optString doesn't throw Exception
val metaKey = CwfMetadataKey.fromKey(key)
if (metaKey != null) {
metadata[metaKey] = meta.getString(key)
}
} }
} }
return metadata return metadata

View file

@ -23,6 +23,8 @@ import info.nightscout.rx.weardata.CUSTOM_VERSION
import info.nightscout.rx.weardata.CwfDrawableFileMap import info.nightscout.rx.weardata.CwfDrawableFileMap
import info.nightscout.rx.weardata.CwfMetadataKey import info.nightscout.rx.weardata.CwfMetadataKey
import info.nightscout.rx.weardata.CwfMetadataMap import info.nightscout.rx.weardata.CwfMetadataMap
import info.nightscout.rx.weardata.JsonKeyValues
import info.nightscout.rx.weardata.JsonKeys
import info.nightscout.rx.weardata.ViewKeys import info.nightscout.rx.weardata.ViewKeys
import info.nightscout.shared.interfaces.ResourceHelper import info.nightscout.shared.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP import info.nightscout.shared.sharedPreferences.SP
@ -82,13 +84,13 @@ class CwfInfosActivity : TranslatedDaggerAppCompatActivity() {
private fun updateGui() { private fun updateGui() {
wearPlugin.savedCustomWatchface?.let { wearPlugin.savedCustomWatchface?.let {
val cwf_authorization = sp.getBoolean(info.nightscout.core.utils.R.string.key_wear_custom_watchface_autorization, false) val cwfAuthorization = sp.getBoolean(info.nightscout.core.utils.R.string.key_wear_custom_watchface_autorization, false)
val metadata = it.metadata val metadata = it.metadata
val drawable = it.drawableDatas[CwfDrawableFileMap.CUSTOM_WATCHFACE]?.toDrawable(resources) val drawable = it.drawableDatas[CwfDrawableFileMap.CUSTOM_WATCHFACE]?.toDrawable(resources)
binding.customWatchface.setImageDrawable(drawable) binding.customWatchface.setImageDrawable(drawable)
title = rh.gs(CwfMetadataKey.CWF_NAME.label, metadata[CwfMetadataKey.CWF_NAME]) title = rh.gs(CwfMetadataKey.CWF_NAME.label, metadata[CwfMetadataKey.CWF_NAME])
metadata[CwfMetadataKey.CWF_AUTHOR_VERSION]?.let { author_version -> metadata[CwfMetadataKey.CWF_AUTHOR_VERSION]?.let { authorVersion ->
title = "${metadata[CwfMetadataKey.CWF_NAME]} ($author_version)" title = "${metadata[CwfMetadataKey.CWF_NAME]} ($authorVersion)"
} }
binding.filelistName.text = rh.gs(CwfMetadataKey.CWF_FILENAME.label, metadata[CwfMetadataKey.CWF_FILENAME] ?: "") binding.filelistName.text = rh.gs(CwfMetadataKey.CWF_FILENAME.label, metadata[CwfMetadataKey.CWF_FILENAME] ?: "")
binding.author.text = rh.gs(CwfMetadataKey.CWF_AUTHOR.label, metadata[CwfMetadataKey.CWF_AUTHOR] ?: "") binding.author.text = rh.gs(CwfMetadataKey.CWF_AUTHOR.label, metadata[CwfMetadataKey.CWF_AUTHOR] ?: "")
@ -99,7 +101,7 @@ class CwfInfosActivity : TranslatedDaggerAppCompatActivity() {
binding.cwfComment.text = rh.gs(CwfMetadataKey.CWF_COMMENT.label, metadata[CwfMetadataKey.CWF_COMMENT] ?: "") binding.cwfComment.text = rh.gs(CwfMetadataKey.CWF_COMMENT.label, metadata[CwfMetadataKey.CWF_COMMENT] ?: "")
if (metadata.count { it.key.isPref } > 0) { if (metadata.count { it.key.isPref } > 0) {
binding.prefLayout.visibility = View.VISIBLE binding.prefLayout.visibility = View.VISIBLE
binding.prefTitle.text = rh.gs(if (cwf_authorization) R.string.cwf_infos_pref_locked else R.string.cwf_infos_pref_required) binding.prefTitle.text = rh.gs(if (cwfAuthorization) R.string.cwf_infos_pref_locked else R.string.cwf_infos_pref_required)
binding.prefRecyclerview.layoutManager = LinearLayoutManager(this) binding.prefRecyclerview.layoutManager = LinearLayoutManager(this)
binding.prefRecyclerview.adapter = PrefRecyclerViewAdapter( binding.prefRecyclerview.adapter = PrefRecyclerViewAdapter(
metadata.filter { it.key.isPref && (it.value.lowercase() == "true" || it.value.lowercase() == "false") }.toList() metadata.filter { it.key.isPref && (it.value.lowercase() == "true" || it.value.lowercase() == "false") }.toList()
@ -192,7 +194,7 @@ class CwfInfosActivity : TranslatedDaggerAppCompatActivity() {
try { try {
val jsonValue = json.optJSONObject(viewKey.key) val jsonValue = json.optJSONObject(viewKey.key)
if (jsonValue != null) { if (jsonValue != null) {
val visibility = jsonValue.optString("visibility") == "visible" val visibility = jsonValue.optString(JsonKeys.VISIBILITY.key) == JsonKeyValues.VISIBLE.key
if (visibility || allViews) if (visibility || allViews)
visibleKeyPairs.add(Pair(viewKey, visibility)) visibleKeyPairs.add(Pair(viewKey, visibility))
} }

View file

@ -3,7 +3,6 @@
package info.nightscout.androidaps.watchfaces package info.nightscout.androidaps.watchfaces
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.ActionBar.LayoutParams
import android.content.Context import android.content.Context
import android.graphics.Color import android.graphics.Color
import android.graphics.ColorFilter import android.graphics.ColorFilter
@ -148,17 +147,16 @@ class CustomWatchface : BaseWatchFace() {
try { try {
val json = JSONObject(it.customWatchfaceData.json) val json = JSONObject(it.customWatchfaceData.json)
val drawableDataMap = it.customWatchfaceData.drawableDatas val drawableDataMap = it.customWatchfaceData.drawableDatas
enableSecond = (if (json.has(ENABLESECOND.key)) json.getBoolean(ENABLESECOND.key) else false) && sp.getBoolean(R.string.key_show_seconds, true) enableSecond = json.optBoolean(ENABLESECOND.key) && sp.getBoolean(R.string.key_show_seconds, true)
highColor = getColor(json.optString(HIGHCOLOR.key), ContextCompat.getColor(this, R.color.dark_highColor))
highColor = if (json.has(HIGHCOLOR.key)) Color.parseColor(json.getString(HIGHCOLOR.key)) else ContextCompat.getColor(this, R.color.dark_highColor) midColor = getColor(json.optString(MIDCOLOR.key), ContextCompat.getColor(this, R.color.inrange))
midColor = if (json.has(MIDCOLOR.key)) Color.parseColor(json.getString(MIDCOLOR.key)) else ContextCompat.getColor(this, R.color.inrange) lowColor = getColor(json.optString(LOWCOLOR.key), ContextCompat.getColor(this, R.color.low))
lowColor = if (json.has(LOWCOLOR.key)) Color.parseColor(json.getString(LOWCOLOR.key)) else ContextCompat.getColor(this, R.color.low) lowBatColor = getColor(json.optString(LOWBATCOLOR.key), ContextCompat.getColor(this, R.color.dark_uploaderBatteryEmpty))
lowBatColor = if (json.has(LOWBATCOLOR.key)) Color.parseColor(json.getString(LOWBATCOLOR.key)) else ContextCompat.getColor(this, R.color.dark_uploaderBatteryEmpty) carbColor = getColor(json.optString(CARBCOLOR.key), ContextCompat.getColor(this, R.color.carbs))
carbColor = if (json.has(CARBCOLOR.key)) Color.parseColor(json.getString(CARBCOLOR.key)) else ContextCompat.getColor(this, R.color.carbs) basalBackgroundColor = getColor(json.optString(BASALBACKGROUNDCOLOR.key), ContextCompat.getColor(this, R.color.basal_dark))
basalBackgroundColor = if (json.has(BASALBACKGROUNDCOLOR.key)) Color.parseColor(json.getString(BASALBACKGROUNDCOLOR.key)) else ContextCompat.getColor(this, R.color.basal_dark) basalCenterColor = getColor(json.optString(BASALCENTERCOLOR.key), ContextCompat.getColor(this, R.color.basal_light))
basalCenterColor = if (json.has(BASALCENTERCOLOR.key)) Color.parseColor(json.getString(BASALCENTERCOLOR.key)) else ContextCompat.getColor(this, R.color.basal_light) gridColor = getColor(json.optString(GRIDCOLOR.key), Color.WHITE)
gridColor = if (json.has(GRIDCOLOR.key)) Color.parseColor(json.getString(GRIDCOLOR.key)) else Color.WHITE pointSize = json.optInt(POINTSIZE.key, 2)
pointSize = if (json.has(POINTSIZE.key)) json.getInt(POINTSIZE.key) else 2
bgColor = when (singleBg.sgvLevel) { bgColor = when (singleBg.sgvLevel) {
1L -> highColor 1L -> highColor
0L -> midColor 0L -> midColor
@ -176,48 +174,49 @@ class CustomWatchface : BaseWatchFace() {
ViewMap.fromId(view.id)?.let { id -> ViewMap.fromId(view.id)?.let { id ->
if (json.has(id.key)) { if (json.has(id.key)) {
val viewJson = json.getJSONObject(id.key) val viewJson = json.getJSONObject(id.key)
val wrapContent = LayoutParams.WRAP_CONTENT val width = (viewJson.optInt(WIDTH.key) * zoomFactor).toInt()
val width = if (viewJson.has(WIDTH.key)) (viewJson.getInt(WIDTH.key) * zoomFactor).toInt() else wrapContent val height = (viewJson.optInt(HEIGHT.key) * zoomFactor).toInt()
val height = if (viewJson.has(HEIGHT.key)) (viewJson.getInt(HEIGHT.key) * zoomFactor).toInt() else wrapContent
val params = FrameLayout.LayoutParams(width, height) val params = FrameLayout.LayoutParams(width, height)
params.topMargin = if (viewJson.has(TOPMARGIN.key)) (viewJson.getInt(TOPMARGIN.key) * zoomFactor).toInt() else 0 params.topMargin = (viewJson.optInt(TOPMARGIN.key) * zoomFactor).toInt()
params.leftMargin = if (viewJson.has(LEFTMARGIN.key)) (viewJson.getInt(LEFTMARGIN.key) * zoomFactor).toInt() else 0 params.leftMargin = (viewJson.optInt(LEFTMARGIN.key) * zoomFactor).toInt()
view.layoutParams = params view.layoutParams = params
view.visibility = if (viewJson.has(VISIBILITY.key)) setVisibility(viewJson.getString(VISIBILITY.key), id.visibility(sp)) else View.GONE view.visibility = setVisibility(viewJson.optString(VISIBILITY.key, JsonKeyValues.GONE.key), id.visibility(sp))
if (view is TextView) { when (view) {
view.rotation = if (viewJson.has(ROTATION.key)) viewJson.getInt(ROTATION.key).toFloat() else 0F is TextView -> {
view.setTextSize(TypedValue.COMPLEX_UNIT_PX, ((if (viewJson.has(TEXTSIZE.key)) viewJson.getInt(TEXTSIZE.key) else 22) * zoomFactor).toFloat()) view.rotation = viewJson.optInt(ROTATION.key).toFloat()
view.gravity = GravityMap.gravity(if (viewJson.has(GRAVITY.key)) viewJson.getString(GRAVITY.key) else GravityMap.CENTER.key) view.setTextSize(TypedValue.COMPLEX_UNIT_PX, (viewJson.optInt(TEXTSIZE.key, 22) * zoomFactor).toFloat())
view.setTypeface( view.gravity = GravityMap.gravity(viewJson.optString(GRAVITY.key, GravityMap.CENTER.key))
FontMap.font(if (viewJson.has(FONT.key)) viewJson.getString(FONT.key) else FontMap.DEFAULT.key), view.setTypeface(
StyleMap.style(if (viewJson.has(FONTSTYLE.key)) viewJson.getString(FONTSTYLE.key) else StyleMap.NORMAL.key) FontMap.font(viewJson.optString(FONT.key, FontMap.DEFAULT.key)),
) StyleMap.style(viewJson.optString(FONTSTYLE.key, StyleMap.NORMAL.key))
if (viewJson.has(FONTCOLOR.key)) )
view.setTextColor(getColor(viewJson.getString(FONTCOLOR.key))) view.setTextColor(getColor(viewJson.optString(FONTCOLOR.key)))
if (viewJson.has(TEXTVALUE.key)) if (viewJson.has(TEXTVALUE.key))
view.text = viewJson.getString(TEXTVALUE.key) view.text = viewJson.getString(TEXTVALUE.key)
}
if (view is ImageView) {
view.clearColorFilter()
val drawable = if (id.key == CwfDrawableFileMap.BACKGROUND.key)
backGroundDrawable
else
drawableDataMap[CwfDrawableFileMap.fromKey(id.key)]?.toDrawable(resources)
drawable?.let {
if (viewJson.has(COLOR.key))
it.colorFilter = changeDrawableColor(getColor(viewJson.getString(COLOR.key)))
else
it.clearColorFilter()
view.setImageDrawable(it)
} ?: apply {
view.setImageDrawable(CwfDrawableFileMap.fromKey(id.key).icon?.let { context.getDrawable(it) })
if (viewJson.has(COLOR.key))
view.setColorFilter(getColor(viewJson.getString(COLOR.key)))
else
view.clearColorFilter()
} }
is ImageView -> {
view.clearColorFilter()
val drawable = if (id.key == CwfDrawableFileMap.BACKGROUND.key)
backGroundDrawable
else
drawableDataMap[CwfDrawableFileMap.fromKey(id.key)]?.toDrawable(resources)
drawable?.let {
if (viewJson.has(COLOR.key))
it.colorFilter = changeDrawableColor(getColor(viewJson.getString(COLOR.key)))
else
it.clearColorFilter()
view.setImageDrawable(it)
} ?: apply {
view.setImageDrawable(CwfDrawableFileMap.fromKey(id.key).icon?.let { context.getDrawable(it) })
if (viewJson.has(COLOR.key))
view.setColorFilter(getColor(viewJson.getString(COLOR.key)))
else
view.clearColorFilter()
}
}
} }
} else { } else {
view.visibility = View.GONE view.visibility = View.GONE
@ -227,9 +226,7 @@ class CustomWatchface : BaseWatchFace() {
} }
} }
} }
binding.background.visibility = View.VISIBLE manageSpecificViews()
updateSecondVisibility()
setSecond() // Update second visibility for time view
} catch (e: Exception) { } catch (e: Exception) {
aapsLogger.debug(LTag.WEAR, "Crash during Custom watch load") aapsLogger.debug(LTag.WEAR, "Crash during Custom watch load")
persistence.store(defaultWatchface(), false) // relaod correct values to avoid crash of watchface persistence.store(defaultWatchface(), false) // relaod correct values to avoid crash of watchface
@ -238,8 +235,8 @@ class CustomWatchface : BaseWatchFace() {
} }
private fun updatePref(metadata: CwfMetadataMap) { private fun updatePref(metadata: CwfMetadataMap) {
val cwf_authorization = metadata[CwfMetadataKey.CWF_AUTHORIZATION]?.toBooleanStrictOrNull() val cwfAuthorization = metadata[CwfMetadataKey.CWF_AUTHORIZATION]?.toBooleanStrictOrNull()
cwf_authorization?.let { authorization -> cwfAuthorization?.let { authorization ->
if (authorization) { if (authorization) {
PrefMap.values().forEach { pref -> PrefMap.values().forEach { pref ->
metadata[CwfMetadataKey.fromKey(pref.key)]?.toBooleanStrictOrNull()?.let { sp.putBoolean(pref.prefKey, it) } metadata[CwfMetadataKey.fromKey(pref.key)]?.toBooleanStrictOrNull()?.let { sp.putBoolean(pref.prefKey, it) }
@ -306,8 +303,7 @@ class CustomWatchface : BaseWatchFace() {
val metadataMap = ZipWatchfaceFormat.loadMetadata(json) val metadataMap = ZipWatchfaceFormat.loadMetadata(json)
val drawableDataMap: CwfDrawableDataMap = mutableMapOf() val drawableDataMap: CwfDrawableDataMap = mutableMapOf()
getResourceByteArray(info.nightscout.shared.R.drawable.watchface_custom)?.let { getResourceByteArray(info.nightscout.shared.R.drawable.watchface_custom)?.let {
val drawableData = DrawableData(it, DrawableFormat.PNG) drawableDataMap[CwfDrawableFileMap.CUSTOM_WATCHFACE] = DrawableData(it, DrawableFormat.PNG)
drawableDataMap[CwfDrawableFileMap.CUSTOM_WATCHFACE] = drawableData
} }
return EventData.ActionSetCustomWatchface(CwfData(json.toString(4), metadataMap, drawableDataMap)) return EventData.ActionSetCustomWatchface(CwfData(json.toString(4), metadataMap, drawableDataMap))
} }
@ -369,12 +365,25 @@ class CustomWatchface : BaseWatchFace() {
return ColorMatrixColorFilter(colorMatrix) return ColorMatrixColorFilter(colorMatrix)
} }
private fun getColor(color: String): Int = private fun getColor(color: String, defaultColor: Int = Color.GRAY): Int =
if (color == JsonKeyValues.BGCOLOR.key) if (color == JsonKeyValues.BGCOLOR.key)
bgColor bgColor
else else
try { Color.parseColor(color) } catch (e: Exception) { Color.GRAY } try { Color.parseColor(color) } catch (e: Exception) { defaultColor }
private fun manageSpecificViews() {
//Background should fill all the watchface and must be visible
val params = FrameLayout.LayoutParams((TEMPLATE_RESOLUTION * zoomFactor).toInt(), (TEMPLATE_RESOLUTION * zoomFactor).toInt())
params.topMargin = 0
params.leftMargin = 0
binding.background.layoutParams = params
binding.background.visibility = View.VISIBLE
// Update second visibility
updateSecondVisibility()
setSecond() // Update second visibility for time view
// Update timePeriod visibility
binding.timePeriod.visibility = (binding.timePeriod.visibility == View.VISIBLE && android.text.format.DateFormat.is24HourFormat(this).not()).toVisibility()
}
private enum class ViewMap(val key: String, @IdRes val id: Int, @StringRes val pref: Int?) { private enum class ViewMap(val key: String, @IdRes val id: Int, @StringRes val pref: Int?) {
BACKGROUND(ViewKeys.BACKGROUND.key, R.id.background, null), BACKGROUND(ViewKeys.BACKGROUND.key, R.id.background, null),