/* * Copyright (C) 2013 The CyanogenMod Project * Copyright (C) 2017 The LineageOS Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.cyanogenmod.setupwizard.setup; import android.app.Activity; import android.app.AlarmManager; import android.app.DatePickerDialog; import android.app.Dialog; import android.app.DialogFragment; import android.app.Fragment; import android.app.FragmentManager; import android.app.TimePickerDialog; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.res.XmlResourceParser; import android.os.Bundle; import android.os.Handler; import android.text.format.DateFormat; import android.util.Log; import android.view.View; import android.widget.AdapterView; import android.widget.DatePicker; import android.widget.SimpleAdapter; import android.widget.Spinner; import android.widget.TextView; import android.widget.TimePicker; import com.cyanogenmod.setupwizard.R; import com.cyanogenmod.setupwizard.cmstats.SetupStats; import com.cyanogenmod.setupwizard.ui.SetupPageFragment; import org.xmlpull.v1.XmlPullParserException; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TimeZone; public class DateTimePage extends SetupPage { public static final String TAG = "DateTimePage"; private static final String KEY_ID = "id"; // value: String private static final String KEY_DISPLAYNAME = "name"; // value: String private static final String KEY_GMT = "gmt"; // value: String private static final String KEY_OFFSET = "offset"; // value: int (Integer) private static final String XMLTAG_TIMEZONE = "timezone"; private static final int HOURS_1 = 60 * 60000; public DateTimePage(Context context, SetupDataCallbacks callbacks) { super(context, callbacks); } @Override public Fragment getFragment(FragmentManager fragmentManager, int action) { Fragment fragment = fragmentManager.findFragmentByTag(getKey()); if (fragment == null) { Bundle args = new Bundle(); args.putString(Page.KEY_PAGE_ARGUMENT, getKey()); args.putInt(Page.KEY_PAGE_ACTION, action); fragment = new DateTimeFragment(); fragment.setArguments(args); } return fragment; } @Override public String getKey() { return TAG; } @Override public int getTitleResId() { return R.string.setup_datetime; } @Override public int getIconResId() { return R.drawable.ic_datetime; } public static class DateTimeFragment extends SetupPageFragment implements TimePickerDialog.OnTimeSetListener, DatePickerDialog.OnDateSetListener { private TimeZone mCurrentTimeZone; private View mDateView; private View mTimeView; private TextView mDateTextView; private TextView mTimeTextView; private final Handler mHandler = new Handler(); @Override public void onResume() { super.onResume(); // Register for time ticks and other reasons for time change IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_TIME_TICK); filter.addAction(Intent.ACTION_TIME_CHANGED); filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); getActivity().registerReceiver(mIntentReceiver, filter, null, null); updateTimeAndDateDisplay(getActivity()); } @Override public void onPause() { super.onPause(); getActivity().unregisterReceiver(mIntentReceiver); } @Override protected void initializePage() { final Spinner spinner = (Spinner) mRootView.findViewById(R.id.timezone_list); final SimpleAdapter adapter = constructTimezoneAdapter(getActivity(), false); mCurrentTimeZone = TimeZone.getDefault(); mDateView = mRootView.findViewById(R.id.date_item); mDateView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { SetupStats.addEvent(SetupStats.Categories.BUTTON_CLICK, "date_picker"); showDatePicker(); } }); mTimeView = mRootView.findViewById(R.id.time_item); mTimeView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { SetupStats.addEvent(SetupStats.Categories.BUTTON_CLICK, "time_picker"); showTimePicker(); } }); mDateTextView = (TextView)mRootView.findViewById(R.id.date_text); mTimeTextView = (TextView)mRootView.findViewById(R.id.time_text); // Pre-select current/default timezone mHandler.post(new Runnable() { @Override public void run() { int tzIndex = getTimeZoneIndex(adapter, mCurrentTimeZone); spinner.setAdapter(adapter); if (tzIndex != -1) { spinner.setSelection(tzIndex); } spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView adapterView, View view, int position, long id) { final Map map = (Map) adapterView.getItemAtPosition(position); final String tzId = (String) map.get(KEY_ID); if (mCurrentTimeZone != null && !mCurrentTimeZone.getID().equals(tzId)) { SetupStats.addEvent(SetupStats.Categories.BUTTON_CLICK, "timezone_picker"); // Update the system timezone value final Activity activity = getActivity(); final AlarmManager alarm = (AlarmManager) activity.getSystemService(Context.ALARM_SERVICE); alarm.setTimeZone(tzId); mCurrentTimeZone = TimeZone.getTimeZone(tzId); SetupStats.addEvent(SetupStats.Categories.SETTING_CHANGED, SetupStats.Action.TIMEZONE_CHANGED, SetupStats.Label.VALUE, mCurrentTimeZone.getDisplayName()); } } @Override public void onNothingSelected(AdapterView adapterView) { } }); } }); // Pre-select current/default date if epoch mHandler.post(new Runnable() { @Override public void run() { final Calendar calendar = Calendar.getInstance(); final boolean isEpoch = calendar.get(Calendar.YEAR) == 1970; if (isEpoch) { // If epoch, set date to a default date setDate(getActivity(), 2016, Calendar.JANUARY, 1); } } }); } private void showDatePicker() { DatePickerFragment datePickerFragment = DatePickerFragment.newInstance(); datePickerFragment.setTargetFragment(this, 0); datePickerFragment.show(getFragmentManager(), DatePickerFragment.TAG); } private void showTimePicker() { TimePickerFragment timePickerFragment = TimePickerFragment.newInstance(); timePickerFragment.setTargetFragment(this, 0); timePickerFragment.show(getFragmentManager(), TimePickerFragment.TAG); } public void updateTimeAndDateDisplay(Context context) { java.text.DateFormat shortDateFormat = DateFormat.getDateFormat(context); final Calendar now = Calendar.getInstance(); mTimeTextView.setText(DateFormat.getTimeFormat(getActivity()).format(now.getTime())); mDateTextView.setText(shortDateFormat.format(now.getTime())); } @Override protected int getLayoutResource() { return R.layout.setup_datetime_page; } @Override public void onDateSet(DatePicker view, int year, int month, int day) { final Activity activity = getActivity(); if (activity != null) { setDate(activity, year, month, day); updateTimeAndDateDisplay(activity); SetupStats.addEvent(SetupStats.Categories.SETTING_CHANGED, SetupStats.Action.DATE_CHANGED, SetupStats.Label.VALUE, month+"/"+day+"/"+year); } } @Override public void onTimeSet(TimePicker view, int hourOfDay, int minute) { final Activity activity = getActivity(); if (activity != null) { setTime(activity, hourOfDay, minute); updateTimeAndDateDisplay(activity); SetupStats.addEvent(SetupStats.Categories.SETTING_CHANGED, SetupStats.Action.TIME_CHANGED, SetupStats.Label.VALUE, hourOfDay+":"+minute); } } private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { final Activity activity = getActivity(); if (activity != null) { updateTimeAndDateDisplay(activity); } } }; } private static SimpleAdapter constructTimezoneAdapter(Context context, boolean sortedByName) { final String[] from = new String[] {KEY_DISPLAYNAME, KEY_GMT}; final int[] to = new int[] {android.R.id.text1, android.R.id.text2}; final String sortKey = (sortedByName ? KEY_DISPLAYNAME : KEY_OFFSET); final TimeZoneComparator comparator = new TimeZoneComparator(sortKey); final List> sortedList = getZones(context); Collections.sort(sortedList, comparator); final SimpleAdapter adapter = new SimpleAdapter(context, sortedList, R.layout.date_time_setup_custom_list_item_2, from, to); return adapter; } private static List> getZones(Context context) { final List> myData = new ArrayList>(); final long date = Calendar.getInstance().getTimeInMillis(); try { XmlResourceParser xrp = context.getResources().getXml(R.xml.timezones); while (xrp.next() != XmlResourceParser.START_TAG) continue; xrp.next(); while (xrp.getEventType() != XmlResourceParser.END_TAG) { while (xrp.getEventType() != XmlResourceParser.START_TAG) { if (xrp.getEventType() == XmlResourceParser.END_DOCUMENT) { return myData; } xrp.next(); } if (xrp.getName().equals(XMLTAG_TIMEZONE)) { String id = xrp.getAttributeValue(0); String displayName = xrp.nextText(); addItem(myData, id, displayName, date); } while (xrp.getEventType() != XmlResourceParser.END_TAG) { xrp.next(); } xrp.next(); } xrp.close(); } catch (XmlPullParserException xppe) { Log.e(TAG, "Ill-formatted timezones.xml file"); } catch (java.io.IOException ioe) { Log.e(TAG, "Unable to read timezones.xml file"); } return myData; } private static void addItem( List> myData, String id, String displayName, long date) { final HashMap map = new HashMap(); map.put(KEY_ID, id); map.put(KEY_DISPLAYNAME, displayName); final TimeZone tz = TimeZone.getTimeZone(id); final int offset = tz.getOffset(date); final int p = Math.abs(offset); final StringBuilder name = new StringBuilder(); name.append("GMT"); if (offset < 0) { name.append('-'); } else { name.append('+'); } name.append(p / (HOURS_1)); name.append(':'); int min = p / 60000; min %= 60; if (min < 10) { name.append('0'); } name.append(min); map.put(KEY_GMT, name.toString()); map.put(KEY_OFFSET, offset); myData.add(map); } private static int getTimeZoneIndex(SimpleAdapter adapter, TimeZone tz) { final String defaultId = tz.getID(); final int listSize = adapter.getCount(); for (int i = 0; i < listSize; i++) { // Using HashMap induces unnecessary warning. final HashMap map = (HashMap)adapter.getItem(i); final String id = (String)map.get(KEY_ID); if (defaultId.equals(id)) { // If current timezone is in this list, move focus to it return i; } } return -1; } private static void setDate(Context context, int year, int month, int day) { Calendar c = Calendar.getInstance(); c.set(Calendar.YEAR, year); c.set(Calendar.MONTH, month); c.set(Calendar.DAY_OF_MONTH, day); long when = c.getTimeInMillis(); if (when / 1000 < Integer.MAX_VALUE) { ((AlarmManager) context.getSystemService(Context.ALARM_SERVICE)).setTime(when); } } private static void setTime(Context context, int hourOfDay, int minute) { Calendar c = Calendar.getInstance(); c.set(Calendar.HOUR_OF_DAY, hourOfDay); c.set(Calendar.MINUTE, minute); c.set(Calendar.SECOND, 0); c.set(Calendar.MILLISECOND, 0); long when = c.getTimeInMillis(); if (when / 1000 < Integer.MAX_VALUE) { ((AlarmManager) context.getSystemService(Context.ALARM_SERVICE)).setTime(when); } } private static class TimeZoneComparator implements Comparator> { private String mSortingKey; public TimeZoneComparator(String sortingKey) { mSortingKey = sortingKey; } public void setSortingKey(String sortingKey) { mSortingKey = sortingKey; } public int compare(HashMap map1, HashMap map2) { Object value1 = map1.get(mSortingKey); Object value2 = map2.get(mSortingKey); /* * This should never happen, but just in-case, put non-comparable * items at the end. */ if (!isComparable(value1)) { return isComparable(value2) ? 1 : 0; } else if (!isComparable(value2)) { return -1; } return ((Comparable) value1).compareTo(value2); } private boolean isComparable(Object value) { return (value != null) && (value instanceof Comparable); } } public static class TimePickerFragment extends DialogFragment implements TimePickerDialog.OnTimeSetListener { private static String TAG = TimePickerFragment.class.getSimpleName(); public static TimePickerFragment newInstance() { TimePickerFragment frag = new TimePickerFragment(); return frag; } @Override public void onTimeSet(TimePicker view, int hourOfDay, int minute) { ((DateTimeFragment) getTargetFragment()).onTimeSet(view, hourOfDay, minute); } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { final Calendar calendar = Calendar.getInstance(); return new TimePickerDialog( getActivity(), this, calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE), DateFormat.is24HourFormat(getActivity())); } } public static class DatePickerFragment extends DialogFragment implements DatePickerDialog.OnDateSetListener { private static String TAG = DatePickerFragment.class.getSimpleName(); public static DatePickerFragment newInstance() { DatePickerFragment frag = new DatePickerFragment(); return frag; } @Override public void onDateSet(DatePicker view, int year, int month, int day) { ((DateTimeFragment) getTargetFragment()).onDateSet(view, year, month, day); } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { final Calendar calendar = Calendar.getInstance(); return new DatePickerDialog( getActivity(), this, calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH)); } } }