• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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.tv.settings.system;
18 
19 import com.android.tv.settings.ActionBehavior;
20 import com.android.tv.settings.ActionKey;
21 import com.android.tv.settings.BaseSettingsActivity;
22 import com.android.tv.settings.R;
23 import com.android.tv.settings.util.SettingsHelper;
24 import com.android.tv.settings.dialog.old.Action;
25 import com.android.tv.settings.dialog.old.ActionAdapter;
26 import com.android.tv.settings.dialog.old.ActionFragment;
27 import com.android.tv.settings.dialog.old.ContentFragment;
28 import com.android.tv.settings.widget.picker.DatePicker;
29 import com.android.tv.settings.widget.picker.TimePicker;
30 import com.android.tv.settings.widget.picker.Picker;
31 import com.android.tv.settings.widget.picker.PickerConstant;
32 
33 import org.xmlpull.v1.XmlPullParserException;
34 
35 import android.app.AlarmManager;
36 import android.content.BroadcastReceiver;
37 import android.content.Context;
38 import android.content.Intent;
39 import android.content.IntentFilter;
40 import android.content.res.XmlResourceParser;
41 import android.os.Bundle;
42 import android.provider.Settings;
43 import android.provider.Settings.SettingNotFoundException;
44 import android.text.format.DateFormat;
45 import android.util.Log;
46 
47 import java.util.ArrayList;
48 import java.util.Calendar;
49 import java.util.Collections;
50 import java.util.Date;
51 import java.util.List;
52 import java.util.TimeZone;
53 
54 public class DateTimeActivity extends BaseSettingsActivity implements ActionAdapter.Listener {
55 
56     private static final String TAG = "DateTimeActivity";
57     private static final boolean DEBUG = false;
58 
59     private static final String HOURS_12 = "12";
60     private static final String HOURS_24 = "24";
61 
62     private static final int HOURS_IN_HALF_DAY = 12;
63 
64     private static final String XMLTAG_TIMEZONE = "timezone";
65 
66     private Calendar mDummyDate;
67     private boolean mIsResumed;
68 
69     private IntentFilter mIntentFilter;
70 
71     private String mNowDate;
72     private String mNowTime;
73 
74     private ArrayList<Action> mTimeZoneActions;
75 
76     private SettingsHelper mHelper;
77 
78     /**
79      * Flag indicating whether this UpdateView call is from onCreate.
80      */
81     private boolean mOnCreateUpdateView = false;
82 
83     /**
84      * Flag indicating whether this UpdateView call is from onResume.
85      */
86     private boolean mOnResumeUpdateView = false;
87 
88     private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
89         @Override
90         public void onReceive(Context context, Intent intent) {
91             if (mIsResumed) {
92                 switch ((ActionType) mState) {
93                     case DATE_TIME_OVERVIEW:
94                     case DATE:
95                     case TIME:
96                         updateTimeAndDateDisplay();
97                 }
98             }
99         }
100     };
101 
102     @Override
onCreate(Bundle savedInstanceState)103     public void onCreate(Bundle savedInstanceState) {
104         mDummyDate = Calendar.getInstance();
105         mIntentFilter = new IntentFilter();
106         mIntentFilter.addAction(Intent.ACTION_TIME_TICK);
107         mIntentFilter.addAction(Intent.ACTION_TIME_CHANGED);
108         mIntentFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
109         mIntentFilter.addAction(Intent.ACTION_DATE_CHANGED);
110         registerReceiver(mIntentReceiver, mIntentFilter);
111 
112         mHelper = new SettingsHelper(getApplicationContext());
113 
114         setSampleDate();
115 
116         updateTimeAndDateStrings();
117         mOnCreateUpdateView = true;
118 
119         super.onCreate(savedInstanceState);
120     }
121 
setSampleDate()122     private void setSampleDate() {
123         Calendar now = Calendar.getInstance();
124         mDummyDate.setTimeZone(now.getTimeZone());
125         // We use December 31st because it's unambiguous when demonstrating the date format.
126         // We use 15:14 so we can demonstrate the 12/24 hour options.
127         mDummyDate.set(now.get(Calendar.YEAR), 11, 31, 15, 14, 0);
128     }
129 
getAutoState(String name)130     private boolean getAutoState(String name) {
131         try {
132             return Settings.Global.getInt(getContentResolver(), name) > 0;
133         } catch (SettingNotFoundException snfe) {
134             return false;
135         }
136     }
137 
setDate(Context context, int year, int month, int day)138     static void setDate(Context context, int year, int month, int day) {
139         Calendar c = Calendar.getInstance();
140 
141         c.set(Calendar.YEAR, year);
142         c.set(Calendar.MONTH, month);
143         c.set(Calendar.DAY_OF_MONTH, day);
144         long when = c.getTimeInMillis();
145 
146         if (when / 1000 < Integer.MAX_VALUE) {
147             ((AlarmManager) context.getSystemService(Context.ALARM_SERVICE)).setTime(when);
148         }
149     }
150 
setTime(Context context, int hourOfDay, int minute)151     static void setTime(Context context, int hourOfDay, int minute) {
152         Calendar c = Calendar.getInstance();
153 
154         c.set(Calendar.HOUR_OF_DAY, hourOfDay);
155         c.set(Calendar.MINUTE, minute);
156         c.set(Calendar.SECOND, 0);
157         c.set(Calendar.MILLISECOND, 0);
158         long when = c.getTimeInMillis();
159 
160         if (when / 1000 < Integer.MAX_VALUE) {
161             ((AlarmManager) context.getSystemService(Context.ALARM_SERVICE)).setTime(when);
162         }
163     }
164 
isTimeFormat24h()165     private boolean isTimeFormat24h() {
166         return DateFormat.is24HourFormat(this);
167     }
168 
setTime24Hour(boolean is24Hour)169     private void setTime24Hour(boolean is24Hour) {
170         Settings.System.putString(getContentResolver(),
171                 Settings.System.TIME_12_24,
172                 is24Hour ? HOURS_24 : HOURS_12);
173     }
174 
setAutoDateTime(boolean on)175     private void setAutoDateTime(boolean on) {
176         Settings.Global.putInt(getContentResolver(), Settings.Global.AUTO_TIME, on ? 1 : 0);
177     }
178 
179     @Override
onResume()180     protected void onResume() {
181         super.onResume();
182         mIsResumed = true;
183         registerReceiver(mIntentReceiver, mIntentFilter);
184 
185         mOnResumeUpdateView = true;
186 
187         updateTimeAndDateDisplay();
188     }
189 
190     @Override
onPause()191     protected void onPause() {
192         super.onPause();
193         mIsResumed = false;
194         unregisterReceiver(mIntentReceiver);
195     }
196 
197     // Updates the member strings to reflect the current date and time.
updateTimeAndDateStrings()198     private void updateTimeAndDateStrings() {
199         final Calendar now = Calendar.getInstance();
200         java.text.DateFormat dateFormat = DateFormat.getDateFormat(this);
201         mNowDate = dateFormat.format(now.getTime());
202         java.text.DateFormat timeFormat = DateFormat.getTimeFormat(this);
203         mNowTime = timeFormat.format(now.getTime());
204     }
205 
206     @Override
onActionClicked(Action action)207     public void onActionClicked(Action action) {
208         /*
209          * For list preferences
210          */
211         final String key = action.getKey();
212         switch ((ActionType) mState) {
213             case TIME_SET_TIME_ZONE:
214                 setTimeZone(key);
215                 updateTimeAndDateStrings();
216                 goBack();
217                 return;
218         }
219         /*
220          * For other preferences
221          */
222         ActionKey<ActionType, ActionBehavior> actionKey = new ActionKey<ActionType, ActionBehavior>(
223                 ActionType.class, ActionBehavior.class, key);
224         final ActionType type = actionKey.getType();
225         final ActionBehavior behavior = actionKey.getBehavior();
226         if (type == null || behavior == null) {
227             // Possible race condition manifested by monkey test.
228             Log.e(TAG, "type or behavior is null - exiting  b/17404946");
229             return;
230         }
231         switch (type) {
232             case DATE:
233             case TIME:
234             case TIME_CHOOSE_FORMAT:
235             case DATE_SET_DATE:
236             case TIME_SET_TIME:
237             case TIME_SET_TIME_ZONE:
238             case AUTO_DATE_TIME:
239                 if (behavior == ActionBehavior.INIT) {
240                     setState(type, true);
241                 }
242                 break;
243         }
244         switch (behavior) {
245             case ON:
246                 if (mState == ActionType.TIME_CHOOSE_FORMAT) {
247                     setTime24Hour(true);
248                     updateTimeAndDateStrings();
249                 } else if (mState == ActionType.AUTO_DATE_TIME) {
250                     setAutoDateTime(true);
251                 }
252                 goBack();
253                 break;
254             case OFF:
255                 if (mState == ActionType.TIME_CHOOSE_FORMAT) {
256                     setTime24Hour(false);
257                     updateTimeAndDateStrings();
258                 } else if (mState == ActionType.AUTO_DATE_TIME) {
259                     setAutoDateTime(false);
260                 }
261                 goBack();
262                 break;
263         }
264     }
265 
266     @Override
getInitialState()267     protected Object getInitialState() {
268         return ActionType.DATE_TIME_OVERVIEW;
269     }
270 
271     // Updates the Date and Time entries in the current view, without resetting the
272     // Action fragment, so we don't trigger an animation.
updateTimeAndDateDisplay()273     protected void updateTimeAndDateDisplay() {
274         updateTimeAndDateStrings();
275 
276         if (mActionFragment instanceof ActionFragment) {
277             ActionAdapter adapter = (ActionAdapter) ((ActionFragment)mActionFragment).getAdapter();
278 
279             if (adapter != null) {
280                 mActions.clear();
281 
282                 switch ((ActionType) mState) {
283                     case DATE_TIME_OVERVIEW:
284                         mActions.add(ActionType.AUTO_DATE_TIME.toAction(mResources,
285                                 mHelper.getStatusStringFromBoolean(getAutoState(
286                                         Settings.Global.AUTO_TIME))));
287                         mActions.add(ActionType.DATE.toAction(mResources, mNowDate));
288                         mActions.add(ActionType.TIME.toAction(mResources, mNowTime));
289                         break;
290                     case DATE:
291                         mActions.add(ActionType.DATE_SET_DATE.toAction(mResources, mNowDate));
292                         break;
293                     case TIME:
294                         mActions.add(ActionType.TIME_SET_TIME.toAction(mResources, mNowTime));
295                         mActions.add(ActionType.TIME_SET_TIME_ZONE.toAction(mResources,
296                                 getCurrentTimeZoneName()));
297                         mActions.add(ActionType.TIME_CHOOSE_FORMAT.toAction(
298                                 mResources, getTimeFormatDescription()));
299                         break;
300                 }
301                 adapter.setActions(mActions);
302                 return;
303             }
304         }
305 
306         // If we don't have an ActionFragment or adapter, fall back to the regular updateView
307         updateView();
308     }
309 
310     @Override
refreshActionList()311     protected void refreshActionList() {
312         mActions.clear();
313         boolean autoTime = getAutoState(Settings.Global.AUTO_TIME);
314         switch ((ActionType)mState) {
315             case DATE_TIME_OVERVIEW:
316                 mActions.add(ActionType.AUTO_DATE_TIME.toAction(mResources,
317                         mHelper.getStatusStringFromBoolean(autoTime)));
318                 mActions.add(ActionType.DATE.toAction(mResources, mNowDate));
319                 mActions.add(ActionType.TIME.toAction(mResources, mNowTime));
320                 break;
321             case DATE:
322                 mActions.add(ActionType.DATE_SET_DATE.toAction(mResources, mNowDate, !autoTime));
323                 break;
324             case TIME:
325                 mActions.add(ActionType.TIME_SET_TIME.toAction(mResources, mNowTime, !autoTime));
326                 mActions.add(ActionType.TIME_SET_TIME_ZONE.toAction(
327                         mResources, getCurrentTimeZoneName()));
328                 mActions.add(ActionType.TIME_CHOOSE_FORMAT.toAction(
329                         mResources, getTimeFormatDescription()));
330                 break;
331             case TIME_CHOOSE_FORMAT:
332                 mActions.add(ActionBehavior.ON.toAction(ActionBehavior.getOnKey(
333                         ActionType.TIME_CHOOSE_FORMAT.name()), mResources, isTimeFormat24h()));
334                 mActions.add(ActionBehavior.OFF.toAction(ActionBehavior.getOffKey(
335                         ActionType.TIME_CHOOSE_FORMAT.name()), mResources, !isTimeFormat24h()));
336                 break;
337             case TIME_SET_TIME_ZONE:
338                 mActions = getZoneActions(this);
339                 break;
340             case AUTO_DATE_TIME:
341                 mActions.add(ActionBehavior.ON.toAction(ActionBehavior.getOnKey(
342                         ActionType.AUTO_DATE_TIME.name()), mResources, autoTime));
343                 mActions.add(ActionBehavior.OFF.toAction(ActionBehavior.getOffKey(
344                         ActionType.AUTO_DATE_TIME.name()), mResources, !autoTime));
345                 break;
346             default:
347                 break;
348         }
349     }
350 
getTimeFormatDescription()351     private String getTimeFormatDescription() {
352         String status = mHelper.getStatusStringFromBoolean(isTimeFormat24h());
353         String desc = String.format("%s (%s)", status,
354                 DateFormat.getTimeFormat(this).format(mDummyDate.getTime()));
355         return desc;
356     }
357 
358     @Override
updateView()359     protected void updateView() {
360         refreshActionList();
361 
362         switch ((ActionType) mState) {
363             case DATE_TIME_OVERVIEW:
364                 if (mOnCreateUpdateView && mOnResumeUpdateView) {
365                     // If current updateView call is due to onResume following onCreate,
366                     // avoid duplicate setView, which will lead to broken animation.
367                     mOnCreateUpdateView = false;
368                     mOnResumeUpdateView = false;
369 
370                     return;
371                 } else {
372                     mOnResumeUpdateView = false;
373                 }
374                 mActionFragment = ActionFragment.newInstance(mActions);
375                 break;
376             case DATE_SET_DATE:
377                 DatePicker datePicker =
378                         DatePicker.newInstance(new String(DateFormat.getDateFormatOrder(this)));
379                 datePicker.setResultListener(new Picker.ResultListener() {
380 
381                     @Override
382                     public void onCommitResult(List<String> result) {
383                         String formatOrder = new String(
384                                 DateFormat.getDateFormatOrder(DateTimeActivity.this)).toUpperCase();
385                         int yIndex = formatOrder.indexOf('Y');
386                         int mIndex = formatOrder.indexOf('M');
387                         int dIndex = formatOrder.indexOf('D');
388                         if (yIndex < 0 || mIndex < 0 || dIndex < 0 ||
389                                 yIndex > 2 || mIndex > 2 || dIndex > 2) {
390                             // Badly formatted input. Use default order.
391                             mIndex = 0;
392                             dIndex = 1;
393                             yIndex = 2;
394                         }
395                         String month = result.get(mIndex);
396                         int day = Integer.parseInt(result.get(dIndex));
397                         int year = Integer.parseInt(result.get(yIndex));
398                         int monthInt = 0;
399                         String[] months = PickerConstant.getInstance(mResources).months;
400                         int totalMonths = months.length;
401                         for (int i = 0; i < totalMonths; i++) {
402                             if (months[i].equals(month)) {
403                                 monthInt = i;
404                             }
405                         }
406 
407                         // turn off Auto date/time
408                         setAutoDateTime(false);
409 
410                         setDate(DateTimeActivity.this, year, monthInt, day);
411                         goBack();
412                     }
413                 });
414                 mActionFragment = datePicker;
415                 break;
416             case TIME_SET_TIME:
417                 Picker timePicker = TimePicker.newInstance(isTimeFormat24h(), true);
418                 timePicker.setResultListener(new Picker.ResultListener() {
419 
420                     @Override
421                     public void onCommitResult(List<String> result) {
422                         boolean is24hFormat = isTimeFormat24h();
423                         int hour = Integer.parseInt(result.get(0));
424                         int minute = Integer.parseInt(result.get(1));
425                         if (!is24hFormat) {
426                             String ampm = result.get(2);
427                             if (ampm.equals(getResources().getStringArray(R.array.ampm)[1])) {
428                                 // PM case, valid hours: 12-23
429                                 hour = (hour % HOURS_IN_HALF_DAY) + HOURS_IN_HALF_DAY;
430                             } else {
431                                 // AM case, valid hours: 0-11
432                                 hour = hour % HOURS_IN_HALF_DAY;
433                             }
434                         }
435 
436                         // turn off Auto date/time
437                         setAutoDateTime(false);
438 
439                         setTime(DateTimeActivity.this, hour, minute);
440                         goBack();
441                     }
442                 });
443                 mActionFragment = timePicker;
444                 break;
445             default:
446                 mActionFragment = ActionFragment.newInstance(mActions);
447                 break;
448         }
449 
450         setViewWithActionFragment(
451                 ((ActionType) mState).getTitle(mResources), getPrevState() != null ?
452                         ((ActionType) getPrevState()).getTitle(mResources)
453                         : getString(R.string.settings_app_name),
454                 ((ActionType) mState).getDescription(mResources), R.drawable.ic_settings_datetime);
455     }
456 
setViewWithActionFragment(String title, String breadcrumb, String description, int iconResId)457     protected void setViewWithActionFragment(String title, String breadcrumb, String description,
458             int iconResId) {
459         mContentFragment = ContentFragment.newInstance(title, breadcrumb, description, iconResId,
460                 getResources().getColor(R.color.icon_background));
461         setContentAndActionFragments(mContentFragment, mActionFragment);
462     }
463 
464     @Override
setProperty(boolean enable)465     protected void setProperty(boolean enable) {
466     }
467 
468     /**
469      * Returns a string representing the current time zone set in the system.
470      */
getCurrentTimeZoneName()471     private String getCurrentTimeZoneName() {
472         final Calendar now = Calendar.getInstance();
473         TimeZone tz = now.getTimeZone();
474 
475         Date date = new Date();
476         return formatOffset(new StringBuilder(), tz.getOffset(date.getTime())).
477                 append(", ").
478                 append(tz.getDisplayName(tz.inDaylightTime(date), TimeZone.LONG)).toString();
479     }
480 
481     /**
482      * Formats the provided timezone offset into a string of the form GMT+XX:XX
483      */
formatOffset(StringBuilder sb, long offset)484     private static StringBuilder formatOffset(StringBuilder sb, long offset) {
485         long off = offset / 1000 / 60;
486 
487         sb.append("GMT");
488         if (off < 0) {
489             sb.append('-');
490             off = -off;
491         } else {
492             sb.append('+');
493         }
494 
495         int hours = (int) (off / 60);
496         int minutes = (int) (off % 60);
497 
498         sb.append((char) ('0' + hours / 10));
499         sb.append((char) ('0' + hours % 10));
500 
501         sb.append(':');
502 
503         sb.append((char) ('0' + minutes / 10));
504         sb.append((char) ('0' + minutes % 10));
505 
506         return sb;
507     }
508 
509     /**
510      * Helper class to hold the time zone data parsed from the Time Zones XML
511      * file.
512      */
513     private class TimeZoneInfo implements Comparable<TimeZoneInfo> {
514         public String tzId;
515         public String tzName;
516         public long tzOffset;
517 
TimeZoneInfo(String id, String name, long offset)518         public TimeZoneInfo(String id, String name, long offset) {
519             tzId = id;
520             tzName = name;
521             tzOffset = offset;
522         }
523 
524         @Override
compareTo(TimeZoneInfo another)525         public int compareTo(TimeZoneInfo another) {
526             return (int) (tzOffset - another.tzOffset);
527         }
528     }
529 
530     /**
531      * Parse the Time Zones information from the XML file and creates Action
532      * objects for each time zone.
533      */
getZoneActions(Context context)534     private ArrayList<Action> getZoneActions(Context context) {
535         if (mTimeZoneActions != null && mTimeZoneActions.size() != 0) {
536             return mTimeZoneActions;
537         }
538 
539         ArrayList<TimeZoneInfo> timeZones = getTimeZones(context);
540 
541         mTimeZoneActions = new ArrayList<Action>();
542 
543         // Sort the Time Zones list in ascending offset order
544         Collections.sort(timeZones);
545 
546         TimeZone currentTz = TimeZone.getDefault();
547 
548         for (TimeZoneInfo tz : timeZones) {
549             StringBuilder name = new StringBuilder();
550             boolean checked = currentTz.getID().equals(tz.tzId);
551             mTimeZoneActions.add(getTimeZoneAction(tz.tzId, tz.tzName,
552                     formatOffset(name, tz.tzOffset).toString(), checked));
553         }
554 
555         return mTimeZoneActions;
556     }
557 
558     /**
559      * Parses the XML time zone information into an array of TimeZoneInfo
560      * objects.
561      */
getTimeZones(Context context)562     private ArrayList<TimeZoneInfo> getTimeZones(Context context) {
563         ArrayList<TimeZoneInfo> timeZones = new ArrayList<TimeZoneInfo>();
564         final long date = Calendar.getInstance().getTimeInMillis();
565         try {
566             XmlResourceParser xrp = context.getResources().getXml(R.xml.timezones);
567             while (xrp.next() != XmlResourceParser.START_TAG)
568                 continue;
569             xrp.next();
570             while (xrp.getEventType() != XmlResourceParser.END_TAG) {
571                 while (xrp.getEventType() != XmlResourceParser.START_TAG &&
572                         xrp.getEventType() != XmlResourceParser.END_DOCUMENT) {
573                     xrp.next();
574                 }
575 
576                 if (xrp.getEventType() == XmlResourceParser.END_DOCUMENT) {
577                     break;
578                 }
579 
580                 if (xrp.getName().equals(XMLTAG_TIMEZONE)) {
581                     String id = xrp.getAttributeValue(0);
582                     String displayName = xrp.nextText();
583                     TimeZone tz = TimeZone.getTimeZone(id);
584                     long offset;
585                     if (tz != null) {
586                         offset = tz.getOffset(date);
587                         timeZones.add(new TimeZoneInfo(id, displayName, offset));
588                     } else {
589                         continue;
590                     }
591                 }
592                 while (xrp.getEventType() != XmlResourceParser.END_TAG) {
593                     xrp.next();
594                 }
595                 xrp.next();
596             }
597             xrp.close();
598         } catch (XmlPullParserException xppe) {
599             Log.e(TAG, "Ill-formatted timezones.xml file");
600         } catch (java.io.IOException ioe) {
601             Log.e(TAG, "Unable to read timezones.xml file");
602         }
603         return timeZones;
604     }
605 
getTimeZoneAction(String tzId, String displayName, String gmt, boolean setChecked)606     private static Action getTimeZoneAction(String tzId, String displayName, String gmt,
607             boolean setChecked) {
608         return new Action.Builder().key(tzId).title(displayName).description(gmt).
609                 checked(setChecked).build();
610     }
611 
setTimeZone(String tzId)612     private void setTimeZone(String tzId) {
613         // Update the system timezone value
614         final AlarmManager alarm = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
615         alarm.setTimeZone(tzId);
616 
617         setSampleDate();
618     }
619 }
620