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.repository
19 
20 import android.app.WallpaperColors
21 import android.graphics.Bitmap
22 import android.graphics.Point
23 import android.graphics.Rect
24 import android.util.LruCache
25 import com.android.wallpaper.asset.Asset
26 import com.android.wallpaper.module.WallpaperPreferences
27 import com.android.wallpaper.module.logging.UserEventLogger.SetWallpaperEntryPoint
28 import com.android.wallpaper.picker.customization.data.content.WallpaperClient
29 import com.android.wallpaper.picker.customization.shared.model.WallpaperDestination
30 import com.android.wallpaper.picker.customization.shared.model.WallpaperModel
31 import com.android.wallpaper.picker.data.WallpaperModel.LiveWallpaperModel
32 import com.android.wallpaper.picker.data.WallpaperModel.StaticWallpaperModel
33 import com.android.wallpaper.picker.preview.shared.model.FullPreviewCropModel
34 import kotlinx.coroutines.CoroutineDispatcher
35 import kotlinx.coroutines.CoroutineScope
36 import kotlinx.coroutines.flow.Flow
37 import kotlinx.coroutines.flow.MutableStateFlow
38 import kotlinx.coroutines.flow.SharingStarted
39 import kotlinx.coroutines.flow.StateFlow
40 import kotlinx.coroutines.flow.asStateFlow
41 import kotlinx.coroutines.flow.flowOn
42 import kotlinx.coroutines.flow.map
43 import kotlinx.coroutines.flow.stateIn
44 import kotlinx.coroutines.withContext
45 
46 /** Encapsulates access to wallpaper-related data. */
47 class WallpaperRepository(
48     private val scope: CoroutineScope,
49     private val client: WallpaperClient,
50     private val wallpaperPreferences: WallpaperPreferences,
51     private val backgroundDispatcher: CoroutineDispatcher,
52 ) {
53     val maxOptions = MAX_OPTIONS
54 
55     private val thumbnailCache = LruCache<String, Bitmap>(maxOptions)
56 
57     /** The ID of the currently-selected wallpaper. */
58     fun selectedWallpaperId(
59         destination: WallpaperDestination,
60     ): StateFlow<String> {
61         return client
62             .recentWallpapers(destination = destination, limit = 1)
63             .map { previews -> currentWallpaperKey(destination, previews) }
64             .flowOn(backgroundDispatcher)
65             .stateIn(
66                 scope = scope,
67                 started = SharingStarted.WhileSubscribed(),
68                 initialValue = currentWallpaperKey(destination, null)
69             )
70     }
71 
72     private fun currentWallpaperKey(
73         destination: WallpaperDestination,
74         previews: List<WallpaperModel>?,
75     ): String {
76         val key =
77             when (destination) {
78                 WallpaperDestination.HOME -> wallpaperPreferences.getHomeWallpaperRecentsKey()
79                 WallpaperDestination.LOCK -> wallpaperPreferences.getLockWallpaperRecentsKey()
80                 else -> error("Unsupported destination")
81             }
82         return key ?: previews?.firstOrNull()?.wallpaperId ?: DEFAULT_KEY
83     }
84 
85     val areRecentsAvailable: Boolean by lazy { client.areRecentsAvailable() }
86 
87     private val _selectingWallpaperId =
88         MutableStateFlow<Map<WallpaperDestination, String?>>(emptyMap())
89     /**
90      * The ID of the wallpaper that is in the process of becoming the selected wallpaper or `null`
91      * if no such transaction is currently taking place.
92      */
93     val selectingWallpaperId: StateFlow<Map<WallpaperDestination, String?>> =
94         _selectingWallpaperId.asStateFlow()
95 
96     /** Lists the most recent wallpapers. The first one is the most recent (current) wallpaper. */
97     fun recentWallpapers(
98         destination: WallpaperDestination,
99         limit: Int,
100     ): Flow<List<WallpaperModel>> {
101         return client
102             .recentWallpapers(destination = destination, limit = limit)
103             .flowOn(backgroundDispatcher)
104     }
105 
106     /** Returns a thumbnail for the wallpaper with the given ID and destination. */
107     suspend fun loadThumbnail(
108         wallpaperId: String,
109         lastUpdatedTimestamp: Long,
110         destination: WallpaperDestination
111     ): Bitmap? {
112         val cacheKey = "$wallpaperId-$lastUpdatedTimestamp"
113         return thumbnailCache[cacheKey]
114             ?: withContext(backgroundDispatcher) {
115                 val thumbnail = client.loadThumbnail(wallpaperId, destination)
116                 if (thumbnail != null) {
117                     thumbnailCache.put(cacheKey, thumbnail)
118                 }
119                 thumbnail
120             }
121     }
122 
123     suspend fun setStaticWallpaper(
124         @SetWallpaperEntryPoint setWallpaperEntryPoint: Int,
125         destination: WallpaperDestination,
126         wallpaperModel: StaticWallpaperModel,
127         bitmap: Bitmap,
128         wallpaperSize: Point,
129         asset: Asset,
130         fullPreviewCropModels: Map<Point, FullPreviewCropModel>? = null,
131     ) {
132         // TODO(b/303317694): provide set wallpaper status as flow
133         withContext(backgroundDispatcher) {
134             client.setStaticWallpaper(
135                 setWallpaperEntryPoint,
136                 destination,
137                 wallpaperModel,
138                 bitmap,
139                 wallpaperSize,
140                 asset,
141                 fullPreviewCropModels,
142             )
143         }
144     }
145 
146     suspend fun setLiveWallpaper(
147         @SetWallpaperEntryPoint setWallpaperEntryPoint: Int,
148         destination: WallpaperDestination,
149         wallpaperModel: LiveWallpaperModel,
150     ) {
151         withContext(backgroundDispatcher) {
152             client.setLiveWallpaper(
153                 setWallpaperEntryPoint,
154                 destination,
155                 wallpaperModel,
156             )
157         }
158     }
159 
160     /** Sets the wallpaper to the one with the given ID. */
161     suspend fun setRecentWallpaper(
162         @SetWallpaperEntryPoint setWallpaperEntryPoint: Int,
163         destination: WallpaperDestination,
164         wallpaperId: String,
165     ) {
166         _selectingWallpaperId.value =
167             _selectingWallpaperId.value.toMutableMap().apply { this[destination] = wallpaperId }
168         withContext(backgroundDispatcher) {
169             client.setRecentWallpaper(
170                 setWallpaperEntryPoint = setWallpaperEntryPoint,
171                 destination = destination,
172                 wallpaperId = wallpaperId,
173             ) {
174                 _selectingWallpaperId.value =
175                     _selectingWallpaperId.value.toMutableMap().apply { this[destination] = null }
176             }
177         }
178     }
179 
180     suspend fun getWallpaperColors(bitmap: Bitmap, cropHints: Map<Point, Rect>?): WallpaperColors? =
181         withContext(backgroundDispatcher) { client.getWallpaperColors(bitmap, cropHints) }
182 
183     companion object {
184         const val DEFAULT_KEY = "default_missing_key"
185         /** The maximum number of options to show, including the currently-selected one. */
186         private const val MAX_OPTIONS = 5
187     }
188 }
189