• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.chips.uievents
18 
19 import com.android.internal.logging.InstanceId
20 import com.android.internal.logging.InstanceIdSequence
21 import com.android.internal.logging.UiEventLogger
22 import com.android.systemui.dagger.SysUISingleton
23 import com.android.systemui.statusbar.chips.call.ui.viewmodel.CallChipViewModel
24 import com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel.CastToOtherDeviceChipViewModel
25 import com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel.ScreenRecordChipViewModel
26 import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.ShareToAppChipViewModel
27 import com.android.systemui.statusbar.pipeline.shared.ui.model.ChipsVisibilityModel
28 import com.android.systemui.util.kotlin.pairwise
29 import javax.inject.Inject
30 import kotlinx.coroutines.coroutineScope
31 import kotlinx.coroutines.flow.Flow
32 import kotlinx.coroutines.flow.distinctUntilChanged
33 import kotlinx.coroutines.flow.map
34 import kotlinx.coroutines.launch
35 
36 /** Does all the UiEvent-related logging for the status bar chips. */
37 @SysUISingleton
38 class StatusBarChipsUiEventLogger @Inject constructor(private val logger: UiEventLogger) {
39     private val instanceIdSequence = InstanceIdSequence(INSTANCE_ID_MAX)
40 
41     /** Get a new instance ID for a status bar chip. */
42     fun createNewInstanceId(): InstanceId {
43         return instanceIdSequence.newInstanceId()
44     }
45 
46     /** Logs that the chip with the given ID was tapped to show additional information. */
47     fun logChipTapToShow(instanceId: InstanceId?) {
48         logger.log(StatusBarChipUiEvent.STATUS_BAR_CHIP_TAP_TO_SHOW, instanceId)
49     }
50 
51     /**
52      * Logs that the chip with the given ID was tapped to hide the additional information that was
53      * previously shown.
54      */
55     fun logChipTapToHide(instanceId: InstanceId?) {
56         logger.log(StatusBarChipUiEvent.STATUS_BAR_CHIP_TAP_TO_HIDE, instanceId)
57     }
58 
59     /** Starts UiEvent logging for the chips. */
60     suspend fun hydrateUiEventLogging(chipsFlow: Flow<ChipsVisibilityModel>) {
61         coroutineScope {
62             launch {
63                 chipsFlow
64                     .map { it.chips }
65                     .distinctUntilChanged()
66                     .pairwise()
67                     .collect { (old, new) ->
68                         val oldActive: Map<String, Pair<InstanceId?, Int>> =
69                             old.active.withIndex().associate {
70                                 it.value.key to Pair(it.value.instanceId, it.index)
71                             }
72                         val newActive: Map<String, Pair<InstanceId?, Int>> =
73                             new.active.withIndex().associate {
74                                 it.value.key to Pair(it.value.instanceId, it.index)
75                             }
76 
77                         // Newly active keys
78                         newActive.keys.minus(oldActive.keys).forEach { key ->
79                             val uiEvent = key.getUiEventForNewChip()
80                             val instanceId = newActive[key]!!.first
81                             val position = newActive[key]!!.second
82                             logger.logWithInstanceIdAndPosition(
83                                 uiEvent,
84                                 /* uid= */ 0,
85                                 /* packageName= */ null,
86                                 instanceId,
87                                 position,
88                             )
89                         }
90 
91                         // Newly inactive keys
92                         oldActive.keys.minus(newActive.keys).forEach { key ->
93                             val instanceId = oldActive[key]?.first
94                             logger.log(StatusBarChipUiEvent.STATUS_BAR_CHIP_REMOVED, instanceId)
95                         }
96                     }
97             }
98         }
99     }
100 
101     companion object {
102         private const val INSTANCE_ID_MAX = 1 shl 20
103 
104         /**
105          * Given a key from an [OngoingActivityChipModel.Active] instance that was just added,
106          * return the right UiEvent type to log.
107          */
108         private fun String.getUiEventForNewChip(): StatusBarChipUiEvent {
109             return when {
110                 this == ScreenRecordChipViewModel.KEY ->
111                     StatusBarChipUiEvent.STATUS_BAR_NEW_CHIP_SCREEN_RECORD
112                 this == ShareToAppChipViewModel.KEY ->
113                     StatusBarChipUiEvent.STATUS_BAR_NEW_CHIP_SHARE_TO_APP
114                 this == CastToOtherDeviceChipViewModel.KEY ->
115                     StatusBarChipUiEvent.STATUS_BAR_NEW_CHIP_CAST_TO_OTHER_DEVICE
116                 this.startsWith(CallChipViewModel.KEY_PREFIX) ->
117                     StatusBarChipUiEvent.STATUS_BAR_NEW_CHIP_CALL
118                 else -> StatusBarChipUiEvent.STATUS_BAR_NEW_CHIP_NOTIFICATION
119             }
120         }
121     }
122 }
123