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         /**
58          * FOR DEVELOPMENT / TESTING ONLY!!!
59          * Forcibly demote *ALL* FSI notifications as if no apps have the app op permission.
60          * NOTE: enabling this implies SHOW_STICKY_HUN_FOR_DENIED_FSI in SystemUI
61          */
62         public static final Flag FSI_FORCE_DEMOTE =
63                 devFlag("persist.sysui.notification.fsi_force_demote");
64 
65         /** Gating the feature which shows FSI-denied notifications as Sticky HUNs */
66         public static final Flag SHOW_STICKY_HUN_FOR_DENIED_FSI =
67                 releasedFlag("persist.sysui.notification.show_sticky_hun_for_denied_fsi");
68 
69         /** Gating the redaction of OTP notifications on the lockscreen */
70         public static final Flag OTP_REDACTION =
71                 devFlag("persist.sysui.notification.otp_redaction");
72 
73         /** Gating the logging of DND state change events. */
74         public static final Flag LOG_DND_STATE_EVENTS =
75                 releasedFlag("persist.sysui.notification.log_dnd_state_events");
76 
77         /** Gating the holding of WakeLocks until NLSes are told about a new notification. */
78         public static final Flag WAKE_LOCK_FOR_POSTING_NOTIFICATION =
79                 releasedFlag("persist.sysui.notification.wake_lock_for_posting_notification");
80 
81         /** Gating storing NotificationRankingUpdate ranking map in shared memory. */
82         public static final Flag RANKING_UPDATE_ASHMEM = devFlag(
83                 "persist.sysui.notification.ranking_update_ashmem");
84 
85         public static final Flag PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS = devFlag(
86                 "persist.sysui.notification.propagate_channel_updates_to_conversations");
87     }
88 
89     //// == End of flags.  Everything below this line is the implementation. == ////
90 
91     /** The interface used for resolving SystemUI SystemProperties Flags to booleans. */
92     public interface FlagResolver {
93         /** Is the flag enabled? */
isEnabled(Flag flag)94         boolean isEnabled(Flag flag);
95     }
96 
97     /** The primary, immutable resolver returned by getResolver() */
98     private static final FlagResolver
99             MAIN_RESOLVER =
100             Build.IS_DEBUGGABLE ? new DebugResolver() : new ProdResolver();
101 
102     /**
103      * On debuggable builds, this can be set to override the resolver returned by getResolver().
104      * This can be useful to override flags when testing components that do not allow injecting the
105      * SystemUiPropertiesFlags resolver they use.
106      * Always set this to null when tests tear down.
107      */
108     @VisibleForTesting
109     public static FlagResolver TEST_RESOLVER = null;
110 
111     /** Get the resolver for this device configuration. */
getResolver()112     public static FlagResolver getResolver() {
113         if (Build.IS_DEBUGGABLE && TEST_RESOLVER != null) {
114             Log.i("SystemUiSystemPropertiesFlags", "Returning debug resolver " + TEST_RESOLVER);
115             return TEST_RESOLVER;
116         }
117         return MAIN_RESOLVER;
118     }
119 
120     /**
121      * Creates a flag that is disabled by default in debuggable builds.
122      * It can be enabled by setting this flag's SystemProperty to 1.
123      *
124      * This flag is ALWAYS disabled in release builds.
125      */
126     @VisibleForTesting
devFlag(String name)127     public static Flag devFlag(String name) {
128         return new Flag(name, false, null);
129     }
130 
131     /**
132      * Creates a flag that is disabled by default in debuggable builds.
133      * It can be enabled or force-disabled by setting this flag's SystemProperty to 1 or 0.
134      * If this flag's SystemProperty is not set, the flag can be enabled by setting the
135      * TEAMFOOD flag's SystemProperty to 1.
136      *
137      * This flag is ALWAYS disabled in release builds.
138      */
139     @VisibleForTesting
teamfoodFlag(String name)140     public static Flag teamfoodFlag(String name) {
141         return new Flag(name, false, TEAMFOOD);
142     }
143 
144     /**
145      * Creates a flag that is enabled by default in debuggable builds.
146      * It can be enabled by setting this flag's SystemProperty to 0.
147      *
148      * This flag is ALWAYS enabled in release builds.
149      */
150     @VisibleForTesting
releasedFlag(String name)151     public static Flag releasedFlag(String name) {
152         return new Flag(name, true, null);
153     }
154 
155     /** Represents a developer-switchable gate for a feature. */
156     public static final class Flag {
157         public final String mSysPropKey;
158         public final boolean mDefaultValue;
159         @Nullable
160         public final Flag mDebugDefault;
161 
162         /** constructs a new flag.  only visible for testing the class */
163         @VisibleForTesting
Flag(@onNull String sysPropKey, boolean defaultValue, @Nullable Flag debugDefault)164         public Flag(@NonNull String sysPropKey, boolean defaultValue, @Nullable Flag debugDefault) {
165             mSysPropKey = sysPropKey;
166             mDefaultValue = defaultValue;
167             mDebugDefault = debugDefault;
168         }
169     }
170 
171     /** Implementation of the interface used in release builds. */
172     @VisibleForTesting
173     public static final class ProdResolver implements
174             FlagResolver {
175         @Override
isEnabled(Flag flag)176         public boolean isEnabled(Flag flag) {
177             return flag.mDefaultValue;
178         }
179     }
180 
181     /** Implementation of the interface used in debuggable builds. */
182     @VisibleForTesting
183     public static class DebugResolver implements FlagResolver {
184         @Override
isEnabled(Flag flag)185         public final boolean isEnabled(Flag flag) {
186             if (flag.mDebugDefault == null) {
187                 return getBoolean(flag.mSysPropKey, flag.mDefaultValue);
188             }
189             return getBoolean(flag.mSysPropKey, isEnabled(flag.mDebugDefault));
190         }
191 
192         /** Look up the value; overridable for tests to avoid needing to set SystemProperties */
193         @VisibleForTesting
getBoolean(String key, boolean defaultValue)194         public boolean getBoolean(String key, boolean defaultValue) {
195             return SystemProperties.getBoolean(key, defaultValue);
196         }
197     }
198 }
199