1d60f09edafea67616d574d4421da220b1615e194Justin Klaassen/*
2c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen * Copyright (C) 2016 The Android Open Source Project
3c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen *
4c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen * Licensed under the Apache License, Version 2.0 (the "License");
5c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen * you may not use this file except in compliance with the License.
6c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen * You may obtain a copy of the License at
7c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen *
8c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen *      http://www.apache.org/licenses/LICENSE-2.0
9c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen *
10c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen * Unless required by applicable law or agreed to in writing, software
11c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen * distributed under the License is distributed on an "AS IS" BASIS,
12c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen * See the License for the specific language governing permissions and
14c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen * limitations under the License.
15c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen */
16c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen
17c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassenpackage com.android.settings.display;
18c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen
19c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassenimport android.app.Dialog;
20c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassenimport android.app.TimePickerDialog;
21c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassenimport android.content.Context;
22c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassenimport android.os.Bundle;
23c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassenimport android.support.v7.preference.DropDownPreference;
24c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassenimport android.support.v7.preference.Preference;
257335b6505d786a6cc0f66e19427bdf739a7f6830Justin Klaassenimport android.support.v7.preference.TwoStatePreference;
26c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassenimport android.widget.TimePicker;
27c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen
28c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassenimport com.android.internal.app.NightDisplayController;
29c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassenimport com.android.internal.logging.MetricsProto.MetricsEvent;
30c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassenimport com.android.settings.R;
31c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassenimport com.android.settings.SettingsPreferenceFragment;
32c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen
33c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassenimport java.text.DateFormat;
34c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassenimport java.util.Calendar;
35c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassenimport java.util.TimeZone;
36c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen
37c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen/**
38c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen * Settings screen for Night display.
39c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen */
407335b6505d786a6cc0f66e19427bdf739a7f6830Justin Klaassenpublic class NightDisplaySettings extends SettingsPreferenceFragment
417335b6505d786a6cc0f66e19427bdf739a7f6830Justin Klaassen        implements NightDisplayController.Callback, Preference.OnPreferenceChangeListener {
42c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen
43c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    private static final String KEY_NIGHT_DISPLAY_AUTO_MODE = "night_display_auto_mode";
44c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    private static final String KEY_NIGHT_DISPLAY_START_TIME = "night_display_start_time";
45c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    private static final String KEY_NIGHT_DISPLAY_END_TIME = "night_display_end_time";
467335b6505d786a6cc0f66e19427bdf739a7f6830Justin Klaassen    private static final String KEY_NIGHT_DISPLAY_ACTIVATED = "night_display_activated";
47c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen
48c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    private static final int DIALOG_START_TIME = 0;
49c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    private static final int DIALOG_END_TIME = 1;
50c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen
51c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    private NightDisplayController mController;
52c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    private DateFormat mTimeFormatter;
53c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen
54c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    private DropDownPreference mAutoModePreference;
55c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    private Preference mStartTimePreference;
56c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    private Preference mEndTimePreference;
577335b6505d786a6cc0f66e19427bdf739a7f6830Justin Klaassen    private TwoStatePreference mActivatedPreference;
58c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen
59c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    @Override
60c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    public void onCreate(Bundle savedInstanceState) {
61c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        super.onCreate(savedInstanceState);
62c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen
63c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        final Context context = getContext();
64c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        mController = new NightDisplayController(context);
65c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen
66c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        mTimeFormatter = android.text.format.DateFormat.getTimeFormat(context);
67c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        mTimeFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
68c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    }
69c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen
70c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    @Override
71c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
72c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        super.onCreatePreferences(savedInstanceState, rootKey);
73c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen
74c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        // Load the preferences from xml.
75c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        addPreferencesFromResource(R.xml.night_display_settings);
76c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen
77c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        mAutoModePreference = (DropDownPreference) findPreference(KEY_NIGHT_DISPLAY_AUTO_MODE);
78c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        mStartTimePreference = findPreference(KEY_NIGHT_DISPLAY_START_TIME);
79c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        mEndTimePreference = findPreference(KEY_NIGHT_DISPLAY_END_TIME);
807335b6505d786a6cc0f66e19427bdf739a7f6830Justin Klaassen        mActivatedPreference = (TwoStatePreference) findPreference(KEY_NIGHT_DISPLAY_ACTIVATED);
81c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen
82c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        mAutoModePreference.setEntries(new CharSequence[] {
83c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen                getString(R.string.night_display_auto_mode_never),
84c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen                getString(R.string.night_display_auto_mode_custom),
85c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen                getString(R.string.night_display_auto_mode_twilight)
86c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        });
87c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        mAutoModePreference.setEntryValues(new CharSequence[] {
88c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen                String.valueOf(NightDisplayController.AUTO_MODE_DISABLED),
89c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen                String.valueOf(NightDisplayController.AUTO_MODE_CUSTOM),
90c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen                String.valueOf(NightDisplayController.AUTO_MODE_TWILIGHT)
91c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        });
92c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        mAutoModePreference.setOnPreferenceChangeListener(this);
937335b6505d786a6cc0f66e19427bdf739a7f6830Justin Klaassen        mActivatedPreference.setOnPreferenceChangeListener(this);
94c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    }
95c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen
96c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    @Override
97c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    public void onStart() {
98c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        super.onStart();
99c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen
100c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        // Listen for changes only while visible.
101c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        mController.setListener(this);
102c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen
103c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        // Update the current state since it have changed while not visible.
104c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        onActivated(mController.isActivated());
105c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        onAutoModeChanged(mController.getAutoMode());
106c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        onCustomStartTimeChanged(mController.getCustomStartTime());
107c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        onCustomEndTimeChanged(mController.getCustomEndTime());
108c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    }
109c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen
110c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    @Override
111c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    public void onStop() {
112c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        super.onStop();
113c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen
114c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        // Stop listening for state changes.
115c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        mController.setListener(null);
116c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    }
117c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen
118c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    @Override
119c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    public boolean onPreferenceTreeClick(Preference preference) {
120c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        if (preference == mStartTimePreference) {
121c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen            showDialog(DIALOG_START_TIME);
122c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen            return true;
123c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        } else if (preference == mEndTimePreference) {
124c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen            showDialog(DIALOG_END_TIME);
125c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen            return true;
126c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        }
127c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        return super.onPreferenceTreeClick(preference);
128c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    }
129c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen
130c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    @Override
131c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    public Dialog onCreateDialog(final int dialogId) {
132c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        if (dialogId == DIALOG_START_TIME || dialogId == DIALOG_END_TIME) {
133c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen            final NightDisplayController.LocalTime initialTime;
134c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen            if (dialogId == DIALOG_START_TIME) {
135c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen                initialTime = mController.getCustomStartTime();
136c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen            } else {
137c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen                initialTime = mController.getCustomEndTime();
138c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen            }
139c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen
140c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen            final Context context = getContext();
141c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen            final boolean use24HourFormat = android.text.format.DateFormat.is24HourFormat(context);
142c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen            return new TimePickerDialog(context, new TimePickerDialog.OnTimeSetListener() {
143c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen                @Override
144c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen                public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
145c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen                    final NightDisplayController.LocalTime time =
146c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen                            new NightDisplayController.LocalTime(hourOfDay, minute);
147c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen                    if (dialogId == DIALOG_START_TIME) {
148c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen                        mController.setCustomStartTime(time);
149c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen                    } else {
150c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen                        mController.setCustomEndTime(time);
151c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen                    }
152c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen                }
153c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen            }, initialTime.hourOfDay, initialTime.minute, use24HourFormat);
154c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        }
155c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        return super.onCreateDialog(dialogId);
156c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    }
157c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen
158c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    @Override
159c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    public void onActivated(boolean activated) {
1607335b6505d786a6cc0f66e19427bdf739a7f6830Justin Klaassen        mActivatedPreference.setChecked(activated);
161c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    }
162c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen
163c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    @Override
164c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    public void onAutoModeChanged(int autoMode) {
165c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        mAutoModePreference.setValue(String.valueOf(autoMode));
166c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen
167c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        final boolean showCustomSchedule = autoMode == NightDisplayController.AUTO_MODE_CUSTOM;
168c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        mStartTimePreference.setVisible(showCustomSchedule);
169c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        mEndTimePreference.setVisible(showCustomSchedule);
170c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    }
171c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen
172c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    private String getFormattedTimeString(NightDisplayController.LocalTime localTime) {
173c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        final Calendar c = Calendar.getInstance();
174c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        c.setTimeZone(mTimeFormatter.getTimeZone());
175c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        c.set(Calendar.HOUR_OF_DAY, localTime.hourOfDay);
176c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        c.set(Calendar.MINUTE, localTime.minute);
177c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        c.set(Calendar.SECOND, 0);
178c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        c.set(Calendar.MILLISECOND, 0);
179c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        return mTimeFormatter.format(c.getTime());
180c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    }
181c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen
182c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    @Override
183c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    public void onCustomStartTimeChanged(NightDisplayController.LocalTime startTime) {
184c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        mStartTimePreference.setSummary(getFormattedTimeString(startTime));
185c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    }
186c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen
187c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    @Override
188c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    public void onCustomEndTimeChanged(NightDisplayController.LocalTime endTime) {
189c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        mEndTimePreference.setSummary(getFormattedTimeString(endTime));
190c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    }
191c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen
192c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    @Override
193c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    public boolean onPreferenceChange(Preference preference, Object newValue) {
194c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        if (preference == mAutoModePreference) {
195c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen            return mController.setAutoMode(Integer.parseInt((String) newValue));
1967335b6505d786a6cc0f66e19427bdf739a7f6830Justin Klaassen        } else if (preference == mActivatedPreference) {
1977335b6505d786a6cc0f66e19427bdf739a7f6830Justin Klaassen            return mController.setActivated((Boolean) newValue);
198c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        }
199c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        return false;
200c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    }
201c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen
202c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    @Override
203c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    protected int getMetricsCategory() {
204c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen        return MetricsEvent.NIGHT_DISPLAY_SETTINGS;
205c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen    }
206c7cd2982cd804b5a3806da208ca71f5e26121e75Justin Klaassen}
207