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