CanvasRenderer.kt

/*
 * Copyright 2020 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 androidx.wear.watchface

import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Rect
import android.icu.util.Calendar
import android.view.SurfaceHolder
import androidx.annotation.IntDef
import androidx.annotation.IntRange
import androidx.annotation.UiThread
import androidx.wear.watchface.style.UserStyleRepository

/** @hide */
@IntDef(
    value = [
        CanvasType.SOFTWARE,
        CanvasType.HARDWARE
    ]
)
public annotation class CanvasType {
    public companion object {
        /** A software canvas will be requested. */
        public const val SOFTWARE: Int = 0

        /**
         * A hardware canvas will be requested. This is usually faster than software rendering,
         * however it can sometimes increase battery usage by rendering at a higher frame rate.
         */
        public const val HARDWARE: Int = 1
    }
}

/**
 * Watch faces that require [Canvas] rendering should extend their [Renderer] from this
 * class.
 */
public abstract class CanvasRenderer(
    /** The [SurfaceHolder] that [render] will draw into. */
    surfaceHolder: SurfaceHolder,

    /** The associated [UserStyleRepository]. */
    userStyleRepository: UserStyleRepository,

    /** The associated [WatchState]. */
    watchState: WatchState,

    /** The type of canvas to use. */
    @CanvasType private val canvasType: Int,

    /**
     * The interval in milliseconds between frames in interactive [DrawMode]s. To render at 60hz
     * set to 16. Note when battery is low, the frame rate will be clamped to 10fps. Watch faces are
     * recommended to use lower frame rates if possible for better battery life. Variable frame
     * rates can also help preserve battery life, e.g. if a watch face has a short animation once
     * per second it can adjust the frame rate inorder to sleep when not animating.
     */
    @IntRange(from = 0, to = 10000)
    interactiveDrawModeUpdateDelayMillis: Long
) : Renderer(surfaceHolder, userStyleRepository, watchState, interactiveDrawModeUpdateDelayMillis) {

    internal override fun renderInternal(
        calendar: Calendar
    ) {
        val canvas = (
            if (canvasType == CanvasType.HARDWARE) {
                surfaceHolder.lockHardwareCanvas()
            } else {
                surfaceHolder.lockCanvas()
            }
            ) ?: return
        try {
            if (watchState.isVisible.value) {
                render(canvas, surfaceHolder.surfaceFrame, calendar)
            } else {
                canvas.drawColor(Color.BLACK)
            }
        } finally {
            surfaceHolder.unlockCanvasAndPost(canvas)
        }
    }

    /** {@inheritDoc} */
    internal override fun takeScreenshot(
        calendar: Calendar,
        renderParameters: RenderParameters
    ): Bitmap {
        val bitmap = Bitmap.createBitmap(
            screenBounds.width(),
            screenBounds.height(),
            Bitmap.Config.ARGB_8888
        )
        val prevRenderParameters = this.renderParameters
        this.renderParameters = renderParameters
        render(Canvas(bitmap), screenBounds, calendar)
        this.renderParameters = prevRenderParameters
        return bitmap
    }

    /**
     * Sub-classes should override this to implement their rendering logic which should respect
     * the current [DrawMode]. For correct functioning watch faces must use the supplied
     * [Calendar] and avoid using any other ways of getting the time.
     *
     * @param canvas The [Canvas] to render into. Don't assume this is always the canvas from
     *     the [SurfaceHolder] backing the display
     * @param bounds A [Rect] describing the bonds of the canvas to draw into
     * @param calendar The current [Calendar]
     */
    @UiThread
    public abstract fun render(
        canvas: Canvas,
        bounds: Rect,
        calendar: Calendar
    )
}