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
18package com.android.mail.ui.settings;
19
20import android.app.AlertDialog;
21import android.content.Context;
22import android.content.DialogInterface;
23import android.content.DialogInterface.OnClickListener;
24import android.os.AsyncTask;
25import android.os.Bundle;
26import android.preference.CheckBoxPreference;
27import android.preference.ListPreference;
28import android.preference.Preference;
29import android.preference.Preference.OnPreferenceChangeListener;
30import android.view.Menu;
31import android.view.MenuInflater;
32import android.view.MenuItem;
33import android.widget.Toast;
34
35import com.android.mail.preferences.MailPrefs;
36import com.android.mail.preferences.MailPrefs.PreferenceKeys;
37import com.android.mail.providers.SuggestionsProvider;
38import com.android.mail.providers.UIProvider.AutoAdvance;
39import com.android.mail.utils.LogUtils;
40import com.android.mail.R;
41import com.google.common.annotations.VisibleForTesting;
42
43/**
44 * This fragment shows general app preferences.
45 */
46public 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
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
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
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
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
141    private void clearDisplayImages() {
142        final ClearPictureApprovalsDialogFragment fragment =
143                ClearPictureApprovalsDialogFragment.newInstance();
144        fragment.show(getActivity().getFragmentManager(),
145                ClearPictureApprovalsDialogFragment.FRAGMENT_TAG);
146    }
147
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
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
183    public void onStop() {
184        super.onStop();
185        if (mClearSearchHistoryDialog != null && mClearSearchHistoryDialog.isShowing()) {
186            mClearSearchHistoryDialog.dismiss();
187        }
188    }
189
190    @Override
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
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
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
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