• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.uioverrides.flags;
18 
19 import static com.android.launcher3.config.FeatureFlags.FlagState.TEAMFOOD;
20 import static com.android.launcher3.uioverrides.flags.FlagsFactory.TEAMFOOD_FLAG;
21 
22 import android.content.Context;
23 import android.os.Handler;
24 import android.os.Process;
25 import android.text.Html;
26 import android.text.TextUtils;
27 import android.util.Log;
28 import android.view.Menu;
29 import android.view.MenuItem;
30 import android.widget.Toast;
31 
32 import androidx.preference.PreferenceDataStore;
33 import androidx.preference.PreferenceFragmentCompat;
34 import androidx.preference.PreferenceGroup;
35 import androidx.preference.PreferenceViewHolder;
36 import androidx.preference.SwitchPreference;
37 
38 import com.android.launcher3.R;
39 import com.android.launcher3.config.FeatureFlags;
40 
41 import java.util.List;
42 import java.util.Set;
43 
44 /**
45  * Dev-build only UI allowing developers to toggle flag settings. See {@link FeatureFlags}.
46  */
47 public final class FlagTogglerPrefUi {
48 
49     private static final String TAG = "FlagTogglerPrefFrag";
50 
51     private final PreferenceFragmentCompat mFragment;
52     private final Context mContext;
53     private final PreferenceDataStore mDataStore = new PreferenceDataStore() {
54 
55         @Override
56         public void putBoolean(String key, boolean value) {
57             FlagsFactory.getSharedPreferences().edit().putBoolean(key, value).apply();
58             updateMenu();
59         }
60 
61         @Override
62         public boolean getBoolean(String key, boolean defaultValue) {
63             return FlagsFactory.getSharedPreferences().getBoolean(key, defaultValue);
64         }
65     };
66 
FlagTogglerPrefUi(PreferenceFragmentCompat fragment)67     public FlagTogglerPrefUi(PreferenceFragmentCompat fragment) {
68         mFragment = fragment;
69         mContext = fragment.getActivity();
70     }
71 
applyTo(PreferenceGroup parent)72     public void applyTo(PreferenceGroup parent) {
73         Set<String> modifiedPrefs = FlagsFactory.getSharedPreferences().getAll().keySet();
74         List<DebugFlag> flags = FlagsFactory.getDebugFlags();
75         flags.sort((f1, f2) -> {
76             // Sort first by any prefs that the user has changed, then alphabetically.
77             int changeComparison = Boolean.compare(
78                     modifiedPrefs.contains(f2.key), modifiedPrefs.contains(f1.key));
79             return changeComparison != 0
80                     ? changeComparison
81                     : f1.key.compareToIgnoreCase(f2.key);
82         });
83 
84         // Ensure that teamfood flag comes on the top
85         if (flags.remove(TEAMFOOD_FLAG)) {
86             flags.add(0, (DebugFlag) TEAMFOOD_FLAG);
87         }
88 
89         // For flag overrides we only want to store when the engineer chose to override the
90         // flag with a different value than the default. That way, when we flip flags in
91         // future, engineers will pick up the new value immediately. To accomplish this, we use a
92         // custom preference data store.
93         for (DebugFlag flag : flags) {
94             SwitchPreference switchPreference = new SwitchPreference(mContext) {
95                 @Override
96                 public void onBindViewHolder(PreferenceViewHolder holder) {
97                     super.onBindViewHolder(holder);
98                     holder.itemView.setOnLongClickListener(v -> {
99                         FlagsFactory.getSharedPreferences().edit().remove(flag.key).apply();
100                         setChecked(getFlagStateFromSharedPrefs(flag));
101                         updateSummary(this, flag);
102                         updateMenu();
103                         return true;
104                     });
105                 }
106             };
107             switchPreference.setKey(flag.key);
108             switchPreference.setDefaultValue(FlagsFactory.getEnabledValue(flag.defaultValue));
109             switchPreference.setChecked(getFlagStateFromSharedPrefs(flag));
110             switchPreference.setTitle(flag.key);
111             updateSummary(switchPreference, flag);
112             switchPreference.setPreferenceDataStore(mDataStore);
113             switchPreference.setOnPreferenceChangeListener((p, v) -> {
114                 new Handler().post(() -> updateSummary(switchPreference, flag));
115                 return true;
116             });
117 
118 
119             parent.addPreference(switchPreference);
120         }
121         updateMenu();
122     }
123 
124     /**
125      * Updates the summary to show the description and whether the flag overrides the default value.
126      */
updateSummary(SwitchPreference switchPreference, DebugFlag flag)127     private void updateSummary(SwitchPreference switchPreference, DebugFlag flag) {
128         String summary = flag.defaultValue == TEAMFOOD
129                 ? "<font color='blue'><b>[TEAMFOOD]</b> </font>" : "";
130         if (FlagsFactory.getSharedPreferences().contains(flag.key)) {
131             summary += "<font color='red'><b>[OVERRIDDEN]</b> </font>";
132         }
133         if (!TextUtils.isEmpty(summary)) {
134             summary += "<br>";
135         }
136         switchPreference.setSummary(Html.fromHtml(summary + flag.description));
137     }
138 
updateMenu()139     private void updateMenu() {
140         mFragment.setHasOptionsMenu(anyChanged());
141         mFragment.getActivity().invalidateOptionsMenu();
142     }
143 
onCreateOptionsMenu(Menu menu)144     public void onCreateOptionsMenu(Menu menu) {
145         if (anyChanged()) {
146             menu.add(0, R.id.menu_apply_flags, 0, "Apply")
147                     .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
148         }
149     }
150 
onOptionsItemSelected(MenuItem item)151     public void onOptionsItemSelected(MenuItem item) {
152         if (item.getItemId() == R.id.menu_apply_flags) {
153             FlagsFactory.getSharedPreferences().edit().commit();
154             Log.e(TAG,
155                     "Killing launcher process " + Process.myPid() + " to apply new flag values");
156             System.exit(0);
157         }
158     }
159 
onStop()160     public void onStop() {
161         if (anyChanged()) {
162             Toast.makeText(mContext, "Flag won't be applied until you restart launcher",
163                     Toast.LENGTH_LONG).show();
164         }
165     }
166 
getFlagStateFromSharedPrefs(DebugFlag flag)167     private boolean getFlagStateFromSharedPrefs(DebugFlag flag) {
168         boolean defaultValue = FlagsFactory.getEnabledValue(flag.defaultValue);
169         return mDataStore.getBoolean(flag.key, defaultValue);
170     }
171 
anyChanged()172     private boolean anyChanged() {
173         for (DebugFlag flag : FlagsFactory.getDebugFlags()) {
174             if (getFlagStateFromSharedPrefs(flag) != flag.get()) {
175                 return true;
176             }
177         }
178         return false;
179     }
180 }
181