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 package com.android.systemui.wallpapers.data.repository 18 19 import android.app.WallpaperInfo 20 import android.app.WallpaperManager 21 import android.content.Context 22 import android.content.Intent 23 import android.content.IntentFilter 24 import android.os.UserHandle 25 import com.android.systemui.broadcast.BroadcastDispatcher 26 import com.android.systemui.dagger.SysUISingleton 27 import com.android.systemui.dagger.qualifiers.Background 28 import com.android.systemui.user.data.model.SelectedUserModel 29 import com.android.systemui.user.data.model.SelectionStatus 30 import com.android.systemui.user.data.repository.UserRepository 31 import com.android.systemui.utils.coroutines.flow.mapLatestConflated 32 import javax.inject.Inject 33 import kotlinx.coroutines.CoroutineDispatcher 34 import kotlinx.coroutines.CoroutineScope 35 import kotlinx.coroutines.flow.Flow 36 import kotlinx.coroutines.flow.MutableStateFlow 37 import kotlinx.coroutines.flow.SharingStarted 38 import kotlinx.coroutines.flow.StateFlow 39 import kotlinx.coroutines.flow.asStateFlow 40 import kotlinx.coroutines.flow.combine 41 import kotlinx.coroutines.flow.filter 42 import kotlinx.coroutines.flow.map 43 import kotlinx.coroutines.flow.onStart 44 import kotlinx.coroutines.flow.stateIn 45 import kotlinx.coroutines.withContext 46 47 /** A repository storing information about the current wallpaper. */ 48 interface WallpaperRepository { 49 /** Emits the current user's current wallpaper. */ 50 val wallpaperInfo: StateFlow<WallpaperInfo?> 51 52 /** Emits true if the current user's current wallpaper supports ambient mode. */ 53 val wallpaperSupportsAmbientMode: StateFlow<Boolean> 54 } 55 56 @SysUISingleton 57 class WallpaperRepositoryImpl 58 @Inject 59 constructor( 60 @Background scope: CoroutineScope, 61 @Background private val bgDispatcher: CoroutineDispatcher, 62 broadcastDispatcher: BroadcastDispatcher, 63 userRepository: UserRepository, 64 private val wallpaperManager: WallpaperManager, 65 context: Context, 66 ) : WallpaperRepository { 67 private val deviceSupportsAodWallpaper = 68 context.resources.getBoolean(com.android.internal.R.bool.config_dozeSupportsAodWallpaper) 69 70 private val wallpaperChanged: Flow<Unit> = 71 broadcastDispatcher 72 .broadcastFlow( 73 IntentFilter(Intent.ACTION_WALLPAPER_CHANGED), 74 user = UserHandle.ALL, 75 ) 76 // The `combine` defining `wallpaperSupportsAmbientMode` will not run until both of the 77 // input flows emit at least once. Since this flow is an input flow, it needs to emit 78 // when it starts up to ensure that the `combine` will run if the user changes before we 79 // receive a ACTION_WALLPAPER_CHANGED intent. 80 // Note that the `selectedUser` flow does *not* need to emit on start because 81 // [UserRepository.selectedUser] is a state flow which will automatically emit a value 82 // on start. <lambda>null83 .onStart { emit(Unit) } 84 85 private val selectedUser: Flow<SelectedUserModel> = 86 userRepository.selectedUser 87 // Only update the wallpaper status once the user selection has finished. <lambda>null88 .filter { it.selectionStatus == SelectionStatus.SELECTION_COMPLETE } 89 90 override val wallpaperInfo: StateFlow<WallpaperInfo?> = 91 if (!wallpaperManager.isWallpaperSupported || !deviceSupportsAodWallpaper) { 92 MutableStateFlow(null).asStateFlow() 93 } else { 94 combine(wallpaperChanged, selectedUser, ::Pair) selectedUsernull95 .mapLatestConflated { (_, selectedUser) -> getWallpaper(selectedUser) } 96 .stateIn( 97 scope, 98 // Always be listening for wallpaper changes. 99 SharingStarted.Eagerly, 100 // The initial value is null, but it should get updated pretty quickly because 101 // the `combine` should immediately kick off a fetch. 102 initialValue = null, 103 ) 104 } 105 106 override val wallpaperSupportsAmbientMode: StateFlow<Boolean> = 107 wallpaperInfo <lambda>null108 .map { 109 // If WallpaperInfo is null, it's ImageWallpaper which never supports ambient mode. 110 it?.supportsAmbientMode() == true 111 } 112 .stateIn( 113 scope, 114 // Always be listening for wallpaper changes. 115 SharingStarted.Eagerly, 116 initialValue = wallpaperInfo.value?.supportsAmbientMode() == true, 117 ) 118 getWallpapernull119 private suspend fun getWallpaper(selectedUser: SelectedUserModel): WallpaperInfo? { 120 return withContext(bgDispatcher) { 121 wallpaperManager.getWallpaperInfoForUser(selectedUser.userInfo.id) 122 } 123 } 124 } 125