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.config; 18 19 import android.content.Context; 20 import android.content.SharedPreferences; 21 import android.os.Process; 22 import android.text.Html; 23 import android.util.Log; 24 import android.view.Menu; 25 import android.view.MenuItem; 26 import android.widget.Toast; 27 28 import com.android.launcher3.R; 29 import com.android.launcher3.config.BaseFlags.TogglableFlag; 30 31 import androidx.preference.PreferenceDataStore; 32 import androidx.preference.PreferenceFragment; 33 import androidx.preference.PreferenceGroup; 34 import androidx.preference.SwitchPreference; 35 36 /** 37 * Dev-build only UI allowing developers to toggle flag settings. See {@link FeatureFlags}. 38 */ 39 public final class FlagTogglerPrefUi { 40 41 private static final String TAG = "FlagTogglerPrefFrag"; 42 43 private final PreferenceFragment mFragment; 44 private final Context mContext; 45 private final SharedPreferences mSharedPreferences; 46 47 private final PreferenceDataStore mDataStore = new PreferenceDataStore() { 48 49 @Override 50 public void putBoolean(String key, boolean value) { 51 for (TogglableFlag flag : FeatureFlags.getTogglableFlags()) { 52 if (flag.getKey().equals(key)) { 53 boolean prevValue = flag.get(); 54 flag.updateStorage(mContext, value); 55 updateMenu(); 56 if (flag.get() != prevValue) { 57 Toast.makeText(mContext, "Flag applied", Toast.LENGTH_SHORT).show(); 58 } 59 } 60 } 61 } 62 63 @Override 64 public boolean getBoolean(String key, boolean defaultValue) { 65 for (TogglableFlag flag : FeatureFlags.getTogglableFlags()) { 66 if (flag.getKey().equals(key)) { 67 return flag.getFromStorage(mContext, defaultValue); 68 } 69 } 70 return defaultValue; 71 } 72 }; 73 FlagTogglerPrefUi(PreferenceFragment fragment)74 public FlagTogglerPrefUi(PreferenceFragment fragment) { 75 mFragment = fragment; 76 mContext = fragment.getActivity(); 77 mSharedPreferences = mContext.getSharedPreferences( 78 FeatureFlags.FLAGS_PREF_NAME, Context.MODE_PRIVATE); 79 } 80 applyTo(PreferenceGroup parent)81 public void applyTo(PreferenceGroup parent) { 82 // For flag overrides we only want to store when the engineer chose to override the 83 // flag with a different value than the default. That way, when we flip flags in 84 // future, engineers will pick up the new value immediately. To accomplish this, we use a 85 // custom preference data store. 86 for (TogglableFlag flag : FeatureFlags.getTogglableFlags()) { 87 SwitchPreference switchPreference = new SwitchPreference(mContext); 88 switchPreference.setKey(flag.getKey()); 89 switchPreference.setDefaultValue(flag.getDefaultValue()); 90 switchPreference.setChecked(getFlagStateFromSharedPrefs(flag)); 91 switchPreference.setTitle(flag.getKey()); 92 updateSummary(switchPreference, flag); 93 switchPreference.setPreferenceDataStore(mDataStore); 94 parent.addPreference(switchPreference); 95 } 96 updateMenu(); 97 } 98 99 /** 100 * Updates the summary to show the description and whether the flag overrides the default value. 101 */ updateSummary(SwitchPreference switchPreference, TogglableFlag flag)102 private void updateSummary(SwitchPreference switchPreference, TogglableFlag flag) { 103 String onWarning = flag.getDefaultValue() ? "" : "<b>OVERRIDDEN</b><br>"; 104 String offWarning = flag.getDefaultValue() ? "<b>OVERRIDDEN</b><br>" : ""; 105 switchPreference.setSummaryOn(Html.fromHtml(onWarning + flag.getDescription())); 106 switchPreference.setSummaryOff(Html.fromHtml(offWarning + flag.getDescription())); 107 } 108 updateMenu()109 private void updateMenu() { 110 mFragment.setHasOptionsMenu(anyChanged()); 111 mFragment.getActivity().invalidateOptionsMenu(); 112 } 113 onCreateOptionsMenu(Menu menu)114 public void onCreateOptionsMenu(Menu menu) { 115 if (anyChanged()) { 116 menu.add(0, R.id.menu_apply_flags, 0, "Apply") 117 .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); 118 } 119 } 120 onOptionsItemSelected(MenuItem item)121 public void onOptionsItemSelected(MenuItem item) { 122 if (item.getItemId() == R.id.menu_apply_flags) { 123 mSharedPreferences.edit().commit(); 124 Log.e(TAG, 125 "Killing launcher process " + Process.myPid() + " to apply new flag values"); 126 System.exit(0); 127 } 128 } 129 onStop()130 public void onStop() { 131 if (anyChanged()) { 132 Toast.makeText(mContext, "Flag won't be applied until you restart launcher", 133 Toast.LENGTH_LONG).show(); 134 } 135 } 136 getFlagStateFromSharedPrefs(TogglableFlag flag)137 private boolean getFlagStateFromSharedPrefs(TogglableFlag flag) { 138 return mDataStore.getBoolean(flag.getKey(), flag.getDefaultValue()); 139 } 140 anyChanged()141 private boolean anyChanged() { 142 for (TogglableFlag flag : FeatureFlags.getTogglableFlags()) { 143 if (getFlagStateFromSharedPrefs(flag) != flag.get()) { 144 return true; 145 } 146 } 147 return false; 148 } 149 }