1/*
2 * Copyright (C) 2008 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.settings;
18
19import android.app.admin.DevicePolicyManager;
20import android.app.Activity;
21import android.app.AlarmManager;
22import android.app.DatePickerDialog;
23import android.app.Dialog;
24import android.app.TimePickerDialog;
25import android.content.BroadcastReceiver;
26import android.content.Context;
27import android.content.Intent;
28import android.content.IntentFilter;
29import android.content.SharedPreferences;
30import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
31import android.os.Bundle;
32import android.preference.CheckBoxPreference;
33import android.preference.ListPreference;
34import android.preference.Preference;
35import android.preference.PreferenceScreen;
36import android.provider.Settings;
37import android.provider.Settings.SettingNotFoundException;
38import android.text.BidiFormatter;
39import android.text.TextDirectionHeuristics;
40import android.text.TextUtils;
41import android.text.format.DateFormat;
42import android.view.View;
43import android.widget.DatePicker;
44import android.widget.TimePicker;
45import java.text.SimpleDateFormat;
46import java.util.Calendar;
47import java.util.Date;
48import java.util.Locale;
49import java.util.TimeZone;
50
51public class DateTimeSettings extends SettingsPreferenceFragment
52        implements OnSharedPreferenceChangeListener,
53                TimePickerDialog.OnTimeSetListener, DatePickerDialog.OnDateSetListener {
54
55    private static final String HOURS_12 = "12";
56    private static final String HOURS_24 = "24";
57
58    // Used for showing the current date format, which looks like "12/31/2010", "2010/12/13", etc.
59    // The date value is dummy (independent of actual date).
60    private Calendar mDummyDate;
61
62    private static final String KEY_DATE_FORMAT = "date_format";
63    private static final String KEY_AUTO_TIME = "auto_time";
64    private static final String KEY_AUTO_TIME_ZONE = "auto_zone";
65
66    private static final int DIALOG_DATEPICKER = 0;
67    private static final int DIALOG_TIMEPICKER = 1;
68
69    // have we been launched from the setup wizard?
70    protected static final String EXTRA_IS_FIRST_RUN = "firstRun";
71
72    private CheckBoxPreference mAutoTimePref;
73    private Preference mTimePref;
74    private Preference mTime24Pref;
75    private CheckBoxPreference mAutoTimeZonePref;
76    private Preference mTimeZone;
77    private Preference mDatePref;
78    private ListPreference mDateFormat;
79
80    @Override
81    public void onCreate(Bundle icicle) {
82        super.onCreate(icicle);
83
84        addPreferencesFromResource(R.xml.date_time_prefs);
85
86        initUI();
87    }
88
89    private void initUI() {
90        boolean autoTimeEnabled = getAutoState(Settings.Global.AUTO_TIME);
91        boolean autoTimeZoneEnabled = getAutoState(Settings.Global.AUTO_TIME_ZONE);
92
93        mAutoTimePref = (CheckBoxPreference) findPreference(KEY_AUTO_TIME);
94
95        DevicePolicyManager dpm = (DevicePolicyManager) getSystemService(Context
96                .DEVICE_POLICY_SERVICE);
97        if (dpm.getAutoTimeRequired()) {
98            mAutoTimePref.setEnabled(false);
99
100            // If Settings.Global.AUTO_TIME is false it will be set to true
101            // by the device policy manager very soon.
102            // Note that this app listens to that change.
103        }
104
105        Intent intent = getActivity().getIntent();
106        boolean isFirstRun = intent.getBooleanExtra(EXTRA_IS_FIRST_RUN, false);
107
108        mDummyDate = Calendar.getInstance();
109
110        mAutoTimePref.setChecked(autoTimeEnabled);
111        mAutoTimeZonePref = (CheckBoxPreference) findPreference(KEY_AUTO_TIME_ZONE);
112        // Override auto-timezone if it's a wifi-only device or if we're still in setup wizard.
113        // TODO: Remove the wifiOnly test when auto-timezone is implemented based on wifi-location.
114        if (Utils.isWifiOnly(getActivity()) || isFirstRun) {
115            getPreferenceScreen().removePreference(mAutoTimeZonePref);
116            autoTimeZoneEnabled = false;
117        }
118        mAutoTimeZonePref.setChecked(autoTimeZoneEnabled);
119
120        mTimePref = findPreference("time");
121        mTime24Pref = findPreference("24 hour");
122        mTimeZone = findPreference("timezone");
123        mDatePref = findPreference("date");
124        mDateFormat = (ListPreference) findPreference(KEY_DATE_FORMAT);
125        if (isFirstRun) {
126            getPreferenceScreen().removePreference(mTime24Pref);
127            getPreferenceScreen().removePreference(mDateFormat);
128        }
129
130        String [] dateFormats = getResources().getStringArray(R.array.date_format_values);
131        String [] formattedDates = new String[dateFormats.length];
132        String currentFormat = getDateFormat();
133        // Initialize if DATE_FORMAT is not set in the system settings
134        // This can happen after a factory reset (or data wipe)
135        if (currentFormat == null) {
136            currentFormat = "";
137        }
138
139        // Prevents duplicated values on date format selector.
140        mDummyDate.set(mDummyDate.get(Calendar.YEAR), mDummyDate.DECEMBER, 31, 13, 0, 0);
141
142        for (int i = 0; i < formattedDates.length; i++) {
143            String formatted =
144                    DateFormat.getDateFormatForSetting(getActivity(), dateFormats[i])
145                    .format(mDummyDate.getTime());
146
147            if (dateFormats[i].length() == 0) {
148                formattedDates[i] = getResources().
149                    getString(R.string.normal_date_format, formatted);
150            } else {
151                formattedDates[i] = formatted;
152            }
153        }
154
155        mDateFormat.setEntries(formattedDates);
156        mDateFormat.setEntryValues(R.array.date_format_values);
157        mDateFormat.setValue(currentFormat);
158
159        mTimePref.setEnabled(!autoTimeEnabled);
160        mDatePref.setEnabled(!autoTimeEnabled);
161        mTimeZone.setEnabled(!autoTimeZoneEnabled);
162    }
163
164    @Override
165    public void onResume() {
166        super.onResume();
167
168        getPreferenceScreen().getSharedPreferences()
169                .registerOnSharedPreferenceChangeListener(this);
170
171        ((CheckBoxPreference)mTime24Pref).setChecked(is24Hour());
172
173        // Register for time ticks and other reasons for time change
174        IntentFilter filter = new IntentFilter();
175        filter.addAction(Intent.ACTION_TIME_TICK);
176        filter.addAction(Intent.ACTION_TIME_CHANGED);
177        filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
178        getActivity().registerReceiver(mIntentReceiver, filter, null, null);
179
180        updateTimeAndDateDisplay(getActivity());
181    }
182
183    @Override
184    public void onPause() {
185        super.onPause();
186        getActivity().unregisterReceiver(mIntentReceiver);
187        getPreferenceScreen().getSharedPreferences()
188                .unregisterOnSharedPreferenceChangeListener(this);
189    }
190
191    public void updateTimeAndDateDisplay(Context context) {
192        java.text.DateFormat shortDateFormat = DateFormat.getDateFormat(context);
193        final Calendar now = Calendar.getInstance();
194        mDummyDate.setTimeZone(now.getTimeZone());
195        // We use December 31st because it's unambiguous when demonstrating the date format.
196        // We use 13:00 so we can demonstrate the 12/24 hour options.
197        mDummyDate.set(now.get(Calendar.YEAR), 11, 31, 13, 0, 0);
198        Date dummyDate = mDummyDate.getTime();
199        mTimePref.setSummary(DateFormat.getTimeFormat(getActivity()).format(now.getTime()));
200        mTimeZone.setSummary(getTimeZoneText(now.getTimeZone(), true));
201        mDatePref.setSummary(shortDateFormat.format(now.getTime()));
202        mDateFormat.setSummary(shortDateFormat.format(dummyDate));
203        mTime24Pref.setSummary(DateFormat.getTimeFormat(getActivity()).format(dummyDate));
204    }
205
206    @Override
207    public void onDateSet(DatePicker view, int year, int month, int day) {
208        final Activity activity = getActivity();
209        if (activity != null) {
210            setDate(activity, year, month, day);
211            updateTimeAndDateDisplay(activity);
212        }
213    }
214
215    @Override
216    public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
217        final Activity activity = getActivity();
218        if (activity != null) {
219            setTime(activity, hourOfDay, minute);
220            updateTimeAndDateDisplay(activity);
221        }
222
223        // We don't need to call timeUpdated() here because the TIME_CHANGED
224        // broadcast is sent by the AlarmManager as a side effect of setting the
225        // SystemClock time.
226    }
227
228    @Override
229    public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
230        if (key.equals(KEY_DATE_FORMAT)) {
231            String format = preferences.getString(key,
232                    getResources().getString(R.string.default_date_format));
233            Settings.System.putString(getContentResolver(),
234                    Settings.System.DATE_FORMAT, format);
235            updateTimeAndDateDisplay(getActivity());
236        } else if (key.equals(KEY_AUTO_TIME)) {
237            boolean autoEnabled = preferences.getBoolean(key, true);
238            Settings.Global.putInt(getContentResolver(), Settings.Global.AUTO_TIME,
239                    autoEnabled ? 1 : 0);
240            mTimePref.setEnabled(!autoEnabled);
241            mDatePref.setEnabled(!autoEnabled);
242        } else if (key.equals(KEY_AUTO_TIME_ZONE)) {
243            boolean autoZoneEnabled = preferences.getBoolean(key, true);
244            Settings.Global.putInt(
245                    getContentResolver(), Settings.Global.AUTO_TIME_ZONE, autoZoneEnabled ? 1 : 0);
246            mTimeZone.setEnabled(!autoZoneEnabled);
247        }
248    }
249
250    @Override
251    public Dialog onCreateDialog(int id) {
252        final Calendar calendar = Calendar.getInstance();
253        switch (id) {
254        case DIALOG_DATEPICKER:
255            DatePickerDialog d = new DatePickerDialog(
256                    getActivity(),
257                    this,
258                    calendar.get(Calendar.YEAR),
259                    calendar.get(Calendar.MONTH),
260                    calendar.get(Calendar.DAY_OF_MONTH));
261            configureDatePicker(d.getDatePicker());
262            return d;
263        case DIALOG_TIMEPICKER:
264            return new TimePickerDialog(
265                    getActivity(),
266                    this,
267                    calendar.get(Calendar.HOUR_OF_DAY),
268                    calendar.get(Calendar.MINUTE),
269                    DateFormat.is24HourFormat(getActivity()));
270        default:
271            throw new IllegalArgumentException();
272        }
273    }
274
275    static void configureDatePicker(DatePicker datePicker) {
276        // The system clock can't represent dates outside this range.
277        Calendar t = Calendar.getInstance();
278        t.clear();
279        t.set(1970, Calendar.JANUARY, 1);
280        datePicker.setMinDate(t.getTimeInMillis());
281        t.clear();
282        t.set(2037, Calendar.DECEMBER, 31);
283        datePicker.setMaxDate(t.getTimeInMillis());
284    }
285
286    /*
287    @Override
288    public void onPrepareDialog(int id, Dialog d) {
289        switch (id) {
290        case DIALOG_DATEPICKER: {
291            DatePickerDialog datePicker = (DatePickerDialog)d;
292            final Calendar calendar = Calendar.getInstance();
293            datePicker.updateDate(
294                    calendar.get(Calendar.YEAR),
295                    calendar.get(Calendar.MONTH),
296                    calendar.get(Calendar.DAY_OF_MONTH));
297            break;
298        }
299        case DIALOG_TIMEPICKER: {
300            TimePickerDialog timePicker = (TimePickerDialog)d;
301            final Calendar calendar = Calendar.getInstance();
302            timePicker.updateTime(
303                    calendar.get(Calendar.HOUR_OF_DAY),
304                    calendar.get(Calendar.MINUTE));
305            break;
306        }
307        default:
308            break;
309        }
310    }
311    */
312    @Override
313    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
314        if (preference == mDatePref) {
315            showDialog(DIALOG_DATEPICKER);
316        } else if (preference == mTimePref) {
317            // The 24-hour mode may have changed, so recreate the dialog
318            removeDialog(DIALOG_TIMEPICKER);
319            showDialog(DIALOG_TIMEPICKER);
320        } else if (preference == mTime24Pref) {
321            final boolean is24Hour = ((CheckBoxPreference)mTime24Pref).isChecked();
322            set24Hour(is24Hour);
323            updateTimeAndDateDisplay(getActivity());
324            timeUpdated(is24Hour);
325        }
326        return super.onPreferenceTreeClick(preferenceScreen, preference);
327    }
328
329    @Override
330    public void onActivityResult(int requestCode, int resultCode,
331            Intent data) {
332        updateTimeAndDateDisplay(getActivity());
333    }
334
335    private void timeUpdated(boolean is24Hour) {
336        Intent timeChanged = new Intent(Intent.ACTION_TIME_CHANGED);
337        timeChanged.putExtra(Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT, is24Hour);
338        getActivity().sendBroadcast(timeChanged);
339    }
340
341    /*  Get & Set values from the system settings  */
342
343    private boolean is24Hour() {
344        return DateFormat.is24HourFormat(getActivity());
345    }
346
347    private void set24Hour(boolean is24Hour) {
348        Settings.System.putString(getContentResolver(),
349                Settings.System.TIME_12_24,
350                is24Hour? HOURS_24 : HOURS_12);
351    }
352
353    private String getDateFormat() {
354        return Settings.System.getString(getContentResolver(),
355                Settings.System.DATE_FORMAT);
356    }
357
358    private boolean getAutoState(String name) {
359        try {
360            return Settings.Global.getInt(getContentResolver(), name) > 0;
361        } catch (SettingNotFoundException snfe) {
362            return false;
363        }
364    }
365
366    /* package */ static void setDate(Context context, int year, int month, int day) {
367        Calendar c = Calendar.getInstance();
368
369        c.set(Calendar.YEAR, year);
370        c.set(Calendar.MONTH, month);
371        c.set(Calendar.DAY_OF_MONTH, day);
372        long when = c.getTimeInMillis();
373
374        if (when / 1000 < Integer.MAX_VALUE) {
375            ((AlarmManager) context.getSystemService(Context.ALARM_SERVICE)).setTime(when);
376        }
377    }
378
379    /* package */ static void setTime(Context context, int hourOfDay, int minute) {
380        Calendar c = Calendar.getInstance();
381
382        c.set(Calendar.HOUR_OF_DAY, hourOfDay);
383        c.set(Calendar.MINUTE, minute);
384        c.set(Calendar.SECOND, 0);
385        c.set(Calendar.MILLISECOND, 0);
386        long when = c.getTimeInMillis();
387
388        if (when / 1000 < Integer.MAX_VALUE) {
389            ((AlarmManager) context.getSystemService(Context.ALARM_SERVICE)).setTime(when);
390        }
391    }
392
393    public static String getTimeZoneText(TimeZone tz, boolean includeName) {
394        Date now = new Date();
395
396        // Use SimpleDateFormat to format the GMT+00:00 string.
397        SimpleDateFormat gmtFormatter = new SimpleDateFormat("ZZZZ");
398        gmtFormatter.setTimeZone(tz);
399        String gmtString = gmtFormatter.format(now);
400
401        // Ensure that the "GMT+" stays with the "00:00" even if the digits are RTL.
402        BidiFormatter bidiFormatter = BidiFormatter.getInstance();
403        Locale l = Locale.getDefault();
404        boolean isRtl = TextUtils.getLayoutDirectionFromLocale(l) == View.LAYOUT_DIRECTION_RTL;
405        gmtString = bidiFormatter.unicodeWrap(gmtString,
406                isRtl ? TextDirectionHeuristics.RTL : TextDirectionHeuristics.LTR);
407
408        if (!includeName) {
409            return gmtString;
410        }
411
412        // Optionally append the time zone name.
413        SimpleDateFormat zoneNameFormatter = new SimpleDateFormat("zzzz");
414        zoneNameFormatter.setTimeZone(tz);
415        String zoneNameString = zoneNameFormatter.format(now);
416
417        // We don't use punctuation here to avoid having to worry about localizing that too!
418        return gmtString + " " + zoneNameString;
419    }
420
421    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
422        @Override
423        public void onReceive(Context context, Intent intent) {
424            final Activity activity = getActivity();
425            if (activity != null) {
426                updateTimeAndDateDisplay(activity);
427            }
428        }
429    };
430}
431