1 /*
2  * Copyright (C) 2021 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 package com.android.systemui.statusbar
17 
18 import android.app.StatusBarManager.DISABLE_BACK
19 import android.app.StatusBarManager.DISABLE_CLOCK
20 import android.app.StatusBarManager.DISABLE_EXPAND
21 import android.app.StatusBarManager.DISABLE_HOME
22 import android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS
23 import android.app.StatusBarManager.DISABLE_NOTIFICATION_ALERTS
24 import android.app.StatusBarManager.DISABLE_ONGOING_CALL_CHIP
25 import android.app.StatusBarManager.DISABLE_RECENT
26 import android.app.StatusBarManager.DISABLE_SEARCH
27 import android.app.StatusBarManager.DISABLE_SYSTEM_INFO
28 import android.app.StatusBarManager.DISABLE2_GLOBAL_ACTIONS
29 import android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE
30 import android.app.StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS
31 import android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS
32 import android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS
33 import com.android.systemui.dagger.SysUISingleton
34 import javax.inject.Inject
35 
36 /**
37  * A singleton that creates concise but readable strings representing the values of the disable
38  * flags for debugging.
39  *
40  * See [CommandQueue.disable] for information about disable flags.
41  *
42  * Note that, for both lists passed in, each flag must have a distinct [DisableFlag.flagIsSetSymbol]
43  * and distinct [DisableFlag.flagNotSetSymbol] within the list. If this isn't true, the logs could
44  * be ambiguous so an [IllegalArgumentException] is thrown.
45  */
46 @SysUISingleton
47 class DisableFlagsLogger constructor(
48     private val disable1FlagsList: List<DisableFlag>,
49     private val disable2FlagsList: List<DisableFlag>
50 ) {
51 
52     @Inject
53     constructor() : this(defaultDisable1FlagsList, defaultDisable2FlagsList)
54 
55     init {
56         if (flagsListHasDuplicateSymbols(disable1FlagsList)) {
57             throw IllegalArgumentException("disable1 flags must have unique symbols")
58         }
59         if (flagsListHasDuplicateSymbols(disable2FlagsList)) {
60             throw IllegalArgumentException("disable2 flags must have unique symbols")
61         }
62     }
63 
64     private fun flagsListHasDuplicateSymbols(list: List<DisableFlag>): Boolean {
65         val numDistinctFlagOffStatus = list.map { it.getFlagStatus(0) }.distinct().count()
66         val numDistinctFlagOnStatus = list
67                 .map { it.getFlagStatus(Int.MAX_VALUE) }
68                 .distinct()
69                 .count()
70         return numDistinctFlagOffStatus < list.count() || numDistinctFlagOnStatus < list.count()
71     }
72 
73     /**
74      * Returns a string representing the, old, new, and new-after-modification disable flag states,
75      * as well as the differences between each of the states.
76      *
77      * Example:
78      *   Old: EnaiHbcRso.qINgr | New: EnaihBcRso.qiNGR (hB.iGR) | New after local modification:
79      *   EnaihBcRso.qInGR (.n)
80      *
81      * A capital character signifies the flag is set and a lowercase character signifies that the
82      * flag isn't set. The flag states will be logged in the same order as the passed-in lists.
83      *
84      * The difference between states is written between parentheses, and won't be included if there
85      * is no difference. the new-after-modification state also won't be included if there's no
86      * difference from the new state.
87      *
88      * @param old the disable state that had been previously sent. Null if we don't need to log the
89      *   previously sent state.
90      * @param new the new disable state that has just been sent.
91      * @param newAfterLocalModification the new disable states after a class has locally modified
92      *   them. Null if the class does not locally modify.
93      */
94     fun getDisableFlagsString(
95         old: DisableState? = null,
96         new: DisableState,
97         newAfterLocalModification: DisableState? = null
98     ): String {
99         val builder = StringBuilder("Received new disable state. ")
100 
101         old?.let {
102             builder.append("Old: ")
103             builder.append(getFlagsString(old))
104             builder.append(" | ")
105         }
106 
107         builder.append("New: ")
108         if (old != null && old != new) {
109             builder.append(getFlagsStringWithDiff(old, new))
110         } else {
111             builder.append(getFlagsString(new))
112         }
113 
114         if (newAfterLocalModification != null && new != newAfterLocalModification) {
115             builder.append(" | New after local modification: ")
116             builder.append(getFlagsStringWithDiff(new, newAfterLocalModification))
117         }
118 
119         return builder.toString()
120     }
121 
122     /**
123      * Returns a string representing [new] state, as well as the difference from [old] to [new]
124      * (if there is one).
125      */
126     private fun getFlagsStringWithDiff(old: DisableState, new: DisableState): String {
127         val builder = StringBuilder()
128         builder.append(getFlagsString(new))
129         builder.append(" ")
130         builder.append(getDiffString(old, new))
131         return builder.toString()
132     }
133 
134     /**
135      * Returns a string representing the difference between [old] and [new], or an empty string if
136      * there is no difference.
137      *
138      * For example, if old was "abc.DE" and new was "aBC.De", the difference returned would be
139      * "(BC.e)".
140      */
141     private fun getDiffString(old: DisableState, new: DisableState): String {
142         if (old == new) {
143             return ""
144         }
145 
146         val builder = StringBuilder("(")
147         disable1FlagsList.forEach {
148             val newSymbol = it.getFlagStatus(new.disable1)
149             if (it.getFlagStatus(old.disable1) != newSymbol) {
150                 builder.append(newSymbol)
151             }
152         }
153         builder.append(".")
154         disable2FlagsList.forEach {
155             val newSymbol = it.getFlagStatus(new.disable2)
156             if (it.getFlagStatus(old.disable2) != newSymbol) {
157                 builder.append(newSymbol)
158             }
159         }
160         builder.append(")")
161         return builder.toString()
162     }
163 
164     /** Returns a string representing the disable flag states, e.g. "EnaihBcRso.qiNGR".  */
165     private fun getFlagsString(state: DisableState): String {
166         val builder = StringBuilder("")
167         disable1FlagsList.forEach { builder.append(it.getFlagStatus(state.disable1)) }
168         builder.append(".")
169         disable2FlagsList.forEach { builder.append(it.getFlagStatus(state.disable2)) }
170         return builder.toString()
171     }
172 
173     /** A POJO representing each disable flag. */
174     class DisableFlag(
175         private val bitMask: Int,
176         private val flagIsSetSymbol: Char,
177         private val flagNotSetSymbol: Char
178     ) {
179 
180         /**
181          * Returns a character representing whether or not this flag is set in [state].
182          *
183          * A capital character signifies the flag is set and a lowercase character signifies that
184          * the flag isn't set.
185          */
186         internal fun getFlagStatus(state: Int): Char =
187             if (0 != state and this.bitMask) this.flagIsSetSymbol
188             else this.flagNotSetSymbol
189     }
190 
191     /** POJO to hold [disable1] and [disable2]. */
192     data class DisableState(val disable1: Int, val disable2: Int)
193 }
194 
195 // LINT.IfChange
196 private val defaultDisable1FlagsList: List<DisableFlagsLogger.DisableFlag> = listOf(
197         DisableFlagsLogger.DisableFlag(DISABLE_EXPAND, 'E', 'e'),
198         DisableFlagsLogger.DisableFlag(DISABLE_NOTIFICATION_ICONS, 'N', 'n'),
199         DisableFlagsLogger.DisableFlag(DISABLE_NOTIFICATION_ALERTS, 'A', 'a'),
200         DisableFlagsLogger.DisableFlag(DISABLE_SYSTEM_INFO, 'I', 'i'),
201         DisableFlagsLogger.DisableFlag(DISABLE_HOME, 'H', 'h'),
202         DisableFlagsLogger.DisableFlag(DISABLE_BACK, 'B', 'b'),
203         DisableFlagsLogger.DisableFlag(DISABLE_CLOCK, 'C', 'c'),
204         DisableFlagsLogger.DisableFlag(DISABLE_RECENT, 'R', 'r'),
205         DisableFlagsLogger.DisableFlag(DISABLE_SEARCH, 'S', 's'),
206         DisableFlagsLogger.DisableFlag(DISABLE_ONGOING_CALL_CHIP, 'O', 'o')
207 )
208 
209 private val defaultDisable2FlagsList: List<DisableFlagsLogger.DisableFlag> = listOf(
210         DisableFlagsLogger.DisableFlag(DISABLE2_QUICK_SETTINGS, 'Q', 'q'),
211         DisableFlagsLogger.DisableFlag(DISABLE2_SYSTEM_ICONS, 'I', 'i'),
212         DisableFlagsLogger.DisableFlag(DISABLE2_NOTIFICATION_SHADE, 'N', 'n'),
213         DisableFlagsLogger.DisableFlag(DISABLE2_GLOBAL_ACTIONS, 'G', 'g'),
214         DisableFlagsLogger.DisableFlag(DISABLE2_ROTATE_SUGGESTIONS, 'R', 'r')
215 )
216 // LINT.ThenChange(frameworks/base/core/java/android/app/StatusBarManager.java)