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 com.android.systemui.Dependency
20 
21 /**
22  * This class promotes best practices for flag guarding System UI view refactors.
23  * * [isEnabled] allows changing an implementation.
24  * * [assertInLegacyMode] allows authors to flag code as being "dead" when the flag gets enabled and
25  *   ensure that it is not being invoked accidentally in the post-flag refactor.
26  * * [isUnexpectedlyInLegacyMode] allows authors to guard new code with a "safe" alternative when
27  *   invoked on flag-disabled builds, but with a check that should crash eng builds or tests when
28  *   the expectation is violated.
29  *
30  * The constructors require that you provide a [FeatureFlags] instance. If you're using this in a
31  * View class, it's acceptable to ue the [forView] constructor methods, which do not require one,
32  * falling back to [Dependency.get]. This fallback should ONLY be used to flag-guard code changes
33  * inside Views where injecting flag values after initialization can be error-prone.
34  */
35 class RefactorFlag
36 private constructor(
37     private val injectedFlags: FeatureFlags?,
38     private val flagName: Any,
39     private val readFlagValue: (FeatureFlags) -> Boolean
40 ) {
41     constructor(
42         flags: FeatureFlags,
43         flag: UnreleasedFlag
<lambda>null44     ) : this(flags, flag, { it.isEnabled(flag) })
45 
<lambda>null46     constructor(flags: FeatureFlags, flag: ReleasedFlag) : this(flags, flag, { it.isEnabled(flag) })
47 
48     /** Whether the flag is enabled. Called to switch between an old behavior and a new behavior. */
<lambda>null49     val isEnabled by lazy {
50         @Suppress("DEPRECATION")
51         val featureFlags = injectedFlags ?: Dependency.get(FeatureFlags::class.java)
52         readFlagValue(featureFlags)
53     }
54 
55     /**
56      * Called to ensure code is only run when the flag is disabled. This will throw an exception if
57      * the flag is enabled to ensure that the refactor author catches issues in testing.
58      *
59      * Example usage:
60      * ```
61      * public void setController(NotificationShelfController notificationShelfController) {
62      *     mShelfRefactor.assertInLegacyMode();
63      *     mController = notificationShelfController;
64      * }
65      * ````
66      */
assertInLegacyModenull67     fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, flagName)
68 
69     /**
70      * Called to ensure code is only run when the flag is enabled. This protects users from the
71      * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
72      * build to ensure that the refactor author catches issues in testing.
73      *
74      * Example usage:
75      * ```
76      * public void setShelfIcons(NotificationIconContainer icons) {
77      *     if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;
78      *     mShelfIcons = icons;
79      * }
80      * ```
81      */
82     fun isUnexpectedlyInLegacyMode(): Boolean =
83         RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, flagName)
84 
85     companion object {
86         private const val TAG = "RefactorFlag"
87 
88         /** Construct a [RefactorFlag] within View construction where injection is impossible. */
89         @JvmStatic
90         @JvmOverloads
91         fun forView(flag: UnreleasedFlag, flags: FeatureFlags? = null) =
92             RefactorFlag(flags, flag) { it.isEnabled(flag) }
93 
94         /** Construct a [RefactorFlag] within View construction where injection is impossible. */
95         @JvmStatic
96         @JvmOverloads
97         fun forView(flag: ReleasedFlag, flags: FeatureFlags? = null) =
98             RefactorFlag(flags, flag) { it.isEnabled(flag) }
99     }
100 }
101