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