1 /*
2  * 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 package com.android.systemui.qs.tiles.base.logging
18 
19 import androidx.annotation.GuardedBy
20 import com.android.systemui.dagger.SysUISingleton
21 import com.android.systemui.log.LogBuffer
22 import com.android.systemui.log.LogBufferFactory
23 import com.android.systemui.log.core.LogLevel
24 import com.android.systemui.log.dagger.QSTilesLogBuffers
25 import com.android.systemui.plugins.statusbar.StatusBarStateController
26 import com.android.systemui.qs.pipeline.shared.TileSpec
27 import com.android.systemui.qs.tiles.viewmodel.QSTileState
28 import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
29 import com.android.systemui.statusbar.StatusBarState
30 import javax.inject.Inject
31 
32 @SysUISingleton
33 class QSTileLogger
34 @Inject
35 constructor(
36     @QSTilesLogBuffers logBuffers: Map<TileSpec, LogBuffer>,
37     private val factory: LogBufferFactory,
38     private val mStatusBarStateController: StatusBarStateController,
39 ) {
40     @GuardedBy("logBufferCache") private val logBufferCache = logBuffers.toMutableMap()
41 
42     /**
43      * Tracks user action when it's first received by the ViewModel and before it reaches the
44      * pipeline
45      */
logUserActionnull46     fun logUserAction(
47         userAction: QSTileUserAction,
48         tileSpec: TileSpec,
49         hasData: Boolean,
50         hasTileState: Boolean,
51     ) {
52         tileSpec
53             .getLogBuffer()
54             .log(
55                 tileSpec.getLogTag(),
56                 LogLevel.DEBUG,
57                 {
58                     str1 = userAction.toLogString()
59                     int1 = mStatusBarStateController.state
60                     bool1 = hasTileState
61                     bool2 = hasData
62                 },
63                 {
64                     "tile $str1: " +
65                         "statusBarState=${StatusBarState.toString(int1)}, " +
66                         "hasState=$bool1, " +
67                         "hasData=$bool2"
68                 }
69             )
70     }
71 
72     /** Tracks user action when it's rejected by false gestures */
logUserActionRejectedByFalsingnull73     fun logUserActionRejectedByFalsing(
74         userAction: QSTileUserAction,
75         tileSpec: TileSpec,
76     ) {
77         tileSpec
78             .getLogBuffer()
79             .log(
80                 tileSpec.getLogTag(),
81                 LogLevel.DEBUG,
82                 { str1 = userAction.toLogString() },
83                 { "tile $str1: rejected by falsing" }
84             )
85     }
86 
87     /** Tracks user action when it's rejected according to the policy */
logUserActionRejectedByPolicynull88     fun logUserActionRejectedByPolicy(
89         userAction: QSTileUserAction,
90         tileSpec: TileSpec,
91         restriction: String,
92     ) {
93         tileSpec
94             .getLogBuffer()
95             .log(
96                 tileSpec.getLogTag(),
97                 LogLevel.DEBUG,
98                 { str1 = userAction.toLogString() },
99                 { "tile $str1: rejected by policy, restriction: $restriction" }
100             )
101     }
102 
103     /**
104      * Tracks user actions when it reaches the pipeline and mixes with the last tile state and data
105      */
logUserActionPipelinenull106     fun <T> logUserActionPipeline(
107         tileSpec: TileSpec,
108         userAction: QSTileUserAction,
109         tileState: QSTileState,
110         data: T,
111     ) {
112         tileSpec
113             .getLogBuffer()
114             .log(
115                 tileSpec.getLogTag(),
116                 LogLevel.DEBUG,
117                 {
118                     str1 = userAction.toLogString()
119                     str2 = tileState.toLogString()
120                     str3 = data.toString().take(DATA_MAX_LENGTH)
121                 },
122                 {
123                     "tile $str1 pipeline: " +
124                         "statusBarState=${StatusBarState.toString(int1)}, " +
125                         "state=$str2, " +
126                         "data=$str3"
127                 }
128             )
129     }
130 
logForceUpdatenull131     fun logForceUpdate(tileSpec: TileSpec) {
132         tileSpec
133             .getLogBuffer()
134             .log(tileSpec.getLogTag(), LogLevel.DEBUG, {}, { "tile data force update" })
135     }
136 
logInitialRequestnull137     fun logInitialRequest(tileSpec: TileSpec) {
138         tileSpec
139             .getLogBuffer()
140             .log(tileSpec.getLogTag(), LogLevel.DEBUG, {}, { "tile data initial update" })
141     }
142 
143     /** Tracks state changes based on the data and trigger event. */
logStateUpdatenull144     fun <T> logStateUpdate(
145         tileSpec: TileSpec,
146         tileState: QSTileState,
147         data: T,
148     ) {
149         tileSpec
150             .getLogBuffer()
151             .log(
152                 tileSpec.getLogTag(),
153                 LogLevel.DEBUG,
154                 {
155                     str1 = tileState.toLogString()
156                     str2 = data.toString().take(DATA_MAX_LENGTH)
157                 },
158                 { "tile state update: state=$str1, data=$str2" }
159             )
160     }
161 
logErrornull162     fun logError(
163         tileSpec: TileSpec,
164         message: String,
165         error: Throwable,
166     ) {
167         tileSpec
168             .getLogBuffer()
169             .log(
170                 tileSpec.getLogTag(),
171                 LogLevel.ERROR,
172                 {},
173                 { message },
174                 error,
175             )
176     }
177 
178     /** Log with level [LogLevel.WARNING] */
logWarningnull179     fun logWarning(
180         tileSpec: TileSpec,
181         message: String,
182     ) {
183         tileSpec
184             .getLogBuffer()
185             .log(tileSpec.getLogTag(), LogLevel.WARNING, { str1 = message }, { str1!! })
186     }
187 
188     /** Log with level [LogLevel.INFO] */
logInfonull189     fun logInfo(
190         tileSpec: TileSpec,
191         message: String,
192     ) {
193         tileSpec
194             .getLogBuffer()
195             .log(tileSpec.getLogTag(), LogLevel.INFO, { str1 = message }, { str1!! })
196     }
197 
logCustomTileUserActionDeliverednull198     fun logCustomTileUserActionDelivered(tileSpec: TileSpec) {
199         tileSpec
200             .getLogBuffer()
201             .log(
202                 tileSpec.getLogTag(),
203                 LogLevel.DEBUG,
204                 {},
205                 { "user action delivered to the service" },
206             )
207     }
208 
getLogTagnull209     private fun TileSpec.getLogTag(): String = "${TAG_FORMAT_PREFIX}_${this.spec}"
210 
211     private fun TileSpec.getLogBuffer(): LogBuffer =
212         synchronized(logBufferCache) {
213             logBufferCache.getOrPut(this) {
214                 factory.create(
215                     this.getLogTag(),
216                     BUFFER_MAX_SIZE /* maxSize */,
217                     false /* systrace */
218                 )
219             }
220         }
221 
toLogStringnull222     private fun QSTileUserAction.toLogString(): String =
223         when (this) {
224             is QSTileUserAction.Click -> "click"
225             is QSTileUserAction.LongClick -> "long click"
226         }
227 
228     /* Shortened version of a data class toString() */
toLogStringnull229     private fun QSTileState.toLogString(): String =
230         "[label=$label, " +
231             "state=$activationState, " +
232             "s_label=$secondaryLabel, " +
233             "cd=$contentDescription, " +
234             "sd=$stateDescription, " +
235             "svi=$sideViewIcon, " +
236             "enabled=$enabledState, " +
237             "a11y=$expandedAccessibilityClassName" +
238             "]"
239 
240     private companion object {
241         const val TAG_FORMAT_PREFIX = "QSLog_tile_"
242         const val DATA_MAX_LENGTH = 50
243         const val BUFFER_MAX_SIZE = 25
244     }
245 }
246