1 /* <lambda>null2 * Copyright (C) 2025 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.statusbar.notification.row 18 19 import com.android.app.tracing.FlowTracing.traceEach 20 import com.android.app.tracing.TraceUtils.traceAsyncClosable 21 import com.android.app.tracing.TrackGroupUtils.trackGroup 22 import com.android.systemui.CoreStartable 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.statusbar.notification.domain.interactor.ActiveNotificationsInteractor 27 import javax.inject.Inject 28 import kotlinx.coroutines.CoroutineScope 29 import kotlinx.coroutines.flow.Flow 30 import kotlinx.coroutines.flow.MutableStateFlow 31 import kotlinx.coroutines.flow.SharingStarted 32 import kotlinx.coroutines.flow.StateFlow 33 import kotlinx.coroutines.flow.map 34 import kotlinx.coroutines.flow.stateIn 35 import kotlinx.coroutines.flow.update 36 import kotlinx.coroutines.launch 37 38 /** 39 * Tracks notification rebindings in progress as a result of a configuration change (such as density 40 * or font size) 41 */ 42 @SysUISingleton 43 class NotificationRebindingTracker 44 @Inject 45 constructor( 46 activeNotificationsInteractor: ActiveNotificationsInteractor, 47 @Background private val bgScope: CoroutineScope, 48 @Application private val appScope: CoroutineScope, 49 ) : CoreStartable { 50 51 private val rebindingKeys = MutableStateFlow(emptySet<String>()) 52 private val activeKeys: Flow<Set<String>> = 53 activeNotificationsInteractor.allRepresentativeNotifications 54 .map { notifications: Map<String, *> -> 55 notifications.map { (notifKey, _) -> notifKey }.toSet() 56 } 57 .traceEach(trackGroup("shade", "activeKeys")) 58 59 /** 60 * Emits the current number of active notification rebinding in progress. 61 * 62 * Note the usaged of the [appScope] instead of the bg one is intentional, as we need the value 63 * immediately also in the same frame if it changes. 64 */ 65 val rebindingInProgressCount: StateFlow<Int> = 66 rebindingKeys 67 .map { it.size } 68 .traceEach(trackGroup("shade", "rebindingInProgressCount"), traceEmissionCount = true) 69 .stateIn(appScope, started = SharingStarted.Eagerly, initialValue = 0) 70 71 override fun start() { 72 syncRebindingKeysWithActiveKeys() 73 } 74 75 private fun syncRebindingKeysWithActiveKeys() { 76 // Let's make sure that the "rebindingKeys" set doesn't contain entries that are not active 77 // anymore. 78 bgScope.launch { 79 activeKeys.collect { activeKeys -> 80 rebindingKeys.update { currentlyBeingInflated -> 81 currentlyBeingInflated.intersect(activeKeys) 82 } 83 } 84 } 85 } 86 87 /** Should be called when the inflation begins */ 88 fun trackRebinding(key: String): RebindFinishedCallback { 89 val endTrace = 90 traceAsyncClosable( 91 trackGroupName = "Notifications", 92 trackName = "Rebinding", 93 sliceName = "Rebinding in progress for $key", 94 ) 95 rebindingKeys.value += key 96 return RebindFinishedCallback { 97 endTrace() 98 rebindingKeys.value -= key 99 } 100 } 101 102 /** 103 * Callback to notify the end of a rebiding. Views are expected to be in the hierarchy when this 104 * is called. 105 */ 106 fun interface RebindFinishedCallback { 107 fun onFinished() 108 } 109 } 110