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