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