1 /*******************************************************************************
2  *      Copyright (C) 2014 Google Inc.
3  *      Licensed to The Android Open Source Project.
4  *
5  *      Licensed under the Apache License, Version 2.0 (the "License");
6  *      you may not use this file except in compliance with the License.
7  *      You may obtain a copy of the License at
8  *
9  *           http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *      Unless required by applicable law or agreed to in writing, software
12  *      distributed under the License is distributed on an "AS IS" BASIS,
13  *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *      See the License for the specific language governing permissions and
15  *      limitations under the License.
16  *******************************************************************************/
17 
18 package com.android.mail.ui.settings;
19 
20 import android.app.AlertDialog;
21 import android.content.Context;
22 import android.content.DialogInterface;
23 import android.content.DialogInterface.OnClickListener;
24 import android.os.AsyncTask;
25 import android.os.Bundle;
26 import android.preference.CheckBoxPreference;
27 import android.preference.ListPreference;
28 import android.preference.Preference;
29 import android.preference.Preference.OnPreferenceChangeListener;
30 import android.view.Menu;
31 import android.view.MenuInflater;
32 import android.view.MenuItem;
33 import android.widget.Toast;
34 
35 import com.android.mail.preferences.MailPrefs;
36 import com.android.mail.preferences.MailPrefs.PreferenceKeys;
37 import com.android.mail.providers.SuggestionsProvider;
38 import com.android.mail.providers.UIProvider.AutoAdvance;
39 import com.android.mail.utils.LogUtils;
40 import com.android.mail.R;
41 import com.google.common.annotations.VisibleForTesting;
42 
43 /**
44  * This fragment shows general app preferences.
45  */
46 public class GeneralPrefsFragment extends MailPreferenceFragment
47         implements OnClickListener, OnPreferenceChangeListener {
48 
49     // Keys used to reference pref widgets which don't map directly to preference entries
50     static final String AUTO_ADVANCE_WIDGET = "auto-advance-widget";
51 
52     static final String CALLED_FROM_TEST = "called-from-test";
53 
54     // Category for removal actions
55     protected static final String REMOVAL_ACTIONS_GROUP = "removal-actions-group";
56 
57     protected MailPrefs mMailPrefs;
58 
59     private AlertDialog mClearSearchHistoryDialog;
60 
61     private ListPreference mAutoAdvance;
62     private static final int[] AUTO_ADVANCE_VALUES = {
63             AutoAdvance.NEWER,
64             AutoAdvance.OLDER,
65             AutoAdvance.LIST
66     };
67 
68     @Override
onCreate(Bundle savedInstanceState)69     public void onCreate(Bundle savedInstanceState) {
70         super.onCreate(savedInstanceState);
71 
72         setHasOptionsMenu(true);
73 
74         mMailPrefs = MailPrefs.get(getActivity());
75 
76         // Set the shared prefs name to use prefs auto-persist behavior by default.
77         // Any pref more complex than the default (say, involving migration), should set
78         // "persistent=false" in the XML and manually handle preference initialization and change.
79         getPreferenceManager()
80                 .setSharedPreferencesName(mMailPrefs.getSharedPreferencesName());
81 
82         addPreferencesFromResource(R.xml.general_preferences);
83 
84         mAutoAdvance = (ListPreference) findPreference(AUTO_ADVANCE_WIDGET);
85     }
86 
87     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)88     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
89         /*
90          * We deliberately do not call super because our menu includes the parent's menu options to
91          * allow custom ordering.
92          */
93         menu.clear();
94         inflater.inflate(R.menu.general_prefs_fragment_menu, menu);
95     }
96 
97     @Override
onOptionsItemSelected(MenuItem item)98     public boolean onOptionsItemSelected(MenuItem item) {
99         final int itemId = item.getItemId();
100         if (itemId == R.id.clear_search_history_menu_item) {
101             clearSearchHistory();
102             return true;
103         } else if (itemId == R.id.clear_picture_approvals_menu_item) {
104             clearDisplayImages();
105             return true;
106         }
107 
108         return super.onOptionsItemSelected(item);
109     }
110 
111     @Override
onPreferenceChange(Preference preference, Object newValue)112     public boolean onPreferenceChange(Preference preference, Object newValue) {
113         if (getActivity() == null) {
114             // Monkeys cause bad things. This callback may be delayed if for some reason the
115             // preference screen was closed really quickly - just bail then.
116             return false;
117         }
118 
119         final String key = preference.getKey();
120 
121         if (PreferenceKeys.REMOVAL_ACTION.equals(key)) {
122             final String removalAction = newValue.toString();
123             mMailPrefs.setRemovalAction(removalAction);
124         } else if (AUTO_ADVANCE_WIDGET.equals(key)) {
125             final int prefsAutoAdvanceMode =
126                     AUTO_ADVANCE_VALUES[mAutoAdvance.findIndexOfValue((String) newValue)];
127             mMailPrefs.setAutoAdvanceMode(prefsAutoAdvanceMode);
128         } else if (!PreferenceKeys.CONVERSATION_LIST_SWIPE.equals(key) &&
129                 !PreferenceKeys.SHOW_SENDER_IMAGES.equals(key) &&
130                 !PreferenceKeys.DEFAULT_REPLY_ALL.equals(key) &&
131                 !PreferenceKeys.CONVERSATION_OVERVIEW_MODE.equals(key) &&
132                 !PreferenceKeys.CONFIRM_DELETE.equals(key) &&
133                 !PreferenceKeys.CONFIRM_ARCHIVE.equals(key) &&
134                 !PreferenceKeys.CONFIRM_SEND.equals(key)) {
135             return false;
136         }
137 
138         return true;
139     }
140 
clearDisplayImages()141     private void clearDisplayImages() {
142         final ClearPictureApprovalsDialogFragment fragment =
143                 ClearPictureApprovalsDialogFragment.newInstance();
144         fragment.show(getActivity().getFragmentManager(),
145                 ClearPictureApprovalsDialogFragment.FRAGMENT_TAG);
146     }
147 
clearSearchHistory()148     private void clearSearchHistory() {
149         mClearSearchHistoryDialog = new AlertDialog.Builder(getActivity())
150                 .setMessage(R.string.clear_history_dialog_message)
151                 .setTitle(R.string.clear_history_dialog_title)
152                 .setIconAttribute(android.R.attr.alertDialogIcon)
153                 .setPositiveButton(R.string.clear, this)
154                 .setNegativeButton(R.string.cancel, this)
155                 .show();
156     }
157 
158 
159     @Override
onClick(DialogInterface dialog, int which)160     public void onClick(DialogInterface dialog, int which) {
161         if (dialog.equals(mClearSearchHistoryDialog)) {
162             if (which == DialogInterface.BUTTON_POSITIVE) {
163                 final Context context = getActivity();
164                 // Clear the history in the background, as it causes a disk
165                 // write.
166                 new AsyncTask<Void, Void, Void>() {
167                     @Override
168                     protected Void doInBackground(Void... params) {
169                         final SuggestionsProvider suggestions =
170                                 new SuggestionsProvider(context);
171                         suggestions.clearHistory();
172                         suggestions.cleanup();
173                         return null;
174                     }
175                 }.execute();
176                 Toast.makeText(getActivity(), R.string.search_history_cleared, Toast.LENGTH_SHORT)
177                         .show();
178             }
179         }
180     }
181 
182     @Override
onStop()183     public void onStop() {
184         super.onStop();
185         if (mClearSearchHistoryDialog != null && mClearSearchHistoryDialog.isShowing()) {
186             mClearSearchHistoryDialog.dismiss();
187         }
188     }
189 
190     @Override
onResume()191     public void onResume() {
192         super.onResume();
193 
194         // Manually initialize the preference views that require massaging. Prefs that require
195         // massaging include:
196         //  1. a prefs UI control that does not map 1:1 to storage
197         //  2. a pref that must obtain its initial value from migrated storage, and for which we
198         //     don't want to always persist a migrated value
199         final int autoAdvanceModeIndex = prefValueToWidgetIndex(AUTO_ADVANCE_VALUES,
200                 mMailPrefs.getAutoAdvanceMode(), AutoAdvance.DEFAULT);
201         mAutoAdvance.setValueIndex(autoAdvanceModeIndex);
202 
203         listenForPreferenceChange(
204                 PreferenceKeys.REMOVAL_ACTION,
205                 PreferenceKeys.CONVERSATION_LIST_SWIPE,
206                 PreferenceKeys.SHOW_SENDER_IMAGES,
207                 PreferenceKeys.DEFAULT_REPLY_ALL,
208                 PreferenceKeys.CONVERSATION_OVERVIEW_MODE,
209                 AUTO_ADVANCE_WIDGET,
210                 PreferenceKeys.CONFIRM_DELETE,
211                 PreferenceKeys.CONFIRM_ARCHIVE,
212                 PreferenceKeys.CONFIRM_SEND
213         );
214     }
215 
supportsArchive()216     protected boolean supportsArchive() {
217         return true;
218     }
219 
220     /**
221      * Converts the prefs value into an index useful for configuring the UI widget, falling back to
222      * the default value if the value from the prefs can't be found for some reason. If neither can
223      * be found, it throws an {@link java.lang.IllegalArgumentException}
224      *
225      * @param conversionArray An array of prefs values, in widget order
226      * @param prefValue Value of the preference
227      * @param defaultValue Default value, as a fallback if we can't map the real value
228      * @return Index of the entry (or fallback) in the conversion array
229      */
230     @VisibleForTesting
prefValueToWidgetIndex(int[] conversionArray, int prefValue, int defaultValue)231     static int prefValueToWidgetIndex(int[] conversionArray, int prefValue, int defaultValue) {
232         for (int i = 0; i < conversionArray.length; i++) {
233             if (conversionArray[i] == prefValue) {
234                 return i;
235             }
236         }
237         LogUtils.e(LogUtils.TAG, "Can't map preference value " + prefValue);
238         for (int i = 0; i < conversionArray.length; i++) {
239             if (conversionArray[i] == defaultValue) {
240                 return i;
241             }
242         }
243         throw new IllegalArgumentException("Can't map default preference value " + prefValue);
244     }
245 
listenForPreferenceChange(String... keys)246     private void listenForPreferenceChange(String... keys) {
247         for (String key : keys) {
248             Preference p = findPreference(key);
249             if (p != null) {
250                 p.setOnPreferenceChangeListener(this);
251             }
252         }
253     }
254 }
255