1 /* 2 * Copyright (C) 2017 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.launcher3.config; 18 19 import static androidx.core.util.Preconditions.checkNotNull; 20 21 import android.content.ContentResolver; 22 import android.content.Context; 23 import android.content.SharedPreferences; 24 import android.database.ContentObserver; 25 import android.os.Handler; 26 import android.os.Looper; 27 import android.provider.Settings; 28 29 import androidx.annotation.GuardedBy; 30 import androidx.annotation.Keep; 31 import androidx.annotation.VisibleForTesting; 32 33 import com.android.launcher3.Utilities; 34 35 import java.util.ArrayList; 36 import java.util.List; 37 import java.util.SortedMap; 38 import java.util.TreeMap; 39 40 /** 41 * Defines a set of flags used to control various launcher behaviors. 42 * 43 * <p>All the flags should be defined here with appropriate default values. 44 * 45 * <p>This class is kept package-private to prevent direct access. 46 */ 47 @Keep 48 abstract class BaseFlags { 49 50 private static final Object sLock = new Object(); 51 @GuardedBy("sLock") 52 private static final List<TogglableFlag> sFlags = new ArrayList<>(); 53 54 static final String FLAGS_PREF_NAME = "featureFlags"; 55 BaseFlags()56 BaseFlags() { 57 throw new UnsupportedOperationException("Don't instantiate BaseFlags"); 58 } 59 showFlagTogglerUi(Context context)60 public static boolean showFlagTogglerUi(Context context) { 61 return Utilities.IS_DEBUG_DEVICE && Utilities.isDevelopersOptionsEnabled(context); 62 } 63 64 public static final boolean IS_DOGFOOD_BUILD = false; 65 66 // When enabled the promise icon is visible in all apps while installation an app. 67 public static final boolean LAUNCHER3_PROMISE_APPS_IN_ALL_APPS = false; 68 69 // Enable moving the QSB on the 0th screen of the workspace 70 public static final boolean QSB_ON_FIRST_SCREEN = true; 71 72 public static final TogglableFlag EXAMPLE_FLAG = new TogglableFlag("EXAMPLE_FLAG", true, 73 "An example flag that doesn't do anything. Useful for testing"); 74 75 //Feature flag to enable pulling down navigation shade from workspace. 76 public static final boolean PULL_DOWN_STATUS_BAR = true; 77 78 // When true, custom widgets are loaded using CustomWidgetParser. 79 public static final boolean ENABLE_CUSTOM_WIDGETS = false; 80 81 // Features to control Launcher3Go behavior 82 public static final boolean GO_DISABLE_WIDGETS = false; 83 84 // When enabled shows a work profile tab in all apps 85 public static final boolean ALL_APPS_TABS_ENABLED = true; 86 87 // When true, overview shows screenshots in the orientation they were taken rather than 88 // trying to make them fit the orientation the device is in. 89 public static final boolean OVERVIEW_USE_SCREENSHOT_ORIENTATION = true; 90 91 /** 92 * Feature flag to handle define config changes dynamically instead of killing the process. 93 */ 94 public static final TogglableFlag APPLY_CONFIG_AT_RUNTIME = new TogglableFlag( 95 "APPLY_CONFIG_AT_RUNTIME", true, "Apply display changes dynamically"); 96 97 public static final TogglableFlag QUICKSTEP_SPRINGS = new TogglableFlag("QUICKSTEP_SPRINGS", 98 false, "Enable springs for quickstep animations"); 99 100 public static final TogglableFlag ADAPTIVE_ICON_WINDOW_ANIM = new TogglableFlag( 101 "ADAPTIVE_ICON_WINDOW_ANIM", true, 102 "Use adaptive icons for window animations."); 103 104 public static final TogglableFlag ENABLE_QUICKSTEP_LIVE_TILE = new TogglableFlag( 105 "ENABLE_QUICKSTEP_LIVE_TILE", false, "Enable live tile in Quickstep overview"); 106 107 public static final TogglableFlag ENABLE_HINTS_IN_OVERVIEW = new TogglableFlag( 108 "ENABLE_HINTS_IN_OVERVIEW", false, 109 "Show chip hints and gleams on the overview screen"); 110 111 public static final TogglableFlag FAKE_LANDSCAPE_UI = new TogglableFlag( 112 "FAKE_LANDSCAPE_UI", false, 113 "Rotate launcher UI instead of using transposed layout"); 114 initialize(Context context)115 public static void initialize(Context context) { 116 // Avoid the disk read for user builds 117 if (Utilities.IS_DEBUG_DEVICE) { 118 synchronized (sLock) { 119 for (TogglableFlag flag : sFlags) { 120 flag.initialize(context); 121 } 122 } 123 } 124 } 125 getTogglableFlags()126 static List<TogglableFlag> getTogglableFlags() { 127 // By Java Language Spec 12.4.2 128 // https://docs.oracle.com/javase/specs/jls/se7/html/jls-12.html#jls-12.4.2, the 129 // TogglableFlag instances on BaseFlags will be created before those on the FeatureFlags 130 // subclass. This code handles flags that are redeclared in FeatureFlags, ensuring the 131 // FeatureFlags one takes priority. 132 SortedMap<String, TogglableFlag> flagsByKey = new TreeMap<>(); 133 synchronized (sLock) { 134 for (TogglableFlag flag : sFlags) { 135 flagsByKey.put(flag.key, flag); 136 } 137 } 138 return new ArrayList<>(flagsByKey.values()); 139 } 140 141 public static class TogglableFlag { 142 private final String key; 143 private final boolean defaultValue; 144 private final String description; 145 private boolean currentValue; 146 TogglableFlag( String key, boolean defaultValue, String description)147 TogglableFlag( 148 String key, 149 boolean defaultValue, 150 String description) { 151 this.key = checkNotNull(key); 152 this.currentValue = this.defaultValue = defaultValue; 153 this.description = checkNotNull(description); 154 synchronized (sLock) { 155 sFlags.add(this); 156 } 157 } 158 159 /** Set the value of this flag. This should only be used in tests. */ 160 @VisibleForTesting setForTests(boolean value)161 void setForTests(boolean value) { 162 currentValue = value; 163 } 164 165 @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) getKey()166 public String getKey() { 167 return key; 168 } initialize(Context context)169 void initialize(Context context) { 170 currentValue = getFromStorage(context, defaultValue); 171 } 172 updateStorage(Context context, boolean value)173 public void updateStorage(Context context, boolean value) { 174 SharedPreferences.Editor editor = context.getSharedPreferences(FLAGS_PREF_NAME, 175 Context.MODE_PRIVATE).edit(); 176 if (value == defaultValue) { 177 editor.remove(key).apply(); 178 } else { 179 editor.putBoolean(key, value).apply(); 180 } 181 } 182 getFromStorage(Context context, boolean defaultValue)183 boolean getFromStorage(Context context, boolean defaultValue) { 184 return context.getSharedPreferences(FLAGS_PREF_NAME, Context.MODE_PRIVATE) 185 .getBoolean(key, defaultValue); 186 } 187 getDefaultValue()188 boolean getDefaultValue() { 189 return defaultValue; 190 } 191 192 /** Returns the value of the flag at process start, including any overrides present. */ get()193 public boolean get() { 194 return currentValue; 195 } 196 getDescription()197 String getDescription() { 198 return description; 199 } 200 201 @Override toString()202 public String toString() { 203 return "TogglableFlag{" 204 + "key=" + key + ", " 205 + "defaultValue=" + defaultValue + ", " 206 + "description=" + description 207 + "}"; 208 } 209 210 @Override equals(Object o)211 public boolean equals(Object o) { 212 if (o == this) { 213 return true; 214 } 215 if (o instanceof TogglableFlag) { 216 TogglableFlag that = (TogglableFlag) o; 217 return (this.key.equals(that.getKey())) 218 && (this.defaultValue == that.getDefaultValue()) 219 && (this.description.equals(that.getDescription())); 220 } 221 return false; 222 } 223 224 @Override hashCode()225 public int hashCode() { 226 int h$ = 1; 227 h$ *= 1000003; 228 h$ ^= key.hashCode(); 229 h$ *= 1000003; 230 h$ ^= defaultValue ? 1231 : 1237; 231 h$ *= 1000003; 232 h$ ^= description.hashCode(); 233 return h$; 234 } 235 } 236 237 /** 238 * Stores the FeatureFlag's value in Settings.Global instead of our SharedPrefs. 239 * This is useful if we want to be able to control this flag from another process. 240 */ 241 public static final class ToggleableGlobalSettingsFlag extends TogglableFlag { 242 private ContentResolver contentResolver; 243 ToggleableGlobalSettingsFlag(String key, boolean defaultValue, String description)244 ToggleableGlobalSettingsFlag(String key, boolean defaultValue, String description) { 245 super(key, defaultValue, description); 246 } 247 248 @Override initialize(Context context)249 public void initialize(Context context) { 250 contentResolver = context.getContentResolver(); 251 contentResolver.registerContentObserver(Settings.Global.getUriFor(getKey()), true, 252 new ContentObserver(new Handler(Looper.getMainLooper())) { 253 @Override 254 public void onChange(boolean selfChange) { 255 superInitialize(context); 256 }}); 257 superInitialize(context); 258 } 259 superInitialize(Context context)260 private void superInitialize(Context context) { 261 super.initialize(context); 262 } 263 264 @Override updateStorage(Context context, boolean value)265 public void updateStorage(Context context, boolean value) { 266 if (contentResolver == null) { 267 return; 268 } 269 Settings.Global.putInt(contentResolver, getKey(), value ? 1 : 0); 270 } 271 272 @Override getFromStorage(Context context, boolean defaultValue)273 boolean getFromStorage(Context context, boolean defaultValue) { 274 if (contentResolver == null) { 275 return defaultValue; 276 } 277 return Settings.Global.getInt(contentResolver, getKey(), defaultValue ? 1 : 0) == 1; 278 } 279 } 280 } 281