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