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 18 package com.android.systemui.keyguard.domain.interactor 19 20 import android.content.Context 21 import android.content.Intent 22 import android.content.IntentFilter 23 import com.android.internal.logging.UiEvent 24 import com.android.internal.logging.UiEventLogger 25 import com.android.systemui.R 26 import com.android.systemui.broadcast.BroadcastDispatcher 27 import com.android.systemui.common.shared.model.Position 28 import com.android.systemui.dagger.SysUISingleton 29 import com.android.systemui.dagger.qualifiers.Application 30 import com.android.systemui.flags.FeatureFlags 31 import com.android.systemui.flags.Flags 32 import com.android.systemui.keyguard.data.repository.KeyguardRepository 33 import com.android.systemui.keyguard.domain.model.KeyguardSettingsPopupMenuModel 34 import com.android.systemui.keyguard.shared.model.KeyguardState 35 import com.android.systemui.plugins.ActivityStarter 36 import javax.inject.Inject 37 import kotlinx.coroutines.CoroutineScope 38 import kotlinx.coroutines.ExperimentalCoroutinesApi 39 import kotlinx.coroutines.flow.Flow 40 import kotlinx.coroutines.flow.MutableStateFlow 41 import kotlinx.coroutines.flow.SharingStarted 42 import kotlinx.coroutines.flow.StateFlow 43 import kotlinx.coroutines.flow.combine 44 import kotlinx.coroutines.flow.flatMapLatest 45 import kotlinx.coroutines.flow.flowOf 46 import kotlinx.coroutines.flow.launchIn 47 import kotlinx.coroutines.flow.map 48 import kotlinx.coroutines.flow.onEach 49 import kotlinx.coroutines.flow.stateIn 50 51 /** Business logic for use-cases related to the keyguard long-press feature. */ 52 @OptIn(ExperimentalCoroutinesApi::class) 53 @SysUISingleton 54 class KeyguardLongPressInteractor 55 @Inject 56 constructor( 57 @Application unsafeContext: Context, 58 @Application scope: CoroutineScope, 59 transitionInteractor: KeyguardTransitionInteractor, 60 repository: KeyguardRepository, 61 private val activityStarter: ActivityStarter, 62 private val logger: UiEventLogger, 63 private val featureFlags: FeatureFlags, 64 broadcastDispatcher: BroadcastDispatcher, 65 ) { 66 private val appContext = unsafeContext.applicationContext 67 68 private val _isLongPressHandlingEnabled: StateFlow<Boolean> = 69 if (isFeatureEnabled()) { 70 combine( 71 transitionInteractor.finishedKeyguardState.map { 72 it == KeyguardState.LOCKSCREEN 73 }, 74 repository.isQuickSettingsVisible, 75 ) { isFullyTransitionedToLockScreen, isQuickSettingsVisible -> 76 isFullyTransitionedToLockScreen && !isQuickSettingsVisible 77 } 78 } else { 79 flowOf(false) 80 } 81 .stateIn( 82 scope = scope, 83 started = SharingStarted.WhileSubscribed(), 84 initialValue = false, 85 ) 86 87 /** Whether the long-press handling feature should be enabled. */ 88 val isLongPressHandlingEnabled: Flow<Boolean> = _isLongPressHandlingEnabled 89 90 private val _menu = MutableStateFlow<KeyguardSettingsPopupMenuModel?>(null) 91 /** Model for a menu that should be shown; `null` when no menu should be shown. */ 92 val menu: Flow<KeyguardSettingsPopupMenuModel?> = 93 isLongPressHandlingEnabled.flatMapLatest { isEnabled -> 94 if (isEnabled) { 95 _menu 96 } else { 97 flowOf(null) 98 } 99 } 100 101 init { 102 if (isFeatureEnabled()) { 103 broadcastDispatcher 104 .broadcastFlow( 105 IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), 106 ) 107 .onEach { hideMenu() } 108 .launchIn(scope) 109 } 110 } 111 112 /** Notifies that the user has long-pressed on the lock screen. */ 113 fun onLongPress(x: Int, y: Int) { 114 if (!_isLongPressHandlingEnabled.value) { 115 return 116 } 117 118 showMenu( 119 x = x, 120 y = y, 121 ) 122 } 123 124 private fun isFeatureEnabled(): Boolean { 125 return featureFlags.isEnabled(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED) && 126 featureFlags.isEnabled(Flags.REVAMPED_WALLPAPER_UI) 127 } 128 129 /** Updates application state to ask to show the menu at the given coordinates. */ 130 private fun showMenu( 131 x: Int, 132 y: Int, 133 ) { 134 _menu.value = 135 KeyguardSettingsPopupMenuModel( 136 position = 137 Position( 138 x = x, 139 y = y, 140 ), 141 onClicked = { 142 hideMenu() 143 navigateToLockScreenSettings() 144 }, 145 onDismissed = { hideMenu() }, 146 ) 147 logger.log(LogEvents.LOCK_SCREEN_LONG_PRESS_POPUP_SHOWN) 148 } 149 150 /** Updates application state to ask to hide the menu. */ 151 private fun hideMenu() { 152 _menu.value = null 153 } 154 155 /** Opens the wallpaper picker screen after the device is unlocked by the user. */ 156 private fun navigateToLockScreenSettings() { 157 logger.log(LogEvents.LOCK_SCREEN_LONG_PRESS_POPUP_CLICKED) 158 activityStarter.dismissKeyguardThenExecute( 159 /* action= */ { 160 appContext.startActivity( 161 Intent(Intent.ACTION_SET_WALLPAPER).apply { 162 flags = Intent.FLAG_ACTIVITY_NEW_TASK 163 appContext 164 .getString(R.string.config_wallpaperPickerPackage) 165 .takeIf { it.isNotEmpty() } 166 ?.let { packageName -> setPackage(packageName) } 167 } 168 ) 169 true 170 }, 171 /* cancel= */ {}, 172 /* afterKeyguardGone= */ true, 173 ) 174 } 175 176 enum class LogEvents( 177 private val _id: Int, 178 ) : UiEventLogger.UiEventEnum { 179 @UiEvent(doc = "The lock screen was long-pressed and we showed the settings popup menu.") 180 LOCK_SCREEN_LONG_PRESS_POPUP_SHOWN(1292), 181 @UiEvent(doc = "The lock screen long-press popup menu was clicked.") 182 LOCK_SCREEN_LONG_PRESS_POPUP_CLICKED(1293), 183 ; 184 185 override fun getId() = _id 186 } 187 } 188