• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.app.WallpaperManager.FLAG_LOCK
22 import android.app.WallpaperManager.FLAG_SYSTEM
23 import android.content.Context
24 import android.content.Intent
25 import android.content.IntentFilter
26 import android.graphics.PointF
27 import android.graphics.RectF
28 import android.os.Bundle
29 import android.os.UserHandle
30 import android.provider.Settings
31 import android.util.Log
32 import android.view.View
33 import com.android.internal.R
34 import com.android.systemui.broadcast.BroadcastDispatcher
35 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
36 import com.android.systemui.dagger.SysUISingleton
37 import com.android.systemui.dagger.qualifiers.Background
38 import com.android.systemui.res.R as SysUIR
39 import com.android.systemui.shade.ShadeDisplayAware
40 import com.android.systemui.shared.Flags.ambientAod
41 import com.android.systemui.shared.Flags.extendedWallpaperEffects
42 import com.android.systemui.user.data.model.SelectedUserModel
43 import com.android.systemui.user.data.model.SelectionStatus
44 import com.android.systemui.user.data.repository.UserRepository
45 import com.android.systemui.util.settings.SecureSettings
46 import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
47 import com.android.systemui.utils.coroutines.flow.mapLatestConflated
48 import javax.inject.Inject
49 import kotlinx.coroutines.CoroutineDispatcher
50 import kotlinx.coroutines.CoroutineScope
51 import kotlinx.coroutines.flow.Flow
52 import kotlinx.coroutines.flow.MutableStateFlow
53 import kotlinx.coroutines.flow.SharingStarted
54 import kotlinx.coroutines.flow.SharingStarted.Companion.WhileSubscribed
55 import kotlinx.coroutines.flow.StateFlow
56 import kotlinx.coroutines.flow.asStateFlow
57 import kotlinx.coroutines.flow.combine
58 import kotlinx.coroutines.flow.filter
59 import kotlinx.coroutines.flow.flowOn
60 import kotlinx.coroutines.flow.map
61 import kotlinx.coroutines.flow.onStart
62 import kotlinx.coroutines.flow.stateIn
63 import kotlinx.coroutines.withContext
64 
65 /** A repository storing information about the current wallpaper. */
66 interface WallpaperRepository {
67     /** Emits the current user's current wallpaper. */
68     val wallpaperInfo: StateFlow<WallpaperInfo?>
69 
70     /**
71      * Emits the current user's lockscreen wallpaper. This will emit the same value as
72      * [wallpaperInfo] if the wallpaper is shared between home and lock screen.
73      */
74     val lockscreenWallpaperInfo: StateFlow<WallpaperInfo?>
75 
76     /** Emits true if the current user's current wallpaper supports ambient mode. */
77     val wallpaperSupportsAmbientMode: Flow<Boolean>
78 
79     /** Set rootView to get its windowToken afterwards */
80     var rootView: View?
81 
82     /** some wallpapers require bounds to be sent from keyguard */
83     val shouldSendFocalArea: StateFlow<Boolean>
84 
85     fun sendLockScreenLayoutChangeCommand(wallpaperFocalAreaBounds: RectF)
86 
87     fun sendTapCommand(tapPosition: PointF)
88 }
89 
90 @SysUISingleton
91 class WallpaperRepositoryImpl
92 @Inject
93 constructor(
94     @Background private val scope: CoroutineScope,
95     @Background private val bgDispatcher: CoroutineDispatcher,
96     broadcastDispatcher: BroadcastDispatcher,
97     userRepository: UserRepository,
98     private val wallpaperManager: WallpaperManager,
99     private val context: Context,
100     private val secureSettings: SecureSettings,
101     @ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
102 ) : WallpaperRepository {
103     private val wallpaperChanged: Flow<Unit> =
104         broadcastDispatcher
105             .broadcastFlow(IntentFilter(Intent.ACTION_WALLPAPER_CHANGED), user = UserHandle.ALL)
106             // The `combine` defining `wallpaperSupportsAmbientMode` will not run until both of the
107             // input flows emit at least once. Since this flow is an input flow, it needs to emit
108             // when it starts up to ensure that the `combine` will run if the user changes before we
109             // receive a ACTION_WALLPAPER_CHANGED intent.
110             // Note that the `selectedUser` flow does *not* need to emit on start because
111             // [UserRepository.selectedUser] is a state flow which will automatically emit a value
112             // on start.
<lambda>null113             .onStart { emit(Unit) }
114 
115     private val selectedUser: Flow<SelectedUserModel> =
116         userRepository.selectedUser
117             // Only update the wallpaper status once the user selection has finished.
<lambda>null118             .filter { it.selectionStatus == SelectionStatus.SELECTION_COMPLETE }
119 
120     override val wallpaperInfo: StateFlow<WallpaperInfo?> = getWallpaperInfo(FLAG_SYSTEM)
121     override val lockscreenWallpaperInfo: StateFlow<WallpaperInfo?> = getWallpaperInfo(FLAG_LOCK)
122     override val wallpaperSupportsAmbientMode: Flow<Boolean> =
123         combine(
124                 secureSettings
125                     .observerFlow(
126                         UserHandle.USER_ALL,
127                         Settings.Secure.DOZE_ALWAYS_ON_WALLPAPER_ENABLED,
128                     )
<lambda>null129                     .onStart { emit(Unit) },
130                 configurationInteractor.onAnyConfigurationChange,
131                 ::Pair,
132             )
<lambda>null133             .map {
134                 val userEnabled =
135                     secureSettings.getInt(Settings.Secure.DOZE_ALWAYS_ON_WALLPAPER_ENABLED, 1) == 1
136                 userEnabled &&
137                     context.resources.getBoolean(R.bool.config_dozeSupportsAodWallpaper) &&
138                     ambientAod()
139             }
140             .flowOn(bgDispatcher)
141 
142     override var rootView: View? = null
143 
sendLockScreenLayoutChangeCommandnull144     override fun sendLockScreenLayoutChangeCommand(wallpaperFocalAreaBounds: RectF) {
145         if (DEBUG) {
146             Log.d(TAG, "sendLockScreenLayoutChangeCommand $wallpaperFocalAreaBounds")
147         }
148         wallpaperManager.sendWallpaperCommand(
149             /* windowToken = */ rootView?.windowToken,
150             /* action = */ WallpaperManager.COMMAND_LOCKSCREEN_LAYOUT_CHANGED,
151             /* x = */ 0,
152             /* y = */ 0,
153             /* z = */ 0,
154             /* extras = */ Bundle().apply {
155                 putFloat("wallpaperFocalAreaLeft", wallpaperFocalAreaBounds.left)
156                 putFloat("wallpaperFocalAreaRight", wallpaperFocalAreaBounds.right)
157                 putFloat("wallpaperFocalAreaTop", wallpaperFocalAreaBounds.top)
158                 putFloat("wallpaperFocalAreaBottom", wallpaperFocalAreaBounds.bottom)
159             },
160         )
161     }
162 
sendTapCommandnull163     override fun sendTapCommand(tapPosition: PointF) {
164         if (DEBUG) {
165             Log.d(TAG, "sendTapCommand $tapPosition")
166         }
167 
168         wallpaperManager.sendWallpaperCommand(
169             /* windowToken = */ rootView?.windowToken,
170             /* action = */ WallpaperManager.COMMAND_LOCKSCREEN_TAP,
171             /* x = */ tapPosition.x.toInt(),
172             /* y = */ tapPosition.y.toInt(),
173             /* z = */ 0,
174             /* extras = */ Bundle(),
175         )
176     }
177 
178     override val shouldSendFocalArea =
179         lockscreenWallpaperInfo
<lambda>null180             .map {
181                 val focalAreaTarget = context.resources.getString(SysUIR.string.focal_area_target)
182                 val shouldSendNotificationLayout = it?.component?.className == focalAreaTarget
183                 shouldSendNotificationLayout
184             }
185             .stateIn(
186                 scope,
187                 if (extendedWallpaperEffects()) SharingStarted.Eagerly else WhileSubscribed(),
188                 initialValue = extendedWallpaperEffects(),
189             )
190 
getWallpapernull191     private suspend fun getWallpaper(
192         selectedUser: SelectedUserModel,
193         which: Int = FLAG_SYSTEM,
194     ): WallpaperInfo? {
195         return withContext(bgDispatcher) {
196             if (which == FLAG_LOCK && wallpaperManager.lockScreenWallpaperExists()) {
197                 wallpaperManager.getWallpaperInfo(FLAG_LOCK, selectedUser.userInfo.id)
198             } else {
199                 wallpaperManager.getWallpaperInfoForUser(selectedUser.userInfo.id)
200             }
201         }
202     }
203 
getWallpaperInfonull204     private fun getWallpaperInfo(which: Int): StateFlow<WallpaperInfo?> =
205         if (!wallpaperManager.isWallpaperSupported) {
206             MutableStateFlow(null).asStateFlow()
207         } else {
208             combine(wallpaperChanged, selectedUser, ::Pair)
selectedUsernull209                 .mapLatestConflated { (_, selectedUser) -> getWallpaper(selectedUser, which) }
210                 .stateIn(
211                     scope,
212                     // Always be listening for wallpaper changes.
213                     SharingStarted.Eagerly,
214                     // The initial value is null, but it should get updated pretty quickly because
215                     // the `combine` should immediately kick off a fetch.
216                     initialValue = null,
217                 )
218         }
219 
220     companion object {
221         private val TAG = WallpaperRepositoryImpl::class.simpleName
222         private val DEBUG = true
223     }
224 }
225