Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 26ee4b75 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Remove AssetLoader from customization lib" into main

parents 9d699265 f3a1ccbb
Loading
Loading
Loading
Loading
+0 −252
Original line number Diff line number Diff line
/*
 * 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.shared.clocks

import android.content.Context
import android.content.res.Resources
import android.graphics.Typeface
import android.graphics.drawable.Drawable
import android.util.TypedValue
import com.android.internal.policy.SystemBarUtils
import com.android.systemui.log.core.Logger
import com.android.systemui.log.core.MessageBuffer
import com.android.systemui.monet.Style as MonetStyle
import java.io.IOException

class AssetLoader
private constructor(
    private val pluginCtx: Context,
    private val sysuiCtx: Context,
    private val baseDir: String,
    var seedColor: Int?,
    var overrideChroma: Float?,
    val typefaceCache: TypefaceCache,
    messageBuffer: MessageBuffer,
) {
    val logger = Logger(messageBuffer, TAG)
    private val resources =
        listOf(
            Pair(pluginCtx.resources, pluginCtx.packageName),
            Pair(sysuiCtx.resources, sysuiCtx.packageName),
        )

    constructor(
        pluginCtx: Context,
        sysuiCtx: Context,
        baseDir: String,
        messageBuffer: MessageBuffer,
    ) : this(
        pluginCtx,
        sysuiCtx,
        baseDir,
        seedColor = null,
        overrideChroma = null,
        typefaceCache =
            TypefaceCache(messageBuffer) {
                // TODO(b/364680873): Move constant to config_clockFontFamily when shipping
                return@TypefaceCache Typeface.create("google-sans-flex-clock", Typeface.NORMAL)
            },
        messageBuffer = messageBuffer,
    )

    fun listAssets(path: String): List<String> {
        return pluginCtx.resources.assets.list("$baseDir$path")?.toList() ?: emptyList()
    }

    fun tryReadString(resStr: String): String? = tryRead(resStr, ::readString)

    fun readString(resStr: String): String {
        val resPair = resolveResourceId(resStr)
        if (resPair == null) {
            throw IOException("Failed to parse string: $resStr")
        }

        val (res, id) = resPair
        return res.getString(id)
    }

    fun readFontAsset(resStr: String): Typeface = typefaceCache.getTypeface(resStr)

    fun tryReadTextAsset(path: String?): String? = tryRead(path, ::readTextAsset)

    fun readTextAsset(path: String): String {
        return pluginCtx.resources.assets.open("$baseDir$path").use { stream ->
            val buffer = ByteArray(stream.available())
            stream.read(buffer)
            String(buffer)
        }
    }

    fun tryReadDrawableAsset(path: String?): Drawable? = tryRead(path, ::readDrawableAsset)

    fun readDrawableAsset(path: String): Drawable {
        var result: Drawable?

        if (path.startsWith("@")) {
            val pair = resolveResourceId(path)
            if (pair == null) {
                throw IOException("Failed to parse $path to an id")
            }
            val (res, id) = pair
            result = res.getDrawable(id)
        } else if (path.endsWith("xml")) {
            // TODO(b/248609434): Support xml files in assets
            throw IOException("Cannot load xml files from assets")
        } else {
            // Attempt to load as if it's a bitmap and directly loadable
            result =
                pluginCtx.resources.assets.open("$baseDir$path").use { stream ->
                    Drawable.createFromResourceStream(
                        pluginCtx.resources,
                        TypedValue(),
                        stream,
                        null,
                    )
                }
        }

        return result ?: throw IOException("Failed to load: $baseDir$path")
    }

    fun parseResourceId(resStr: String): Triple<String?, String, String> {
        if (!resStr.startsWith("@")) {
            throw IOException("Invalid resource id: $resStr; Must start with '@'")
        }

        // Parse out resource string
        val parts = resStr.drop(1).split('/', ':')
        return when (parts.size) {
            2 -> Triple(null, parts[0], parts[1])
            3 -> Triple(parts[0], parts[1], parts[2])
            else -> throw IOException("Failed to parse resource string: $resStr")
        }
    }

    fun resolveResourceId(resStr: String): Pair<Resources, Int>? {
        val (packageName, category, name) = parseResourceId(resStr)
        return resolveResourceId(packageName, category, name)
    }

    fun resolveResourceId(
        packageName: String?,
        category: String,
        name: String,
    ): Pair<Resources, Int>? {
        for ((res, ctxPkgName) in resources) {
            val result = res.getIdentifier(name, category, packageName ?: ctxPkgName)
            if (result != 0) {
                return Pair(res, result)
            }
        }
        return null
    }

    private fun <TArg : Any, TRes : Any> tryRead(arg: TArg?, fn: (TArg) -> TRes): TRes? {
        try {
            if (arg == null) {
                return null
            }
            return fn(arg)
        } catch (ex: IOException) {
            logger.w("Failed to read $arg", ex)
            return null
        }
    }

    fun assetExists(path: String): Boolean {
        try {
            if (path.startsWith("@")) {
                val pair = resolveResourceId(path)
                return pair != null
            } else {
                val stream = pluginCtx.resources.assets.open("$baseDir$path")
                if (stream == null) {
                    return false
                }

                stream.close()
                return true
            }
        } catch (ex: IOException) {
            return false
        }
    }

    fun copy(messageBuffer: MessageBuffer? = null): AssetLoader =
        AssetLoader(
            pluginCtx,
            sysuiCtx,
            baseDir,
            seedColor,
            overrideChroma,
            typefaceCache,
            messageBuffer ?: logger.buffer,
        )

    fun setSeedColor(seedColor: Int?, style: MonetStyle?) {
        this.seedColor = seedColor
    }

    fun getClockPaddingStart(): Int {
        val result = resolveResourceId(null, "dimen", "clock_padding_start")
        if (result != null) {
            val (res, id) = result
            return res.getDimensionPixelSize(id)
        }
        return -1
    }

    fun getStatusBarHeight(): Int {
        val display = pluginCtx.getDisplayNoVerify()
        if (display != null) {
            return SystemBarUtils.getStatusBarHeight(pluginCtx.resources, display.cutout)
        }

        logger.w("No display available; falling back to android.R.dimen.status_bar_height")
        val statusBarHeight = resolveResourceId("android", "dimen", "status_bar_height")
        if (statusBarHeight != null) {
            val (res, resId) = statusBarHeight
            return res.getDimensionPixelSize(resId)
        }

        throw Exception("Could not fetch StatusBarHeight")
    }

    fun getResourcesId(name: String): Int = getResource("id", name) { _, id -> id }

    fun getDimen(name: String): Int = getResource("dimen", name, Resources::getDimensionPixelSize)

    fun getString(name: String): String = getResource("string", name, Resources::getString)

    private fun <T> getResource(
        category: String,
        name: String,
        getter: (res: Resources, id: Int) -> T,
    ): T {
        val result = resolveResourceId(null, category, name)
        if (result != null) {
            val (res, id) = result
            if (id == -1) throw Exception("Cannot find id of $id from $TAG")
            return getter(res, id)
        }
        throw Exception("Cannot find id of $name from $TAG")
    }

    companion object {
        private val TAG = AssetLoader::class.simpleName!!
    }
}
+9 −20
Original line number Diff line number Diff line
@@ -16,12 +16,9 @@

package com.android.systemui.shared.clocks

import android.content.Context
import android.content.res.Resources
import android.graphics.Rect
import androidx.annotation.VisibleForTesting
import com.android.systemui.log.core.Logger
import com.android.systemui.log.core.MessageBuffer
import com.android.systemui.plugins.clocks.AlarmData
import com.android.systemui.plugins.clocks.ClockAnimations
import com.android.systemui.plugins.clocks.ClockEvents
@@ -37,31 +34,22 @@ import java.util.Locale
import java.util.TimeZone

class ComposedDigitalLayerController(
    private val ctx: Context,
    private val resources: Resources,
    private val assets: AssetLoader, // TODO(b/364680879): Remove and replace w/ resources
    private val clockCtx: ClockContext,
    private val layer: ComposedDigitalHandLayer,
    messageBuffer: MessageBuffer,
) : SimpleClockLayerController {
    private val logger = Logger(messageBuffer, ComposedDigitalLayerController::class.simpleName!!)
    private val logger =
        Logger(clockCtx.messageBuffer, ComposedDigitalLayerController::class.simpleName!!)

    val layerControllers = mutableListOf<SimpleClockLayerController>()
    val dozeState = DefaultClockController.AnimationState(1F)

    override val view = FlexClockView(ctx, assets, messageBuffer)
    override val view = FlexClockView(clockCtx)

    init {
        layer.digitalLayers.forEach {
            val childView = SimpleDigitalClockTextView(ctx, messageBuffer)
            val childView = SimpleDigitalClockTextView(clockCtx)
            val controller =
                SimpleDigitalHandLayerController(
                    ctx,
                    resources,
                    assets,
                    it as DigitalHandLayer,
                    childView,
                    messageBuffer,
                )
                SimpleDigitalHandLayerController(clockCtx, it as DigitalHandLayer, childView)

            view.addView(childView)
            layerControllers.add(controller)
@@ -156,8 +144,9 @@ class ComposedDigitalLayerController(
                val color =
                    when {
                        theme.seedColor != null -> theme.seedColor!!
                        theme.isDarkTheme -> resources.getColor(android.R.color.system_accent1_100)
                        else -> resources.getColor(android.R.color.system_accent2_600)
                        theme.isDarkTheme ->
                            clockCtx.resources.getColor(android.R.color.system_accent1_100)
                        else -> clockCtx.resources.getColor(android.R.color.system_accent2_600)
                    }

                view.updateColor(color)
+26 −11
Original line number Diff line number Diff line
@@ -15,10 +15,10 @@ package com.android.systemui.shared.clocks

import android.content.Context
import android.content.res.Resources
import android.graphics.Typeface
import android.view.LayoutInflater
import com.android.systemui.customization.R
import com.android.systemui.log.core.LogLevel
import com.android.systemui.log.core.LogcatOnlyMessageBuffer
import com.android.systemui.log.core.MessageBuffer
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockFontAxis
import com.android.systemui.plugins.clocks.ClockFontAxisSetting
@@ -33,6 +33,15 @@ import com.android.systemui.shared.clocks.view.VerticalAlignment
private val TAG = DefaultClockProvider::class.simpleName
const val DEFAULT_CLOCK_ID = "DEFAULT"

data class ClockContext(
    val context: Context,
    val resources: Resources,
    val settings: ClockSettings,
    val typefaceCache: TypefaceCache,
    val messageBuffers: ClockMessageBuffers,
    val messageBuffer: MessageBuffer,
)

/** Provides the default system clock */
class DefaultClockProvider(
    val ctx: Context,
@@ -55,18 +64,24 @@ class DefaultClockProvider(
        }

        return if (isClockReactiveVariantsEnabled) {
            val buffer =
                messageBuffers?.infraMessageBuffer ?: LogcatOnlyMessageBuffer(LogLevel.INFO)
            val assets = AssetLoader(ctx, ctx, "clocks/", buffer)
            assets.setSeedColor(settings.seedColor, null)
            val buffers = messageBuffers ?: ClockMessageBuffers(LogUtil.DEFAULT_MESSAGE_BUFFER)
            val fontAxes = ClockFontAxis.merge(FlexClockController.FONT_AXES, settings.axes)
            val clockSettings = settings.copy(axes = fontAxes.map { it.toSetting() })
            val typefaceCache =
                TypefaceCache(buffers.infraMessageBuffer) {
                    // TODO(b/364680873): Move constant to config_clockFontFamily when shipping
                    return@TypefaceCache Typeface.create("google-sans-flex-clock", Typeface.NORMAL)
                }
            FlexClockController(
                ClockContext(
                    ctx,
                    resources,
                settings.copy(axes = fontAxes.map { it.toSetting() }),
                assets,
                    clockSettings,
                    typefaceCache,
                    buffers,
                    buffers.infraMessageBuffer,
                ),
                FLEX_DESIGN,
                messageBuffers,
            )
        } else {
            DefaultClockController(
+11 −29
Original line number Diff line number Diff line
@@ -16,8 +16,6 @@

package com.android.systemui.shared.clocks

import android.content.Context
import android.content.res.Resources
import com.android.systemui.customization.R
import com.android.systemui.plugins.clocks.AlarmData
import com.android.systemui.plugins.clocks.AxisType
@@ -26,8 +24,6 @@ import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockEvents
import com.android.systemui.plugins.clocks.ClockFontAxis
import com.android.systemui.plugins.clocks.ClockFontAxisSetting
import com.android.systemui.plugins.clocks.ClockMessageBuffers
import com.android.systemui.plugins.clocks.ClockSettings
import com.android.systemui.plugins.clocks.ThemeConfig
import com.android.systemui.plugins.clocks.WeatherData
import com.android.systemui.plugins.clocks.ZenData
@@ -38,42 +34,28 @@ import java.util.TimeZone

/** Controller for the default flex clock */
class FlexClockController(
    private val ctx: Context,
    private val resources: Resources,
    private val settings: ClockSettings,
    private val assets: AssetLoader, // TODO(b/364680879): Remove and replace w/ resources
    private val clockCtx: ClockContext,
    val design: ClockDesign, // TODO(b/364680879): Remove when done inlining
    val messageBuffers: ClockMessageBuffers?,
) : ClockController {
    override val smallClock = run {
        val buffer = messageBuffers?.smallClockMessageBuffer ?: LogUtil.DEFAULT_MESSAGE_BUFFER
    override val smallClock =
        FlexClockFaceController(
            ctx,
            resources,
            assets.copy(messageBuffer = buffer),
            clockCtx.copy(messageBuffer = clockCtx.messageBuffers.smallClockMessageBuffer),
            design.small ?: design.large!!,
            false,
            buffer,
            isLargeClock = false,
        )
    }

    override val largeClock = run {
        val buffer = messageBuffers?.largeClockMessageBuffer ?: LogUtil.DEFAULT_MESSAGE_BUFFER
    override val largeClock =
        FlexClockFaceController(
            ctx,
            resources,
            assets.copy(messageBuffer = buffer),
            clockCtx.copy(messageBuffer = clockCtx.messageBuffers.largeClockMessageBuffer),
            design.large ?: design.small!!,
            true,
            buffer,
            isLargeClock = true,
        )
    }

    override val config: ClockConfig by lazy {
        ClockConfig(
            DEFAULT_CLOCK_ID,
            resources.getString(R.string.clock_default_name),
            resources.getString(R.string.clock_default_description),
            clockCtx.resources.getString(R.string.clock_default_name),
            clockCtx.resources.getString(R.string.clock_default_description),
            isReactiveToTone = true,
        )
    }
@@ -125,8 +107,8 @@ class FlexClockController(
        }

    override fun initialize(isDarkTheme: Boolean, dozeFraction: Float, foldFraction: Float) {
        val theme = ThemeConfig(isDarkTheme, assets.seedColor)
        events.onFontAxesChanged(settings.axes)
        val theme = ThemeConfig(isDarkTheme, clockCtx.settings.seedColor)
        events.onFontAxesChanged(clockCtx.settings.axes)

        smallClock.run {
            events.onThemeChanged(theme)
+7 −26
Original line number Diff line number Diff line
@@ -16,15 +16,12 @@

package com.android.systemui.shared.clocks

import android.content.Context
import android.content.res.Resources
import android.graphics.Rect
import android.view.Gravity
import android.view.View
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.widget.FrameLayout
import com.android.systemui.customization.R
import com.android.systemui.log.core.MessageBuffer
import com.android.systemui.plugins.clocks.AlarmData
import com.android.systemui.plugins.clocks.ClockAnimations
import com.android.systemui.plugins.clocks.ClockEvents
@@ -45,22 +42,19 @@ import kotlin.math.max

// TODO(b/364680879): Merge w/ ComposedDigitalLayerController
class FlexClockFaceController(
    ctx: Context,
    private val resources: Resources,
    val assets: AssetLoader, // TODO(b/364680879): Remove and replace w/ resources
    clockCtx: ClockContext,
    face: ClockFace,
    private val isLargeClock: Boolean,
    messageBuffer: MessageBuffer,
) : ClockFaceController {
    override val view: View
        get() = layerController.view

    override val config = ClockFaceConfig(hasCustomPositionUpdatedAnimation = true)

    override var theme = ThemeConfig(true, assets.seedColor)
    override var theme = ThemeConfig(true, clockCtx.settings.seedColor)

    private val keyguardLargeClockTopMargin =
        resources.getDimensionPixelSize(R.dimen.keyguard_large_clock_top_margin)
        clockCtx.resources.getDimensionPixelSize(R.dimen.keyguard_large_clock_top_margin)
    val layerController: SimpleClockLayerController
    val timespecHandler = DigitalTimespecHandler(DigitalTimespec.TIME_FULL_FORMAT, "hh:mm")

@@ -72,23 +66,10 @@ class FlexClockFaceController(

        layerController =
            if (isLargeClock) {
                ComposedDigitalLayerController(
                    ctx,
                    resources,
                    assets,
                    layer as ComposedDigitalHandLayer,
                    messageBuffer,
                )
                ComposedDigitalLayerController(clockCtx, layer as ComposedDigitalHandLayer)
            } else {
                val childView = SimpleDigitalClockTextView(ctx, messageBuffer)
                SimpleDigitalHandLayerController(
                    ctx,
                    resources,
                    assets,
                    layer as DigitalHandLayer,
                    childView,
                    messageBuffer,
                )
                val childView = SimpleDigitalClockTextView(clockCtx)
                SimpleDigitalHandLayerController(clockCtx, layer as DigitalHandLayer, childView)
            }
        layerController.view.layoutParams = lp
    }
@@ -97,7 +78,7 @@ class FlexClockFaceController(
    private fun offsetGlyphsForStepClockAnimation(
        clockStartLeft: Int,
        direction: Int,
        fraction: Float
        fraction: Float,
    ) {
        (view as? FlexClockView)?.offsetGlyphsForStepClockAnimation(
            clockStartLeft,
Loading