1 /*
<lambda>null2  * Copyright (C) 2022 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 
18 package com.android.wallpaper.picker.customization.ui.binder
19 
20 import android.app.Activity
21 import android.app.WallpaperColors
22 import android.content.Intent
23 import android.content.pm.ActivityInfo
24 import android.content.res.Configuration
25 import android.graphics.Bitmap
26 import android.graphics.Color
27 import android.graphics.drawable.BitmapDrawable
28 import android.graphics.drawable.ColorDrawable
29 import android.graphics.drawable.Drawable
30 import android.os.Bundle
31 import android.service.wallpaper.WallpaperService
32 import android.view.SurfaceView
33 import android.view.View
34 import android.view.View.OnAttachStateChangeListener
35 import android.view.ViewGroup
36 import android.widget.ImageView
37 import androidx.cardview.widget.CardView
38 import androidx.core.view.isVisible
39 import androidx.lifecycle.DefaultLifecycleObserver
40 import androidx.lifecycle.Lifecycle
41 import androidx.lifecycle.LifecycleOwner
42 import androidx.lifecycle.lifecycleScope
43 import androidx.lifecycle.repeatOnLifecycle
44 import com.android.systemui.monet.ColorScheme
45 import com.android.wallpaper.R
46 import com.android.wallpaper.asset.Asset
47 import com.android.wallpaper.asset.BitmapCachingAsset
48 import com.android.wallpaper.asset.CurrentWallpaperAsset
49 import com.android.wallpaper.config.BaseFlags
50 import com.android.wallpaper.model.LiveWallpaperInfo
51 import com.android.wallpaper.model.Screen
52 import com.android.wallpaper.model.WallpaperInfo
53 import com.android.wallpaper.picker.FixedWidthDisplayRatioFrameLayout
54 import com.android.wallpaper.picker.WorkspaceSurfaceHolderCallback
55 import com.android.wallpaper.picker.customization.animation.view.LoadingAnimation
56 import com.android.wallpaper.picker.customization.ui.section.ScreenPreviewClickView
57 import com.android.wallpaper.picker.customization.ui.view.WallpaperSurfaceView
58 import com.android.wallpaper.picker.customization.ui.viewmodel.AnimationStateViewModel
59 import com.android.wallpaper.picker.customization.ui.viewmodel.ScreenPreviewViewModel
60 import com.android.wallpaper.util.ResourceUtils
61 import com.android.wallpaper.util.WallpaperConnection
62 import com.android.wallpaper.util.WallpaperSurfaceCallback
63 import java.util.concurrent.CompletableFuture
64 import java.util.concurrent.atomic.AtomicBoolean
65 import kotlinx.coroutines.DisposableHandle
66 import kotlinx.coroutines.launch
67 
68 /**
69  * Binds between view and view-model for rendering the preview of the home screen or the lock
70  * screen.
71  */
72 object ScreenPreviewBinder {
73     interface Binding {
74         fun sendMessage(
75             id: Int,
76             args: Bundle = Bundle.EMPTY,
77         )
78 
79         fun destroy()
80 
81         fun surface(): SurfaceView
82     }
83 
84     /**
85      * Binds the view to the given [viewModel].
86      *
87      * Note that if [dimWallpaper] is `true`, the wallpaper will be dimmed (to help highlight
88      * something that is changing on top of the wallpaper, for example, the lock screen shortcuts or
89      * the clock).
90      */
91     // TODO (b/274443705): incorporate color picker to allow preview loading on color change
92     // TODO (b/274443705): make loading animation more continuous on reveal
93     // TODO (b/274443705): adjust for better timing on animation reveal
94     @JvmStatic
95     fun bind(
96         activity: Activity,
97         previewView: CardView,
98         viewModel: ScreenPreviewViewModel,
99         lifecycleOwner: LifecycleOwner,
100         offsetToStart: Boolean,
101         dimWallpaper: Boolean = false,
102         onWallpaperPreviewDirty: () -> Unit,
103         animationStateViewModel: AnimationStateViewModel? = null,
104         isWallpaperAlwaysVisible: Boolean = true,
105         mirrorSurface: SurfaceView? = null,
106         onClick: (() -> Unit)? = null,
107     ): Binding {
108         val workspaceSurface: SurfaceView = previewView.requireViewById(R.id.workspace_surface)
109         val wallpaperSurface: WallpaperSurfaceView =
110             previewView.requireViewById(R.id.wallpaper_surface)
111         val thumbnailRequested = AtomicBoolean(false)
112         // Tracks whether the live preview should be shown, since a) visibility updates may arrive
113         // before the engine is ready, and b) we need this state for onResume
114         // TODO(b/287618705) Remove this
115         val showLivePreview = AtomicBoolean(isWallpaperAlwaysVisible)
116         val fixedWidthDisplayFrameLayout = previewView.parent as? FixedWidthDisplayRatioFrameLayout
117         val screenPreviewClickView = fixedWidthDisplayFrameLayout?.parent as? ScreenPreviewClickView
118         if (screenPreviewClickView != null) {
119             onClick?.let { screenPreviewClickView.setOnPreviewClickedListener(it) }
120         }
121 
122         previewView.isClickable = (onClick != null)
123         onClick?.let { previewView.setOnClickListener { it() } }
124 
125         previewView.contentDescription =
126             activity.resources.getString(viewModel.previewContentDescription)
127 
128         var wallpaperIsReadyForReveal = false
129         val surfaceViewsReady = {
130             wallpaperSurface.setBackgroundColor(Color.TRANSPARENT)
131             workspaceSurface.visibility = View.VISIBLE
132         }
133         wallpaperSurface.setZOrderOnTop(false)
134 
135         val flags = BaseFlags.get()
136         val isPageTransitionsFeatureEnabled = flags.isPageTransitionsFeatureEnabled(activity)
137         val isMultiCropEnabled = flags.isMultiCropEnabled()
138 
139         val showLoadingAnimation =
140             flags.isPreviewLoadingAnimationEnabled(activity.applicationContext)
141         var loadingAnimation: LoadingAnimation? = null
142         val loadingView: ImageView = previewView.requireViewById(R.id.loading_view)
143 
144         if (dimWallpaper) {
145             previewView.requireViewById<View>(R.id.wallpaper_dimming_scrim).isVisible = true
146             workspaceSurface.setZOrderOnTop(true)
147         }
148 
149         previewView.radius =
150             previewView.resources.getDimension(R.dimen.wallpaper_picker_entry_card_corner_radius)
151 
152         var previewSurfaceCallback: WorkspaceSurfaceHolderCallback? = null
153         var wallpaperSurfaceCallback: WallpaperSurfaceCallback? = null
154         var wallpaperConnection: WallpaperConnection? = null
155         var wallpaperInfo: WallpaperInfo? = null
156         var animationState: AnimationStateViewModel.AnimationState? = null
157         var loadingImageDrawable: Drawable? = null
158         var animationTimeToRestore: Long? = null
159         var animationTransitionProgress: Float? = null
160         var animationColorToRestore: Int? = null
161         var currentWallpaperThumbnail: Bitmap? = null
162 
163         var disposableHandle: DisposableHandle? = null
164 
165         val cleanupWallpaperConnectionRunnable = Runnable {
166             disposableHandle?.dispose()
167             wallpaperConnection?.destroy()
168             wallpaperConnection = null
169         }
170 
171         fun cleanupWallpaperConnection() {
172             // If existing, remove any scheduled cleanups...
173             previewView.removeCallbacks(cleanupWallpaperConnectionRunnable)
174             // ...and cleanup immediately
175             cleanupWallpaperConnectionRunnable.run()
176         }
177 
178         val job =
179             lifecycleOwner.lifecycleScope.launch {
180                 launch {
181                     val lifecycleObserver =
182                         object : DefaultLifecycleObserver {
183                             override fun onStart(owner: LifecycleOwner) {
184                                 super.onStart(owner)
185                                 if (showLoadingAnimation) {
186                                     if (loadingAnimation == null) {
187                                         animationState =
188                                             animationStateViewModel?.getAnimationState(
189                                                 viewModel.screen
190                                             )
191                                         loadingImageDrawable = animationState?.drawable
192                                         // TODO (b/290054874): investigate why app restarts twice
193                                         // The lines below are a workaround for the issue of
194                                         // wallpaper picker lifecycle restarting twice after a
195                                         // config change; because of this, on second start, saved
196                                         // instance state would always return null. Instead we would
197                                         // like the saved instance state on the first restart to
198                                         // pass through to the second.
199                                         animationTimeToRestore = animationState?.time
200                                         animationTransitionProgress =
201                                             animationState?.transitionProgress
202                                         animationColorToRestore = animationState?.color
203                                         // a null drawable means the loading animation should not
204                                         // be played
205                                         loadingImageDrawable?.let {
206                                             loadingView.setImageDrawable(it)
207                                             loadingAnimation =
208                                                 LoadingAnimation(
209                                                     loadingView,
210                                                     LoadingAnimation.RevealType.CIRCULAR,
211                                                     LoadingAnimation.TIME_OUT_DURATION_MS
212                                                 )
213                                         }
214                                     }
215                                 }
216                             }
217 
218                             override fun onDestroy(owner: LifecycleOwner) {
219                                 super.onDestroy(owner)
220                                 if (isPageTransitionsFeatureEnabled) {
221                                     cleanupWallpaperConnection()
222                                 }
223                             }
224 
225                             override fun onStop(owner: LifecycleOwner) {
226                                 super.onStop(owner)
227                                 animationTimeToRestore =
228                                     loadingAnimation?.getElapsedTime() ?: animationTimeToRestore
229                                 animationTransitionProgress =
230                                     loadingAnimation?.getTransitionProgress()
231                                         ?: animationTransitionProgress
232                                 loadingAnimation?.end()
233                                 loadingAnimation = null
234                                 // To ensure reveal animation is only played after a theme config
235                                 // change from wallpaper/color switch, only save the current loading
236                                 // image if this is a configuration change restart and reset to
237                                 // null otherwise
238                                 animationStateViewModel?.saveAnimationState(
239                                     viewModel.screen,
240                                     // Check if activity is changing configurations, and check that
241                                     // the set of changing configurations does not include screen
242                                     // size changes (such as rotation and folding/unfolding device)
243                                     // Note: activity.changingConfigurations is not 100% accurate
244                                     if (
245                                         activity.isChangingConfigurations &&
246                                             (activity.changingConfigurations.and(
247                                                 ActivityInfo.CONFIG_SCREEN_SIZE
248                                             ) == 0)
249                                     ) {
250                                         AnimationStateViewModel.AnimationState(
251                                             loadingImageDrawable,
252                                             animationTimeToRestore,
253                                             animationTransitionProgress,
254                                             animationColorToRestore,
255                                         )
256                                     } else null
257                                 )
258                                 wallpaperIsReadyForReveal = false
259                                 if (isPageTransitionsFeatureEnabled) {
260                                     // delay cleanup to prevent flicker between onStop and page
261                                     // transition animation start
262                                     previewView.postDelayed(cleanupWallpaperConnectionRunnable, 100)
263                                 } else {
264                                     cleanupWallpaperConnectionRunnable.run()
265                                 }
266                             }
267 
268                             override fun onPause(owner: LifecycleOwner) {
269                                 super.onPause(owner)
270                                 wallpaperConnection?.setVisibility(false)
271                             }
272                         }
273 
274                     lifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) {
275                         previewSurfaceCallback =
276                             WorkspaceSurfaceHolderCallback(
277                                 workspaceSurface,
278                                 viewModel.previewUtils,
279                                 viewModel.getInitialExtras(),
280                             )
281                         workspaceSurface.holder.addCallback(previewSurfaceCallback)
282                         if (!dimWallpaper) {
283                             workspaceSurface.setZOrderMediaOverlay(true)
284                         }
285 
286                         wallpaperSurfaceCallback =
287                             WallpaperSurfaceCallback(
288                                 previewView.context,
289                                 previewView,
290                                 wallpaperSurface,
291                                 CompletableFuture.completedFuture(
292                                     WallpaperInfo.ColorInfo(
293                                         /* wallpaperColors= */ null,
294                                         ResourceUtils.getColorAttr(
295                                             previewView.context,
296                                             android.R.attr.colorSecondary,
297                                         )
298                                     )
299                                 ),
300                             ) {
301                                 maybeLoadThumbnail(
302                                     activity = activity,
303                                     wallpaperInfo = wallpaperInfo,
304                                     surfaceCallback = wallpaperSurfaceCallback,
305                                     offsetToStart =
306                                         if (isMultiCropEnabled) false else offsetToStart,
307                                     onSurfaceViewsReady = surfaceViewsReady,
308                                     thumbnailRequested = thumbnailRequested
309                                 )
310                                 if (showLoadingAnimation) {
311                                     val colorAccent =
312                                         animationColorToRestore
313                                             ?: ResourceUtils.getColorAttr(
314                                                 activity,
315                                                 android.R.attr.colorAccent
316                                             )
317                                     val night =
318                                         (previewView.resources.configuration.uiMode and
319                                             Configuration.UI_MODE_NIGHT_MASK ==
320                                             Configuration.UI_MODE_NIGHT_YES)
321                                     loadingAnimation?.updateColor(ColorScheme(colorAccent, night))
322                                     loadingAnimation?.setupRevealAnimation(
323                                         animationTimeToRestore,
324                                         animationTransitionProgress
325                                     )
326                                     val isStaticWallpaper =
327                                         wallpaperInfo != null && wallpaperInfo !is LiveWallpaperInfo
328                                     wallpaperIsReadyForReveal =
329                                         isStaticWallpaper || wallpaperIsReadyForReveal
330                                     if (wallpaperIsReadyForReveal) {
331                                         loadingAnimation?.playRevealAnimation()
332                                     }
333                                 }
334                             }
335                         wallpaperSurface.holder.addCallback(wallpaperSurfaceCallback)
336                         if (!dimWallpaper) {
337                             wallpaperSurface.setZOrderMediaOverlay(true)
338                         }
339 
340                         if (!isWallpaperAlwaysVisible) {
341                             wallpaperSurface.visibilityCallback = { visible: Boolean ->
342                                 showLivePreview.set(visible)
343                                 wallpaperConnection?.setVisibility(showLivePreview.get())
344                             }
345                         }
346 
347                         lifecycleOwner.lifecycle.addObserver(lifecycleObserver)
348                     }
349 
350                     lifecycleOwner.lifecycle.removeObserver(lifecycleObserver)
351                     workspaceSurface.holder.removeCallback(previewSurfaceCallback)
352                     previewSurfaceCallback?.cleanUp()
353                     wallpaperSurface.holder.removeCallback(wallpaperSurfaceCallback)
354                     wallpaperSurfaceCallback?.homeImageWallpaper?.post {
355                         wallpaperSurfaceCallback?.cleanUp()
356                     }
357                 }
358 
359                 launch {
360                     lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
361                         var initialWallpaperUpdate = true
362                         viewModel.shouldReloadWallpaper().collect { shouldReload ->
363                             viewModel.getWallpaperInfo(forceReload = shouldReload)
364                             // Do not update screen preview on initial update,since the initial
365                             // update results from starting or resuming the activity.
366                             if (initialWallpaperUpdate) {
367                                 initialWallpaperUpdate = false
368                             } else if (shouldReload) {
369                                 onWallpaperPreviewDirty()
370                             }
371                         }
372                     }
373                 }
374 
375                 launch {
376                     lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
377                         viewModel.wallpaperThumbnail().collect { thumbnail ->
378                             currentWallpaperThumbnail = thumbnail
379                         }
380                     }
381                 }
382 
383                 launch {
384                     lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
385                         viewModel.workspaceUpdateEvents()?.collect {
386                             workspaceSurface.holder.removeCallback(previewSurfaceCallback)
387                             previewSurfaceCallback?.cleanUp()
388                             removeAndReadd(workspaceSurface)
389                             previewSurfaceCallback =
390                                 WorkspaceSurfaceHolderCallback(
391                                     workspaceSurface,
392                                     viewModel.previewUtils,
393                                     viewModel.getInitialExtras(),
394                                 )
395                             workspaceSurface.holder.addCallback(previewSurfaceCallback)
396                         }
397                     }
398                 }
399 
400                 if (showLoadingAnimation) {
401                     launch {
402                         lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
403                             viewModel.isLoading.collect { isLoading ->
404                                 if (isLoading) {
405                                     loadingAnimation?.cancel()
406 
407                                     // Loading is started, create a new loading animation
408                                     // with the current wallpaper as background.
409                                     // First, try to get the wallpaper image from
410                                     // wallpaperSurfaceCallback, this is the best solution for
411                                     // static and live wallpapers but not for creative wallpapers
412                                     val wallpaperPreviewImage =
413                                         wallpaperSurfaceCallback?.homeImageWallpaper
414                                     // If wallpaper drawable was not loaded, and the preview
415                                     // drawable is the placeholder color drawable, use the wallpaper
416                                     // thumbnail instead: the best solution for creative wallpapers
417                                     val animationBackground: Drawable? =
418                                         if (wallpaperPreviewImage?.drawable is ColorDrawable) {
419                                             currentWallpaperThumbnail?.let { thumbnail ->
420                                                 BitmapDrawable(activity.resources, thumbnail)
421                                             } ?: wallpaperPreviewImage.drawable
422                                         } else wallpaperPreviewImage?.drawable
423                                     animationBackground?.let {
424                                         loadingView.setImageDrawable(animationBackground)
425                                         loadingAnimation =
426                                             LoadingAnimation(
427                                                 loadingView,
428                                                 LoadingAnimation.RevealType.CIRCULAR,
429                                                 LoadingAnimation.TIME_OUT_DURATION_MS
430                                             )
431                                     }
432                                     loadingImageDrawable = animationBackground
433                                     val colorAccent =
434                                         ResourceUtils.getColorAttr(
435                                             activity,
436                                             android.R.attr.colorAccent
437                                         )
438                                     val night =
439                                         (previewView.resources.configuration.uiMode and
440                                             Configuration.UI_MODE_NIGHT_MASK ==
441                                             Configuration.UI_MODE_NIGHT_YES)
442                                     animationColorToRestore = colorAccent
443                                     loadingAnimation?.updateColor(ColorScheme(colorAccent, night))
444                                     loadingAnimation?.playLoadingAnimation()
445                                 }
446                             }
447                         }
448                     }
449                 }
450 
451                 launch {
452                     lifecycleOwner.repeatOnLifecycle(
453                         if (isPageTransitionsFeatureEnabled) {
454                             Lifecycle.State.STARTED
455                         } else {
456                             Lifecycle.State.RESUMED
457                         }
458                     ) {
459                         lifecycleOwner.lifecycleScope.launch {
460                             wallpaperInfo = viewModel.getWallpaperInfo(forceReload = false)
461                             maybeLoadThumbnail(
462                                 activity = activity,
463                                 wallpaperInfo = wallpaperInfo,
464                                 surfaceCallback = wallpaperSurfaceCallback,
465                                 offsetToStart = if (isMultiCropEnabled) false else offsetToStart,
466                                 onSurfaceViewsReady = surfaceViewsReady,
467                                 thumbnailRequested = thumbnailRequested
468                             )
469                             if (showLoadingAnimation && wallpaperInfo !is LiveWallpaperInfo) {
470                                 loadingAnimation?.playRevealAnimation()
471                             }
472                             (wallpaperInfo as? LiveWallpaperInfo)?.let { liveWallpaperInfo ->
473                                 if (isPageTransitionsFeatureEnabled) {
474                                     cleanupWallpaperConnection()
475                                 }
476                                 val connection =
477                                     wallpaperConnection
478                                         ?: createWallpaperConnection(
479                                                 liveWallpaperInfo,
480                                                 previewView,
481                                                 viewModel,
482                                                 wallpaperSurface,
483                                                 mirrorSurface,
484                                                 viewModel.screen
485                                             ) {
486                                                 surfaceViewsReady()
487                                                 if (showLoadingAnimation) {
488                                                     wallpaperIsReadyForReveal = true
489                                                     loadingAnimation?.playRevealAnimation()
490                                                 }
491                                             }
492                                             .also { wallpaperConnection = it }
493                                 if (!previewView.isAttachedToWindow) {
494                                     // Sometimes the service gets connected before the view
495                                     // is valid.
496                                     // TODO(b/284233455): investigate why and remove this workaround
497                                     val listener =
498                                         object : OnAttachStateChangeListener {
499                                             override fun onViewAttachedToWindow(v: View) {
500                                                 connection.connect()
501                                                 connection.setVisibility(showLivePreview.get())
502                                                 previewView.removeOnAttachStateChangeListener(this)
503                                             }
504 
505                                             override fun onViewDetachedFromWindow(v: View) {
506                                                 // Do nothing
507                                             }
508                                         }
509 
510                                     previewView.addOnAttachStateChangeListener(listener)
511                                     disposableHandle = DisposableHandle {
512                                         previewView.removeOnAttachStateChangeListener(listener)
513                                     }
514                                 } else {
515                                     connection.connect()
516                                     connection.setVisibility(showLivePreview.get())
517                                 }
518                             }
519                         }
520                     }
521                 }
522             }
523 
524         return object : Binding {
525             override fun sendMessage(id: Int, args: Bundle) {
526                 previewSurfaceCallback?.send(id, args)
527             }
528 
529             override fun destroy() {
530                 job.cancel()
531                 // We want to remove the SurfaceView from its parent and add it back. This causes
532                 // the hierarchy to treat the SurfaceView as "dirty" which will cause it to render
533                 // itself anew the next time the bind function is invoked.
534                 removeAndReadd(workspaceSurface)
535             }
536 
537             override fun surface(): SurfaceView {
538                 return wallpaperSurface
539             }
540         }
541     }
542 
543     private fun createWallpaperConnection(
544         liveWallpaperInfo: LiveWallpaperInfo,
545         previewView: CardView,
546         viewModel: ScreenPreviewViewModel,
547         wallpaperSurface: SurfaceView,
548         mirrorSurface: SurfaceView?,
549         screen: Screen,
550         onEngineShown: () -> Unit
551     ) =
552         WallpaperConnection(
553             Intent(WallpaperService.SERVICE_INTERFACE).apply {
554                 setClassName(
555                     liveWallpaperInfo.wallpaperComponent.packageName,
556                     liveWallpaperInfo.wallpaperComponent.serviceName
557                 )
558             },
559             previewView.context,
560             object : WallpaperConnection.WallpaperConnectionListener {
561                 override fun onWallpaperColorsChanged(colors: WallpaperColors?, displayId: Int) {
562                     viewModel.onWallpaperColorsChanged(colors)
563                 }
564 
565                 override fun onEngineShown() {
566                     onEngineShown()
567                 }
568             },
569             wallpaperSurface,
570             mirrorSurface,
571             screen.toFlag(),
572             WallpaperConnection.WhichPreview.PREVIEW_CURRENT
573         )
574 
575     private fun removeAndReadd(view: View) {
576         (view.parent as? ViewGroup)?.let { parent ->
577             val indexInParent = parent.indexOfChild(view)
578             if (indexInParent >= 0) {
579                 parent.removeView(view)
580                 parent.addView(view, indexInParent)
581             }
582         }
583     }
584 
585     private fun maybeLoadThumbnail(
586         activity: Activity,
587         wallpaperInfo: WallpaperInfo?,
588         surfaceCallback: WallpaperSurfaceCallback?,
589         offsetToStart: Boolean,
590         onSurfaceViewsReady: () -> Unit,
591         thumbnailRequested: AtomicBoolean
592     ) {
593         if (wallpaperInfo == null || surfaceCallback == null) {
594             return
595         }
596 
597         val imageView = surfaceCallback.homeImageWallpaper
598         val thumbAsset: Asset = wallpaperInfo.getThumbAsset(activity)
599         if (imageView != null && imageView.drawable == null) {
600             if (!thumbnailRequested.compareAndSet(false, true)) {
601                 return
602             }
603             // Respect offsetToStart only for CurrentWallpaperAssetVN otherwise true.
604             BitmapCachingAsset(activity, thumbAsset)
605                 .loadPreviewImage(
606                     activity,
607                     imageView,
608                     ResourceUtils.getColorAttr(activity, android.R.attr.colorSecondary),
609                     /* offsetToStart= */ thumbAsset !is CurrentWallpaperAsset || offsetToStart,
610                     wallpaperInfo.wallpaperCropHints
611                 )
612             if (wallpaperInfo !is LiveWallpaperInfo) {
613                 imageView.addOnLayoutChangeListener(
614                     object : View.OnLayoutChangeListener {
615                         override fun onLayoutChange(
616                             v: View?,
617                             left: Int,
618                             top: Int,
619                             right: Int,
620                             bottom: Int,
621                             oldLeft: Int,
622                             oldTop: Int,
623                             oldRight: Int,
624                             oldBottom: Int
625                         ) {
626                             v?.removeOnLayoutChangeListener(this)
627                             onSurfaceViewsReady()
628                         }
629                     }
630                 )
631             }
632         }
633     }
634 }
635