chore: Begone system clock related function

This commit is contained in:
Pun Butrach
2025-11-16 19:43:10 +07:00
parent 2eb571a439
commit b62191d9fc
18 changed files with 0 additions and 1526 deletions
@@ -1,6 +0,0 @@
package com.android.systemui.plugins.clocks
data class AlarmData(
val nextAlarmMillis: Long?,
val descriptionId: String?,
)
@@ -1,49 +0,0 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.android.systemui.plugins.clocks
import com.android.systemui.plugins.annotations.ProtectedInterface
/** Methods which trigger various clock animations */
@ProtectedInterface
interface ClockAnimations {
/** Runs an enter animation (if any) */
fun enter()
/** Sets how far into AOD the device currently is. */
fun doze(fraction: Float)
/** Sets how far into the folding animation the device is. */
fun fold(fraction: Float)
/** Runs the battery animation (if any). */
fun charge()
/** Runs when the clock's position changed during the move animation. */
fun onPositionAnimated(anim: ClockPositionAnimationArgs)
/**
* Runs when swiping clock picker, swipingFraction: 1.0 -> clock is scaled up in the preview,
* 0.0 -> clock is scaled down in the shade; previewRatio is previewSize / screenSize
*/
fun onPickerCarouselSwiping(swipingFraction: Float)
/** Runs when an animation when the view is tapped on the lockscreen */
fun onFidgetTap(x: Float, y: Float)
/** Update reactive axes for this clock */
fun onFontAxesChanged(style: ClockAxisStyle)
}
data class ClockPositionAnimationArgs(val fromLeft: Int, val direction: Int, val fraction: Float)
@@ -1,61 +0,0 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.android.systemui.plugins.clocks
/**
* Exposes the rendering capabilities of this clock to SystemUI so that it can be hosted and render
* correctly in SystemUI's process. Ideally all clocks could be rendered identically, but in
* practice we different clocks require different behavior from SystemUI.
*/
data class ClockConfig(
val id: ClockId,
/** Localized name of the clock */
val name: String,
/** Localized accessibility description for the clock */
val description: String,
/** Transition to AOD should move smartspace like large clock instead of small clock */
val useAlternateSmartspaceAODTransition: Boolean = false,
/** True if the clock is large frame clock, which will use weather in compose. */
val useCustomClockScene: Boolean = false,
)
/** Render configuration options for a specific clock face. */
data class ClockFaceConfig(
/** Expected interval between calls to onTimeTick. Can always reduce to PER_MINUTE in AOD. */
val tickRate: ClockTickRate = ClockTickRate.PER_MINUTE,
/** Call to check whether the clock consumes weather data */
val hasCustomWeatherDataDisplay: Boolean = false,
/**
* Whether this clock has a custom position update animation. If true, the keyguard will call
* `onPositionAnimated` to notify the clock of a position update animation. If false, a default
* animation will be used (e.g. a simple translation).
*/
val hasCustomPositionUpdatedAnimation: Boolean = false,
/** True if the clock is large frame clock, which will use weatherBlueprint in compose. */
val useCustomClockScene: Boolean = false,
)
/** Tick rates for clocks */
enum class ClockTickRate(val value: Int) {
PER_MINUTE(2), // Update the clock once per minute.
PER_SECOND(1), // Update the clock once per second.
PER_FRAME(0), // Update the clock every second.
}
@@ -1,48 +0,0 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.android.systemui.plugins.clocks
import com.android.systemui.plugins.annotations.ProtectedInterface
import com.android.systemui.plugins.annotations.SimpleProperty
import java.io.PrintWriter
/** Interface for controlling an active clock */
@ProtectedInterface
interface ClockController {
@get:SimpleProperty
/** A small version of the clock, appropriate for smaller viewports */
val smallClock: ClockFaceController
@get:SimpleProperty
/** A large version of the clock, appropriate when a bigger viewport is available */
val largeClock: ClockFaceController
@get:SimpleProperty
/** Determines the way the hosting app should behave when rendering either clock face */
val config: ClockConfig
@get:SimpleProperty
/** Events that this clock may need to respond to */
val events: ClockEvents
@get:SimpleProperty
/** Listener group that will receive events from the clock */
val eventListeners: ClockEventListeners
/** Initializes various rendering parameters. If never called, provides reasonable defaults. */
fun initialize(isDarkTheme: Boolean, dozeFraction: Float, foldFraction: Float)
/** Optional method for dumping debug information */
fun dump(pw: PrintWriter)
}
@@ -1,83 +0,0 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.android.systemui.plugins.clocks
import android.content.Context
import android.icu.util.TimeZone
import android.text.format.DateFormat
import com.android.systemui.plugins.annotations.ProtectedInterface
import com.android.systemui.plugins.annotations.ProtectedReturn
import java.util.Locale
/** Denotes format kind that should be used when rendering the clock */
enum class TimeFormatKind {
HALF_DAY,
FULL_DAY;
companion object {
fun getFromContext(context: Context): TimeFormatKind {
return lookup(DateFormat.is24HourFormat(context))
}
fun getFromContext(context: Context, userId: Int): TimeFormatKind {
return lookup(DateFormat.is24HourFormat(context, userId))
}
fun lookup(is24Hr: Boolean): TimeFormatKind {
return if (is24Hr) FULL_DAY else HALF_DAY
}
}
}
/** Events that should call when various rendering parameters change */
@ProtectedInterface
interface ClockEvents {
@get:ProtectedReturn("return false;")
/** Set to enable or disable swipe interaction */
var isReactiveTouchInteractionEnabled: Boolean // TODO(b/364664388): Remove/Rename
/** Call whenever timezone changes */
fun onTimeZoneChanged(timeZone: TimeZone)
/** Call whenever the text time format kind changes */
fun onTimeFormatChanged(formatKind: TimeFormatKind)
/** Call whenever the locale changes */
fun onLocaleChanged(locale: Locale)
/** Call whenever the weather data should update */
fun onWeatherDataChanged(data: WeatherData)
/** Call with alarm information */
fun onAlarmDataChanged(data: AlarmData)
/** Call with zen/dnd information */
fun onZenDataChanged(data: ZenData)
}
class ClockEventListeners {
private val listeners = mutableListOf<ClockEventListener>()
fun attach(listener: ClockEventListener) = listeners.add(listener)
fun detach(listener: ClockEventListener) = listeners.remove(listener)
fun fire(func: ClockEventListener.() -> Unit) = listeners.forEach { it.func() }
}
interface ClockEventListener {
fun onBoundsChanged(bounds: VRectF)
fun onChangeComplete()
}
@@ -1,62 +0,0 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.android.systemui.plugins.clocks
import android.view.View
import com.android.systemui.plugins.annotations.ProtectedInterface
import com.android.systemui.plugins.annotations.SimpleProperty
/** Interface for a specific clock face version rendered by the clock */
@ProtectedInterface
interface ClockFaceController {
@get:SimpleProperty
@Deprecated("Prefer use of layout")
/** View that renders the clock face */
val view: View
@get:SimpleProperty
/** Layout specification for this clock */
val layout: ClockFaceLayout
@get:SimpleProperty
/** Determines the way the hosting app should behave when rendering this clock face */
val config: ClockFaceConfig
@get:SimpleProperty
/** Current theme information the clock is using */
var theme: ThemeConfig
@get:SimpleProperty
/** Events specific to this clock face */
val events: ClockFaceEvents
@get:SimpleProperty
/** Triggers for various animations */
val animations: ClockAnimations
companion object {
fun ClockFaceController.updateTheme(mutateTheme: (ThemeConfig) -> ThemeConfig) {
val theme = mutateTheme(this.theme)
// Themes with null seeds are always considered to have changed since we don't know
// at this point if the context's color theme has been updated externally.
// TODO(b/364680879): Capture context's color and compare when applicable.
val hasChanged = this.theme != theme || theme.seedColor == null
if (!hasChanged) return
events.onThemeChanged(theme)
this.theme = theme
}
}
}
@@ -1,91 +0,0 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.android.systemui.plugins.clocks
import android.content.Context
import android.graphics.Color
import android.graphics.Rect
import com.android.systemui.monet.ColorScheme
import com.android.systemui.plugins.annotations.ProtectedInterface
import com.android.systemui.shared.Flags.ambientAod
/** Events that have specific data about the related face */
@ProtectedInterface
interface ClockFaceEvents {
/** Call every tick to update the rendered time */
fun onTimeTick()
/**
* Call whenever the theme or seedColor is updated
*
* Theme can be specific to the clock face.
* - isDarkTheme -> clock should be light
* - !isDarkTheme -> clock should be dark
*/
fun onThemeChanged(theme: ThemeConfig)
/**
* Call whenever font settings change. Pass in a target font size in pixels. The specific clock
* design is allowed to ignore this target size on a case-by-case basis.
*/
fun onFontSettingChanged(fontSizePx: Float)
/**
* Target region information for the clock face. For small clock, this will match the bounds of
* the parent view mostly, but have a target height based on the height of the default clock.
* For large clocks, the parent view is the entire device size, but most clocks will want to
* render within the centered targetRect to avoid obstructing other elements. The specified
* targetRegion is relative to the parent view.
*/
@Deprecated("No longer necessary, pending removal")
fun onTargetRegionChanged(targetRegion: Rect?)
/** Called to notify the clock about its display. */
fun onSecondaryDisplayChanged(onSecondaryDisplay: Boolean)
}
/** Contains Theming information for the clock face */
data class ThemeConfig(
/** True if the clock should use dark theme (light text on dark background) */
val isDarkTheme: Boolean,
/**
* A clock specific seed color to use when theming, if any was specified by the user. A null
* value denotes that we should use the seed color for the current system theme.
*/
val seedColor: Int?,
) {
fun getDefaultColor(context: Context): Int {
return when {
seedColor != null -> seedColor!!
isDarkTheme -> context.resources.getColor(android.R.color.system_accent1_100)
else -> context.resources.getColor(android.R.color.system_accent2_600)
}
}
fun getAodColor(context: Context): Int {
return if (!ambientAod()) {
Color.WHITE
} else {
seedColor?.let {
val colorScheme =
ColorScheme(
it,
false, // darkTheme is not used for palette generation
)
colorScheme.accent1.s100
} ?: context.resources.getColor(android.R.color.system_accent1_100)
}
}
}
@@ -1,60 +0,0 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.android.systemui.plugins.clocks
import android.view.View
import androidx.constraintlayout.widget.ConstraintSet
import com.android.systemui.plugins.annotations.GeneratedImport
import com.android.systemui.plugins.annotations.ProtectedInterface
import com.android.systemui.plugins.annotations.ProtectedReturn
/** Specifies layout information for the clock face */
@ProtectedInterface
@GeneratedImport("java.util.ArrayList")
@GeneratedImport("android.view.View")
interface ClockFaceLayout {
@get:ProtectedReturn("return new ArrayList<View>();")
/** All clock views to add to the root constraint layout before applying constraints. */
val views: List<View>
@ProtectedReturn("return constraints;")
/** Custom constraints to apply to Lockscreen ConstraintLayout. */
fun applyConstraints(constraints: ConstraintSet): ConstraintSet
@ProtectedReturn("return constraints;")
/** Custom constraints to apply to the external display presentation ConstraintLayout. */
fun applyExternalDisplayPresentationConstraints(constraints: ConstraintSet): ConstraintSet
@ProtectedReturn("return constraints;")
/** Custom constraints to apply to preview ConstraintLayout. */
fun applyPreviewConstraints(
clockPreviewConfig: ClockPreviewConfig,
constraints: ConstraintSet,
): ConstraintSet
/** Apply specified AOD BurnIn parameters to this layout */
fun applyAodBurnIn(aodBurnInModel: AodClockBurnInModel)
}
/** Data class to contain AOD BurnIn information for correct aod rendering */
data class AodClockBurnInModel(
/** Scale that the clock should render at to mitigate burnin */
val scale: Float,
/** X-Translation for the clock to mitigate burnin */
val translationX: Float,
/** Y-Translation for the clock to mitigate burnin */
val translationY: Float,
)
@@ -1,30 +0,0 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.android.systemui.plugins.clocks
import com.android.systemui.log.core.MessageBuffer
/** MessageBuffers for clocks that want to log information to SystemUI dumps */
data class ClockMessageBuffers(
/** Message buffer for general infrastructure */
val infraMessageBuffer: MessageBuffer,
/** Message buffer for small clock rendering */
val smallClockMessageBuffer: MessageBuffer,
/** Message buffer for large clock rendering */
val largeClockMessageBuffer: MessageBuffer,
) {
constructor(buffer: MessageBuffer) : this(buffer, buffer, buffer) {}
}
@@ -1,126 +0,0 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.android.systemui.plugins.clocks
import android.graphics.drawable.Drawable
data class ClockPickerConfig
@JvmOverloads
constructor(
val id: String,
/** Localized name of the clock */
val name: String,
/** Localized accessibility description for the clock */
val description: String,
/* Static & lightweight thumbnail version of the clock */
val thumbnail: Drawable,
/** True if the clock will react to tone changes in the seed color */
val isReactiveToTone: Boolean = true,
/** Font axes that can be modified on this clock */
val axes: List<ClockFontAxis> = listOf(),
/** Presets for this clock. Null indicates the preset list should be disabled. */
val presetConfig: AxisPresetConfig? = null,
)
data class AxisPresetConfig(
/** Groups of Presets. Each group can be used together in a single control. */
val groups: List<Group>,
/** Preset item currently being used, null when the current style is not a preset */
val current: IndexedStyle? = null,
) {
/** The selected clock axis style, and its indices */
data class IndexedStyle(
/** Index of the group that this clock axis style appears in */
val groupIndex: Int,
/** Index of the preset within the group */
val presetIndex: Int,
/** Reference to the style in question */
val style: ClockAxisStyle,
)
/** A group of preset styles */
data class Group(
/* List of preset styles in this group */
val presets: List<ClockAxisStyle>,
/* Icon to use when this preset-group is active */
val icon: Drawable,
)
fun findStyle(style: ClockAxisStyle): IndexedStyle? {
groups.forEachIndexed { groupIndex, group ->
group.presets.forEachIndexed { presetIndex, preset ->
if (preset == style) {
return@findStyle IndexedStyle(
groupIndex = groupIndex,
presetIndex = presetIndex,
style = preset,
)
}
}
}
return null
}
}
/** Represents an Axis that can be modified */
data class ClockFontAxis(
/** Axis key, not user renderable */
val key: String,
/** Intended mode of user interaction */
val type: AxisType,
/** Maximum value the axis supports */
val maxValue: Float,
/** Minimum value the axis supports */
val minValue: Float,
/** Current value the axis is set to */
val currentValue: Float,
/** User-renderable name of the axis */
val name: String,
/** Description of the axis */
val description: String,
) {
companion object {
fun List<ClockFontAxis>.merge(axisStyle: ClockAxisStyle): List<ClockFontAxis> {
return this.map { axis ->
axisStyle.get(axis.key)?.let { axis.copy(currentValue = it) } ?: axis
}
.toList()
}
}
}
/** Axis user interaction modes */
enum class AxisType {
/** Continuous range between minValue & maxValue. */
Float,
/** Only minValue & maxValue are valid. No intermediate values between them are allowed. */
Boolean,
}
@@ -1,36 +0,0 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.plugins.clocks
data class ClockPreviewConfig(
val isShadeLayoutWide: Boolean,
val isSceneContainerFlagEnabled: Boolean,
val statusBarHeight: Int,
val splitShadeTopMargin: Int,
val clockTopMargin: Int,
val statusViewMarginHorizontal: Int,
val lockViewId: Int? = null,
val udfpsTop: Float? = null,
) {
fun getSmallClockTopPadding(statusBarHeight: Int = this.statusBarHeight): Int {
return if (isShadeLayoutWide) {
splitShadeTopMargin - if (isSceneContainerFlagEnabled) statusBarHeight else 0
} else {
clockTopMargin + if (!isSceneContainerFlagEnabled) statusBarHeight else 0
}
}
}
@@ -1,74 +0,0 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.android.systemui.plugins.clocks
import android.content.Context
import com.android.systemui.plugins.Plugin
import com.android.systemui.plugins.annotations.GeneratedImport
import com.android.systemui.plugins.annotations.ProtectedInterface
import com.android.systemui.plugins.annotations.ProtectedReturn
import com.android.systemui.plugins.annotations.ProvidesInterface
/** A Plugin which exposes the ClockProvider interface */
@ProtectedInterface
@ProvidesInterface(action = ClockProviderPlugin.ACTION, version = ClockProviderPlugin.VERSION)
interface ClockProviderPlugin : Plugin, ClockProvider {
companion object {
const val ACTION = "com.android.systemui.action.PLUGIN_CLOCK_PROVIDER"
const val VERSION = 1
}
}
/** Interface for building clocks and providing information about those clocks */
@ProtectedInterface
@GeneratedImport("java.util.List")
@GeneratedImport("java.util.ArrayList")
interface ClockProvider {
/** Initializes the clock provider with debug log buffers */
fun initialize(buffers: ClockMessageBuffers?)
@ProtectedReturn("return new ArrayList<ClockMetadata>();")
/** Returns metadata for all clocks this provider knows about */
fun getClocks(): List<ClockMetadata>
@ProtectedReturn("return null;")
/** Initializes and returns the target clock design */
fun createClock(ctx: Context, settings: ClockSettings): ClockController?
@ProtectedReturn("return new ClockPickerConfig(\"\", \"\", \"\", null);")
/** Settings configuration parameters for the clock */
fun getClockPickerConfig(settings: ClockSettings): ClockPickerConfig
}
/** Identifies a clock design */
typealias ClockId = String
/** Some metadata about a clock design */
data class ClockMetadata(
/** Id for the clock design. */
val clockId: ClockId,
/**
* true if this clock is deprecated and should not be used. The ID may still show up in certain
* locations to help migrations, but it will not be selectable by new users.
*/
val isDeprecated: Boolean = false,
/**
* Optional mapping of a legacy clock to a new id. This will map users that already are using
* `clockId` to the `replacementTarget` instead. The provider should still support the old id
* w/o crashing, but can consider it deprecated and the id reserved.
*/
val replacementTarget: ClockId? = null,
)
@@ -1,152 +0,0 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.android.systemui.plugins.clocks
import org.json.JSONArray
import org.json.JSONObject
/** Structure for keeping clock-specific settings */
data class ClockSettings(
val clockId: ClockId? = null,
val seedColor: Int? = null,
val axes: ClockAxisStyle = ClockAxisStyle(),
) {
// Exclude metadata from equality checks
var metadata: JSONObject = JSONObject()
companion object {
private val KEY_CLOCK_ID = "clockId"
private val KEY_SEED_COLOR = "seedColor"
private val KEY_METADATA = "metadata"
private val KEY_AXIS_LIST = "axes"
fun toJson(setting: ClockSettings): JSONObject {
return JSONObject().apply {
put(KEY_CLOCK_ID, setting.clockId)
put(KEY_SEED_COLOR, setting.seedColor)
put(KEY_METADATA, setting.metadata)
put(KEY_AXIS_LIST, ClockAxisStyle.toJson(setting.axes))
}
}
fun fromJson(json: JSONObject): ClockSettings {
val clockId = if (!json.isNull(KEY_CLOCK_ID)) json.getString(KEY_CLOCK_ID) else null
val seedColor = if (!json.isNull(KEY_SEED_COLOR)) json.getInt(KEY_SEED_COLOR) else null
val axisList = json.optJSONArray(KEY_AXIS_LIST)?.let(ClockAxisStyle::fromJson)
return ClockSettings(clockId, seedColor, axisList ?: ClockAxisStyle()).apply {
metadata = json.optJSONObject(KEY_METADATA) ?: JSONObject()
}
}
}
}
class ClockAxisStyle {
private val settings: MutableMap<String, Float>
// Iterable would be implemented on ClockAxisStyle directly,
// but that doesn't appear to work with plugins/dynamic libs.
val items: Iterable<Map.Entry<String, Float>>
get() = settings.asIterable()
val isEmpty: Boolean
get() = settings.isEmpty()
constructor(initialize: ClockAxisStyle.() -> Unit = {}) {
settings = mutableMapOf()
this.initialize()
}
constructor(style: ClockAxisStyle) {
settings = style.settings.toMutableMap()
}
constructor(items: Map<String, Float>) {
settings = items.toMutableMap()
}
constructor(key: String, value: Float) {
settings = mutableMapOf(key to value)
}
constructor(items: List<ClockFontAxis>) {
settings = items.associate { it.key to it.currentValue }.toMutableMap()
}
fun copy(initialize: ClockAxisStyle.() -> Unit): ClockAxisStyle {
return ClockAxisStyle(this).apply { initialize() }
}
operator fun get(key: String): Float? = settings[key]
operator fun set(key: String, value: Float) = put(key, value)
fun put(key: String, value: Float) {
settings.put(key, value)
}
fun toFVar(): String {
val sb = StringBuilder()
for (axis in settings) {
if (sb.length > 0) sb.append(", ")
sb.append("'${axis.key}' ${axis.value.toInt()}")
}
return sb.toString()
}
fun copyWith(replacements: ClockAxisStyle): ClockAxisStyle {
val result = ClockAxisStyle(this)
for ((key, value) in replacements.settings) {
result[key] = value
}
return result
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is ClockAxisStyle) return false
return settings == other.settings
}
companion object {
private val KEY_AXIS_KEY = "key"
private val KEY_AXIS_VALUE = "value"
fun fromJson(jsonArray: JSONArray): ClockAxisStyle {
val result = ClockAxisStyle()
for (i in 0..jsonArray.length() - 1) {
val obj = jsonArray.getJSONObject(i)
if (obj == null) continue
result.put(
key = obj.getString(KEY_AXIS_KEY),
value = obj.getDouble(KEY_AXIS_VALUE).toFloat(),
)
}
return result
}
fun toJson(style: ClockAxisStyle): JSONArray {
return JSONArray().apply {
for ((key, value) in style.settings) {
put(
JSONObject().apply {
put(KEY_AXIS_KEY, key)
put(KEY_AXIS_VALUE, value)
}
)
}
}
}
}
}
@@ -1,49 +0,0 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.plugins.clocks
import android.view.View
/**
* Defines several view ids which are useful for sharing views between the host process and the
* client. Normally these would be defined in ids.xml, but android_library is incapable of being
* dynamically referenced by the plugin apks. This approach means the identifiers are no longer
* compile-time constants, but is preferable as it eliminates our need to look them up them by name.
*/
object ClockViewIds {
val LOCKSCREEN_CLOCK_VIEW_LARGE = View.generateViewId()
val LOCKSCREEN_CLOCK_VIEW_SMALL = View.generateViewId()
// View ids for different digit views
val HOUR_DIGIT_PAIR = View.generateViewId()
val MINUTE_DIGIT_PAIR = View.generateViewId()
val HOUR_FIRST_DIGIT = View.generateViewId()
val HOUR_SECOND_DIGIT = View.generateViewId()
val MINUTE_FIRST_DIGIT = View.generateViewId()
val MINUTE_SECOND_DIGIT = View.generateViewId()
val TIME_FULL_FORMAT = View.generateViewId()
val DATE_FORMAT = View.generateViewId()
// View ids for elements in large weather clock
// TODO(b/364680879): Move these to the weather clock apk w/ WeatherClockSection
val WEATHER_CLOCK_TIME = View.generateViewId()
val WEATHER_CLOCK_DATE = View.generateViewId()
val WEATHER_CLOCK_ICON = View.generateViewId()
val WEATHER_CLOCK_TEMP = View.generateViewId()
val WEATHER_CLOCK_ALARM_DND = View.generateViewId()
val WEATHER_CLOCK_DATE_BARRIER_BOTTOM = View.generateViewId()
}
@@ -1,228 +0,0 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.plugins.clocks
import android.graphics.Point
import android.graphics.PointF
import android.graphics.Rect
import android.graphics.RectF
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.sqrt
private val X_MASK: ULong = 0xFFFFFFFF00000000U
private val Y_MASK: ULong = 0x00000000FFFFFFFFU
private fun unpackX(data: ULong): Int = ((data and X_MASK) shr 32).toInt()
private fun unpackY(data: ULong): Int = ((data and Y_MASK) shr 0).toInt()
private fun pack(x: Int, y: Int): ULong {
return ((x.toULong() shl 32) and X_MASK) or ((y.toULong() shl 0) and Y_MASK)
}
@JvmInline
value class VPointF(val data: ULong) {
val x: Float
get() = Float.fromBits(unpackX(data))
val y: Float
get() = Float.fromBits(unpackY(data))
constructor(pt: PointF) : this(pt.x, pt.y)
constructor(x: Int, y: Int) : this(x.toFloat(), y.toFloat())
constructor(x: Int, y: Float) : this(x.toFloat(), y)
constructor(x: Float, y: Int) : this(x, y.toFloat())
constructor(x: Float, y: Float) : this(pack(x.toBits(), y.toBits()))
fun toPointF() = PointF(x, y)
fun toLong(): Long = data.toLong()
fun lengthSq(): Float = x * x + y * y
fun length(): Float = sqrt(lengthSq())
fun abs() = VPointF(abs(x), abs(y))
fun dot(pt: VPointF): Float = x * pt.x + y * pt.y
fun normalize(): VPointF {
val length = this.length()
return VPointF(x / length, y / length)
}
operator fun component1(): Float = x
operator fun component2(): Float = y
override fun toString() = "($x, $y)"
operator fun plus(pt: VPoint) = VPointF(x + pt.x, y + pt.y)
operator fun plus(pt: VPointF) = VPointF(x + pt.x, y + pt.y)
operator fun plus(value: Int) = VPointF(x + value, y + value)
operator fun plus(value: Float) = VPointF(x + value, y + value)
operator fun minus(pt: VPoint) = VPointF(x - pt.x, y - pt.y)
operator fun minus(pt: VPointF) = VPointF(x - pt.x, y - pt.y)
operator fun minus(value: Int) = VPointF(x - value, y - value)
operator fun minus(value: Float) = VPointF(x - value, y - value)
operator fun times(pt: VPoint) = VPointF(x * pt.x, y * pt.y)
operator fun times(pt: VPointF) = VPointF(x * pt.x, y * pt.y)
operator fun times(value: Int) = VPointF(x * value, y * value)
operator fun times(value: Float) = VPointF(x * value, y * value)
operator fun div(pt: VPoint) = VPointF(x / pt.x, y / pt.y)
operator fun div(pt: VPointF) = VPointF(x / pt.x, y / pt.y)
operator fun div(value: Int) = VPointF(x / value, y / value)
operator fun div(value: Float) = VPointF(x / value, y / value)
companion object {
val ZERO = VPointF(0, 0)
fun fromLong(data: Long) = VPointF(data.toULong())
fun max(lhs: VPointF, rhs: VPointF) = VPointF(max(lhs.x, rhs.x), max(lhs.y, rhs.y))
fun min(lhs: VPointF, rhs: VPointF) = VPointF(min(lhs.x, rhs.x), min(lhs.y, rhs.y))
operator fun Float.plus(value: VPointF) = VPointF(this + value.x, this + value.y)
operator fun Int.minus(value: VPointF) = VPointF(this - value.x, this - value.y)
operator fun Float.minus(value: VPointF) = VPointF(this - value.x, this - value.y)
operator fun Int.times(value: VPointF) = VPointF(this * value.x, this * value.y)
operator fun Float.times(value: VPointF) = VPointF(this * value.x, this * value.y)
operator fun Int.div(value: VPointF) = VPointF(this / value.x, this / value.y)
operator fun Float.div(value: VPointF) = VPointF(this / value.x, this / value.y)
val RectF.center: VPointF
get() = VPointF(centerX(), centerY())
val RectF.size: VPointF
get() = VPointF(width(), height())
}
}
@JvmInline
value class VPoint(val data: ULong) {
val x: Int
get() = unpackX(data)
val y: Int
get() = unpackY(data)
constructor(x: Int, y: Int) : this(pack(x, y))
fun toPoint() = Point(x, y)
fun toLong(): Long = data.toLong()
fun abs() = VPoint(abs(x), abs(y))
operator fun component1(): Int = x
operator fun component2(): Int = y
override fun toString() = "($x, $y)"
operator fun plus(pt: VPoint) = VPoint(x + pt.x, y + pt.y)
operator fun plus(pt: VPointF) = VPointF(x + pt.x, y + pt.y)
operator fun plus(value: Int) = VPoint(x + value, y + value)
operator fun plus(value: Float) = VPointF(x + value, y + value)
operator fun minus(pt: VPoint) = VPoint(x - pt.x, y - pt.y)
operator fun minus(pt: VPointF) = VPointF(x - pt.x, y - pt.y)
operator fun minus(value: Int) = VPoint(x - value, y - value)
operator fun minus(value: Float) = VPointF(x - value, y - value)
operator fun times(pt: VPoint) = VPoint(x * pt.x, y * pt.y)
operator fun times(pt: VPointF) = VPointF(x * pt.x, y * pt.y)
operator fun times(value: Int) = VPoint(x * value, y * value)
operator fun times(value: Float) = VPointF(x * value, y * value)
operator fun div(pt: VPoint) = VPoint(x / pt.x, y / pt.y)
operator fun div(pt: VPointF) = VPointF(x / pt.x, y / pt.y)
operator fun div(value: Int) = VPoint(x / value, y / value)
operator fun div(value: Float) = VPointF(x / value, y / value)
companion object {
val ZERO = VPoint(0, 0)
fun fromLong(data: Long) = VPoint(data.toULong())
fun max(lhs: VPoint, rhs: VPoint) = VPoint(max(lhs.x, rhs.x), max(lhs.y, rhs.y))
fun min(lhs: VPoint, rhs: VPoint) = VPoint(min(lhs.x, rhs.x), min(lhs.y, rhs.y))
operator fun Int.plus(value: VPoint) = VPoint(this + value.x, this + value.y)
operator fun Float.plus(value: VPoint) = VPointF(this + value.x, this + value.y)
operator fun Int.minus(value: VPoint) = VPoint(this - value.x, this - value.y)
operator fun Float.minus(value: VPoint) = VPointF(this - value.x, this - value.y)
operator fun Int.times(value: VPoint) = VPoint(this * value.x, this * value.y)
operator fun Float.times(value: VPoint) = VPointF(this * value.x, this * value.y)
operator fun Int.div(value: VPoint) = VPoint(this / value.x, this / value.y)
operator fun Float.div(value: VPoint) = VPointF(this / value.x, this / value.y)
val Rect.center: VPoint
get() = VPoint(centerX(), centerY())
val Rect.size: VPoint
get() = VPoint(width(), height())
}
}
@@ -1,200 +0,0 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.plugins.clocks
import android.graphics.Rect
import android.graphics.RectF
import android.util.Half
private val LEFT_MASK: ULong = 0xFFFF000000000000U
private val TOP_MASK: ULong = 0x0000FFFF00000000U
private val RIGHT_MASK: ULong = 0x00000000FFFF0000U
private val BOTTOM_MASK: ULong = 0x000000000000FFFFU
private fun unpackLeft(data: ULong): Short = ((data and LEFT_MASK) shr 48).toShort()
private fun unpackTop(data: ULong): Short = ((data and TOP_MASK) shr 32).toShort()
private fun unpackRight(data: ULong): Short = ((data and RIGHT_MASK) shr 16).toShort()
private fun unpackBottom(data: ULong): Short = ((data and BOTTOM_MASK) shr 0).toShort()
private fun pack(left: Short, top: Short, right: Short, bottom: Short): ULong {
return ((left.toULong() shl 48) and LEFT_MASK) or
((top.toULong() shl 32) and TOP_MASK) or
((right.toULong() shl 16) and RIGHT_MASK) or
((bottom.toULong() shl 0) and BOTTOM_MASK)
}
@JvmInline
value class VRectF(val data: ULong) {
val left: Float
get() = fromBits(unpackLeft(data))
val top: Float
get() = fromBits(unpackTop(data))
val right: Float
get() = fromBits(unpackRight(data))
val bottom: Float
get() = fromBits(unpackBottom(data))
val width: Float
get() = right - left
val height: Float
get() = bottom - top
constructor(rect: RectF) : this(rect.left, rect.top, rect.right, rect.bottom)
constructor(
rect: Rect
) : this(
left = rect.left.toFloat(),
top = rect.top.toFloat(),
right = rect.right.toFloat(),
bottom = rect.bottom.toFloat(),
)
constructor(
left: Float,
top: Float,
right: Float,
bottom: Float,
) : this(pack(toBits(left), toBits(top), toBits(right), toBits(bottom)))
val center: VPointF
get() = VPointF(left, top) + size / 2f
val size: VPointF
get() = VPointF(width, height)
fun toRectF(): RectF = RectF(left, top, right, bottom)
fun toLong(): Long = data.toLong()
override fun toString() = "($left, $top) -> ($right, $bottom)"
companion object {
private fun toBits(value: Float): Short = Half.halfToShortBits(Half.toHalf(value))
private fun fromBits(value: Short): Float = Half.toFloat(Half.intBitsToHalf(value.toInt()))
fun fromLong(data: Long) = VRectF(data.toULong())
fun fromCenter(center: VPointF, size: VPointF): VRectF {
return VRectF(
center.x - size.x / 2,
center.y - size.y / 2,
center.x + size.x / 2,
center.y + size.y / 2,
)
}
fun fromTopLeft(pos: VPointF, size: VPointF): VRectF {
return VRectF(pos.x, pos.y, pos.x + size.x, pos.y + size.y)
}
val ZERO = VRectF(0f, 0f, 0f, 0f)
}
}
@JvmInline
value class VRect(val data: ULong) {
val left: Int
get() = unpackLeft(data).toInt()
val top: Int
get() = unpackTop(data).toInt()
val right: Int
get() = unpackRight(data).toInt()
val bottom: Int
get() = unpackBottom(data).toInt()
val width: Int
get() = right - left
val height: Int
get() = bottom - top
constructor(
rect: Rect
) : this(
left = rect.left.toShort(),
top = rect.top.toShort(),
right = rect.right.toShort(),
bottom = rect.bottom.toShort(),
)
constructor(
left: Int,
top: Int,
right: Int,
bottom: Int,
) : this(
left = left.toShort(),
top = top.toShort(),
right = right.toShort(),
bottom = bottom.toShort(),
)
constructor(
left: Short,
top: Short,
right: Short,
bottom: Short,
) : this(pack(left, top, right, bottom))
val center: VPoint
get() = VPoint(left, top) + size / 2
val size: VPoint
get() = VPoint(width, height)
fun toRect(): Rect = Rect(left, top, right, bottom)
fun toLong(): Long = data.toLong()
override fun toString() = "($left, $top) -> ($right, $bottom)"
companion object {
val ZERO = VRect(0, 0, 0, 0)
fun fromLong(data: Long) = VRect(data.toULong())
fun fromCenter(center: VPoint, size: VPoint): VRect {
return VRect(
(center.x - size.x / 2).toShort(),
(center.y - size.y / 2).toShort(),
(center.x + size.x / 2).toShort(),
(center.y + size.y / 2).toShort(),
)
}
fun fromTopLeft(pos: VPoint, size: VPoint): VRect {
return VRect(
pos.x.toShort(),
pos.y.toShort(),
(pos.x + size.x).toShort(),
(pos.y + size.y).toShort(),
)
}
}
}
@@ -1,149 +0,0 @@
package com.android.systemui.plugins.clocks
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.annotation.VisibleForTesting
import androidx.core.text.util.LocalePreferences
typealias WeatherTouchAction = (View) -> Unit
data class WeatherData(
val description: String,
val state: WeatherStateIcon,
val useCelsius: Boolean,
val temperature: Int,
val touchAction: WeatherTouchAction? = null,
) {
companion object {
const val DEBUG = true
private const val TAG = "WeatherData"
@VisibleForTesting const val DESCRIPTION_KEY = "description"
@VisibleForTesting const val STATE_KEY = "state"
@VisibleForTesting const val USE_CELSIUS_KEY = "use_celsius"
@VisibleForTesting const val TEMPERATURE_KEY = "temperature"
private const val INVALID_WEATHER_ICON_STATE = -1
@JvmStatic
@JvmOverloads
fun fromBundle(extras: Bundle, touchAction: WeatherTouchAction? = null): WeatherData? {
val description = extras.getString(DESCRIPTION_KEY)
val state =
WeatherStateIcon.fromInt(extras.getInt(STATE_KEY, INVALID_WEATHER_ICON_STATE))
val temperature = readIntFromBundle(extras, TEMPERATURE_KEY)
if (
description == null ||
state == null ||
!extras.containsKey(USE_CELSIUS_KEY) ||
temperature == null
) {
if (DEBUG) {
Log.w(TAG, "Weather data did not parse from $extras")
}
return null
} else {
val result =
WeatherData(
description = description,
state = state,
useCelsius = extras.getBoolean(USE_CELSIUS_KEY),
temperature = temperature,
touchAction = touchAction,
)
if (DEBUG) {
Log.i(TAG, "Weather data parsed $result from $extras")
}
return result
}
}
private fun readIntFromBundle(extras: Bundle, key: String): Int? {
try {
return extras.getString(key)?.toInt()
} catch (e: Exception) {
return null
}
}
fun getPlaceholderWeatherData(): WeatherData {
return getPlaceholderWeatherData(
LocalePreferences.getTemperatureUnit() == LocalePreferences.TemperatureUnit.CELSIUS
)
}
private const val DESCRIPTION_PLACEHODLER = ""
private const val TEMPERATURE_FAHRENHEIT_PLACEHOLDER = 58
private const val TEMPERATURE_CELSIUS_PLACEHOLDER = 21
private val WEATHERICON_PLACEHOLDER = WeatherData.WeatherStateIcon.MOSTLY_SUNNY
fun getPlaceholderWeatherData(useCelsius: Boolean): WeatherData {
return WeatherData(
description = DESCRIPTION_PLACEHODLER,
state = WEATHERICON_PLACEHOLDER,
temperature =
if (useCelsius) TEMPERATURE_CELSIUS_PLACEHOLDER
else TEMPERATURE_FAHRENHEIT_PLACEHOLDER,
useCelsius = useCelsius,
)
}
}
// Values for WeatherStateIcon must stay in sync with go/g3-WeatherStateIcon
enum class WeatherStateIcon(val id: Int, val icon: String) {
UNKNOWN_ICON(0, ""),
// Clear, day & night.
SUNNY(1, "a"),
CLEAR_NIGHT(2, "f"),
// Mostly clear, day & night.
MOSTLY_SUNNY(3, "b"),
MOSTLY_CLEAR_NIGHT(4, "n"),
// Partly cloudy, day & night.
PARTLY_CLOUDY(5, "b"),
PARTLY_CLOUDY_NIGHT(6, "n"),
// Mostly cloudy, day & night.
MOSTLY_CLOUDY_DAY(7, "e"),
MOSTLY_CLOUDY_NIGHT(8, "e"),
CLOUDY(9, "e"),
HAZE_FOG_DUST_SMOKE(10, "d"),
DRIZZLE(11, "c"),
HEAVY_RAIN(12, "c"),
SHOWERS_RAIN(13, "c"),
// Scattered showers, day & night.
SCATTERED_SHOWERS_DAY(14, "c"),
SCATTERED_SHOWERS_NIGHT(15, "c"),
// Isolated scattered thunderstorms, day & night.
ISOLATED_SCATTERED_TSTORMS_DAY(16, "i"),
ISOLATED_SCATTERED_TSTORMS_NIGHT(17, "i"),
STRONG_TSTORMS(18, "i"),
BLIZZARD(19, "j"),
BLOWING_SNOW(20, "j"),
FLURRIES(21, "h"),
HEAVY_SNOW(22, "j"),
// Scattered snow showers, day & night.
SCATTERED_SNOW_SHOWERS_DAY(23, "h"),
SCATTERED_SNOW_SHOWERS_NIGHT(24, "h"),
SNOW_SHOWERS_SNOW(25, "g"),
MIXED_RAIN_HAIL_RAIN_SLEET(26, "h"),
SLEET_HAIL(27, "h"),
TORNADO(28, "l"),
TROPICAL_STORM_HURRICANE(29, "m"),
WINDY_BREEZY(30, "k"),
WINTRY_MIX_RAIN_SNOW(31, "h");
companion object {
@JvmStatic fun fromInt(value: Int) = entries.firstOrNull { it.id == value }
}
}
override fun toString(): String {
val unit = if (useCelsius) "C" else "F"
return "$state (\"$description\") $temperature°$unit"
}
}
@@ -1,22 +0,0 @@
package com.android.systemui.plugins.clocks
import android.provider.Settings.Global.ZEN_MODE_ALARMS
import android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
import android.provider.Settings.Global.ZEN_MODE_NO_INTERRUPTIONS
import android.provider.Settings.Global.ZEN_MODE_OFF
data class ZenData(
val zenMode: ZenMode,
val descriptionId: String?,
) {
enum class ZenMode(val zenMode: Int) {
OFF(ZEN_MODE_OFF),
IMPORTANT_INTERRUPTIONS(ZEN_MODE_IMPORTANT_INTERRUPTIONS),
NO_INTERRUPTIONS(ZEN_MODE_NO_INTERRUPTIONS),
ALARMS(ZEN_MODE_ALARMS);
companion object {
fun fromInt(zenMode: Int) = values().firstOrNull { it.zenMode == zenMode }
}
}
}