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