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.internal.config.sysui;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.os.Build;
22 import android.os.SystemProperties;
23 import android.util.Log;
24 
25 import com.android.internal.annotations.VisibleForTesting;
26 
27 /**
28  * Provides a central definition of debug SystemUI's SystemProperties flags, and their defaults.
29  *
30  * The main feature of this class is that it encodes a system-wide default for each flag which can
31  *  be updated by engineers with a single-line CL.
32  *
33  * NOTE: Because flag values returned by this class are not cached, it is important that developers
34  *  understand the intricacies of changing values and how that applies to their own code.
35  *  Generally, the best practice is to set the property, and then restart the device so that any
36  *  processes with stale state can be updated.  However, if your code has no state derived from the
37  *  flag value and queries it any time behavior is relevant, then it may be safe to change the flag
38  *  and not immediately reboot.
39  *
40  * To enable flags in debuggable builds, use the following commands:
41  *
42  * $ adb shell setprop persist.sysui.whatever_the_flag true
43  * $ adb reboot
44  *
45  * @hide
46  */
47 public class SystemUiSystemPropertiesFlags {
48 
49     /** The teamfood flag allows multiple features to be opted into at once. */
50     public static final Flag TEAMFOOD = devFlag("persist.sysui.teamfood");
51 
52     /**
53      * Flags related to notification features
54      */
55     public static final class NotificationFlags {
56 
57         /** Gating the logging of DND state change events. */
58         public static final Flag LOG_DND_STATE_EVENTS =
59                 releasedFlag("persist.sysui.notification.log_dnd_state_events");
60 
61         /** Gating storing NotificationRankingUpdate ranking map in shared memory. */
62         public static final Flag RANKING_UPDATE_ASHMEM = devFlag(
63                 "persist.sysui.notification.ranking_update_ashmem");
64 
65         public static final Flag PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS = releasedFlag(
66                 "persist.sysui.notification.propagate_channel_updates_to_conversations");
67 
68         // TODO b/291899544: for released flags, use resource config values
69         /** Value used by polite notif. feature */
70         public static final Flag NOTIF_COOLDOWN_T1 = devFlag(
71                 "persist.debug.sysui.notification.notif_cooldown_t1", 60000);
72         /** Value used by polite notif. feature */
73         public static final Flag NOTIF_COOLDOWN_T2 = devFlag(
74                 "persist.debug.sysui.notification.notif_cooldown_t2", 10000);
75         /** Value used by polite notif. feature */
76         public static final Flag NOTIF_VOLUME1 = devFlag(
77                 "persist.debug.sysui.notification.notif_volume1", 30);
78         public static final Flag NOTIF_VOLUME2 = devFlag(
79                 "persist.debug.sysui.notification.notif_volume2", 0);
80         /** Value used by polite notif. feature. -1 to ignore the counter */
81         public static final Flag NOTIF_COOLDOWN_COUNTER_RESET = devFlag(
82                 "persist.debug.sysui.notification.notif_cooldown_counter_reset", 10);
83 
84         /** Value used by polite notif. feature */
85         public static final Flag NOTIF_AVALANCHE_TIMEOUT = devFlag(
86                 "persist.debug.sysui.notification.notif_avalanche_timeout", 120_000);
87 
88         /** b/303716154: For debugging only: use short bitmap duration. */
89         public static final Flag DEBUG_SHORT_BITMAP_DURATION = devFlag(
90                 "persist.sysui.notification.debug_short_bitmap_duration");
91     }
92 
93     //// == End of flags.  Everything below this line is the implementation. == ////
94 
95     /** The interface used for resolving SystemUI SystemProperties Flags to booleans. */
96     public interface FlagResolver {
97         /** Is the flag enabled? */
isEnabled(Flag flag)98         boolean isEnabled(Flag flag);
99         /** Get the flag value (integer) */
getIntValue(Flag flag)100         int getIntValue(Flag flag);
101         /** Get the flag value (string) */
getStringValue(Flag flag)102         String getStringValue(Flag flag);
103     }
104 
105     /** The primary, immutable resolver returned by getResolver() */
106     private static final FlagResolver
107             MAIN_RESOLVER =
108             Build.IS_DEBUGGABLE ? new DebugResolver() : new ProdResolver();
109 
110     /**
111      * On debuggable builds, this can be set to override the resolver returned by getResolver().
112      * This can be useful to override flags when testing components that do not allow injecting the
113      * SystemUiPropertiesFlags resolver they use.
114      * Always set this to null when tests tear down.
115      */
116     @VisibleForTesting
117     public static FlagResolver TEST_RESOLVER = null;
118 
119     /** Get the resolver for this device configuration. */
getResolver()120     public static FlagResolver getResolver() {
121         if (Build.IS_DEBUGGABLE && TEST_RESOLVER != null) {
122             Log.i("SystemUiSystemPropertiesFlags", "Returning debug resolver " + TEST_RESOLVER);
123             return TEST_RESOLVER;
124         }
125         return MAIN_RESOLVER;
126     }
127 
128     /**
129      * Creates a flag that is disabled by default in debuggable builds.
130      * It can be enabled by setting this flag's SystemProperty to 1.
131      *
132      * This flag is ALWAYS disabled in release builds.
133      */
134     @VisibleForTesting
devFlag(String name)135     public static Flag devFlag(String name) {
136         return new Flag(name, false, null);
137     }
138 
139     /**
140      * Creates a flag that with a default integer value in debuggable builds.
141      */
142     @VisibleForTesting
devFlag(String name, int defaultValue)143     public static Flag devFlag(String name, int defaultValue) {
144         return new Flag(name, defaultValue, null);
145     }
146 
147     /**
148      * Creates a flag that with a default string value in debuggable builds.
149      */
150     @VisibleForTesting
devFlag(String name, String defaultValue)151     public static Flag devFlag(String name, String defaultValue) {
152         return new Flag(name, defaultValue, null);
153     }
154 
155     /**
156      * Creates a flag that is disabled by default in debuggable builds.
157      * It can be enabled or force-disabled by setting this flag's SystemProperty to 1 or 0.
158      * If this flag's SystemProperty is not set, the flag can be enabled by setting the
159      * TEAMFOOD flag's SystemProperty to 1.
160      *
161      * This flag is ALWAYS disabled in release builds.
162      */
163     @VisibleForTesting
teamfoodFlag(String name)164     public static Flag teamfoodFlag(String name) {
165         return new Flag(name, false, TEAMFOOD);
166     }
167 
168     /**
169      * Creates a flag that is enabled by default in debuggable builds.
170      * It can be enabled by setting this flag's SystemProperty to 0.
171      *
172      * This flag is ALWAYS enabled in release builds.
173      */
174     @VisibleForTesting
releasedFlag(String name)175     public static Flag releasedFlag(String name) {
176         return new Flag(name, true, null);
177     }
178 
179     /** Represents a developer-switchable gate for a feature. */
180     public static final class Flag {
181         public final String mSysPropKey;
182         public final boolean mDefaultValue;
183         public final int mDefaultIntValue;
184         public final String mDefaultStringValue;
185         @Nullable
186         public final Flag mDebugDefault;
187 
188         /** constructs a new flag.  only visible for testing the class */
189         @VisibleForTesting
Flag(@onNull String sysPropKey, boolean defaultValue, @Nullable Flag debugDefault)190         public Flag(@NonNull String sysPropKey, boolean defaultValue, @Nullable Flag debugDefault) {
191             mSysPropKey = sysPropKey;
192             mDefaultValue = defaultValue;
193             mDebugDefault = debugDefault;
194             mDefaultIntValue = 0;
195             mDefaultStringValue = null;
196         }
197 
Flag(@onNull String sysPropKey, int defaultValue, @Nullable Flag debugDefault)198         public Flag(@NonNull String sysPropKey, int defaultValue, @Nullable Flag debugDefault) {
199             mSysPropKey = sysPropKey;
200             mDefaultIntValue = defaultValue;
201             mDebugDefault = debugDefault;
202             mDefaultValue = false;
203             mDefaultStringValue = null;
204         }
205 
Flag(@onNull String sysPropKey, String defaultValue, @Nullable Flag debugDefault)206         public Flag(@NonNull String sysPropKey, String defaultValue, @Nullable Flag debugDefault) {
207             mSysPropKey = sysPropKey;
208             mDefaultStringValue = defaultValue;
209             mDebugDefault = debugDefault;
210             mDefaultValue = false;
211             mDefaultIntValue = 0;
212         }
213     }
214 
215     /** Implementation of the interface used in release builds. */
216     @VisibleForTesting
217     public static final class ProdResolver implements
218             FlagResolver {
219         @Override
isEnabled(Flag flag)220         public boolean isEnabled(Flag flag) {
221             return flag.mDefaultValue;
222         }
223 
224         @Override
getIntValue(Flag flag)225         public int getIntValue(Flag flag) {
226             return flag.mDefaultIntValue;
227         }
228 
229         @Override
getStringValue(Flag flag)230         public String getStringValue(Flag flag) {
231             return flag.mDefaultStringValue;
232         }
233     }
234 
235     /** Implementation of the interface used in debuggable builds. */
236     @VisibleForTesting
237     public static class DebugResolver implements FlagResolver {
238         @Override
isEnabled(Flag flag)239         public final boolean isEnabled(Flag flag) {
240             if (flag.mDebugDefault == null) {
241                 return getBoolean(flag.mSysPropKey, flag.mDefaultValue);
242             }
243             return getBoolean(flag.mSysPropKey, isEnabled(flag.mDebugDefault));
244         }
245 
246         /** Look up the value; overridable for tests to avoid needing to set SystemProperties */
247         @VisibleForTesting
getBoolean(String key, boolean defaultValue)248         public boolean getBoolean(String key, boolean defaultValue) {
249             return SystemProperties.getBoolean(key, defaultValue);
250         }
251 
252         /** Look up the value; overridable for tests to avoid needing to set SystemProperties */
253         @VisibleForTesting
getIntValue(Flag flag)254         public int getIntValue(Flag flag) {
255             if (flag.mDebugDefault == null) {
256                 return SystemProperties.getInt(flag.mSysPropKey, flag.mDefaultIntValue);
257             }
258             return SystemProperties.getInt(flag.mSysPropKey, getIntValue(flag.mDebugDefault));
259         }
260 
261         /** Look up the value; overridable for tests to avoid needing to set SystemProperties */
262         @VisibleForTesting
getStringValue(Flag flag)263         public String getStringValue(Flag flag) {
264             if (flag.mDebugDefault == null) {
265                 return SystemProperties.get(flag.mSysPropKey, flag.mDefaultStringValue);
266             }
267             return SystemProperties.get(flag.mSysPropKey, getStringValue(flag.mDebugDefault));
268         }
269     }
270 }
271