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