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.util.Log 20 import com.android.systemui.Dependency 21 22 /** 23 * This class promotes best practices for flag guarding System UI view refactors. 24 * * [isEnabled] allows changing an implementation. 25 * * [assertDisabled] allows authors to flag code as being "dead" when the flag gets enabled and 26 * ensure that it is not being invoked accidentally in the post-flag refactor. 27 * * [expectEnabled] allows authors to guard new code with a "safe" alternative when invoked on 28 * flag-disabled builds, but with a check that should crash eng builds or tests when the 29 * expectation is violated. 30 * 31 * The constructors prefer that you provide a [FeatureFlags] instance, but does not require it, 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 ViewRefactorFlag 36 private constructor( 37 private val injectedFlags: FeatureFlags?, 38 private val flag: BooleanFlag, 39 private val readFlagValue: (FeatureFlags) -> Boolean 40 ) { 41 @JvmOverloads 42 constructor( 43 flags: FeatureFlags? = null, 44 flag: UnreleasedFlag 45 ) : this(flags, flag, { it.isEnabled(flag) }) 46 47 @JvmOverloads 48 constructor( 49 flags: FeatureFlags? = null, 50 flag: ReleasedFlag 51 ) : this(flags, flag, { it.isEnabled(flag) }) 52 53 /** Whether the flag is enabled. Called to switch between an old behavior and a new behavior. */ 54 val isEnabled by lazy { 55 @Suppress("DEPRECATION") 56 val featureFlags = injectedFlags ?: Dependency.get(FeatureFlags::class.java) 57 readFlagValue(featureFlags) 58 } 59 60 /** 61 * Called to ensure code is only run when the flag is disabled. This will throw an exception if 62 * the flag is enabled to ensure that the refactor author catches issues in testing. 63 * 64 * Example usage: 65 * ``` 66 * public void setController(NotificationShelfController notificationShelfController) { 67 * mShelfRefactor.assertDisabled(); 68 * mController = notificationShelfController; 69 * } 70 * ```` 71 */ 72 fun assertDisabled() = check(!isEnabled) { "Code path not supported when $flag is enabled." } 73 74 /** 75 * Called to ensure code is only run when the flag is enabled. This protects users from the 76 * unintended behaviors caused by accidentally running new logic, while also crashing on an eng 77 * build to ensure that the refactor author catches issues in testing. 78 * 79 * Example usage: 80 * ``` 81 * public void setShelfIcons(NotificationIconContainer icons) { 82 * if (mShelfRefactor.expectEnabled()) { 83 * mShelfIcons = icons; 84 * } 85 * } 86 * ``` 87 */ 88 fun expectEnabled(): Boolean { 89 if (!isEnabled) { 90 val message = "Code path not supported when $flag is disabled." 91 Log.wtf(TAG, message, Exception(message)) 92 } 93 return isEnabled 94 } 95 96 private companion object { 97 private const val TAG = "ViewRefactorFlag" 98 } 99 } 100