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