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.di.modules.BackgroundDispatcher 34 import com.android.wallpaper.picker.preview.shared.model.FullPreviewCropModel 35 import javax.inject.Inject 36 import javax.inject.Singleton 37 import kotlinx.coroutines.CoroutineDispatcher 38 import kotlinx.coroutines.CoroutineScope 39 import kotlinx.coroutines.flow.Flow 40 import kotlinx.coroutines.flow.MutableStateFlow 41 import kotlinx.coroutines.flow.SharingStarted 42 import kotlinx.coroutines.flow.StateFlow 43 import kotlinx.coroutines.flow.asStateFlow 44 import kotlinx.coroutines.flow.flowOn 45 import kotlinx.coroutines.flow.map 46 import kotlinx.coroutines.flow.stateIn 47 import kotlinx.coroutines.withContext 48 49 /** Encapsulates access to wallpaper-related data. */ 50 @Singleton 51 class WallpaperRepository 52 @Inject 53 constructor( 54 @BackgroundDispatcher private val scope: CoroutineScope, 55 private val client: WallpaperClient, 56 private val wallpaperPreferences: WallpaperPreferences, 57 @BackgroundDispatcher private val backgroundDispatcher: CoroutineDispatcher, 58 ) { 59 val maxOptions = MAX_OPTIONS 60 61 private val thumbnailCache = LruCache<String, Bitmap>(maxOptions) 62 63 /** The ID of the currently-selected wallpaper. */ 64 fun selectedWallpaperId(destination: WallpaperDestination): StateFlow<String> { 65 return client 66 .recentWallpapers(destination = destination, limit = 1) 67 .map { previews -> currentWallpaperKey(destination, previews) } 68 .flowOn(backgroundDispatcher) 69 .stateIn( 70 scope = scope, 71 started = SharingStarted.WhileSubscribed(), 72 initialValue = currentWallpaperKey(destination, null), 73 ) 74 } 75 76 private fun currentWallpaperKey( 77 destination: WallpaperDestination, 78 previews: List<WallpaperModel>?, 79 ): String { 80 val key = 81 when (destination) { 82 WallpaperDestination.HOME -> wallpaperPreferences.getHomeWallpaperRecentsKey() 83 WallpaperDestination.LOCK -> wallpaperPreferences.getLockWallpaperRecentsKey() 84 else -> error("Unsupported destination") 85 } 86 return key ?: previews?.firstOrNull()?.wallpaperId ?: DEFAULT_KEY 87 } 88 89 val areRecentsAvailable: Boolean by lazy { client.areRecentsAvailable() } 90 91 private val _selectingWallpaperId = 92 MutableStateFlow<Map<WallpaperDestination, String?>>(emptyMap()) 93 /** 94 * The ID of the wallpaper that is in the process of becoming the selected wallpaper or `null` 95 * if no such transaction is currently taking place. 96 */ 97 val selectingWallpaperId: StateFlow<Map<WallpaperDestination, String?>> = 98 _selectingWallpaperId.asStateFlow() 99 100 /** Lists the most recent wallpapers. The first one is the most recent (current) wallpaper. */ 101 fun recentWallpapers( 102 destination: WallpaperDestination, 103 limit: Int, 104 ): Flow<List<WallpaperModel>> { 105 return client 106 .recentWallpapers(destination = destination, limit = limit) 107 .flowOn(backgroundDispatcher) 108 } 109 110 /** Returns a thumbnail for the wallpaper with the given ID and destination. */ 111 suspend fun loadThumbnail( 112 wallpaperId: String, 113 lastUpdatedTimestamp: Long, 114 destination: WallpaperDestination, 115 ): Bitmap? { 116 val cacheKey = "$wallpaperId-$lastUpdatedTimestamp" 117 return thumbnailCache[cacheKey] 118 ?: withContext(backgroundDispatcher) { 119 val thumbnail = client.loadThumbnail(wallpaperId, destination) 120 if (thumbnail != null) { 121 thumbnailCache.put(cacheKey, thumbnail) 122 } 123 thumbnail 124 } 125 } 126 127 suspend fun setStaticWallpaper( 128 @SetWallpaperEntryPoint setWallpaperEntryPoint: Int, 129 destination: WallpaperDestination, 130 wallpaperModel: StaticWallpaperModel, 131 bitmap: Bitmap, 132 wallpaperSize: Point, 133 asset: Asset, 134 fullPreviewCropModels: Map<Point, FullPreviewCropModel>? = null, 135 ) { 136 // TODO(b/303317694): provide set wallpaper status as flow 137 withContext(backgroundDispatcher) { 138 client.setStaticWallpaper( 139 setWallpaperEntryPoint, 140 destination, 141 wallpaperModel, 142 bitmap, 143 wallpaperSize, 144 asset, 145 fullPreviewCropModels, 146 ) 147 } 148 } 149 150 suspend fun setLiveWallpaper( 151 @SetWallpaperEntryPoint setWallpaperEntryPoint: Int, 152 destination: WallpaperDestination, 153 wallpaperModel: LiveWallpaperModel, 154 ) { 155 withContext(backgroundDispatcher) { 156 client.setLiveWallpaper(setWallpaperEntryPoint, destination, wallpaperModel) 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