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.statusbar.notification 18 19 import android.icu.text.SimpleDateFormat 20 import android.util.IndentingPrintWriter 21 import com.android.systemui.Dumpable 22 import com.android.systemui.Flags.notificationColorUpdateLogger 23 import com.android.systemui.dagger.SysUISingleton 24 import com.android.systemui.dump.DumpManager 25 import com.android.systemui.flags.FeatureFlagsClassic 26 import com.android.systemui.util.Compile 27 import com.android.systemui.util.asIndenting 28 import com.android.systemui.util.printCollection 29 import com.android.systemui.util.withIncreasedIndent 30 import com.google.errorprone.annotations.CompileTimeConstant 31 import java.io.PrintWriter 32 import java.util.Locale 33 import java.util.SortedSet 34 import java.util.TreeSet 35 import javax.inject.Inject 36 37 @SysUISingleton 38 class ColorUpdateLogger 39 @Inject 40 constructor( 41 val featureFlags: FeatureFlagsClassic, 42 dumpManager: DumpManager, 43 ) : Dumpable { 44 45 inline val isEnabled 46 get() = Compile.IS_DEBUG && notificationColorUpdateLogger() 47 private val frames: MutableList<Frame> = mutableListOf() 48 49 init { 50 dumpManager.registerDumpable(this) 51 if (isEnabled) { 52 instance = this 53 } 54 } 55 56 @JvmOverloads logTriggerEventnull57 fun logTriggerEvent(@CompileTimeConstant type: String, extra: String? = null) { 58 if (!isEnabled) return 59 val event = Event(type = type, extraValue = extra) 60 val didAppend = frames.lastOrNull()?.tryAddTrigger(event) == true 61 if (!didAppend) { 62 frames.add(Frame(event)) 63 if (frames.size > maxFrames) frames.removeAt(0) 64 } 65 } 66 67 @JvmOverloads logEventnull68 fun logEvent(@CompileTimeConstant type: String, extra: String? = null) { 69 if (!isEnabled) return 70 val frame = frames.lastOrNull() ?: return 71 frame.events.add(Event(type = type, extraValue = extra)) 72 frame.trim() 73 } 74 75 @JvmOverloads logNotificationEventnull76 fun logNotificationEvent( 77 @CompileTimeConstant type: String, 78 key: String, 79 extra: String? = null 80 ) { 81 if (!isEnabled) return 82 val frame = frames.lastOrNull() ?: return 83 frame.events.add(Event(type = type, extraValue = extra, notificationKey = key)) 84 frame.trim() 85 } 86 dumpnull87 override fun dump(pwOrig: PrintWriter, args: Array<out String>) { 88 val pw = pwOrig.asIndenting() 89 pw.println("enabled: $isEnabled") 90 pw.printCollection("frames", frames) { it.dump(pw) } 91 } 92 93 private class Frame(event: Event) { 94 val startTime: Long = event.time 95 val events: MutableList<Event> = mutableListOf(event) <lambda>null96 val triggers: SortedSet<String> = TreeSet<String>().apply { add(event.type) } 97 var trimmedEvents: Int = 0 98 tryAddTriggernull99 fun tryAddTrigger(newEvent: Event): Boolean { 100 if (newEvent.time < startTime) return false 101 if (newEvent.time - startTime > triggerStartsNewFrameAge) return false 102 if (newEvent.type in triggers) return false 103 triggers.add(newEvent.type) 104 events.add(newEvent) 105 trim() 106 return true 107 } 108 trimnull109 fun trim() { 110 if (events.size > maxEventsPerFrame) { 111 events.removeAt(0) 112 trimmedEvents++ 113 } 114 } 115 dumpnull116 fun dump(pw: IndentingPrintWriter) { 117 pw.println("Frame") 118 pw.withIncreasedIndent { 119 pw.println("startTime: ${timeString(startTime)}") 120 pw.printCollection("triggers", triggers) 121 pw.println("trimmedEvents: $trimmedEvents") 122 pw.printCollection("events", events) { it.dump(pw) } 123 } 124 } 125 } 126 127 private class Event( 128 @CompileTimeConstant val type: String, 129 val extraValue: String? = null, 130 val notificationKey: String? = null, 131 ) { 132 val time: Long = System.currentTimeMillis() 133 dumpnull134 fun dump(pw: IndentingPrintWriter) { 135 pw.append(timeString(time)).append(": ").append(type) 136 extraValue?.let { pw.append(" ").append(it) } 137 notificationKey?.let { pw.append(" ---- ").append(logKey(it)) } 138 pw.println() 139 } 140 } 141 142 private companion object { 143 @JvmStatic 144 var instance: ColorUpdateLogger? = null 145 private set 146 private const val maxFrames = 5 147 private const val maxEventsPerFrame = 250 148 private const val triggerStartsNewFrameAge = 5000 149 150 private val dateFormat = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US) timeStringnull151 private fun timeString(time: Long): String = dateFormat.format(time) 152 } 153 } 154