1/*
2 * Copyright (C) 2007 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
17package com.android.calendar;
18
19import android.app.Activity;
20import android.app.FragmentManager;
21import android.app.backup.BackupManager;
22import android.content.Context;
23import android.content.Intent;
24import android.content.SharedPreferences;
25import android.content.SharedPreferences.Editor;
26import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
27import android.media.Ringtone;
28import android.media.RingtoneManager;
29import android.net.Uri;
30import android.os.Bundle;
31import android.os.Vibrator;
32import android.preference.CheckBoxPreference;
33import android.preference.ListPreference;
34import android.preference.Preference;
35import android.preference.Preference.OnPreferenceChangeListener;
36import android.preference.Preference.OnPreferenceClickListener;
37import android.preference.PreferenceCategory;
38import android.preference.PreferenceFragment;
39import android.preference.PreferenceManager;
40import android.preference.PreferenceScreen;
41import android.preference.RingtonePreference;
42import android.provider.CalendarContract;
43import android.provider.CalendarContract.CalendarCache;
44import android.provider.SearchRecentSuggestions;
45import android.text.TextUtils;
46import android.text.format.Time;
47import android.widget.Toast;
48
49import com.android.calendar.alerts.AlertReceiver;
50import com.android.timezonepicker.TimeZoneInfo;
51import com.android.timezonepicker.TimeZonePickerDialog;
52import com.android.timezonepicker.TimeZonePickerDialog.OnTimeZoneSetListener;
53import com.android.timezonepicker.TimeZonePickerUtils;
54
55public class GeneralPreferences extends PreferenceFragment implements
56        OnSharedPreferenceChangeListener, OnPreferenceChangeListener, OnTimeZoneSetListener {
57    // The name of the shared preferences file. This name must be maintained for historical
58    // reasons, as it's what PreferenceManager assigned the first time the file was created.
59    static final String SHARED_PREFS_NAME = "com.android.calendar_preferences";
60    static final String SHARED_PREFS_NAME_NO_BACKUP = "com.android.calendar_preferences_no_backup";
61
62    private static final String FRAG_TAG_TIME_ZONE_PICKER = "TimeZonePicker";
63
64    // Preference keys
65    public static final String KEY_HIDE_DECLINED = "preferences_hide_declined";
66    public static final String KEY_WEEK_START_DAY = "preferences_week_start_day";
67    public static final String KEY_SHOW_WEEK_NUM = "preferences_show_week_num";
68    public static final String KEY_DAYS_PER_WEEK = "preferences_days_per_week";
69    public static final String KEY_SKIP_SETUP = "preferences_skip_setup";
70
71    public static final String KEY_CLEAR_SEARCH_HISTORY = "preferences_clear_search_history";
72
73    public static final String KEY_ALERTS_CATEGORY = "preferences_alerts_category";
74    public static final String KEY_ALERTS = "preferences_alerts";
75    public static final String KEY_ALERTS_VIBRATE = "preferences_alerts_vibrate";
76    public static final String KEY_ALERTS_RINGTONE = "preferences_alerts_ringtone";
77    public static final String KEY_ALERTS_POPUP = "preferences_alerts_popup";
78
79    public static final String KEY_SHOW_CONTROLS = "preferences_show_controls";
80
81    public static final String KEY_DEFAULT_REMINDER = "preferences_default_reminder";
82    public static final int NO_REMINDER = -1;
83    public static final String NO_REMINDER_STRING = "-1";
84    public static final int REMINDER_DEFAULT_TIME = 10; // in minutes
85
86    public static final String KEY_DEFAULT_CELL_HEIGHT = "preferences_default_cell_height";
87    public static final String KEY_VERSION = "preferences_version";
88
89    /** Key to SharePreference for default view (CalendarController.ViewType) */
90    public static final String KEY_START_VIEW = "preferred_startView";
91    /**
92     *  Key to SharePreference for default detail view (CalendarController.ViewType)
93     *  Typically used by widget
94     */
95    public static final String KEY_DETAILED_VIEW = "preferred_detailedView";
96    public static final String KEY_DEFAULT_CALENDAR = "preference_defaultCalendar";
97
98    // These must be in sync with the array preferences_week_start_day_values
99    public static final String WEEK_START_DEFAULT = "-1";
100    public static final String WEEK_START_SATURDAY = "7";
101    public static final String WEEK_START_SUNDAY = "1";
102    public static final String WEEK_START_MONDAY = "2";
103
104    // These keys are kept to enable migrating users from previous versions
105    private static final String KEY_ALERTS_TYPE = "preferences_alerts_type";
106    private static final String ALERT_TYPE_ALERTS = "0";
107    private static final String ALERT_TYPE_STATUS_BAR = "1";
108    private static final String ALERT_TYPE_OFF = "2";
109    static final String KEY_HOME_TZ_ENABLED = "preferences_home_tz_enabled";
110    static final String KEY_HOME_TZ = "preferences_home_tz";
111
112    // Default preference values
113    public static final int DEFAULT_START_VIEW = CalendarController.ViewType.WEEK;
114    public static final int DEFAULT_DETAILED_VIEW = CalendarController.ViewType.DAY;
115    public static final boolean DEFAULT_SHOW_WEEK_NUM = false;
116    // This should match the XML file.
117    public static final String DEFAULT_RINGTONE = "content://settings/system/notification_sound";
118
119    CheckBoxPreference mAlert;
120    CheckBoxPreference mVibrate;
121    RingtonePreference mRingtone;
122    CheckBoxPreference mPopup;
123    CheckBoxPreference mUseHomeTZ;
124    CheckBoxPreference mHideDeclined;
125    Preference mHomeTZ;
126    TimeZonePickerUtils mTzPickerUtils;
127    ListPreference mWeekStart;
128    ListPreference mDefaultReminder;
129
130    private String mTimeZoneId;
131
132    /** Return a properly configured SharedPreferences instance */
133    public static SharedPreferences getSharedPreferences(Context context) {
134        return context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
135    }
136
137    /** Set the default shared preferences in the proper context */
138    public static void setDefaultValues(Context context) {
139        PreferenceManager.setDefaultValues(context, SHARED_PREFS_NAME, Context.MODE_PRIVATE,
140                R.xml.general_preferences, false);
141    }
142
143    @Override
144    public void onCreate(Bundle icicle) {
145        super.onCreate(icicle);
146
147        final Activity activity = getActivity();
148
149        // Make sure to always use the same preferences file regardless of the package name
150        // we're running under
151        final PreferenceManager preferenceManager = getPreferenceManager();
152        final SharedPreferences sharedPreferences = getSharedPreferences(activity);
153        preferenceManager.setSharedPreferencesName(SHARED_PREFS_NAME);
154
155        // Load the preferences from an XML resource
156        addPreferencesFromResource(R.xml.general_preferences);
157
158        final PreferenceScreen preferenceScreen = getPreferenceScreen();
159        mAlert = (CheckBoxPreference) preferenceScreen.findPreference(KEY_ALERTS);
160        mVibrate = (CheckBoxPreference) preferenceScreen.findPreference(KEY_ALERTS_VIBRATE);
161        Vibrator vibrator = (Vibrator) activity.getSystemService(Context.VIBRATOR_SERVICE);
162        if (vibrator == null || !vibrator.hasVibrator()) {
163            PreferenceCategory mAlertGroup = (PreferenceCategory) preferenceScreen
164                    .findPreference(KEY_ALERTS_CATEGORY);
165            mAlertGroup.removePreference(mVibrate);
166        }
167
168        mRingtone = (RingtonePreference) preferenceScreen.findPreference(KEY_ALERTS_RINGTONE);
169        String ringToneUri = Utils.getRingTonePreference(activity);
170
171        // Set the ringToneUri to the backup-able shared pref only so that
172        // the Ringtone dialog will open up with the correct value.
173        final Editor editor = preferenceScreen.getEditor();
174        editor.putString(GeneralPreferences.KEY_ALERTS_RINGTONE, ringToneUri).apply();
175
176        String ringtoneDisplayString = getRingtoneTitleFromUri(activity, ringToneUri);
177        mRingtone.setSummary(ringtoneDisplayString == null ? "" : ringtoneDisplayString);
178
179        mPopup = (CheckBoxPreference) preferenceScreen.findPreference(KEY_ALERTS_POPUP);
180        mUseHomeTZ = (CheckBoxPreference) preferenceScreen.findPreference(KEY_HOME_TZ_ENABLED);
181        mHideDeclined = (CheckBoxPreference) preferenceScreen.findPreference(KEY_HIDE_DECLINED);
182        mWeekStart = (ListPreference) preferenceScreen.findPreference(KEY_WEEK_START_DAY);
183        mDefaultReminder = (ListPreference) preferenceScreen.findPreference(KEY_DEFAULT_REMINDER);
184        mHomeTZ = preferenceScreen.findPreference(KEY_HOME_TZ);
185        mWeekStart.setSummary(mWeekStart.getEntry());
186        mDefaultReminder.setSummary(mDefaultReminder.getEntry());
187
188        // This triggers an asynchronous call to the provider to refresh the data in shared pref
189        mTimeZoneId = Utils.getTimeZone(activity, null);
190
191        SharedPreferences prefs = CalendarUtils.getSharedPreferences(activity,
192                Utils.SHARED_PREFS_NAME);
193
194        // Utils.getTimeZone will return the currentTimeZone instead of the one
195        // in the shared_pref if home time zone is disabled. So if home tz is
196        // off, we will explicitly read it.
197        if (!prefs.getBoolean(KEY_HOME_TZ_ENABLED, false)) {
198            mTimeZoneId = prefs.getString(KEY_HOME_TZ, Time.getCurrentTimezone());
199        }
200
201        mHomeTZ.setOnPreferenceClickListener(new OnPreferenceClickListener() {
202            @Override
203            public boolean onPreferenceClick(Preference preference) {
204                showTimezoneDialog();
205                return true;
206            }
207        });
208
209        if (mTzPickerUtils == null) {
210            mTzPickerUtils = new TimeZonePickerUtils(getActivity());
211        }
212        CharSequence timezoneName = mTzPickerUtils.getGmtDisplayName(getActivity(), mTimeZoneId,
213                System.currentTimeMillis(), false);
214        mHomeTZ.setSummary(timezoneName != null ? timezoneName : mTimeZoneId);
215
216        TimeZonePickerDialog tzpd = (TimeZonePickerDialog) activity.getFragmentManager()
217                .findFragmentByTag(FRAG_TAG_TIME_ZONE_PICKER);
218        if (tzpd != null) {
219            tzpd.setOnTimeZoneSetListener(this);
220        }
221
222        migrateOldPreferences(sharedPreferences);
223
224        updateChildPreferences();
225    }
226
227    private void showTimezoneDialog() {
228        final Activity activity = getActivity();
229        if (activity == null) {
230            return;
231        }
232
233        Bundle b = new Bundle();
234        b.putLong(TimeZonePickerDialog.BUNDLE_START_TIME_MILLIS, System.currentTimeMillis());
235        b.putString(TimeZonePickerDialog.BUNDLE_TIME_ZONE, Utils.getTimeZone(activity, null));
236
237        FragmentManager fm = getActivity().getFragmentManager();
238        TimeZonePickerDialog tzpd = (TimeZonePickerDialog) fm
239                .findFragmentByTag(FRAG_TAG_TIME_ZONE_PICKER);
240        if (tzpd != null) {
241            tzpd.dismiss();
242        }
243        tzpd = new TimeZonePickerDialog();
244        tzpd.setArguments(b);
245        tzpd.setOnTimeZoneSetListener(this);
246        tzpd.show(fm, FRAG_TAG_TIME_ZONE_PICKER);
247    }
248
249    @Override
250    public void onStart() {
251        super.onStart();
252        getPreferenceScreen().getSharedPreferences()
253                .registerOnSharedPreferenceChangeListener(this);
254        setPreferenceListeners(this);
255    }
256
257    /**
258     * Sets up all the preference change listeners to use the specified
259     * listener.
260     */
261    private void setPreferenceListeners(OnPreferenceChangeListener listener) {
262        mUseHomeTZ.setOnPreferenceChangeListener(listener);
263        mHomeTZ.setOnPreferenceChangeListener(listener);
264        mWeekStart.setOnPreferenceChangeListener(listener);
265        mDefaultReminder.setOnPreferenceChangeListener(listener);
266        mRingtone.setOnPreferenceChangeListener(listener);
267        mHideDeclined.setOnPreferenceChangeListener(listener);
268        mVibrate.setOnPreferenceChangeListener(listener);
269    }
270
271    @Override
272    public void onStop() {
273        getPreferenceScreen().getSharedPreferences()
274                .unregisterOnSharedPreferenceChangeListener(this);
275        setPreferenceListeners(null);
276        super.onStop();
277    }
278
279    @Override
280    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
281        Activity a = getActivity();
282        if (key.equals(KEY_ALERTS)) {
283            updateChildPreferences();
284            if (a != null) {
285                Intent intent = new Intent();
286                intent.setClass(a, AlertReceiver.class);
287                if (mAlert.isChecked()) {
288                    intent.setAction(AlertReceiver.ACTION_DISMISS_OLD_REMINDERS);
289                } else {
290                    intent.setAction(AlertReceiver.EVENT_REMINDER_APP_ACTION);
291                }
292                a.sendBroadcast(intent);
293            }
294        }
295        if (a != null) {
296            BackupManager.dataChanged(a.getPackageName());
297        }
298    }
299
300    /**
301     * Handles time zone preference changes
302     */
303    @Override
304    public boolean onPreferenceChange(Preference preference, Object newValue) {
305        String tz;
306        final Activity activity = getActivity();
307        if (preference == mUseHomeTZ) {
308            if ((Boolean)newValue) {
309                tz = mTimeZoneId;
310            } else {
311                tz = CalendarCache.TIMEZONE_TYPE_AUTO;
312            }
313            Utils.setTimeZone(activity, tz);
314            return true;
315        } else if (preference == mHideDeclined) {
316            mHideDeclined.setChecked((Boolean) newValue);
317            Intent intent = new Intent(Utils.getWidgetScheduledUpdateAction(activity));
318            intent.setDataAndType(CalendarContract.CONTENT_URI, Utils.APPWIDGET_DATA_TYPE);
319            activity.sendBroadcast(intent);
320            return true;
321        } else if (preference == mWeekStart) {
322            mWeekStart.setValue((String) newValue);
323            mWeekStart.setSummary(mWeekStart.getEntry());
324        } else if (preference == mDefaultReminder) {
325            mDefaultReminder.setValue((String) newValue);
326            mDefaultReminder.setSummary(mDefaultReminder.getEntry());
327        } else if (preference == mRingtone) {
328            if (newValue instanceof String) {
329                Utils.setRingTonePreference(activity, (String) newValue);
330                String ringtone = getRingtoneTitleFromUri(activity, (String) newValue);
331                mRingtone.setSummary(ringtone == null ? "" : ringtone);
332            }
333            return true;
334        } else if (preference == mVibrate) {
335            mVibrate.setChecked((Boolean) newValue);
336            return true;
337        } else {
338            return true;
339        }
340        return false;
341    }
342
343    public String getRingtoneTitleFromUri(Context context, String uri) {
344        if (TextUtils.isEmpty(uri)) {
345            return null;
346        }
347
348        Ringtone ring = RingtoneManager.getRingtone(getActivity(), Uri.parse(uri));
349        if (ring != null) {
350            return ring.getTitle(context);
351        }
352        return null;
353    }
354
355    /**
356     * If necessary, upgrades previous versions of preferences to the current
357     * set of keys and values.
358     * @param prefs the preferences to upgrade
359     */
360    private void migrateOldPreferences(SharedPreferences prefs) {
361        // If needed, migrate vibration setting from a previous version
362
363        mVibrate.setChecked(Utils.getDefaultVibrate(getActivity(), prefs));
364
365        // If needed, migrate the old alerts type settin
366        if (!prefs.contains(KEY_ALERTS) && prefs.contains(KEY_ALERTS_TYPE)) {
367            String type = prefs.getString(KEY_ALERTS_TYPE, ALERT_TYPE_STATUS_BAR);
368            if (type.equals(ALERT_TYPE_OFF)) {
369                mAlert.setChecked(false);
370                mPopup.setChecked(false);
371                mPopup.setEnabled(false);
372            } else if (type.equals(ALERT_TYPE_STATUS_BAR)) {
373                mAlert.setChecked(true);
374                mPopup.setChecked(false);
375                mPopup.setEnabled(true);
376            } else if (type.equals(ALERT_TYPE_ALERTS)) {
377                mAlert.setChecked(true);
378                mPopup.setChecked(true);
379                mPopup.setEnabled(true);
380            }
381            // clear out the old setting
382            prefs.edit().remove(KEY_ALERTS_TYPE).commit();
383        }
384    }
385
386    /**
387     * Keeps the dependent settings in sync with the parent preference, so for
388     * example, when notifications are turned off, we disable the preferences
389     * for configuring the exact notification behavior.
390     */
391    private void updateChildPreferences() {
392        if (mAlert.isChecked()) {
393            mVibrate.setEnabled(true);
394            mRingtone.setEnabled(true);
395            mPopup.setEnabled(true);
396        } else {
397            mVibrate.setEnabled(false);
398            mRingtone.setEnabled(false);
399            mPopup.setEnabled(false);
400        }
401    }
402
403
404    @Override
405    public boolean onPreferenceTreeClick(
406            PreferenceScreen preferenceScreen, Preference preference) {
407        final String key = preference.getKey();
408        if (KEY_CLEAR_SEARCH_HISTORY.equals(key)) {
409            SearchRecentSuggestions suggestions = new SearchRecentSuggestions(getActivity(),
410                    Utils.getSearchAuthority(getActivity()),
411                    CalendarRecentSuggestionsProvider.MODE);
412            suggestions.clearHistory();
413            Toast.makeText(getActivity(), R.string.search_history_cleared,
414                    Toast.LENGTH_SHORT).show();
415            return true;
416        } else {
417            return super.onPreferenceTreeClick(preferenceScreen, preference);
418        }
419    }
420
421    @Override
422    public void onTimeZoneSet(TimeZoneInfo tzi) {
423        if (mTzPickerUtils == null) {
424            mTzPickerUtils = new TimeZonePickerUtils(getActivity());
425        }
426
427        final CharSequence timezoneName = mTzPickerUtils.getGmtDisplayName(
428                getActivity(), tzi.mTzId, System.currentTimeMillis(), false);
429        mHomeTZ.setSummary(timezoneName);
430        Utils.setTimeZone(getActivity(), tzi.mTzId);
431    }
432}
433