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 
17 package com.android.calendar;
18 
19 import android.app.Activity;
20 import android.app.FragmentManager;
21 import android.app.backup.BackupManager;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.SharedPreferences;
25 import android.content.SharedPreferences.Editor;
26 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
27 import android.media.Ringtone;
28 import android.media.RingtoneManager;
29 import android.net.Uri;
30 import android.os.Bundle;
31 import android.os.Vibrator;
32 import android.preference.CheckBoxPreference;
33 import android.preference.ListPreference;
34 import android.preference.Preference;
35 import android.preference.Preference.OnPreferenceChangeListener;
36 import android.preference.Preference.OnPreferenceClickListener;
37 import android.preference.PreferenceCategory;
38 import android.preference.PreferenceFragment;
39 import android.preference.PreferenceManager;
40 import android.preference.PreferenceScreen;
41 import android.preference.RingtonePreference;
42 import android.provider.CalendarContract;
43 import android.provider.CalendarContract.CalendarCache;
44 import android.provider.SearchRecentSuggestions;
45 import android.text.TextUtils;
46 import android.text.format.Time;
47 import android.widget.Toast;
48 
49 import com.android.calendar.alerts.AlertReceiver;
50 import com.android.timezonepicker.TimeZoneInfo;
51 import com.android.timezonepicker.TimeZonePickerDialog;
52 import com.android.timezonepicker.TimeZonePickerDialog.OnTimeZoneSetListener;
53 import com.android.timezonepicker.TimeZonePickerUtils;
54 
55 public 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 */
getSharedPreferences(Context context)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 */
setDefaultValues(Context 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
onCreate(Bundle icicle)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 
showTimezoneDialog()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
onStart()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      */
setPreferenceListeners(OnPreferenceChangeListener listener)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
onStop()272     public void onStop() {
273         getPreferenceScreen().getSharedPreferences()
274                 .unregisterOnSharedPreferenceChangeListener(this);
275         setPreferenceListeners(null);
276         super.onStop();
277     }
278 
279     @Override
onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key)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
onPreferenceChange(Preference preference, Object newValue)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 
getRingtoneTitleFromUri(Context context, String uri)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      */
migrateOldPreferences(SharedPreferences prefs)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      */
updateChildPreferences()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
onPreferenceTreeClick( PreferenceScreen preferenceScreen, Preference preference)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
onTimeZoneSet(TimeZoneInfo tzi)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