| /* |
| * Copyright (C) 2016 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; |
| |
| import android.app.AlarmManager; |
| import android.app.DatePickerDialog; |
| import android.app.Dialog; |
| import android.app.DialogFragment; |
| 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.ImageView; |
| import android.widget.SimpleAdapter; |
| import android.widget.Spinner; |
| import android.widget.TextView; |
| import android.widget.TimePicker; |
| |
| import com.cyanogenmod.setupwizard.util.SetupWizardUtils; |
| |
| 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 DateTimeActivity extends BaseSetupWizardActivity implements |
| TimePickerDialog.OnTimeSetListener, DatePickerDialog.OnDateSetListener { |
| |
| public static final String TAG = DateTimeActivity.class.getSimpleName(); |
| |
| 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; |
| |
| private TimeZone mCurrentTimeZone; |
| private View mDateView; |
| private View mTimeView; |
| private TextView mDateTextView; |
| private TextView mTimeTextView; |
| |
| |
| private final Handler mHandler = new Handler(); |
| |
| private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| updateTimeAndDateDisplay(); |
| } |
| }; |
| |
| @Override |
| protected void onCreate(Bundle savedInstanceState) { |
| super.onCreate(savedInstanceState); |
| setNextText(R.string.next); |
| |
| final Spinner spinner = (Spinner) findViewById(R.id.timezone_list); |
| final SimpleAdapter adapter = constructTimezoneAdapter(this, false); |
| mCurrentTimeZone = TimeZone.getDefault(); |
| mDateView = findViewById(R.id.date_item); |
| mDateView.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View view) { |
| showDatePicker(); |
| } |
| }); |
| mTimeView = findViewById(R.id.time_item); |
| mTimeView.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View view) { |
| showTimePicker(); |
| } |
| }); |
| mDateTextView = (TextView)findViewById(R.id.date_text); |
| mTimeTextView = (TextView)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)) { |
| // Update the system timezone value |
| final AlarmManager alarm = (AlarmManager) getSystemService(Context.ALARM_SERVICE); |
| alarm.setTimeZone(tzId); |
| mCurrentTimeZone = TimeZone.getTimeZone(tzId); |
| } |
| |
| } |
| |
| @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 build date |
| long timestamp = SetupWizardUtils.getBuildDateTimestamp(); |
| if (timestamp > 0) { |
| calendar.setTimeInMillis(timestamp * 1000); |
| setDate(DateTimeActivity.this, calendar.get(Calendar.YEAR), |
| calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH)); |
| } else { |
| // no build date available, use a sane default |
| setDate(DateTimeActivity.this, 2017, Calendar.JANUARY, 1); |
| } |
| } |
| } |
| }); |
| } |
| |
| @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); |
| registerReceiver(mIntentReceiver, filter, null, null); |
| |
| updateTimeAndDateDisplay(); |
| } |
| |
| @Override |
| public void onPause() { |
| super.onPause(); |
| unregisterReceiver(mIntentReceiver); |
| } |
| |
| @Override |
| protected int getTransition() { |
| return TRANSITION_ID_SLIDE; |
| } |
| |
| @Override |
| protected int getLayoutResId() { |
| return R.layout.setup_datetime_page; |
| } |
| |
| @Override |
| protected int getTitleResId() { |
| return R.string.setup_datetime; |
| } |
| |
| @Override |
| protected int getIconResId() { |
| return R.drawable.ic_datetime; |
| } |
| |
| @Override |
| public void onDateSet(DatePicker view, int year, int month, int day) { |
| setDate(this, year, month, day); |
| updateTimeAndDateDisplay(); |
| } |
| |
| @Override |
| public void onTimeSet(TimePicker view, int hourOfDay, int minute) { |
| setTime(this, hourOfDay, minute); |
| updateTimeAndDateDisplay(); |
| } |
| |
| private void showDatePicker() { |
| DatePickerFragment datePickerFragment = DatePickerFragment.newInstance(); |
| datePickerFragment.show(getFragmentManager(), DatePickerFragment.TAG); |
| } |
| |
| private void showTimePicker() { |
| TimePickerFragment timePickerFragment = TimePickerFragment.newInstance(); |
| timePickerFragment.show(getFragmentManager(), TimePickerFragment.TAG); |
| } |
| |
| private void updateTimeAndDateDisplay() { |
| java.text.DateFormat shortDateFormat = DateFormat.getDateFormat(this); |
| final Calendar now = Calendar.getInstance(); |
| mTimeTextView.setText(DateFormat.getTimeFormat(this).format(now.getTime())); |
| mDateTextView.setText(shortDateFormat.format(now.getTime())); |
| } |
| |
| 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<HashMap<String, Object>> 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<HashMap<String, Object>> getZones(Context context) { |
| final List<HashMap<String, Object>> 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<HashMap<String, Object>> myData, String id, String displayName, long date) { |
| final HashMap<String, Object> 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<String, Object> 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<HashMap<?, ?>> { |
| 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) { |
| ((DateTimeActivity)getActivity()).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) { |
| ((DateTimeActivity)getActivity()).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)); |
| } |
| } |
| } |