blob: 8a6d3a171e44f0b415a7d56c866f48c4d1d4610e [file] [log] [blame]
cretin45d4cea552016-04-25 11:00:04 -07001/*
2 * Copyright (C) 2016 The CyanogenMod Project
Christian Odercff63862020-11-09 17:14:42 +01003 * Copyright (C) 2017-2018,2020 The LineageOS Project
cretin45d4cea552016-04-25 11:00:04 -07004 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
Michael Bestasc83309e2018-02-03 17:42:13 +020018package org.lineageos.setupwizard;
cretin45d4cea552016-04-25 11:00:04 -070019
20import android.app.AlarmManager;
21import android.app.DatePickerDialog;
22import android.app.Dialog;
23import android.app.DialogFragment;
24import android.app.TimePickerDialog;
25import android.content.BroadcastReceiver;
26import android.content.Context;
27import android.content.Intent;
28import android.content.IntentFilter;
29import android.content.res.XmlResourceParser;
30import android.os.Bundle;
31import android.os.Handler;
32import android.text.format.DateFormat;
33import android.util.Log;
34import android.view.View;
35import android.widget.AdapterView;
36import android.widget.DatePicker;
cretin45d4cea552016-04-25 11:00:04 -070037import android.widget.SimpleAdapter;
38import android.widget.Spinner;
39import android.widget.TextView;
40import android.widget.TimePicker;
41
Michael Bestasc83309e2018-02-03 17:42:13 +020042import org.lineageos.setupwizard.util.SetupWizardUtils;
Michael Wc3b20082017-10-09 21:13:00 +020043
cretin45d4cea552016-04-25 11:00:04 -070044import org.xmlpull.v1.XmlPullParserException;
45
46import java.util.ArrayList;
47import java.util.Calendar;
48import java.util.Collections;
49import java.util.Comparator;
50import java.util.HashMap;
51import java.util.List;
52import java.util.Map;
53import java.util.TimeZone;
54
55public class DateTimeActivity extends BaseSetupWizardActivity implements
56 TimePickerDialog.OnTimeSetListener, DatePickerDialog.OnDateSetListener {
57
58 public static final String TAG = DateTimeActivity.class.getSimpleName();
59
60 private static final String KEY_ID = "id"; // value: String
61 private static final String KEY_DISPLAYNAME = "name"; // value: String
62 private static final String KEY_GMT = "gmt"; // value: String
63 private static final String KEY_OFFSET = "offset"; // value: int (Integer)
64 private static final String XMLTAG_TIMEZONE = "timezone";
65
66 private static final int HOURS_1 = 60 * 60000;
67
68 private TimeZone mCurrentTimeZone;
cretin45d4cea552016-04-25 11:00:04 -070069 private TextView mDateTextView;
70 private TextView mTimeTextView;
71
72
73 private final Handler mHandler = new Handler();
74
75 private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
76 @Override
77 public void onReceive(Context context, Intent intent) {
78 updateTimeAndDateDisplay();
79 }
80 };
81
82 @Override
83 protected void onCreate(Bundle savedInstanceState) {
84 super.onCreate(savedInstanceState);
85 setNextText(R.string.next);
86
87 final Spinner spinner = (Spinner) findViewById(R.id.timezone_list);
88 final SimpleAdapter adapter = constructTimezoneAdapter(this, false);
89 mCurrentTimeZone = TimeZone.getDefault();
Michael W2236d292018-07-15 15:09:20 +020090 View dateView = findViewById(R.id.date_item);
91 dateView.setOnClickListener((view) -> showDatePicker());
92 View timeView = findViewById(R.id.time_item);
93 timeView.setOnClickListener((view) -> showTimePicker());
cretin45d4cea552016-04-25 11:00:04 -070094 mDateTextView = (TextView)findViewById(R.id.date_text);
95 mTimeTextView = (TextView)findViewById(R.id.time_text);
96 // Pre-select current/default timezone
Michael W2236d292018-07-15 15:09:20 +020097 mHandler.post(() -> {
98 int tzIndex = getTimeZoneIndex(adapter, mCurrentTimeZone);
99 spinner.setAdapter(adapter);
100 if (tzIndex != -1) {
101 spinner.setSelection(tzIndex);
cretin45d4cea552016-04-25 11:00:04 -0700102 }
Michael W2236d292018-07-15 15:09:20 +0200103 spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
104 @Override
105 public void onItemSelected(AdapterView<?> adapterView, View view, int position,
106 long id) {
107 final Map<?, ?> map = (Map<?, ?>) adapterView.getItemAtPosition(position);
108 final String tzId = (String) map.get(KEY_ID);
109 if (mCurrentTimeZone != null && !mCurrentTimeZone.getID().equals(tzId)) {
110 // Update the system timezone value
111 final AlarmManager alarm =
112 (AlarmManager) getSystemService(Context.ALARM_SERVICE);
113 alarm.setTimeZone(tzId);
114 mCurrentTimeZone = TimeZone.getTimeZone(tzId);
115 }
116
117 }
118
119 @Override
120 public void onNothingSelected(AdapterView<?> adapterView) {
121 }
122 });
cretin45d4cea552016-04-25 11:00:04 -0700123 });
124 // Pre-select current/default date if epoch
Michael W2236d292018-07-15 15:09:20 +0200125 mHandler.post(() -> {
126 final Calendar calendar = Calendar.getInstance();
127 final boolean isEpoch = calendar.get(Calendar.YEAR) == 1970;
128 if (isEpoch) {
129 // If epoch, set date to build date
130 long timestamp = SetupWizardUtils.getBuildDateTimestamp();
131 if (timestamp > 0) {
132 calendar.setTimeInMillis(timestamp * 1000);
133 setDate(DateTimeActivity.this, calendar.get(Calendar.YEAR),
134 calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH));
135 } else {
136 // no build date available, use a sane default
137 setDate(DateTimeActivity.this, 2017, Calendar.JANUARY, 1);
cretin45d4cea552016-04-25 11:00:04 -0700138 }
139 }
140 });
141 }
142
143 @Override
144 public void onResume() {
145 super.onResume();
146 // Register for time ticks and other reasons for time change
147 IntentFilter filter = new IntentFilter();
148 filter.addAction(Intent.ACTION_TIME_TICK);
149 filter.addAction(Intent.ACTION_TIME_CHANGED);
150 filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
151 registerReceiver(mIntentReceiver, filter, null, null);
152
153 updateTimeAndDateDisplay();
154 }
155
156 @Override
157 public void onPause() {
158 super.onPause();
159 unregisterReceiver(mIntentReceiver);
160 }
161
162 @Override
cretin45d4cea552016-04-25 11:00:04 -0700163 protected int getLayoutResId() {
164 return R.layout.setup_datetime_page;
165 }
166
167 @Override
168 protected int getTitleResId() {
169 return R.string.setup_datetime;
170 }
171
172 @Override
173 protected int getIconResId() {
174 return R.drawable.ic_datetime;
175 }
176
177 @Override
178 public void onDateSet(DatePicker view, int year, int month, int day) {
179 setDate(this, year, month, day);
180 updateTimeAndDateDisplay();
181 }
182
183 @Override
184 public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
185 setTime(this, hourOfDay, minute);
186 updateTimeAndDateDisplay();
187 }
188
189 private void showDatePicker() {
190 DatePickerFragment datePickerFragment = DatePickerFragment.newInstance();
191 datePickerFragment.show(getFragmentManager(), DatePickerFragment.TAG);
192 }
193
194 private void showTimePicker() {
195 TimePickerFragment timePickerFragment = TimePickerFragment.newInstance();
196 timePickerFragment.show(getFragmentManager(), TimePickerFragment.TAG);
197 }
198
199 private void updateTimeAndDateDisplay() {
200 java.text.DateFormat shortDateFormat = DateFormat.getDateFormat(this);
201 final Calendar now = Calendar.getInstance();
202 mTimeTextView.setText(DateFormat.getTimeFormat(this).format(now.getTime()));
203 mDateTextView.setText(shortDateFormat.format(now.getTime()));
204 }
205
206 private static SimpleAdapter constructTimezoneAdapter(Context context,
207 boolean sortedByName) {
208 final String[] from = new String[] {KEY_DISPLAYNAME, KEY_GMT};
209 final int[] to = new int[] {android.R.id.text1, android.R.id.text2};
210
211 final String sortKey = (sortedByName ? KEY_DISPLAYNAME : KEY_OFFSET);
212 final TimeZoneComparator comparator = new TimeZoneComparator(sortKey);
213 final List<HashMap<String, Object>> sortedList = getZones(context);
214 Collections.sort(sortedList, comparator);
215 final SimpleAdapter adapter = new SimpleAdapter(context,
216 sortedList,
217 R.layout.date_time_setup_custom_list_item_2,
218 from,
219 to);
220
221 return adapter;
222 }
223
224 private static List<HashMap<String, Object>> getZones(Context context) {
225 final List<HashMap<String, Object>> myData = new ArrayList();
226 final long date = Calendar.getInstance().getTimeInMillis();
227 try {
228 XmlResourceParser xrp = context.getResources().getXml(R.xml.timezones);
229 while (xrp.next() != XmlResourceParser.START_TAG)
230 continue;
231 xrp.next();
232 while (xrp.getEventType() != XmlResourceParser.END_TAG) {
233 while (xrp.getEventType() != XmlResourceParser.START_TAG) {
234 if (xrp.getEventType() == XmlResourceParser.END_DOCUMENT) {
235 return myData;
236 }
237 xrp.next();
238 }
239 if (xrp.getName().equals(XMLTAG_TIMEZONE)) {
240 String id = xrp.getAttributeValue(0);
241 String displayName = xrp.nextText();
242 addItem(myData, id, displayName, date);
243 }
244 while (xrp.getEventType() != XmlResourceParser.END_TAG) {
245 xrp.next();
246 }
247 xrp.next();
248 }
249 xrp.close();
250 } catch (XmlPullParserException xppe) {
251 Log.e(TAG, "Ill-formatted timezones.xml file");
252 } catch (java.io.IOException ioe) {
253 Log.e(TAG, "Unable to read timezones.xml file");
254 }
255
256 return myData;
257 }
258
259 private static void addItem(
260 List<HashMap<String, Object>> myData, String id, String displayName, long date) {
261 final HashMap<String, Object> map = new HashMap();
262 map.put(KEY_ID, id);
263 map.put(KEY_DISPLAYNAME, displayName);
264 final TimeZone tz = TimeZone.getTimeZone(id);
265 final int offset = tz.getOffset(date);
266 final int p = Math.abs(offset);
267 final StringBuilder name = new StringBuilder();
268 name.append("GMT");
269
270 if (offset < 0) {
271 name.append('-');
272 } else {
273 name.append('+');
274 }
275
276 name.append(p / (HOURS_1));
277 name.append(':');
278
279 int min = p / 60000;
280 min %= 60;
281
282 if (min < 10) {
283 name.append('0');
284 }
285 name.append(min);
286
287 map.put(KEY_GMT, name.toString());
288 map.put(KEY_OFFSET, offset);
289
290 myData.add(map);
291 }
292
293 private static int getTimeZoneIndex(SimpleAdapter adapter, TimeZone tz) {
294 final String defaultId = tz.getID();
295 final int listSize = adapter.getCount();
296 for (int i = 0; i < listSize; i++) {
297 // Using HashMap<String, Object> induces unnecessary warning.
298 final HashMap<?,?> map = (HashMap<?,?>)adapter.getItem(i);
299 final String id = (String)map.get(KEY_ID);
300 if (defaultId.equals(id)) {
301 // If current timezone is in this list, move focus to it
302 return i;
303 }
304 }
305 return -1;
306 }
307
308 private static void setDate(Context context, int year, int month, int day) {
309 Calendar c = Calendar.getInstance();
310
311 c.set(Calendar.YEAR, year);
312 c.set(Calendar.MONTH, month);
313 c.set(Calendar.DAY_OF_MONTH, day);
314 long when = c.getTimeInMillis();
315
316 if (when / 1000 < Integer.MAX_VALUE) {
317 ((AlarmManager) context.getSystemService(Context.ALARM_SERVICE)).setTime(when);
318 }
319 }
320
321 private static void setTime(Context context, int hourOfDay, int minute) {
322 Calendar c = Calendar.getInstance();
323
324 c.set(Calendar.HOUR_OF_DAY, hourOfDay);
325 c.set(Calendar.MINUTE, minute);
326 c.set(Calendar.SECOND, 0);
327 c.set(Calendar.MILLISECOND, 0);
328 long when = c.getTimeInMillis();
329
330 if (when / 1000 < Integer.MAX_VALUE) {
331 ((AlarmManager) context.getSystemService(Context.ALARM_SERVICE)).setTime(when);
332 }
333 }
334
335 private static class TimeZoneComparator implements Comparator<HashMap<?, ?>> {
336 private String mSortingKey;
337
338 public TimeZoneComparator(String sortingKey) {
339 mSortingKey = sortingKey;
340 }
341
342 public void setSortingKey(String sortingKey) {
343 mSortingKey = sortingKey;
344 }
345
346 public int compare(HashMap<?, ?> map1, HashMap<?, ?> map2) {
347 Object value1 = map1.get(mSortingKey);
348 Object value2 = map2.get(mSortingKey);
349
350 /*
351 * This should never happen, but just in-case, put non-comparable
352 * items at the end.
353 */
354 if (!isComparable(value1)) {
355 return isComparable(value2) ? 1 : 0;
356 } else if (!isComparable(value2)) {
357 return -1;
358 }
359
360 return ((Comparable) value1).compareTo(value2);
361 }
362
363 private boolean isComparable(Object value) {
364 return (value != null) && (value instanceof Comparable);
365 }
366 }
367
368 public static class TimePickerFragment extends DialogFragment
369 implements TimePickerDialog.OnTimeSetListener {
370
371 private static String TAG = TimePickerFragment.class.getSimpleName();
372
373 public static TimePickerFragment newInstance() {
374 TimePickerFragment frag = new TimePickerFragment();
375 return frag;
376 }
377
378 @Override
379 public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
380 ((DateTimeActivity)getActivity()).onTimeSet(view, hourOfDay, minute);
381 }
382
383 @Override
384 public Dialog onCreateDialog(Bundle savedInstanceState) {
385 final Calendar calendar = Calendar.getInstance();
386 return new TimePickerDialog(
387 getActivity(),
388 this,
389 calendar.get(Calendar.HOUR_OF_DAY),
390 calendar.get(Calendar.MINUTE),
391 DateFormat.is24HourFormat(getActivity()));
392 }
393
394 }
395
396 public static class DatePickerFragment extends DialogFragment
397 implements DatePickerDialog.OnDateSetListener {
398
399 private static String TAG = DatePickerFragment.class.getSimpleName();
400
401 public static DatePickerFragment newInstance() {
402 DatePickerFragment frag = new DatePickerFragment();
403 return frag;
404 }
405
406 @Override
407 public void onDateSet(DatePicker view, int year, int month, int day) {
408 ((DateTimeActivity)getActivity()).onDateSet(view, year, month, day);
409 }
410
411 @Override
412 public Dialog onCreateDialog(Bundle savedInstanceState) {
413 final Calendar calendar = Calendar.getInstance();
414 return new DatePickerDialog(
415 getActivity(),
416 this,
417 calendar.get(Calendar.YEAR),
418 calendar.get(Calendar.MONTH),
419 calendar.get(Calendar.DAY_OF_MONTH));
420 }
421 }
422}