blob: cc60a761cf0c48bc4a82833d4960bc133f3de66b [file] [log] [blame]
cretin45d4cea552016-04-25 11:00:04 -07001/*
Michael Bestasec050052024-02-14 20:35:43 +02002 * SPDX-FileCopyrightText: 2016 The CyanogenMod Project
3 * SPDX-FileCopyrightText: 2017-2024 The LineageOS Project
4 * SPDX-License-Identifier: Apache-2.0
cretin45d4cea552016-04-25 11:00:04 -07005 */
6
Michael Bestasc83309e2018-02-03 17:42:13 +02007package org.lineageos.setupwizard;
cretin45d4cea552016-04-25 11:00:04 -07008
9import android.app.AlarmManager;
10import android.app.DatePickerDialog;
11import android.app.Dialog;
cretin45d4cea552016-04-25 11:00:04 -070012import android.app.TimePickerDialog;
13import android.content.BroadcastReceiver;
14import android.content.Context;
15import android.content.Intent;
16import android.content.IntentFilter;
cretin45d4cea552016-04-25 11:00:04 -070017import android.os.Bundle;
18import android.os.Handler;
Oliver Scottf58c0f42024-01-08 14:52:12 -050019import android.os.Looper;
cretin45d4cea552016-04-25 11:00:04 -070020import android.text.format.DateFormat;
cretin45d4cea552016-04-25 11:00:04 -070021import android.view.View;
22import android.widget.AdapterView;
23import android.widget.DatePicker;
cretin45d4cea552016-04-25 11:00:04 -070024import android.widget.SimpleAdapter;
25import android.widget.Spinner;
26import android.widget.TextView;
27import android.widget.TimePicker;
28
Oliver Scottfd0fdb82024-01-10 18:39:27 -050029import androidx.annotation.NonNull;
Oliver Scottf58c0f42024-01-08 14:52:12 -050030import androidx.fragment.app.DialogFragment;
31
Michael W8dc3cf82020-05-11 14:44:18 +020032import com.android.settingslib.datetime.ZoneGetter;
33
Michael Bestasc83309e2018-02-03 17:42:13 +020034import org.lineageos.setupwizard.util.SetupWizardUtils;
Michael Wc3b20082017-10-09 21:13:00 +020035
cretin45d4cea552016-04-25 11:00:04 -070036import java.util.Calendar;
cretin45d4cea552016-04-25 11:00:04 -070037import java.util.Comparator;
cretin45d4cea552016-04-25 11:00:04 -070038import java.util.List;
39import java.util.Map;
40import java.util.TimeZone;
41
42public class DateTimeActivity extends BaseSetupWizardActivity implements
43 TimePickerDialog.OnTimeSetListener, DatePickerDialog.OnDateSetListener {
44
Michael Bestasb7b34b92021-08-25 19:13:30 +030045 private static final String KEY_ID = "id"; // value: String
46 private static final String KEY_DISPLAYNAME = "name"; // value: String
47 private static final String KEY_GMT = "gmt"; // value: String
48 private static final String KEY_OFFSET = "offset"; // value: int (Integer)
cretin45d4cea552016-04-25 11:00:04 -070049
50 private TimeZone mCurrentTimeZone;
cretin45d4cea552016-04-25 11:00:04 -070051 private TextView mDateTextView;
52 private TextView mTimeTextView;
53
Oliver Scottf58c0f42024-01-08 14:52:12 -050054 private final Handler mHandler = new Handler(Looper.getMainLooper());
cretin45d4cea552016-04-25 11:00:04 -070055
Michael Bestasb7b34b92021-08-25 19:13:30 +030056 private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
cretin45d4cea552016-04-25 11:00:04 -070057 @Override
58 public void onReceive(Context context, Intent intent) {
Michael Bestasb7b34b92021-08-25 19:13:30 +030059 updateTimeAndDateDisplay();
cretin45d4cea552016-04-25 11:00:04 -070060 }
61 };
62
63 @Override
64 protected void onCreate(Bundle savedInstanceState) {
65 super.onCreate(savedInstanceState);
66 setNextText(R.string.next);
Timi Rautamäki3ca81562022-01-15 16:42:42 +000067 getGlifLayout().setDescriptionText(getString(R.string.date_time_summary));
cretin45d4cea552016-04-25 11:00:04 -070068
Oliver Scott51fde0a2024-01-06 22:15:26 -050069 final Spinner spinner = findViewById(R.id.timezone_list);
Michael W8dc3cf82020-05-11 14:44:18 +020070 final SimpleAdapter adapter = constructTimezoneAdapter(this);
cretin45d4cea552016-04-25 11:00:04 -070071 mCurrentTimeZone = TimeZone.getDefault();
Michael W2236d292018-07-15 15:09:20 +020072 View dateView = findViewById(R.id.date_item);
73 dateView.setOnClickListener((view) -> showDatePicker());
74 View timeView = findViewById(R.id.time_item);
75 timeView.setOnClickListener((view) -> showTimePicker());
Oliver Scott51fde0a2024-01-06 22:15:26 -050076 mDateTextView = findViewById(R.id.date_text);
77 mTimeTextView = findViewById(R.id.time_text);
cretin45d4cea552016-04-25 11:00:04 -070078 // Pre-select current/default timezone
Michael W2236d292018-07-15 15:09:20 +020079 mHandler.post(() -> {
80 int tzIndex = getTimeZoneIndex(adapter, mCurrentTimeZone);
81 spinner.setAdapter(adapter);
82 if (tzIndex != -1) {
83 spinner.setSelection(tzIndex);
cretin45d4cea552016-04-25 11:00:04 -070084 }
Michael W2236d292018-07-15 15:09:20 +020085 spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
86 @Override
87 public void onItemSelected(AdapterView<?> adapterView, View view, int position,
Michael Bestasb7b34b92021-08-25 19:13:30 +030088 long id) {
Michael W2236d292018-07-15 15:09:20 +020089 final Map<?, ?> map = (Map<?, ?>) adapterView.getItemAtPosition(position);
90 final String tzId = (String) map.get(KEY_ID);
91 if (mCurrentTimeZone != null && !mCurrentTimeZone.getID().equals(tzId)) {
92 // Update the system timezone value
93 final AlarmManager alarm =
94 (AlarmManager) getSystemService(Context.ALARM_SERVICE);
95 alarm.setTimeZone(tzId);
96 mCurrentTimeZone = TimeZone.getTimeZone(tzId);
97 }
98
99 }
100
101 @Override
102 public void onNothingSelected(AdapterView<?> adapterView) {
103 }
104 });
cretin45d4cea552016-04-25 11:00:04 -0700105 });
106 // Pre-select current/default date if epoch
Michael W2236d292018-07-15 15:09:20 +0200107 mHandler.post(() -> {
108 final Calendar calendar = Calendar.getInstance();
109 final boolean isEpoch = calendar.get(Calendar.YEAR) == 1970;
110 if (isEpoch) {
111 // If epoch, set date to build date
112 long timestamp = SetupWizardUtils.getBuildDateTimestamp();
113 if (timestamp > 0) {
114 calendar.setTimeInMillis(timestamp * 1000);
115 setDate(DateTimeActivity.this, calendar.get(Calendar.YEAR),
116 calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH));
117 } else {
118 // no build date available, use a sane default
119 setDate(DateTimeActivity.this, 2017, Calendar.JANUARY, 1);
cretin45d4cea552016-04-25 11:00:04 -0700120 }
121 }
122 });
123 }
124
125 @Override
126 public void onResume() {
127 super.onResume();
128 // Register for time ticks and other reasons for time change
129 IntentFilter filter = new IntentFilter();
130 filter.addAction(Intent.ACTION_TIME_TICK);
131 filter.addAction(Intent.ACTION_TIME_CHANGED);
132 filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
133 registerReceiver(mIntentReceiver, filter, null, null);
134
135 updateTimeAndDateDisplay();
136 }
137
138 @Override
139 public void onPause() {
140 super.onPause();
141 unregisterReceiver(mIntentReceiver);
142 }
143
144 @Override
cretin45d4cea552016-04-25 11:00:04 -0700145 protected int getLayoutResId() {
146 return R.layout.setup_datetime_page;
147 }
148
149 @Override
150 protected int getTitleResId() {
151 return R.string.setup_datetime;
152 }
153
154 @Override
155 protected int getIconResId() {
156 return R.drawable.ic_datetime;
157 }
158
159 @Override
160 public void onDateSet(DatePicker view, int year, int month, int day) {
Michael Bestasb7b34b92021-08-25 19:13:30 +0300161 setDate(this, year, month, day);
162 updateTimeAndDateDisplay();
cretin45d4cea552016-04-25 11:00:04 -0700163 }
164
165 @Override
166 public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
Michael Bestasb7b34b92021-08-25 19:13:30 +0300167 setTime(this, hourOfDay, minute);
168 updateTimeAndDateDisplay();
cretin45d4cea552016-04-25 11:00:04 -0700169 }
170
171 private void showDatePicker() {
172 DatePickerFragment datePickerFragment = DatePickerFragment.newInstance();
Oliver Scottf58c0f42024-01-08 14:52:12 -0500173 datePickerFragment.show(getSupportFragmentManager(), DatePickerFragment.TAG);
cretin45d4cea552016-04-25 11:00:04 -0700174 }
175
176 private void showTimePicker() {
177 TimePickerFragment timePickerFragment = TimePickerFragment.newInstance();
Oliver Scottf58c0f42024-01-08 14:52:12 -0500178 timePickerFragment.show(getSupportFragmentManager(), TimePickerFragment.TAG);
cretin45d4cea552016-04-25 11:00:04 -0700179 }
180
181 private void updateTimeAndDateDisplay() {
182 java.text.DateFormat shortDateFormat = DateFormat.getDateFormat(this);
183 final Calendar now = Calendar.getInstance();
184 mTimeTextView.setText(DateFormat.getTimeFormat(this).format(now.getTime()));
185 mDateTextView.setText(shortDateFormat.format(now.getTime()));
186 }
187
Michael W8dc3cf82020-05-11 14:44:18 +0200188 private static SimpleAdapter constructTimezoneAdapter(Context context) {
Michael Bestasb7b34b92021-08-25 19:13:30 +0300189 final String[] from = new String[]{KEY_DISPLAYNAME, KEY_GMT};
190 final int[] to = new int[]{android.R.id.text1, android.R.id.text2};
cretin45d4cea552016-04-25 11:00:04 -0700191
Michael W8dc3cf82020-05-11 14:44:18 +0200192 final TimeZoneComparator comparator = new TimeZoneComparator(KEY_OFFSET);
193 final List<Map<String, Object>> sortedList = ZoneGetter.getZonesList(context);
Oliver Scottfd0fdb82024-01-10 18:39:27 -0500194 sortedList.sort(comparator);
195
196 return new SimpleAdapter(context,
cretin45d4cea552016-04-25 11:00:04 -0700197 sortedList,
198 R.layout.date_time_setup_custom_list_item_2,
199 from,
200 to);
cretin45d4cea552016-04-25 11:00:04 -0700201 }
202
cretin45d4cea552016-04-25 11:00:04 -0700203 private static int getTimeZoneIndex(SimpleAdapter adapter, TimeZone tz) {
204 final String defaultId = tz.getID();
205 final int listSize = adapter.getCount();
206 for (int i = 0; i < listSize; i++) {
Michael Bestasb7b34b92021-08-25 19:13:30 +0300207 final Map<?, ?> map = (Map<?, ?>) adapter.getItem(i);
208 final String id = (String) map.get(KEY_ID);
cretin45d4cea552016-04-25 11:00:04 -0700209 if (defaultId.equals(id)) {
210 // If current timezone is in this list, move focus to it
211 return i;
212 }
213 }
214 return -1;
215 }
216
217 private static void setDate(Context context, int year, int month, int day) {
218 Calendar c = Calendar.getInstance();
219
220 c.set(Calendar.YEAR, year);
221 c.set(Calendar.MONTH, month);
222 c.set(Calendar.DAY_OF_MONTH, day);
223 long when = c.getTimeInMillis();
224
225 if (when / 1000 < Integer.MAX_VALUE) {
226 ((AlarmManager) context.getSystemService(Context.ALARM_SERVICE)).setTime(when);
227 }
228 }
229
230 private static void setTime(Context context, int hourOfDay, int minute) {
231 Calendar c = Calendar.getInstance();
232
233 c.set(Calendar.HOUR_OF_DAY, hourOfDay);
234 c.set(Calendar.MINUTE, minute);
235 c.set(Calendar.SECOND, 0);
236 c.set(Calendar.MILLISECOND, 0);
237 long when = c.getTimeInMillis();
238
239 if (when / 1000 < Integer.MAX_VALUE) {
240 ((AlarmManager) context.getSystemService(Context.ALARM_SERVICE)).setTime(when);
241 }
242 }
243
Michael W8dc3cf82020-05-11 14:44:18 +0200244 private static class TimeZoneComparator implements Comparator<Map<?, ?>> {
Oliver Scottfd0fdb82024-01-10 18:39:27 -0500245 private final String mSortingKey;
cretin45d4cea552016-04-25 11:00:04 -0700246
247 public TimeZoneComparator(String sortingKey) {
248 mSortingKey = sortingKey;
249 }
250
Michael W8dc3cf82020-05-11 14:44:18 +0200251 public int compare(Map<?, ?> map1, Map<?, ?> map2) {
cretin45d4cea552016-04-25 11:00:04 -0700252 Object value1 = map1.get(mSortingKey);
253 Object value2 = map2.get(mSortingKey);
254
255 /*
256 * This should never happen, but just in-case, put non-comparable
257 * items at the end.
258 */
259 if (!isComparable(value1)) {
260 return isComparable(value2) ? 1 : 0;
261 } else if (!isComparable(value2)) {
262 return -1;
263 }
264
265 return ((Comparable) value1).compareTo(value2);
266 }
267
268 private boolean isComparable(Object value) {
Oliver Scottfd0fdb82024-01-10 18:39:27 -0500269 return (value instanceof Comparable);
cretin45d4cea552016-04-25 11:00:04 -0700270 }
271 }
272
273 public static class TimePickerFragment extends DialogFragment
274 implements TimePickerDialog.OnTimeSetListener {
275
Michael Bestasb7b34b92021-08-25 19:13:30 +0300276 private static final String TAG = TimePickerFragment.class.getSimpleName();
cretin45d4cea552016-04-25 11:00:04 -0700277
278 public static TimePickerFragment newInstance() {
Oliver Scottfd0fdb82024-01-10 18:39:27 -0500279 return new TimePickerFragment();
cretin45d4cea552016-04-25 11:00:04 -0700280 }
281
282 @Override
283 public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
Michael Bestasb7b34b92021-08-25 19:13:30 +0300284 ((DateTimeActivity) getActivity()).onTimeSet(view, hourOfDay, minute);
cretin45d4cea552016-04-25 11:00:04 -0700285 }
286
Oliver Scottfd0fdb82024-01-10 18:39:27 -0500287 @NonNull
cretin45d4cea552016-04-25 11:00:04 -0700288 @Override
289 public Dialog onCreateDialog(Bundle savedInstanceState) {
290 final Calendar calendar = Calendar.getInstance();
291 return new TimePickerDialog(
292 getActivity(),
293 this,
294 calendar.get(Calendar.HOUR_OF_DAY),
295 calendar.get(Calendar.MINUTE),
296 DateFormat.is24HourFormat(getActivity()));
297 }
cretin45d4cea552016-04-25 11:00:04 -0700298 }
299
300 public static class DatePickerFragment extends DialogFragment
301 implements DatePickerDialog.OnDateSetListener {
302
Michael Bestasb7b34b92021-08-25 19:13:30 +0300303 private static final String TAG = DatePickerFragment.class.getSimpleName();
cretin45d4cea552016-04-25 11:00:04 -0700304
305 public static DatePickerFragment newInstance() {
Oliver Scottfd0fdb82024-01-10 18:39:27 -0500306 return new DatePickerFragment();
cretin45d4cea552016-04-25 11:00:04 -0700307 }
308
309 @Override
310 public void onDateSet(DatePicker view, int year, int month, int day) {
Michael Bestasb7b34b92021-08-25 19:13:30 +0300311 ((DateTimeActivity) getActivity()).onDateSet(view, year, month, day);
cretin45d4cea552016-04-25 11:00:04 -0700312 }
313
Oliver Scottfd0fdb82024-01-10 18:39:27 -0500314 @NonNull
cretin45d4cea552016-04-25 11:00:04 -0700315 @Override
316 public Dialog onCreateDialog(Bundle savedInstanceState) {
317 final Calendar calendar = Calendar.getInstance();
318 return new DatePickerDialog(
319 getActivity(),
320 this,
321 calendar.get(Calendar.YEAR),
322 calendar.get(Calendar.MONTH),
323 calendar.get(Calendar.DAY_OF_MONTH));
324 }
325 }
326}