1 /*
2  * 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.keyguard.data.repository
18 
19 import android.content.Context
20 import android.os.UserHandle
21 import android.provider.Settings
22 import com.android.keyguard.ClockEventController
23 import com.android.systemui.dagger.SysUISingleton
24 import com.android.systemui.dagger.qualifiers.Application
25 import com.android.systemui.dagger.qualifiers.Background
26 import com.android.systemui.flags.FeatureFlagsClassic
27 import com.android.systemui.flags.Flags
28 import com.android.systemui.keyguard.shared.model.ClockSize
29 import com.android.systemui.keyguard.shared.model.ClockSizeSetting
30 import com.android.systemui.plugins.clocks.ClockController
31 import com.android.systemui.plugins.clocks.ClockId
32 import com.android.systemui.res.R
33 import com.android.systemui.scene.shared.flag.SceneContainerFlag
34 import com.android.systemui.shared.clocks.ClockRegistry
35 import com.android.systemui.util.settings.SecureSettings
36 import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
37 import javax.inject.Inject
38 import kotlinx.coroutines.CoroutineDispatcher
39 import kotlinx.coroutines.CoroutineScope
40 import kotlinx.coroutines.channels.awaitClose
41 import kotlinx.coroutines.flow.Flow
42 import kotlinx.coroutines.flow.MutableStateFlow
43 import kotlinx.coroutines.flow.SharingStarted
44 import kotlinx.coroutines.flow.StateFlow
45 import kotlinx.coroutines.flow.asStateFlow
46 import kotlinx.coroutines.flow.callbackFlow
47 import kotlinx.coroutines.flow.map
48 import kotlinx.coroutines.flow.mapNotNull
49 import kotlinx.coroutines.flow.onStart
50 import kotlinx.coroutines.flow.stateIn
51 import kotlinx.coroutines.withContext
52 
53 interface KeyguardClockRepository {
54     /**
55      * clock size determined by notificationPanelViewController, LARGE or SMALL
56      *
57      * @deprecated When scene container flag is on use clockSize from domain level.
58      */
59     val clockSize: StateFlow<ClockSize>
60 
61     /** clock size selected in picker, DYNAMIC or SMALL */
62     val selectedClockSize: StateFlow<ClockSizeSetting>
63 
64     /** clock id, selected from clock carousel in wallpaper picker */
65     val currentClockId: Flow<ClockId>
66 
67     val currentClock: StateFlow<ClockController?>
68 
69     val previewClock: Flow<ClockController>
70 
71     val clockEventController: ClockEventController
72 
73     val shouldForceSmallClock: Boolean
74 
setClockSizenull75     fun setClockSize(size: ClockSize)
76 }
77 
78 @SysUISingleton
79 class KeyguardClockRepositoryImpl
80 @Inject
81 constructor(
82     private val secureSettings: SecureSettings,
83     private val clockRegistry: ClockRegistry,
84     override val clockEventController: ClockEventController,
85     @Background private val backgroundDispatcher: CoroutineDispatcher,
86     @Application private val applicationScope: CoroutineScope,
87     @Application private val applicationContext: Context,
88     private val featureFlags: FeatureFlagsClassic,
89 ) : KeyguardClockRepository {
90 
91     /** Receive SMALL or LARGE clock should be displayed on keyguard. */
92     private val _clockSize: MutableStateFlow<ClockSize> = MutableStateFlow(ClockSize.LARGE)
93     override val clockSize: StateFlow<ClockSize> = _clockSize.asStateFlow()
94 
95     override fun setClockSize(size: ClockSize) {
96         SceneContainerFlag.assertInLegacyMode()
97         _clockSize.value = size
98     }
99 
100     override val selectedClockSize: StateFlow<ClockSizeSetting> =
101         secureSettings
102             .observerFlow(
103                 names = arrayOf(Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK),
104                 userId = UserHandle.USER_SYSTEM,
105             )
106             .onStart { emit(Unit) } // Forces an initial update.
107             .map { withContext(backgroundDispatcher) { getClockSize() } }
108             .stateIn(
109                 scope = applicationScope,
110                 started = SharingStarted.WhileSubscribed(),
111                 initialValue = getClockSize()
112             )
113 
114     override val currentClockId: Flow<ClockId> =
115         callbackFlow {
116                 fun send() {
117                     trySend(clockRegistry.currentClockId)
118                 }
119 
120                 val listener =
121                     object : ClockRegistry.ClockChangeListener {
122                         override fun onCurrentClockChanged() {
123                             send()
124                         }
125                     }
126                 clockRegistry.registerClockChangeListener(listener)
127                 send()
128                 awaitClose { clockRegistry.unregisterClockChangeListener(listener) }
129             }
130             .mapNotNull { it }
131 
132     override val currentClock: StateFlow<ClockController?> =
133         currentClockId
134             .map {
135                 clockEventController.clock = clockRegistry.createCurrentClock()
136                 clockEventController.clock
137             }
138             .stateIn(
139                 scope = applicationScope,
140                 started = SharingStarted.WhileSubscribed(),
141                 initialValue = clockRegistry.createCurrentClock()
142             )
143 
144     override val previewClock: Flow<ClockController> =
145         currentClockId.map {
146             // We should create a new instance for each collect call
147             // cause in preview, the same clock will be attached to different view
148             // at the same time
149             clockRegistry.createCurrentClock()
150         }
151 
152     override val shouldForceSmallClock: Boolean
153         get() =
154             featureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE) &&
155                 // True on small landscape screens
156                 applicationContext.resources.getBoolean(R.bool.force_small_clock_on_lockscreen)
157 
158     private fun getClockSize(): ClockSizeSetting {
159         return ClockSizeSetting.fromSettingValue(
160             secureSettings.getIntForUser(
161                 Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK,
162                 /* defaultValue= */ 1,
163                 UserHandle.USER_CURRENT
164             )
165         )
166     }
167 }
168