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 }