• 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 
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