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.disableflags
17 
18 import android.app.StatusBarManager.DISABLE2_GLOBAL_ACTIONS
19 import android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE
20 import android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS
21 import android.app.StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS
22 import android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS
23 import android.app.StatusBarManager.DISABLE_BACK
24 import android.app.StatusBarManager.DISABLE_CLOCK
25 import android.app.StatusBarManager.DISABLE_EXPAND
26 import android.app.StatusBarManager.DISABLE_HOME
27 import android.app.StatusBarManager.DISABLE_NOTIFICATION_ALERTS
28 import android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS
29 import android.app.StatusBarManager.DISABLE_ONGOING_CALL_CHIP
30 import android.app.StatusBarManager.DISABLE_RECENT
31 import android.app.StatusBarManager.DISABLE_SEARCH
32 import android.app.StatusBarManager.DISABLE_SYSTEM_INFO
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 
flagsListHasDuplicateSymbolsnull64     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 new and new-after-modification disable flag states,
75      * as well as the differences between them (if there are any).
76      *
77      * Example if [new] and [newAfterLocalModification] are different:
78      *   New: EnaihBcRso.qiNGR | New after local modification: EnaihBCRso.qInGR (changed: C.In)
79      *
80      * Example if [new] and [newAfterLocalModification] are the same:
81      *   New: EnaihBcRso.qiNGR
82      *
83      * A capital character signifies the flag is set and a lowercase character signifies that the
84      * flag isn't set. The flag states will be logged in the same order as the passed-in lists.
85      *
86      * The difference between states is written between parentheses, and won't be included if there
87      * is no difference. the new-after-modification state also won't be included if there's no
88      * difference from the new state.
89      *
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      */
getDisableFlagsStringnull94     fun getDisableFlagsString(
95         new: DisableState,
96         newAfterLocalModification: DisableState? = null
97     ): String {
98         val builder = StringBuilder("Received new disable state: ")
99 
100         builder.append(getFlagsString(new))
101 
102         if (newAfterLocalModification != null && new != newAfterLocalModification) {
103             builder.append(" | New after local modification: ")
104             builder.append(getFlagsString(newAfterLocalModification))
105             builder.append(" ")
106             builder.append(getDiffString(new, newAfterLocalModification))
107         }
108 
109         return builder.toString()
110     }
111 
112     /**
113      * Returns a string representing the difference between [old] and [new].
114      *
115      * - If [old] was "abc.DE" and [new] was "aBC.De", the difference returned would be
116      *   "(changed: BC.e)".
117      * - If [old] and [new] are the same, the difference returned would be "(unchanged)".
118      */
getDiffStringnull119     private fun getDiffString(old: DisableState, new: DisableState): String {
120         if (old == new) {
121             return "(unchanged)"
122         }
123 
124         val builder = StringBuilder("(")
125         builder.append("changed: ")
126         disable1FlagsList.forEach {
127             val newSymbol = it.getFlagStatus(new.disable1)
128             if (it.getFlagStatus(old.disable1) != newSymbol) {
129                 builder.append(newSymbol)
130             }
131         }
132         builder.append(".")
133         disable2FlagsList.forEach {
134             val newSymbol = it.getFlagStatus(new.disable2)
135             if (it.getFlagStatus(old.disable2) != newSymbol) {
136                 builder.append(newSymbol)
137             }
138         }
139         builder.append(")")
140         return builder.toString()
141     }
142 
143     /** Returns a string representing the disable flag states, e.g. "EnaihBcRso.qiNGR".  */
getFlagsStringnull144     private fun getFlagsString(state: DisableState): String {
145         val builder = StringBuilder("")
146         disable1FlagsList.forEach { builder.append(it.getFlagStatus(state.disable1)) }
147         builder.append(".")
148         disable2FlagsList.forEach { builder.append(it.getFlagStatus(state.disable2)) }
149         return builder.toString()
150     }
151 
152     /** A POJO representing each disable flag. */
153     class DisableFlag(
154         private val bitMask: Int,
155         private val flagIsSetSymbol: Char,
156         private val flagNotSetSymbol: Char
157     ) {
158 
159         /**
160          * Returns a character representing whether or not this flag is set in [state].
161          *
162          * A capital character signifies the flag is set and a lowercase character signifies that
163          * the flag isn't set.
164          */
getFlagStatusnull165         internal fun getFlagStatus(state: Int): Char =
166             if (0 != state and this.bitMask) this.flagIsSetSymbol
167             else this.flagNotSetSymbol
168     }
169 
170     /** POJO to hold [disable1] and [disable2]. */
171     data class DisableState(val disable1: Int, val disable2: Int)
172 }
173 
174 // LINT.IfChange
175 private val defaultDisable1FlagsList: List<DisableFlagsLogger.DisableFlag> = listOf(
176         DisableFlagsLogger.DisableFlag(DISABLE_EXPAND, 'E', 'e'),
177         DisableFlagsLogger.DisableFlag(DISABLE_NOTIFICATION_ICONS, 'N', 'n'),
178         DisableFlagsLogger.DisableFlag(DISABLE_NOTIFICATION_ALERTS, 'A', 'a'),
179         DisableFlagsLogger.DisableFlag(DISABLE_SYSTEM_INFO, 'I', 'i'),
180         DisableFlagsLogger.DisableFlag(DISABLE_HOME, 'H', 'h'),
181         DisableFlagsLogger.DisableFlag(DISABLE_BACK, 'B', 'b'),
182         DisableFlagsLogger.DisableFlag(DISABLE_CLOCK, 'C', 'c'),
183         DisableFlagsLogger.DisableFlag(DISABLE_RECENT, 'R', 'r'),
184         DisableFlagsLogger.DisableFlag(DISABLE_SEARCH, 'S', 's'),
185         DisableFlagsLogger.DisableFlag(DISABLE_ONGOING_CALL_CHIP, 'O', 'o')
186 )
187 
188 private val defaultDisable2FlagsList: List<DisableFlagsLogger.DisableFlag> = listOf(
189         DisableFlagsLogger.DisableFlag(DISABLE2_QUICK_SETTINGS, 'Q', 'q'),
190         DisableFlagsLogger.DisableFlag(DISABLE2_SYSTEM_ICONS, 'I', 'i'),
191         DisableFlagsLogger.DisableFlag(DISABLE2_NOTIFICATION_SHADE, 'N', 'n'),
192         DisableFlagsLogger.DisableFlag(DISABLE2_GLOBAL_ACTIONS, 'G', 'g'),
193         DisableFlagsLogger.DisableFlag(DISABLE2_ROTATE_SUGGESTIONS, 'R', 'r')
194 )
195 // LINT.ThenChange(frameworks/base/core/java/android/app/StatusBarManager.java)
196