1 /* <lambda>null2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.android.wallpaper.weathereffects 18 19 import android.content.Context 20 import android.graphics.Bitmap 21 import android.util.Log 22 import android.util.Size 23 import android.util.SizeF 24 import android.view.SurfaceHolder 25 import com.google.android.torus.canvas.engine.CanvasWallpaperEngine 26 import com.google.android.wallpaper.weathereffects.shared.model.WallpaperImageModel 27 import com.google.android.wallpaper.weathereffects.domain.WeatherEffectsInteractor 28 import com.google.android.wallpaper.weathereffects.graphics.WeatherEffect 29 import com.google.android.wallpaper.weathereffects.graphics.fog.FogEffect 30 import com.google.android.wallpaper.weathereffects.graphics.fog.FogEffectConfig 31 import com.google.android.wallpaper.weathereffects.graphics.none.NoEffect 32 import com.google.android.wallpaper.weathereffects.graphics.rain.RainEffect 33 import com.google.android.wallpaper.weathereffects.graphics.rain.RainEffectConfig 34 import com.google.android.wallpaper.weathereffects.graphics.snow.SnowEffect 35 import com.google.android.wallpaper.weathereffects.graphics.snow.SnowEffectConfig 36 import com.google.android.wallpaper.weathereffects.provider.WallpaperInfoContract 37 import kotlinx.coroutines.CoroutineScope 38 import kotlinx.coroutines.Job 39 import kotlinx.coroutines.launch 40 41 class WeatherEngine( 42 defaultHolder: SurfaceHolder, 43 private val context: Context, 44 hardwareAccelerated: Boolean = true 45 ) : CanvasWallpaperEngine(defaultHolder, hardwareAccelerated) { 46 47 private val currentAssets: WallpaperImageModel? = null 48 private var activeEffect: WeatherEffect? = null 49 private set(value) { 50 field = value 51 if (shouldTriggerUpdate()) { 52 startUpdateLoop() 53 } else { 54 stopUpdateLoop() 55 } 56 } 57 58 private var collectWallpaperImageJob: Job? = null 59 private lateinit var interactor: WeatherEffectsInteractor 60 private lateinit var applicationScope: CoroutineScope 61 62 override fun onCreate(isFirstActiveInstance: Boolean) { 63 Log.d(TAG, "Engine created.") 64 } 65 66 override fun onResize(size: Size) { 67 activeEffect?.resize(size.toSizeF()) 68 if (activeEffect is NoEffect) { 69 render { canvas -> activeEffect!!.draw(canvas) } 70 } 71 } 72 73 fun initialize( 74 applicationScope: CoroutineScope, 75 interactor: WeatherEffectsInteractor, 76 ) { 77 this.interactor = interactor 78 this.applicationScope = applicationScope 79 80 if (interactor.wallpaperImageModel.value == null) { 81 applicationScope.launch { 82 interactor.loadWallpaper() 83 } 84 } 85 } 86 87 override fun onResume() { 88 if (shouldTriggerUpdate()) { 89 startUpdateLoop() 90 } 91 collectWallpaperImageJob = applicationScope.launch { 92 interactor.wallpaperImageModel.collect { asset -> 93 if (asset == null || asset == currentAssets) return@collect 94 95 createWeatherEffect(asset.foreground, asset.background, asset.weatherEffect) 96 } 97 } 98 } 99 100 override fun onPause() { 101 stopUpdateLoop() 102 activeEffect?.reset() 103 collectWallpaperImageJob?.cancel() 104 } 105 106 107 override fun onDestroy(isLastActiveInstance: Boolean) { 108 activeEffect?.release() 109 activeEffect = null 110 } 111 112 override fun onUpdate(deltaMillis: Long, frameTimeNanos: Long) { 113 super.onUpdate(deltaMillis, frameTimeNanos) 114 activeEffect?.update(deltaMillis, frameTimeNanos) 115 116 renderWithFpsLimit(frameTimeNanos) { canvas -> activeEffect?.draw(canvas) } 117 } 118 119 private fun createWeatherEffect( 120 foreground: Bitmap, 121 background: Bitmap, 122 weatherEffect: WallpaperInfoContract.WeatherEffect? = null 123 ) { 124 activeEffect?.release() 125 activeEffect = null 126 127 when (weatherEffect) { 128 WallpaperInfoContract.WeatherEffect.RAIN -> { 129 val rainConfig = RainEffectConfig(context.assets, foreground, background) 130 activeEffect = RainEffect(rainConfig, screenSize.toSizeF(), context.mainExecutor) 131 } 132 133 WallpaperInfoContract.WeatherEffect.FOG -> { 134 val fogConfig = FogEffectConfig( 135 context.assets, foreground, background, context.resources.displayMetrics.density 136 ) 137 activeEffect = FogEffect(fogConfig, screenSize.toSizeF()) 138 } 139 140 WallpaperInfoContract.WeatherEffect.SNOW -> { 141 val snowConfig = SnowEffectConfig(context, foreground, background) 142 activeEffect = SnowEffect(snowConfig, screenSize.toSizeF(), context.mainExecutor) 143 } 144 145 else -> { 146 activeEffect = NoEffect(foreground, background, screenSize.toSizeF()) 147 } 148 } 149 150 render { canvas -> activeEffect?.draw(canvas) } 151 } 152 153 private fun shouldTriggerUpdate(): Boolean { 154 return activeEffect != null && activeEffect !is NoEffect 155 } 156 157 private fun Size.toSizeF(): SizeF = SizeF(width.toFloat(), height.toFloat()) 158 159 private companion object { 160 161 private val TAG = WeatherEngine::class.java.simpleName 162 } 163 } 164