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.data.repository
19 
20 import android.os.Handler
21 import android.util.Log
22 import androidx.annotation.VisibleForTesting
23 import com.android.systemui.dagger.SysUISingleton
24 import com.android.systemui.dagger.qualifiers.Main
25 import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
26 import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint.Companion.DEFAULT
27 import com.android.systemui.keyguard.ui.view.layout.blueprints.KeyguardBlueprintModule
28 import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Config
29 import com.android.systemui.util.ThreadAssert
30 import java.io.PrintWriter
31 import java.util.TreeMap
32 import javax.inject.Inject
33 import kotlinx.coroutines.flow.MutableSharedFlow
34 import kotlinx.coroutines.flow.MutableStateFlow
35 
36 /**
37  * Manages blueprint changes for the lockscreen.
38  *
39  * To add a blueprint, create a class that implements LockscreenBlueprint and bind it to the map in
40  * the dagger module: [KeyguardBlueprintModule]
41  *
42  * A Blueprint determines how the layout should be constrained on a high level.
43  *
44  * A Section is a modular piece of code that implements the constraints. The blueprint uses the
45  * sections to define the constraints.
46  */
47 @SysUISingleton
48 class KeyguardBlueprintRepository
49 @Inject
50 constructor(
51     blueprints: Set<@JvmSuppressWildcards KeyguardBlueprint>,
52     @Main val handler: Handler,
53     val assert: ThreadAssert,
54 ) {
55     // This is TreeMap so that we can order the blueprints and assign numerical values to the
56     // blueprints in the adb tool.
57     private val blueprintIdMap: TreeMap<String, KeyguardBlueprint> =
58         TreeMap<String, KeyguardBlueprint>().apply { putAll(blueprints.associateBy { it.id }) }
59     val blueprint: MutableStateFlow<KeyguardBlueprint> = MutableStateFlow(blueprintIdMap[DEFAULT]!!)
60     val refreshTransition = MutableSharedFlow<Config>(extraBufferCapacity = 1)
61     @VisibleForTesting var targetTransitionConfig: Config? = null
62 
63     /**
64      * Emits the blueprint value to the collectors.
65      *
66      * @param blueprintId
67      * @return whether the transition has succeeded.
68      */
69     fun applyBlueprint(blueprintId: String?): Boolean {
70         val blueprint = blueprintIdMap[blueprintId]
71         if (blueprint == null) {
72             Log.e(
73                 TAG,
74                 "Could not find blueprint with id: $blueprintId. " +
75                     "Perhaps it was not added to KeyguardBlueprintModule?"
76             )
77             return false
78         }
79 
80         if (blueprint == this.blueprint.value) {
81             return true
82         }
83 
84         this.blueprint.value = blueprint
85         return true
86     }
87 
88     /**
89      * Re-emits the last emitted blueprint value if possible. This is delayed until next frame to
90      * dedupe requests and determine the correct transition to execute.
91      */
92     fun refreshBlueprint(config: Config = Config.DEFAULT) {
93         fun scheduleCallback() {
94             // We use a handler here instead of a CoroutineDispatcher because the one provided by
95             // @Main CoroutineDispatcher is currently Dispatchers.Main.immediate, which doesn't
96             // delay the callback, and instead runs it immediately.
97             handler.post {
98                 assert.isMainThread()
99                 targetTransitionConfig?.let {
100                     val success = refreshTransition.tryEmit(it)
101                     if (!success) {
102                         Log.e(TAG, "refreshBlueprint: Failed to emit blueprint refresh: $it")
103                     }
104                 }
105                 targetTransitionConfig = null
106             }
107         }
108 
109         assert.isMainThread()
110         if ((targetTransitionConfig?.type?.priority ?: Int.MIN_VALUE) < config.type.priority) {
111             if (targetTransitionConfig == null) scheduleCallback()
112             targetTransitionConfig = config
113         }
114     }
115 
116     /** Prints all available blueprints to the PrintWriter. */
117     fun printBlueprints(pw: PrintWriter) {
118         blueprintIdMap.onEachIndexed { index, entry -> pw.println("$index: ${entry.key}") }
119     }
120 
121     companion object {
122         private const val TAG = "KeyguardBlueprintRepository"
123     }
124 }
125