• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
18 package com.android.wallpaper.picker.customization.data.content
19 
20 import android.app.Flags.liveWallpaperContentHandling
21 import android.app.WallpaperColors
22 import android.app.WallpaperManager
23 import android.app.WallpaperManager.FLAG_LOCK
24 import android.app.WallpaperManager.FLAG_SYSTEM
25 import android.app.WallpaperManager.SetWallpaperFlags
26 import android.app.wallpaper.WallpaperDescription
27 import android.content.ComponentName
28 import android.content.ContentResolver
29 import android.content.ContentValues
30 import android.content.Context
31 import android.database.ContentObserver
32 import android.graphics.Bitmap
33 import android.graphics.BitmapFactory
34 import android.graphics.Color
35 import android.graphics.Point
36 import android.graphics.Rect
37 import android.net.Uri
38 import android.os.Handler
39 import android.util.Log
40 import androidx.exifinterface.media.ExifInterface
41 import com.android.app.tracing.TraceUtils.traceAsync
42 import com.android.wallpaper.asset.Asset
43 import com.android.wallpaper.asset.BitmapUtils
44 import com.android.wallpaper.asset.CurrentWallpaperAsset
45 import com.android.wallpaper.asset.StreamableAsset
46 import com.android.wallpaper.model.LiveWallpaperPrefMetadata
47 import com.android.wallpaper.model.Screen
48 import com.android.wallpaper.model.StaticWallpaperPrefMetadata
49 import com.android.wallpaper.model.WallpaperInfo
50 import com.android.wallpaper.model.WallpaperModelsPair
51 import com.android.wallpaper.module.InjectorProvider
52 import com.android.wallpaper.module.RecentWallpaperManager
53 import com.android.wallpaper.module.WallpaperPreferences
54 import com.android.wallpaper.module.logging.UserEventLogger
55 import com.android.wallpaper.module.logging.UserEventLogger.SetWallpaperEntryPoint
56 import com.android.wallpaper.picker.customization.shared.model.WallpaperDestination
57 import com.android.wallpaper.picker.customization.shared.model.WallpaperDestination.BOTH
58 import com.android.wallpaper.picker.customization.shared.model.WallpaperDestination.Companion.toDestinationInt
59 import com.android.wallpaper.picker.customization.shared.model.WallpaperDestination.Companion.toSetWallpaperFlags
60 import com.android.wallpaper.picker.customization.shared.model.WallpaperDestination.HOME
61 import com.android.wallpaper.picker.customization.shared.model.WallpaperDestination.LOCK
62 import com.android.wallpaper.picker.customization.shared.model.WallpaperModel as RecentWallpaperModel
63 import com.android.wallpaper.picker.data.WallpaperModel.LiveWallpaperModel
64 import com.android.wallpaper.picker.data.WallpaperModel.StaticWallpaperModel
65 import com.android.wallpaper.picker.di.modules.BackgroundDispatcher
66 import com.android.wallpaper.picker.preview.shared.model.FullPreviewCropModel
67 import com.android.wallpaper.util.CurrentWallpaperInfoUtils.getCurrentWallpapers
68 import com.android.wallpaper.util.WallpaperCropUtils
69 import com.android.wallpaper.util.converter.WallpaperModelFactory
70 import dagger.hilt.android.qualifiers.ApplicationContext
71 import java.io.IOException
72 import java.io.InputStream
73 import javax.inject.Inject
74 import javax.inject.Singleton
75 import kotlinx.coroutines.CancellableContinuation
76 import kotlinx.coroutines.CoroutineScope
77 import kotlinx.coroutines.flow.MutableStateFlow
78 import kotlinx.coroutines.flow.asStateFlow
79 import kotlinx.coroutines.flow.filterNotNull
80 import kotlinx.coroutines.flow.map
81 import kotlinx.coroutines.launch
82 import kotlinx.coroutines.suspendCancellableCoroutine
83 
84 @Singleton
85 class WallpaperClientImpl
86 @Inject
87 constructor(
88     @ApplicationContext private val context: Context,
89     private val wallpaperManager: WallpaperManager,
90     private val wallpaperPreferences: WallpaperPreferences,
91     private val wallpaperModelFactory: WallpaperModelFactory,
92     private val logger: UserEventLogger,
93     private val recentWallpaperManager: RecentWallpaperManager,
94     @BackgroundDispatcher val backgroundScope: CoroutineScope,
95 ) : WallpaperClient {
96 
97     private var recentsContentProviderAvailable: Boolean? = null
98     private val recentHomeWallpapers = MutableStateFlow<List<RecentWallpaperModel>?>(null)
99     private val recentLockWallpapers = MutableStateFlow<List<RecentWallpaperModel>?>(null)
100 
101     init {
102         backgroundScope.launch {
103             recentHomeWallpapers.value = queryRecentWallpapers(destination = HOME)
104             recentLockWallpapers.value = queryRecentWallpapers(destination = LOCK)
105         }
106 
107         if (areRecentsAvailable()) {
108             context.contentResolver.registerContentObserver(
109                 LIST_RECENTS_URI,
110                 /* notifyForDescendants= */ true,
111                 object : ContentObserver(null) {
112                     override fun onChange(selfChange: Boolean) {
113                         backgroundScope.launch {
114                             recentHomeWallpapers.value = queryRecentWallpapers(destination = HOME)
115                             recentLockWallpapers.value = queryRecentWallpapers(destination = LOCK)
116                         }
117                     }
118                 },
119             )
120         }
121     }
122 
123     override fun recentWallpapers(destination: WallpaperDestination, limit: Int) =
124         when (destination) {
125             HOME ->
126                 recentHomeWallpapers.asStateFlow().filterNotNull().map { wallpapers ->
127                     if (wallpapers.size > limit) {
128                         wallpapers.subList(0, limit)
129                     } else {
130                         wallpapers
131                     }
132                 }
133             LOCK ->
134                 recentLockWallpapers.asStateFlow().filterNotNull().map { wallpapers ->
135                     if (wallpapers.size > limit) {
136                         wallpapers.subList(0, limit)
137                     } else {
138                         wallpapers
139                     }
140                 }
141             BOTH ->
142                 throw IllegalStateException(
143                     "Destination $destination should not be used for getting recent wallpapers."
144                 )
145         }
146 
147     override suspend fun setStaticWallpaper(
148         @SetWallpaperEntryPoint setWallpaperEntryPoint: Int,
149         destination: WallpaperDestination,
150         wallpaperModel: StaticWallpaperModel,
151         bitmap: Bitmap,
152         wallpaperSize: Point,
153         asset: Asset,
154         fullPreviewCropModels: Map<Point, FullPreviewCropModel>?,
155     ) {
156         if (destination == HOME || destination == BOTH) {
157             // Disable rotation wallpaper when setting to home screen. Daily rotation rotates
158             // both home and lock screen wallpaper when lock screen is not set; otherwise daily
159             // rotation only rotates home screen while lock screen wallpaper stays as what it's
160             // set to.
161             stopWallpaperRotation()
162         }
163 
164         traceAsync(TAG, "setStaticWallpaper") {
165             val cropHintsWithParallax =
166                 fullPreviewCropModels?.let { cropModels ->
167                     cropModels.mapValues { it.value.adjustCropForParallax(wallpaperSize) }
168                 } ?: emptyMap()
169             val managerId =
170                 wallpaperManager.setStaticWallpaperToSystem(
171                     asset.getStreamOrFromBitmap(bitmap),
172                     bitmap,
173                     cropHintsWithParallax,
174                     destination,
175                     asset,
176                 )
177 
178             wallpaperPreferences.setStaticWallpaperMetadata(
179                 metadata = wallpaperModel.getMetadata(bitmap, managerId),
180                 destination = destination,
181             )
182 
183             logger.logWallpaperApplied(
184                 collectionId = wallpaperModel.commonWallpaperData.id.collectionId,
185                 wallpaperId = wallpaperModel.commonWallpaperData.id.wallpaperId,
186                 effects = null,
187                 setWallpaperEntryPoint = setWallpaperEntryPoint,
188                 destination =
189                     UserEventLogger.toWallpaperDestinationForLogging(destination.toDestinationInt()),
190             )
191 
192             // Save the static wallpaper to recent wallpapers
193             // TODO(b/309138446): check if we can update recent with all cropHints from WM later
194             wallpaperPreferences.addStaticWallpaperToRecentWallpapers(
195                 destination,
196                 wallpaperModel,
197                 bitmap,
198                 cropHintsWithParallax,
199             )
200         }
201     }
202 
203     private fun stopWallpaperRotation() {
204         wallpaperPreferences.setWallpaperPresentationMode(
205             WallpaperPreferences.PRESENTATION_MODE_STATIC
206         )
207         wallpaperPreferences.clearDailyRotations()
208     }
209 
210     /**
211      * Use [WallpaperManager] to set a static wallpaper to the system.
212      *
213      * @return Wallpaper manager ID
214      */
215     private fun WallpaperManager.setStaticWallpaperToSystem(
216         inputStream: InputStream?,
217         bitmap: Bitmap,
218         cropHints: Map<Point, Rect>,
219         destination: WallpaperDestination,
220         asset: Asset,
221     ): Int {
222         // The InputStream of current wallpaper points to system wallpaper file which will be
223         // overwritten during set wallpaper and reads 0 bytes, use Bitmap instead.
224         return if (inputStream != null && asset !is CurrentWallpaperAsset) {
225             setStreamWithCrops(
226                 inputStream,
227                 cropHints,
228                 /* allowBackup= */ true,
229                 destination.toSetWallpaperFlags(),
230             )
231         } else {
232             setBitmapWithCrops(
233                 bitmap,
234                 cropHints,
235                 /* allowBackup= */ true,
236                 destination.toSetWallpaperFlags(),
237             )
238         }
239     }
240 
241     private fun StaticWallpaperModel.getMetadata(
242         bitmap: Bitmap,
243         managerId: Int,
244     ): StaticWallpaperPrefMetadata {
245         val bitmapHash = BitmapUtils.generateHashCode(bitmap)
246         return StaticWallpaperPrefMetadata(
247             commonWallpaperData.attributions,
248             commonWallpaperData.exploreActionUrl,
249             commonWallpaperData.id.collectionId,
250             bitmapHash,
251             managerId,
252             commonWallpaperData.id.uniqueId,
253             imageWallpaperData?.uri,
254         )
255     }
256 
257     /**
258      * Save wallpaper metadata in the preference for two purposes:
259      * 1. Quickly reconstruct the currently-selected wallpaper when opening the app
260      * 2. Snapshot logging
261      */
262     private fun WallpaperPreferences.setStaticWallpaperMetadata(
263         metadata: StaticWallpaperPrefMetadata,
264         destination: WallpaperDestination,
265     ) {
266         when (destination) {
267             HOME -> {
268                 clearHomeWallpaperMetadata()
269                 setHomeStaticImageWallpaperMetadata(metadata)
270             }
271             LOCK -> {
272                 clearLockWallpaperMetadata()
273                 setLockStaticImageWallpaperMetadata(metadata)
274             }
275             BOTH -> {
276                 clearHomeWallpaperMetadata()
277                 setHomeStaticImageWallpaperMetadata(metadata)
278                 clearLockWallpaperMetadata()
279                 setLockStaticImageWallpaperMetadata(metadata)
280             }
281         }
282     }
283 
284     override suspend fun setLiveWallpaper(
285         setWallpaperEntryPoint: Int,
286         destination: WallpaperDestination,
287         wallpaperModel: LiveWallpaperModel,
288     ) {
289         if (destination == HOME || destination == BOTH) {
290             // Disable rotation wallpaper when setting to home screen. Daily rotation rotates
291             // both home and lock screen wallpaper when lock screen is not set; otherwise daily
292             // rotation only rotates home screen while lock screen wallpaper stays as what it's
293             // set to.
294             stopWallpaperRotation()
295         }
296 
297         traceAsync(TAG, "setLiveWallpaper") {
298             val managerId = wallpaperManager.setLiveWallpaperToSystem(wallpaperModel, destination)
299 
300             wallpaperPreferences.setLiveWallpaperMetadata(
301                 metadata = wallpaperModel.getMetadata(managerId),
302                 destination = destination,
303             )
304 
305             logger.logWallpaperApplied(
306                 collectionId = wallpaperModel.commonWallpaperData.id.collectionId,
307                 wallpaperId = wallpaperModel.commonWallpaperData.id.wallpaperId,
308                 effects = wallpaperModel.liveWallpaperData.effectNames,
309                 setWallpaperEntryPoint = setWallpaperEntryPoint,
310                 destination =
311                     UserEventLogger.toWallpaperDestinationForLogging(destination.toDestinationInt()),
312             )
313 
314             wallpaperPreferences.addLiveWallpaperToRecentWallpapers(destination, wallpaperModel)
315         }
316     }
317 
318     private fun tryAndroidBSetComponent(
319         wallpaperModel: LiveWallpaperModel,
320         destination: WallpaperDestination,
321     ): Boolean {
322         try {
323             val method =
324                 wallpaperManager.javaClass.getMethod(
325                     "setWallpaperComponentWithDescription",
326                     WallpaperDescription::class.java,
327                     Int::class.javaPrimitiveType,
328                 )
329             method.invoke(
330                 wallpaperManager,
331                 wallpaperModel.liveWallpaperData.description,
332                 destination.toSetWallpaperFlags(),
333             )
334             return true
335         } catch (e: NoSuchMethodException) {
336             return false
337         }
338     }
339 
340     private fun tryAndroidUSetComponent(
341         wallpaperModel: LiveWallpaperModel,
342         destination: WallpaperDestination,
343     ): Boolean {
344         try {
345             val method =
346                 wallpaperManager.javaClass.getMethod(
347                     "setWallpaperComponentWithFlags",
348                     ComponentName::class.java,
349                     Int::class.javaPrimitiveType,
350                 )
351             method.invoke(
352                 wallpaperManager,
353                 wallpaperModel.commonWallpaperData.id.componentName,
354                 destination.toSetWallpaperFlags(),
355             )
356             if (liveWallpaperContentHandling()) {
357                 Log.w(
358                     TAG,
359                     "live wallpaper content handling enabled, but Android U setWallpaperComponentWithFlags called",
360                 )
361             }
362             return true
363         } catch (e: NoSuchMethodException) {
364             return false
365         }
366     }
367 
368     /**
369      * Use [WallpaperManager] to set a live wallpaper to the system.
370      *
371      * @return Wallpaper manager ID
372      */
373     private fun WallpaperManager.setLiveWallpaperToSystem(
374         wallpaperModel: LiveWallpaperModel,
375         destination: WallpaperDestination,
376     ): Int {
377         if (tryAndroidBSetComponent(wallpaperModel, destination)) {
378             // intentional no-op
379         } else if (tryAndroidUSetComponent(wallpaperModel, destination)) {
380             // intentional no-op
381         } else {
382             setWallpaperComponent(wallpaperModel.commonWallpaperData.id.componentName)
383         }
384 
385         // Be careful that WallpaperManager.getWallpaperId can only accept either
386         // WallpaperManager.FLAG_SYSTEM or WallpaperManager.FLAG_LOCK.
387         // If destination is BOTH, either flag should return the same wallpaper manager ID.
388         return getWallpaperId(
389             if (destination == BOTH || destination == HOME) FLAG_SYSTEM else FLAG_LOCK
390         )
391     }
392 
393     private fun LiveWallpaperModel.getMetadata(managerId: Int): LiveWallpaperPrefMetadata {
394         return LiveWallpaperPrefMetadata(
395             commonWallpaperData.attributions,
396             liveWallpaperData.systemWallpaperInfo.serviceName,
397             liveWallpaperData.effectNames,
398             commonWallpaperData.id.collectionId,
399             managerId,
400         )
401     }
402 
403     /**
404      * Save wallpaper metadata in the preference for two purposes:
405      * 1. Quickly reconstruct the currently-selected wallpaper when opening the app
406      * 2. Snapshot logging
407      */
408     private fun WallpaperPreferences.setLiveWallpaperMetadata(
409         metadata: LiveWallpaperPrefMetadata,
410         destination: WallpaperDestination,
411     ) {
412         when (destination) {
413             HOME -> {
414                 clearHomeWallpaperMetadata()
415                 setHomeLiveWallpaperMetadata(metadata)
416             }
417             LOCK -> {
418                 clearLockWallpaperMetadata()
419                 setLockLiveWallpaperMetadata(metadata)
420             }
421             BOTH -> {
422                 clearHomeWallpaperMetadata()
423                 setHomeLiveWallpaperMetadata(metadata)
424                 clearLockWallpaperMetadata()
425                 setLockLiveWallpaperMetadata(metadata)
426             }
427         }
428     }
429 
430     override suspend fun setRecentWallpaper(
431         @SetWallpaperEntryPoint setWallpaperEntryPoint: Int,
432         destination: WallpaperDestination,
433         wallpaperId: String,
434         onDone: () -> Unit,
435     ) {
436         val updateValues = ContentValues()
437         updateValues.put(KEY_ID, wallpaperId)
438         updateValues.put(KEY_SCREEN, destination.asString())
439         updateValues.put(KEY_SET_WALLPAPER_ENTRY_POINT, setWallpaperEntryPoint)
440         traceAsync(TAG, "setRecentWallpaper") {
441             val updatedRowCount =
442                 context.contentResolver.update(SET_WALLPAPER_URI, updateValues, null)
443             if (updatedRowCount == 0) {
444                 Log.e(TAG, "Error setting wallpaper: $wallpaperId")
445             }
446             onDone.invoke()
447         }
448     }
449 
450     private suspend fun queryRecentWallpapers(
451         destination: WallpaperDestination
452     ): List<RecentWallpaperModel> =
453         if (!areRecentsAvailable()) {
454             listOf(getCurrentWallpaperFromFactory(destination))
455         } else {
456             queryAllRecentWallpapers(destination)
457         }
458 
459     private fun queryAllRecentWallpapers(
460         destination: WallpaperDestination
461     ): List<RecentWallpaperModel> {
462         context.contentResolver
463             .query(
464                 LIST_RECENTS_URI.buildUpon().appendPath(destination.asString()).build(),
465                 arrayOf(KEY_ID, KEY_PLACEHOLDER_COLOR, KEY_LAST_UPDATED),
466                 null,
467                 null,
468             )
469             .use { cursor ->
470                 if (cursor == null || cursor.count == 0) {
471                     return emptyList()
472                 }
473 
474                 return buildList {
475                     val idColumnIndex = cursor.getColumnIndex(KEY_ID)
476                     val placeholderColorColumnIndex = cursor.getColumnIndex(KEY_PLACEHOLDER_COLOR)
477                     val lastUpdatedColumnIndex = cursor.getColumnIndex(KEY_LAST_UPDATED)
478                     val titleColumnIndex = cursor.getColumnIndex(TITLE)
479                     while (cursor.moveToNext()) {
480                         val wallpaperId = cursor.getString(idColumnIndex)
481                         val placeholderColor = cursor.getInt(placeholderColorColumnIndex)
482                         val lastUpdated = cursor.getLong(lastUpdatedColumnIndex)
483                         val title =
484                             if (titleColumnIndex > -1) cursor.getString(titleColumnIndex) else null
485 
486                         add(
487                             RecentWallpaperModel(
488                                 wallpaperId = wallpaperId,
489                                 placeholderColor = placeholderColor,
490                                 lastUpdated = lastUpdated,
491                                 title = title,
492                             )
493                         )
494                     }
495                 }
496             }
497     }
498 
499     private suspend fun getCurrentWallpaperFromFactory(
500         destination: WallpaperDestination
501     ): RecentWallpaperModel {
502         val currentWallpapers =
503             getCurrentWallpapers(context, updateRecents = false, forceRefresh = false) {
504                 info,
505                 screen ->
506                 recentWallpaperManager.getCurrentWallpaperBitmapUri(info, screen)
507             }
508         val wallpaper: WallpaperInfo =
509             if (destination == LOCK) {
510                 currentWallpapers.second
511             } else {
512                 currentWallpapers.first
513             }
514         val colors = wallpaperManager.getWallpaperColors(destination.toSetWallpaperFlags())
515 
516         return RecentWallpaperModel(
517             wallpaperId = wallpaper.wallpaperId,
518             placeholderColor = colors?.primaryColor?.toArgb() ?: Color.TRANSPARENT,
519             title = wallpaper.getTitle(context),
520         )
521     }
522 
523     override suspend fun getCurrentWallpaperModels(forceRefresh: Boolean): WallpaperModelsPair {
524         val currentWallpapers =
525             getCurrentWallpapers(context, updateRecents = false, forceRefresh) { info, screen ->
526                 recentWallpaperManager.getCurrentWallpaperBitmapUri(info, screen)
527             }
528         val homeWallpaper = currentWallpapers.first
529         val lockWallpaper = currentWallpapers.second
530         return WallpaperModelsPair(
531             wallpaperModelFactory.getWallpaperModel(context, homeWallpaper),
532             wallpaperModelFactory.getWallpaperModel(context, lockWallpaper),
533         )
534     }
535 
536     override suspend fun loadThumbnail(
537         wallpaperId: String,
538         destination: WallpaperDestination,
539     ): Bitmap? {
540         if (areRecentsAvailable()) {
541             try {
542                 // We're already using this in a suspend function, so we're okay.
543                 @Suppress("BlockingMethodInNonBlockingContext")
544                 context.contentResolver
545                     .openFile(
546                         GET_THUMBNAIL_BASE_URI.buildUpon()
547                             .appendPath(wallpaperId)
548                             .appendQueryParameter(KEY_DESTINATION, destination.asString())
549                             .build(),
550                         "r",
551                         null,
552                     )
553                     .use { file ->
554                         if (file == null) {
555                             Log.e(TAG, "Error getting wallpaper preview: $wallpaperId")
556                         } else {
557                             return BitmapFactory.decodeFileDescriptor(file.fileDescriptor)
558                         }
559                     }
560             } catch (e: IOException) {
561                 Log.e(
562                     TAG,
563                     "Error getting wallpaper preview: $wallpaperId, destination: ${destination.asString()}",
564                     e,
565                 )
566             }
567         } else {
568             val currentWallpapers =
569                 getCurrentWallpapers(context, updateRecents = false, forceRefresh = false) {
570                     info,
571                     screen ->
572                     recentWallpaperManager.getCurrentWallpaperBitmapUri(info, screen)
573                 }
574             val wallpaper =
575                 if (currentWallpapers.first.wallpaperId == wallpaperId) {
576                     currentWallpapers.first
577                 } else if (currentWallpapers.second.wallpaperId == wallpaperId) {
578                     currentWallpapers.second
579                 } else null
580             return wallpaper?.getThumbAsset(context)?.getLowResBitmap(context)
581         }
582 
583         return null
584     }
585 
586     override fun areRecentsAvailable(): Boolean {
587         if (recentsContentProviderAvailable == null) {
588             recentsContentProviderAvailable =
589                 try {
590                     context.packageManager.resolveContentProvider(AUTHORITY, 0) != null
591                 } catch (e: Exception) {
592                     Log.w(
593                         TAG,
594                         "Exception trying to resolve recents content provider, skipping it",
595                         e,
596                     )
597                     false
598                 }
599         }
600         return recentsContentProviderAvailable == true
601     }
602 
603     override fun getCurrentCropHints(
604         displaySizes: List<Point>,
605         @SetWallpaperFlags which: Int,
606     ): Map<Point, Rect>? {
607         val flags = InjectorProvider.getInjector().getFlags()
608         if (!flags.isMultiCropEnabled()) {
609             return null
610         }
611         val cropHints: List<Rect>? =
612             wallpaperManager.getBitmapCrops(displaySizes, which, /* originalBitmap= */ true)
613 
614         return cropHints?.indices?.associate { displaySizes[it] to cropHints[it] }
615     }
616 
617     override suspend fun getWallpaperColors(
618         bitmap: Bitmap,
619         cropHints: Map<Point, Rect>?,
620     ): WallpaperColors? {
621         return wallpaperManager.getWallpaperColors(bitmap, cropHints)
622     }
623 
624     override fun getWallpaperColors(screen: Screen): WallpaperColors? {
625         return wallpaperManager.getWallpaperColors(
626             if (screen == Screen.LOCK_SCREEN) {
627                 FLAG_LOCK
628             } else {
629                 FLAG_SYSTEM
630             }
631         )
632     }
633 
634     fun WallpaperDestination.asString(): String {
635         return when (this) {
636             BOTH -> SCREEN_ALL
637             HOME -> SCREEN_HOME
638             LOCK -> SCREEN_LOCK
639         }
640     }
641 
642     /**
643      * Adjusts cropHints for parallax effect.
644      *
645      * [WallpaperCropUtils.calculateCropRect] calculates based on the scaled size, the scale depends
646      * on the view size hosting the preview and the wallpaper zoom of the preview on that view,
647      * whereas the rest of multi-crop is based on full wallpaper size. So scaled back at the end.
648      *
649      * If [CropSizeModel] is null, returns the original cropHint without parallax.
650      *
651      * @param wallpaperSize full wallpaper image size.
652      */
653     private fun FullPreviewCropModel.adjustCropForParallax(wallpaperSize: Point): Rect {
654         return cropSizeModel?.let {
655             WallpaperCropUtils.calculateCropRect(
656                     context,
657                     it.hostViewSize,
658                     it.cropViewSize,
659                     wallpaperSize,
660                     cropHint,
661                     it.wallpaperZoom,
662                     /* cropExtraWidth= */ true,
663                 )
664                 .apply {
665                     scale(1f / it.wallpaperZoom)
666                     if (right > wallpaperSize.x) right = wallpaperSize.x
667                     if (bottom > wallpaperSize.y) bottom = wallpaperSize.y
668                 }
669         } ?: cropHint
670     }
671 
672     private suspend fun Asset.getStreamOrFromBitmap(bitmap: Bitmap): InputStream? =
673         suspendCancellableCoroutine { k: CancellableContinuation<InputStream?> ->
674             if (this is StreamableAsset) {
675                 if (exifOrientation != ExifInterface.ORIENTATION_NORMAL) {
676                     k.resumeWith(Result.success(BitmapUtils.bitmapToInputStream(bitmap)))
677                 } else {
678                     fetchInputStream { k.resumeWith(Result.success(it)) }
679                 }
680             } else {
681                 k.resumeWith(Result.success(null))
682             }
683         }
684 
685     override fun addOnColorsChangedListener(
686         listener: (WallpaperColors?, Int) -> Unit,
687         handler: Handler,
688     ) {
689         wallpaperManager.addOnColorsChangedListener(listener, handler)
690     }
691 
692     override fun removeOnColorsChangedListener(listener: (WallpaperColors?, Int) -> Unit) {
693         wallpaperManager.removeOnColorsChangedListener(listener)
694     }
695 
696     companion object {
697         private const val TAG = "WallpaperClientImpl"
698         private const val AUTHORITY = "com.google.android.apps.wallpaper.recents"
699 
700         /** Path for making a content provider request to set the wallpaper. */
701         private const val PATH_SET_WALLPAPER = "set_recent_wallpaper"
702         /** Path for making a content provider request to query for the recent wallpapers. */
703         private const val PATH_LIST_RECENTS = "list_recent"
704         /** Path for making a content provider request to query for the thumbnail of a wallpaper. */
705         private const val PATH_GET_THUMBNAIL = "thumb"
706 
707         private val BASE_URI =
708             Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY).build()
709         /** [Uri] for making a content provider request to set the wallpaper. */
710         private val SET_WALLPAPER_URI = BASE_URI.buildUpon().appendPath(PATH_SET_WALLPAPER).build()
711         /** [Uri] for making a content provider request to query for the recent wallpapers. */
712         private val LIST_RECENTS_URI = BASE_URI.buildUpon().appendPath(PATH_LIST_RECENTS).build()
713         /**
714          * [Uri] for making a content provider request to query for the thumbnail of a wallpaper.
715          */
716         private val GET_THUMBNAIL_BASE_URI =
717             BASE_URI.buildUpon().appendPath(PATH_GET_THUMBNAIL).build()
718 
719         /** Key for a parameter used to pass the wallpaper ID to/from the content provider. */
720         private const val KEY_ID = "id"
721         /** Key for a parameter used to pass the screen to/from the content provider. */
722         private const val KEY_SCREEN = "screen"
723         /** Key for a parameter used to pass the wallpaper destination to/from content provider. */
724         private const val KEY_DESTINATION = "destination"
725         /** Key for a parameter used to pass the screen to/from the content provider. */
726         private const val KEY_SET_WALLPAPER_ENTRY_POINT = "set_wallpaper_entry_point"
727         private const val KEY_LAST_UPDATED = "last_updated"
728         private const val SCREEN_ALL = "all_screens"
729         private const val SCREEN_HOME = "home_screen"
730         private const val SCREEN_LOCK = "lock_screen"
731 
732         private const val TITLE = "title"
733         /**
734          * Key for a parameter used to get the placeholder color for a wallpaper from the content
735          * provider.
736          */
737         private const val KEY_PLACEHOLDER_COLOR = "placeholder_color"
738     }
739 }
740