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