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.animation.Animator
20 import android.animation.AnimatorListenerAdapter
21 import android.annotation.SuppressLint
22 import android.app.WallpaperManager
23 import android.content.ComponentName
24 import android.content.Context
25 import android.content.Intent
26 import android.os.Bundle
27 import android.view.MotionEvent
28 import android.view.SurfaceView
29 import android.view.View
30 import android.widget.Button
31 import android.widget.FrameLayout
32 import android.widget.SeekBar
33 import android.widget.TextView
34 import androidx.constraintlayout.widget.ConstraintLayout
35 import com.google.android.torus.core.activity.TorusViewerActivity
36 import com.google.android.torus.core.engine.TorusEngine
37 import com.google.android.torus.utils.extensions.setImmersiveFullScreen
38 import com.google.android.wallpaper.weathereffects.dagger.BackgroundScope
39 import com.google.android.wallpaper.weathereffects.dagger.MainScope
40 import com.google.android.wallpaper.weathereffects.data.repository.WallpaperFileUtils
41 import com.google.android.wallpaper.weathereffects.domain.WeatherEffectsInteractor
42 import com.google.android.wallpaper.weathereffects.provider.WallpaperInfoContract
43 import com.google.android.wallpaper.weathereffects.shared.model.WallpaperFileModel
44 import kotlinx.coroutines.CoroutineScope
45 import kotlinx.coroutines.delay
46 import kotlinx.coroutines.launch
47 import java.io.File
48 import javax.inject.Inject
49 
50 class WallpaperEffectsDebugActivity : TorusViewerActivity() {
51 
52     @Inject
53     @MainScope
54     lateinit var mainScope: CoroutineScope
55     @Inject
56     @BackgroundScope
57     lateinit var bgScope: CoroutineScope
58     @Inject
59     lateinit var context: Context
60     @Inject
61     lateinit var interactor: WeatherEffectsInteractor
62 
63     private lateinit var rootView: FrameLayout
64     private lateinit var surfaceView: SurfaceView
65     private var engine: WeatherEngine? = null
66     private var weatherEffect: WallpaperInfoContract.WeatherEffect? = null
67     private var assetIndex = 0
68     private val fgCachedAssetPaths: ArrayList<String> = arrayListOf()
69     private val bgCachedAssetPaths: ArrayList<String> = arrayListOf()
70 
71     /** It will be initialized on [onCreate]. */
72     private var intensity: Float = 0.8f
73 
74     override fun getWallpaperEngine(context: Context, surfaceView: SurfaceView): TorusEngine {
75         this.surfaceView = surfaceView
76         val engine = WeatherEngine(
77             surfaceView.holder,
78             mainScope,
79             interactor,
80             context,
81             isDebugActivity = true
82         )
83         this.engine = engine
84         return engine
85     }
86 
87     @SuppressLint("ClickableViewAccessibility")
88     override fun onCreate(savedInstanceState: Bundle?) {
89         WallpaperEffectsDebugApplication.graph.inject(this)
90         super.onCreate(savedInstanceState)
91 
92         setContentView(R.layout.debug_activity)
93         setImmersiveFullScreen()
94 
95         writeAssetsToCache()
96 
97         rootView = requireViewById(R.id.main_layout)
98         rootView.requireViewById<FrameLayout>(R.id.wallpaper_layout).addView(surfaceView)
99 
100         rootView.requireViewById<Button>(R.id.rain).setOnClickListener {
101             weatherEffect = WallpaperInfoContract.WeatherEffect.RAIN
102             updateWallpaper()
103             setDebugText(context.getString(R.string.generating))
104         }
105         rootView.requireViewById<Button>(R.id.fog).setOnClickListener {
106             weatherEffect = WallpaperInfoContract.WeatherEffect.FOG
107             updateWallpaper()
108             setDebugText(context.getString(R.string.generating))
109         }
110         rootView.requireViewById<Button>(R.id.snow).setOnClickListener {
111             weatherEffect = WallpaperInfoContract.WeatherEffect.SNOW
112             updateWallpaper()
113             setDebugText(context.getString(R.string.generating))
114         }
115         rootView.requireViewById<Button>(R.id.sunny).setOnClickListener {
116             weatherEffect = WallpaperInfoContract.WeatherEffect.SUN
117             updateWallpaper()
118             setDebugText(context.getString(R.string.generating))
119         }
120         rootView.requireViewById<Button>(R.id.clear).setOnClickListener {
121             weatherEffect = null
122 
123             updateWallpaper()
124         }
125         rootView.requireViewById<Button>(R.id.change_asset).setOnClickListener {
126             assetIndex = (assetIndex + 1) % fgCachedAssetPaths.size
127 
128             updateWallpaper()
129         }
130 
131         rootView.requireViewById<Button>(R.id.set_wallpaper).setOnClickListener {
132             val i = Intent()
133             i.action = WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER
134             i.putExtra(
135                 WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT,
136                 ComponentName(this, WeatherWallpaperService::class.java)
137             )
138             this.startActivityForResult(i, SET_WALLPAPER_REQUEST_CODE)
139             saveWallpaper()
140         }
141 
142         rootView.requireViewById<FrameLayout>(R.id.wallpaper_layout)
143             .setOnTouchListener { view, event ->
144                 when (event?.action) {
145                     MotionEvent.ACTION_DOWN -> {
146                         if (rootView.requireViewById<ConstraintLayout>(R.id.buttons).visibility
147                             == View.GONE) {
148                             showButtons()
149                         } else {
150                             hideButtons()
151                         }
152                     }
153                 }
154 
155                 view.onTouchEvent(event)
156             }
157 
158         setDebugText()
159         val seekBar = rootView.requireViewById<SeekBar>(R.id.seekBar)
160         seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
161             override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
162                 // Convert progress to a value between 0 and 1
163                 val value = progress.toFloat() / 100f
164                 engine?.setTargetIntensity(value)
165                 intensity = value
166             }
167 
168             override fun onStartTrackingTouch(seekBar: SeekBar?) {
169                 hideButtons()
170             }
171 
172             override fun onStopTrackingTouch(seekBar: SeekBar?) {
173                 showButtons()
174             }
175         })
176         intensity = seekBar.progress.toFloat() / 100f
177 
178         // This avoids that the initial state after installing is showing a black screen.
179         if (!WallpaperFileUtils.hasBitmapsInLocalStorage(applicationContext)) updateWallpaper()
180     }
181 
182     private fun writeAssetsToCache() {
183         // Writes test files from assets to local cache dir
184         // (on the main thread!.. only ok for the debug app)
185         fgCachedAssetPaths.apply {
186             clear()
187             addAll(
188                 listOf(
189                     /* TODO(b/300991599): Add debug assets. */
190                     FOREGROUND_IMAGE_1,
191                     FOREGROUND_IMAGE_2,
192                     FOREGROUND_IMAGE_3,
193                 ).map { getFileFromAssets(it).absolutePath })
194         }
195         bgCachedAssetPaths.apply {
196             clear()
197             addAll(
198                 listOf(
199                     /* TODO(b/300991599): Add debug assets. */
200                     BACKGROUND_IMAGE_1,
201                     BACKGROUND_IMAGE_2,
202                     BACKGROUND_IMAGE_3,
203                 ).map { getFileFromAssets(it).absolutePath })
204         }
205     }
206 
207     private fun getFileFromAssets(fileName: String): File {
208         return File(context.cacheDir, fileName).also {
209             if (!it.exists()) {
210                 it.outputStream().use { cache ->
211                     context.assets.open(fileName).use { inputStream ->
212                         inputStream.copyTo(cache)
213                     }
214                 }
215             }
216         }
217     }
218 
219     private fun updateWallpaper() {
220         mainScope.launch {
221             val fgPath = fgCachedAssetPaths[assetIndex]
222             val bgPath = bgCachedAssetPaths[assetIndex]
223             interactor.updateWallpaper(
224                 WallpaperFileModel(
225                     fgPath,
226                     bgPath,
227                     weatherEffect,
228                 )
229             )
230             engine?.setTargetIntensity(intensity)
231             setDebugText(
232                 "Wallpaper updated successfully.\n* Weather: " +
233                         "$weatherEffect\n* Foreground: $fgPath\n* Background: $bgPath"
234             )
235         }
236     }
237 
238     private fun saveWallpaper() {
239         bgScope.launch {
240             interactor.saveWallpaper()
241         }
242     }
243 
244     private fun setDebugText(text: String? = null) {
245         val output = rootView.requireViewById<TextView>(R.id.output)
246         output.text = text
247 
248         if (text.isNullOrEmpty()) {
249             output.visibility = View.INVISIBLE
250         } else {
251             output.visibility = View.VISIBLE
252             mainScope.launch {
253                 // Make the text disappear after 3 sec.
254                 delay(3000L)
255                 output.visibility = View.INVISIBLE
256             }
257         }
258     }
259 
260     private fun showButtons() {
261         val buttons = rootView.requireViewById<ConstraintLayout>(R.id.buttons)
262         buttons.visibility = View.VISIBLE
263         buttons.animate().alpha(1f).setDuration(400).setListener(null)
264     }
265 
266     private fun hideButtons() {
267         val buttons = rootView.requireViewById<ConstraintLayout>(R.id.buttons)
268         buttons.animate()
269             .alpha(0f)
270             .setDuration(400)
271             .setListener(object : AnimatorListenerAdapter() {
272                 override fun onAnimationEnd(animation: Animator) {
273                     buttons.visibility = View.GONE
274                 }
275             })
276     }
277 
278     private companion object {
279         // TODO(b/300991599): Add debug assets.
280         private const val FOREGROUND_IMAGE_1 = "test-foreground.png"
281         private const val BACKGROUND_IMAGE_1 = "test-background.png"
282         private const val FOREGROUND_IMAGE_2 = "test-foreground2.png"
283         private const val BACKGROUND_IMAGE_2 = "test-background2.png"
284         private const val FOREGROUND_IMAGE_3 = "test-foreground3.png"
285         private const val BACKGROUND_IMAGE_3 = "test-background3.png"
286         private const val SET_WALLPAPER_REQUEST_CODE = 2
287     }
288 }
289