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