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.flags
18 
19 import android.os.Build
20 import android.util.Log
21 
22 /**
23  * Utilities for writing your own objects to uphold refactor flag conventions.
24  *
25  * Example usage:
26  * ```
27  * object SomeRefactor {
28  *     const val FLAG_NAME = Flags.SOME_REFACTOR
29  *     val token: FlagToken get() = FlagToken(FLAG_NAME, isEnabled)
30  *     @JvmStatic inline val isEnabled get() = Flags.someRefactor()
31  *     @JvmStatic inline fun isUnexpectedlyInLegacyMode() =
32  *         RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
33  *     @JvmStatic inline fun assertInLegacyMode() =
34  *         RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
35  * }
36  * ```
37  *
38  * Legacy mode crashes can be disabled with the command:
39  * ```
40  * adb shell setprop log.tag.RefactorFlagAssert silent
41  * ```
42  */
43 @Suppress("NOTHING_TO_INLINE")
44 object RefactorFlagUtils {
45     /**
46      * Called to ensure code is only run when the flag is enabled. This protects users from the
47      * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
48      * build to ensure that the refactor author catches issues in testing.
49      *
50      * Example usage:
51      * ```
52      * public void setNewController(SomeController someController) {
53      *     if (SomeRefactor.isUnexpectedlyInLegacyMode()) return;
54      *     mSomeController = someController;
55      * }
56      * ```
57      */
isUnexpectedlyInLegacyModenull58     inline fun isUnexpectedlyInLegacyMode(isEnabled: Boolean, flagName: Any): Boolean {
59         val inLegacyMode = !isEnabled
60         if (inLegacyMode) {
61             assertOnEngBuild("New code path expects $flagName to be enabled.")
62         }
63         return inLegacyMode
64     }
65 
66     /**
67      * Called to ensure code is only run when the flag is disabled. This will throw an exception if
68      * the flag is enabled to ensure that the refactor author catches issues in testing.
69      *
70      * Example usage:
71      * ```
72      * public void setSomeLegacyController(SomeController someController) {
73      *     SomeRefactor.assertInLegacyMode();
74      *     mSomeController = someController;
75      * }
76      * ````
77      */
assertInLegacyModenull78     inline fun assertInLegacyMode(isEnabled: Boolean, flagName: Any) =
79         check(!isEnabled) { "Legacy code path not supported when $flagName is enabled." }
80 
81     /**
82      * Called to ensure the new code is only run when the flag is enabled. This will throw an
83      * exception if the flag is disabled to ensure that the refactor author catches issues in
84      * testing.
85      *
86      * Example usage:
87      * ```
88      * public void setSomeNewController(SomeController someController) {
89      *     SomeRefactor.assertInNewMode();
90      *     mSomeController = someController;
91      * }
92      * ````
93      */
assertInNewModenull94     inline fun assertInNewMode(isEnabled: Boolean, flagName: Any) =
95         check(isEnabled) { "New code path not supported when $flagName is disabled." }
96 
97     /**
98      * This will [Log.wtf] with the given message, assuming [ASSERT_TAG] is loggable at that level.
99      * This means an engineer can prevent this from crashing by running the command:
100      * ```
101      * adb shell setprop log.tag.RefactorFlagAssert silent
102      * ```
103      */
assertOnEngBuildnull104     fun assertOnEngBuild(message: String) {
105         if (Log.isLoggable(ASSERT_TAG, Log.ASSERT)) {
106             val exception = if (Build.isDebuggable()) IllegalStateException(message) else null
107             Log.wtf(ASSERT_TAG, message, exception)
108         } else if (Log.isLoggable(STANDARD_TAG, Log.WARN)) {
109             Log.w(STANDARD_TAG, message)
110         }
111     }
112 
113     /**
114      * Tag used to determine if an incorrect flag guard should crash System UI running an eng build.
115      * This is enabled by default. To disable, run:
116      * ```
117      * adb shell setprop log.tag.RefactorFlagAssert silent
118      * ```
119      */
120     private const val ASSERT_TAG = "RefactorFlagAssert"
121 
122     /** Tag used for non-crashing logs or when the [ASSERT_TAG] has been silenced. */
123     private const val STANDARD_TAG = "RefactorFlag"
124 }
125 
126 /** An object which allows dependency tracking */
127 data class FlagToken(val name: String, val isEnabled: Boolean) {
toStringnull128     override fun toString(): String = "$name (${if (isEnabled) "enabled" else "disabled"})"
129 }
130