1 /*
<lambda>null2  * Copyright (C) 2024 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.android.wallpaper.picker.preview.data.repository
18 
19 import android.app.WallpaperInfo
20 import android.app.WallpaperManager
21 import android.content.ComponentName
22 import android.content.Context
23 import android.content.Intent
24 import android.content.pm.PackageManager
25 import android.net.Uri
26 import android.os.Bundle
27 import android.os.Handler
28 import android.os.Looper
29 import android.service.wallpaper.WallpaperService
30 import android.stats.style.StyleEnums
31 import android.util.Log
32 import com.android.wallpaper.config.BaseFlags
33 import com.android.wallpaper.effects.Effect
34 import com.android.wallpaper.effects.EffectContract
35 import com.android.wallpaper.effects.EffectsController
36 import com.android.wallpaper.effects.EffectsController.EffectEnumInterface
37 import com.android.wallpaper.module.logging.UserEventLogger
38 import com.android.wallpaper.picker.data.LiveWallpaperData
39 import com.android.wallpaper.picker.data.WallpaperId
40 import com.android.wallpaper.picker.data.WallpaperModel
41 import com.android.wallpaper.picker.data.WallpaperModel.LiveWallpaperModel
42 import com.android.wallpaper.picker.data.WallpaperModel.StaticWallpaperModel
43 import com.android.wallpaper.picker.di.modules.BackgroundDispatcher
44 import com.android.wallpaper.picker.preview.data.repository.ImageEffectsRepository.EffectStatus
45 import com.android.wallpaper.picker.preview.shared.model.ImageEffectsModel
46 import com.android.wallpaper.widget.floatingsheetcontent.WallpaperEffectsView2.EffectTextRes
47 import dagger.hilt.android.qualifiers.ApplicationContext
48 import dagger.hilt.android.scopes.ActivityRetainedScoped
49 import java.io.IOException
50 import javax.inject.Inject
51 import kotlinx.coroutines.CoroutineDispatcher
52 import kotlinx.coroutines.flow.MutableStateFlow
53 import kotlinx.coroutines.flow.asStateFlow
54 import kotlinx.coroutines.withContext
55 import org.xmlpull.v1.XmlPullParserException
56 
57 @ActivityRetainedScoped
58 class ImageEffectsRepositoryImpl
59 @Inject
60 constructor(
61     @ApplicationContext private val context: Context,
62     private val effectsController: EffectsController,
63     private val logger: UserEventLogger,
64     @BackgroundDispatcher private val bgDispatcher: CoroutineDispatcher,
65 ) : ImageEffectsRepository {
66     private val _imageEffectsModel =
67         MutableStateFlow(ImageEffectsModel(EffectStatus.EFFECT_DISABLE))
68     override val imageEffectsModel = _imageEffectsModel.asStateFlow()
69     private val _wallpaperEffect = MutableStateFlow<Effect?>(null)
70     override val wallpaperEffect = _wallpaperEffect.asStateFlow()
71     // This StaticWallpaperModel is set when initializing the repository and used for
72     // 1. Providing essential data to construct LiveWallpaperData when effect is enabled
73     // 2. Reverting back to the original static image wallpaper when effect is disabled
74     private lateinit var staticWallpaperModel: StaticWallpaperModel
75     private lateinit var onWallpaperUpdated: (wallpaper: WallpaperModel) -> Unit
76 
77     private val timeOutHandler: Handler = Handler(Looper.getMainLooper())
78     private var startGeneratingTime = 0L
79     private var startDownloadTime = 0L
80 
81     /** Returns whether effects are available at all on the device */
82     override fun areEffectsAvailable(): Boolean {
83         return effectsController.areEffectsAvailable()
84     }
85 
86     override suspend fun initializeEffect(
87         staticWallpaperModel: StaticWallpaperModel,
88         onWallpaperModelUpdated: (wallpaper: WallpaperModel) -> Unit
89     ) {
90         this.staticWallpaperModel = staticWallpaperModel
91         onWallpaperUpdated = onWallpaperModelUpdated
92 
93         withContext(bgDispatcher) {
94             val listener =
95                 EffectsController.EffectsServiceListener {
96                     effect,
97                     bundle,
98                     resultCode,
99                     originalStatusCode,
100                     errorMessage ->
101                     timeOutHandler.removeCallbacksAndMessages(null)
102                     when (resultCode) {
103                         EffectsController.RESULT_PROBE_SUCCESS -> {
104                             _imageEffectsModel.value =
105                                 ImageEffectsModel(EffectStatus.EFFECT_READY, resultCode)
106                         }
107                         EffectsController.RESULT_PROBE_ERROR -> {
108                             // Do nothing intended
109                         }
110                         EffectsController.RESULT_ERROR_PROBE_SUPPORT_FOREGROUND -> {
111                             if (BaseFlags.get().isWallpaperEffectModelDownloadEnabled()) {
112                                 _imageEffectsModel.value =
113                                     ImageEffectsModel(
114                                         EffectStatus.EFFECT_DOWNLOAD_READY,
115                                         resultCode,
116                                     )
117                             }
118                         }
119                         EffectsController.RESULT_PROBE_FOREGROUND_DOWNLOADING -> {
120                             _imageEffectsModel.value =
121                                 ImageEffectsModel(
122                                     EffectStatus.EFFECT_DOWNLOAD_IN_PROGRESS,
123                                     resultCode,
124                                 )
125                         }
126                         EffectsController.RESULT_FOREGROUND_DOWNLOAD_SUCCEEDED -> {
127                             _imageEffectsModel.value =
128                                 ImageEffectsModel(EffectStatus.EFFECT_READY, resultCode)
129                             logger.logEffectForegroundDownload(
130                                 getEffectNameForLogging(),
131                                 StyleEnums.EFFECT_APPLIED_ON_SUCCESS,
132                                 System.currentTimeMillis() - startDownloadTime,
133                             )
134                         }
135                         EffectsController.RESULT_FOREGROUND_DOWNLOAD_FAILED -> {
136                             _imageEffectsModel.value =
137                                 ImageEffectsModel(
138                                     EffectStatus.EFFECT_DOWNLOAD_FAILED,
139                                     resultCode,
140                                     errorMessage,
141                                 )
142                             logger.logEffectForegroundDownload(
143                                 getEffectNameForLogging(),
144                                 StyleEnums.EFFECT_APPLIED_ON_FAILED,
145                                 System.currentTimeMillis() - startDownloadTime,
146                             )
147                         }
148                         EffectsController.RESULT_SUCCESS,
149                         EffectsController.RESULT_SUCCESS_WITH_GENERATION_ERROR -> {
150                             _imageEffectsModel.value =
151                                 ImageEffectsModel(
152                                     EffectStatus.EFFECT_APPLIED,
153                                     resultCode,
154                                     errorMessage,
155                                 )
156                             bundle.getCinematicWallpaperModel(effect)?.let {
157                                 onWallpaperUpdated.invoke(it)
158                             }
159                             logger.logEffectApply(
160                                 getEffectNameForLogging(),
161                                 StyleEnums.EFFECT_APPLIED_ON_SUCCESS,
162                                 /* timeElapsedMillis= */ System.currentTimeMillis() -
163                                     startGeneratingTime,
164                                 /* resultCode= */ originalStatusCode,
165                             )
166                         }
167                         EffectsController.RESULT_SUCCESS_REUSED -> {
168                             _imageEffectsModel.value =
169                                 ImageEffectsModel(EffectStatus.EFFECT_APPLIED, resultCode)
170                             bundle.getCinematicWallpaperModel(effect)?.let {
171                                 onWallpaperUpdated.invoke(it)
172                             }
173                         }
174                         EffectsController.RESULT_ERROR_TRY_AGAIN_LATER -> {
175                             _imageEffectsModel.value =
176                                 ImageEffectsModel(
177                                     EffectStatus.EFFECT_APPLY_FAILED,
178                                     resultCode,
179                                     errorMessage,
180                                 )
181                             logger.logEffectApply(
182                                 getEffectNameForLogging(),
183                                 StyleEnums.EFFECT_APPLIED_ON_FAILED,
184                                 /* timeElapsedMillis= */ System.currentTimeMillis() -
185                                     startGeneratingTime,
186                                 /* resultCode= */ originalStatusCode
187                             )
188                         }
189                         else -> {
190                             _imageEffectsModel.value =
191                                 ImageEffectsModel(
192                                     EffectStatus.EFFECT_APPLY_FAILED,
193                                     resultCode,
194                                     errorMessage,
195                                 )
196                             logger.logEffectApply(
197                                 getEffectNameForLogging(),
198                                 StyleEnums.EFFECT_APPLIED_ON_FAILED,
199                                 /* timeElapsedMillis= */ System.currentTimeMillis() -
200                                     startGeneratingTime,
201                                 /* resultCode= */ originalStatusCode,
202                             )
203                         }
204                     }
205                 }
206             effectsController.setListener(listener)
207 
208             effectsController.contentUri.let { uri ->
209                 if (Uri.EMPTY.equals(uri)) {
210                     return@withContext
211                 }
212 
213                 // Query effect provider
214                 context.contentResolver
215                     .query(
216                         uri,
217                         /* projection= */ null,
218                         /* selection= */ null,
219                         /* selectionArgs= */ null,
220                         /* sortOrder= */ null
221                     )
222                     ?.use {
223                         while (it.moveToNext()) {
224                             val titleRow: Int = it.getColumnIndex(EffectContract.KEY_EFFECT_TITLE)
225                             val idRow: Int = it.getColumnIndex(EffectContract.KEY_EFFECT_ID)
226                             _wallpaperEffect.value =
227                                 Effect(
228                                     it.getInt(idRow),
229                                     it.getString(titleRow),
230                                     effectsController.targetEffect,
231                                 )
232                         }
233                     }
234             }
235 
236             if (effectsController.isEffectTriggered) {
237                 _imageEffectsModel.value = ImageEffectsModel(EffectStatus.EFFECT_READY)
238             } else {
239                 effectsController.triggerEffect(context)
240             }
241         }
242     }
243 
244     private fun Bundle.getCinematicWallpaperModel(
245         effect: EffectEnumInterface
246     ): LiveWallpaperModel? {
247         val componentName =
248             if (containsKey(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT)) {
249                 getParcelable<ComponentName>(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT)
250             } else {
251                 null
252             }
253                 ?: return null
254 
255         val assetId =
256             if (containsKey(EffectContract.ASSET_ID)) {
257                 getInt(EffectContract.ASSET_ID).toString()
258             } else null
259 
260         val resolveInfos =
261             Intent(WallpaperService.SERVICE_INTERFACE)
262                 .apply { setClassName(componentName.packageName, componentName.className) }
263                 .let {
264                     context.packageManager.queryIntentServices(it, PackageManager.GET_META_DATA)
265                 }
266         if (resolveInfos.isEmpty()) {
267             Log.w(TAG, "Couldn't find live wallpaper for " + componentName.className)
268             return null
269         }
270 
271         try {
272             val wallpaperInfo = WallpaperInfo(context, resolveInfos[0])
273             val commonWallpaperData =
274                 staticWallpaperModel.commonWallpaperData.copy(
275                     id =
276                         WallpaperId(
277                             componentName = componentName,
278                             uniqueId =
279                                 if (assetId != null) "${wallpaperInfo.serviceName}_$assetId"
280                                 else wallpaperInfo.serviceName,
281                             collectionId = staticWallpaperModel.commonWallpaperData.id.collectionId,
282                         ),
283                 )
284             val liveWallpaperData =
285                 LiveWallpaperData(
286                     groupName = "",
287                     systemWallpaperInfo = wallpaperInfo,
288                     isTitleVisible = false,
289                     isApplied = false,
290                     isEffectWallpaper = effectsController.isEffectsWallpaper(wallpaperInfo),
291                     effectNames = effect.toString(),
292                 )
293             return LiveWallpaperModel(
294                 commonWallpaperData = commonWallpaperData,
295                 liveWallpaperData = liveWallpaperData,
296                 creativeWallpaperData = null,
297                 internalLiveWallpaperData = null,
298             )
299         } catch (e: XmlPullParserException) {
300             Log.w(TAG, "Skipping wallpaper " + resolveInfos[0].serviceInfo, e)
301             return null
302         } catch (e: IOException) {
303             Log.w(TAG, "Skipping wallpaper " + resolveInfos[0].serviceInfo, e)
304             return null
305         }
306     }
307 
308     override fun enableImageEffect(effect: EffectEnumInterface) {
309         startGeneratingTime = System.currentTimeMillis()
310         _imageEffectsModel.value = ImageEffectsModel(EffectStatus.EFFECT_APPLY_IN_PROGRESS)
311         val uri = staticWallpaperModel.imageWallpaperData?.uri ?: return
312         effectsController.generateEffect(effect, uri)
313         timeOutHandler.postDelayed(
314             {
315                 wallpaperEffect.value?.let { effectsController.interruptGenerate(it) }
316                 _imageEffectsModel.value =
317                     ImageEffectsModel(
318                         EffectStatus.EFFECT_APPLY_FAILED,
319                         null,
320                         effectsController.retryInstruction,
321                     )
322                 logger.logEffectApply(
323                     getEffectNameForLogging(),
324                     StyleEnums.EFFECT_APPLIED_ON_FAILED,
325                     System.currentTimeMillis() - startGeneratingTime,
326                     EffectsController.ERROR_ORIGINAL_TIME_OUT,
327                 )
328             },
329             TIME_OUT_TIME_IN_MS
330         )
331     }
332 
333     override fun disableImageEffect() {
334         _imageEffectsModel.value = ImageEffectsModel(EffectStatus.EFFECT_READY)
335         logger.logEffectApply(
336             wallpaperEffect.value?.type.toString(),
337             StyleEnums.EFFECT_APPLIED_OFF,
338             0L,
339             0,
340         )
341         onWallpaperUpdated.invoke(staticWallpaperModel)
342     }
343 
344     override fun destroy() {
345         timeOutHandler.removeCallbacksAndMessages(null)
346         effectsController.removeListener()
347         // We need to call interruptGenerate() and destroy() to make sure there is no cached effect
348         // wallpaper overriding the currently-selected effect wallpaper preview.
349         wallpaperEffect.value?.let { effectsController.interruptGenerate(it) }
350         effectsController.destroy()
351         _wallpaperEffect.value = null
352     }
353 
354     override fun isTargetEffect(effect: EffectEnumInterface): Boolean {
355         return effectsController.isTargetEffect(effect)
356     }
357 
358     override fun getEffectTextRes(): EffectTextRes {
359         return EffectTextRes(
360             effectsController.effectTitle,
361             effectsController.effectFailedTitle,
362             effectsController.effectSubTitle,
363             effectsController.retryInstruction,
364             effectsController.noEffectInstruction,
365         )
366     }
367 
368     /**
369      * This function triggers the downloading of the machine learning models. The downloading occurs
370      * in the foreground off the main thread so it's safe to trigger it from the main thread.
371      */
372     override fun startEffectsModelDownload(effect: Effect) {
373         _imageEffectsModel.value = ImageEffectsModel(EffectStatus.EFFECT_DOWNLOAD_IN_PROGRESS)
374         effectsController.startForegroundDownload(effect)
375         startDownloadTime = System.currentTimeMillis()
376         logger.logEffectForegroundDownload(
377             getEffectNameForLogging(),
378             StyleEnums.EFFECT_APPLIED_STARTED,
379             0,
380         )
381     }
382 
383     override fun interruptEffectsModelDownload(effect: Effect) {
384         _imageEffectsModel.value = ImageEffectsModel(EffectStatus.EFFECT_DOWNLOAD_READY)
385         effectsController.interruptForegroundDownload(effect)
386     }
387 
388     private fun getEffectNameForLogging(): String {
389         val effect = wallpaperEffect.value
390         return effect?.type?.toString() ?: EffectsController.Effect.UNKNOWN.toString()
391     }
392 
393     companion object {
394         private const val TAG = "EffectsRepository"
395         private const val TIME_OUT_TIME_IN_MS = 90000L
396     }
397 }
398