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