blob: 5d8da5985768633c8f7784370c43809a5e1f575d [file] [log] [blame]
/*
* 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.shared.clocks
import android.content.res.Resources
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.icu.text.NumberFormat
import android.util.TypedValue
import android.view.LayoutInflater
import com.android.internal.colorextraction.ColorExtractor
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.Clock
import com.android.systemui.plugins.ClockAnimation
import com.android.systemui.plugins.ClockEvents
import com.android.systemui.plugins.ClockId
import com.android.systemui.plugins.ClockMetadata
import com.android.systemui.plugins.ClockProvider
import com.android.systemui.shared.R
import java.io.PrintWriter
import java.util.Locale
import java.util.TimeZone
import javax.inject.Inject
private val TAG = DefaultClockProvider::class.simpleName
const val DEFAULT_CLOCK_NAME = "Default Clock"
const val DEFAULT_CLOCK_ID = "DEFAULT"
/** Provides the default system clock */
class DefaultClockProvider @Inject constructor(
val layoutInflater: LayoutInflater,
@Main val resources: Resources
) : ClockProvider {
override fun getClocks(): List<ClockMetadata> =
listOf(ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME))
override fun createClock(id: ClockId): Clock {
if (id != DEFAULT_CLOCK_ID) {
throw IllegalArgumentException("$id is unsupported by $TAG")
}
return DefaultClock(layoutInflater, resources)
}
override fun getClockThumbnail(id: ClockId): Drawable? {
if (id != DEFAULT_CLOCK_ID) {
throw IllegalArgumentException("$id is unsupported by $TAG")
}
// TODO: Update placeholder to actual resource
return resources.getDrawable(R.drawable.clock_default_thumbnail, null)
}
}
/**
* Controls the default clock visuals.
*
* This serves as an adapter between the clock interface and the
* AnimatableClockView used by the existing lockscreen clock.
*/
class DefaultClock(
private val layoutInflater: LayoutInflater,
private val resources: Resources
) : Clock {
override val smallClock =
layoutInflater.inflate(R.layout.clock_default_small, null) as AnimatableClockView
override val largeClock =
layoutInflater.inflate(R.layout.clock_default_large, null) as AnimatableClockView
private val clocks = listOf(smallClock, largeClock)
private val burmeseNf = NumberFormat.getInstance(Locale.forLanguageTag("my"))
private val burmeseNumerals = burmeseNf.format(FORMAT_NUMBER.toLong())
private val burmeseLineSpacing =
resources.getFloat(R.dimen.keyguard_clock_line_spacing_scale_burmese)
private val defaultLineSpacing = resources.getFloat(R.dimen.keyguard_clock_line_spacing_scale)
override val events = object : ClockEvents {
override fun onTimeTick() = clocks.forEach { it.refreshTime() }
override fun onTimeFormatChanged(is24Hr: Boolean) =
clocks.forEach { it.refreshFormat(is24Hr) }
override fun onTimeZoneChanged(timeZone: TimeZone) =
clocks.forEach { it.onTimeZoneChanged(timeZone) }
override fun onFontSettingChanged() {
smallClock.setTextSize(
TypedValue.COMPLEX_UNIT_PX,
resources.getDimensionPixelSize(R.dimen.small_clock_text_size).toFloat()
)
largeClock.setTextSize(
TypedValue.COMPLEX_UNIT_PX,
resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat()
)
}
override fun onColorPaletteChanged(palette: ColorExtractor.GradientColors) =
clocks.forEach { it.setColors(DOZE_COLOR, palette.mainColor) }
override fun onLocaleChanged(locale: Locale) {
val nf = NumberFormat.getInstance(locale)
if (nf.format(FORMAT_NUMBER.toLong()) == burmeseNumerals) {
clocks.forEach { it.setLineSpacingScale(burmeseLineSpacing) }
} else {
clocks.forEach { it.setLineSpacingScale(defaultLineSpacing) }
}
clocks.forEach { it.refreshFormat() }
}
}
override val animation = object : ClockAnimation {
override fun initialize(dozeFraction: Float, foldFraction: Float) {
dozeState = AnimationState(dozeFraction)
foldState = AnimationState(foldFraction)
if (foldState.isActive) {
clocks.forEach { it.animateFoldAppear(false) }
} else {
clocks.forEach { it.animateDoze(dozeState.isActive, false) }
}
}
override fun enter() {
if (dozeState.isActive) {
clocks.forEach { it.animateAppearOnLockscreen() }
}
}
override fun charge() = clocks.forEach { it.animateCharge { dozeState.isActive } }
private var foldState = AnimationState(0f)
override fun fold(fraction: Float) {
val (hasChanged, hasJumped) = foldState.update(fraction)
if (hasChanged) {
clocks.forEach { it.animateFoldAppear(!hasJumped) }
}
}
private var dozeState = AnimationState(0f)
override fun doze(fraction: Float) {
val (hasChanged, hasJumped) = dozeState.update(fraction)
if (hasChanged) {
clocks.forEach { it.animateDoze(dozeState.isActive, !hasJumped) }
}
}
}
private class AnimationState(
var fraction: Float
) {
var isActive: Boolean = fraction < 0.5f
fun update(newFraction: Float): Pair<Boolean, Boolean> {
val wasActive = isActive
val hasJumped = (fraction == 0f && newFraction == 1f) ||
(fraction == 1f && newFraction == 0f)
isActive = newFraction > fraction
fraction = newFraction
return Pair(wasActive != isActive, hasJumped)
}
}
init {
events.onLocaleChanged(Locale.getDefault())
}
override fun dump(pw: PrintWriter) = clocks.forEach { it.dump(pw) }
companion object {
private const val DOZE_COLOR = Color.WHITE
private const val FORMAT_NUMBER = 1234567890
}
}