ZenModeSettings.java revision 7ad86dce1c1dd07bb06bd996b0daa6b059eeca21
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.settings.notification;
18
19import android.app.AlertDialog;
20import android.app.Dialog;
21import android.app.DialogFragment;
22import android.app.FragmentManager;
23import android.app.INotificationManager;
24import android.app.TimePickerDialog;
25import android.content.Context;
26import android.content.DialogInterface;
27import android.content.DialogInterface.OnDismissListener;
28import android.content.pm.PackageManager;
29import android.content.res.Resources;
30import android.database.ContentObserver;
31import android.graphics.Typeface;
32import android.net.Uri;
33import android.os.AsyncTask;
34import android.os.Bundle;
35import android.os.Handler;
36import android.os.ServiceManager;
37import android.preference.Preference;
38import android.preference.Preference.OnPreferenceChangeListener;
39import android.preference.Preference.OnPreferenceClickListener;
40import android.preference.PreferenceCategory;
41import android.preference.PreferenceScreen;
42import android.preference.SwitchPreference;
43import android.provider.Settings.Global;
44import android.service.notification.Condition;
45import android.service.notification.ZenModeConfig;
46import android.text.format.DateFormat;
47import android.util.Log;
48import android.util.SparseArray;
49import android.view.View;
50import android.view.ViewGroup;
51import android.widget.Switch;
52import android.widget.TextView;
53import android.widget.TimePicker;
54
55import com.android.settings.R;
56import com.android.settings.SettingsActivity;
57import com.android.settings.SettingsPreferenceFragment;
58import com.android.settings.Utils;
59import com.android.settings.search.BaseSearchIndexProvider;
60import com.android.settings.search.Indexable;
61import com.android.settings.search.SearchIndexableRaw;
62import com.android.settings.widget.SwitchBar;
63
64import java.util.ArrayList;
65import java.util.Calendar;
66import java.util.List;
67import java.util.Objects;
68
69public class ZenModeSettings extends SettingsPreferenceFragment implements Indexable,
70        SwitchBar.OnSwitchChangeListener {
71    private static final String TAG = "ZenModeSettings";
72    private static final boolean DEBUG = true;
73
74    private static final String KEY_GENERAL = "general";
75    private static final String KEY_CALLS = "phone_calls";
76    private static final String KEY_MESSAGES = "messages";
77    private static final String KEY_STARRED = "starred";
78
79    private static final String KEY_AUTOMATIC = "automatic";
80    private static final String KEY_WHEN = "when";
81    private static final String KEY_START_TIME = "start_time";
82    private static final String KEY_END_TIME = "end_time";
83
84    private static final String KEY_AUTOMATION = "automation";
85    private static final String KEY_ENTRY = "entry";
86    private static final String KEY_CONDITION_PROVIDERS = "manage_condition_providers";
87
88    private static SparseArray<String> allKeyTitles(Context context) {
89        final SparseArray<String> rt = new SparseArray<String>();
90        rt.put(R.string.zen_mode_general_category, KEY_GENERAL);
91        if (Utils.isVoiceCapable(context)) {
92            rt.put(R.string.zen_mode_phone_calls, KEY_CALLS);
93        }
94        rt.put(R.string.zen_mode_messages, KEY_MESSAGES);
95        rt.put(R.string.zen_mode_from_starred, KEY_STARRED);
96        rt.put(R.string.zen_mode_automatic_category, KEY_AUTOMATIC);
97        rt.put(R.string.zen_mode_when, KEY_WHEN);
98        rt.put(R.string.zen_mode_start_time, KEY_START_TIME);
99        rt.put(R.string.zen_mode_end_time, KEY_END_TIME);
100        rt.put(R.string.zen_mode_automation_category, KEY_AUTOMATION);
101        rt.put(R.string.manage_condition_providers, KEY_CONDITION_PROVIDERS);
102        return rt;
103    }
104
105    private final Handler mHandler = new Handler();
106    private final SettingsObserver mSettingsObserver = new SettingsObserver();
107
108    private SwitchBar mSwitchBar;
109    private Switch mSwitch;
110    private Context mContext;
111    private PackageManager mPM;
112    private ZenModeConfig mConfig;
113    private boolean mDisableListeners;
114    private SwitchPreference mCalls;
115    private SwitchPreference mMessages;
116    private DropDownPreference mStarred;
117    private DropDownPreference mWhen;
118    private TimePickerPreference mStart;
119    private TimePickerPreference mEnd;
120    private PreferenceCategory mAutomationCategory;
121    private Preference mEntry;
122    private Preference mConditionProviders;
123    private AlertDialog mDialog;
124    private boolean mIgnoreNext;
125
126    @Override
127    public void onSwitchChanged(Switch switchView, final boolean isChecked) {
128        if (DEBUG) Log.d(TAG, "onPreferenceChange isChecked=" + isChecked
129                + " mIgnoreNext=" + mIgnoreNext);
130        if (mIgnoreNext) {
131            mIgnoreNext = false;
132        }
133        AsyncTask.execute(new Runnable() {
134            @Override
135            public void run() {
136                final int v = isChecked ? Global.ZEN_MODE_ON : Global.ZEN_MODE_OFF;
137                putZenModeSetting(v);
138                final int n = ConditionProviderSettings.getEnabledProviderCount(mContext);
139                if (n > 0) {
140                    mHandler.post(isChecked ? mShowDialog : mHideDialog);
141                }
142            }
143        });
144    }
145
146    @Override
147    public void onActivityCreated(Bundle savedInstanceState) {
148        super.onActivityCreated(savedInstanceState);
149        mContext = getActivity();
150        mPM = mContext.getPackageManager();
151        final Resources res = mContext.getResources();
152        final int p = res.getDimensionPixelSize(R.dimen.content_margin_left);
153
154        addPreferencesFromResource(R.xml.zen_mode_settings);
155        final PreferenceScreen root = getPreferenceScreen();
156
157        mConfig = getZenModeConfig();
158        if (DEBUG) Log.d(TAG, "Loaded mConfig=" + mConfig);
159
160        mSwitchBar = ((SettingsActivity) mContext).getSwitchBar();
161        mSwitch = mSwitchBar.getSwitch();
162
163        final PreferenceCategory general = (PreferenceCategory) root.findPreference(KEY_GENERAL);
164
165        mCalls = (SwitchPreference) general.findPreference(KEY_CALLS);
166        if (Utils.isVoiceCapable(mContext)) {
167            mCalls.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
168                @Override
169                public boolean onPreferenceChange(Preference preference, Object newValue) {
170                    if (mDisableListeners) return true;
171                    final boolean val = (Boolean) newValue;
172                    if (val == mConfig.allowCalls) return true;
173                    if (DEBUG) Log.d(TAG, "onPrefChange allowCalls=" + val);
174                    final ZenModeConfig newConfig = mConfig.copy();
175                    newConfig.allowCalls = val;
176                    return setZenModeConfig(newConfig);
177                }
178            });
179        } else {
180            general.removePreference(mCalls);
181            mCalls = null;
182        }
183
184        mMessages = (SwitchPreference) general.findPreference(KEY_MESSAGES);
185        mMessages.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
186            @Override
187            public boolean onPreferenceChange(Preference preference, Object newValue) {
188                if (mDisableListeners) return true;
189                final boolean val = (Boolean) newValue;
190                if (val == mConfig.allowMessages) return true;
191                if (DEBUG) Log.d(TAG, "onPrefChange allowMessages=" + val);
192                final ZenModeConfig newConfig = mConfig.copy();
193                newConfig.allowMessages = val;
194                return setZenModeConfig(newConfig);
195            }
196        });
197
198        mStarred = new DropDownPreference(mContext);
199        mStarred.setKey(KEY_STARRED);
200        mStarred.setTitle(R.string.zen_mode_from);
201        mStarred.setDropDownWidth(R.dimen.zen_mode_dropdown_width);
202        mStarred.addItem(R.string.zen_mode_from_anyone, ZenModeConfig.SOURCE_ANYONE);
203        mStarred.addItem(R.string.zen_mode_from_starred, ZenModeConfig.SOURCE_STAR);
204        mStarred.addItem(R.string.zen_mode_from_contacts, ZenModeConfig.SOURCE_CONTACT);
205        mStarred.setCallback(new DropDownPreference.Callback() {
206            @Override
207            public boolean onItemSelected(int pos, Object newValue) {
208                if (mDisableListeners) return true;
209                final int val = (Integer) newValue;
210                if (val == mConfig.allowFrom) return true;
211                if (DEBUG) Log.d(TAG, "onPrefChange allowFrom=" +
212                        ZenModeConfig.sourceToString(val));
213                final ZenModeConfig newConfig = mConfig.copy();
214                newConfig.allowFrom = val;
215                return setZenModeConfig(newConfig);
216            }
217        });
218        general.addPreference(mStarred);
219
220        final Preference alarmInfo = new Preference(mContext) {
221            @Override
222            public View getView(View convertView, ViewGroup parent) {
223                final TextView tv = new TextView(mContext);
224                tv.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.ITALIC));
225                tv.setPadding(p, p, p, p);
226                tv.setText(R.string.zen_mode_alarm_info);
227                return tv;
228            }
229        };
230        alarmInfo.setPersistent(false);
231        alarmInfo.setSelectable(false);
232        general.addPreference(alarmInfo);
233
234        final PreferenceCategory auto = (PreferenceCategory) root.findPreference(KEY_AUTOMATIC);
235
236        mWhen = new DropDownPreference(mContext);
237        mWhen.setKey(KEY_WHEN);
238        mWhen.setTitle(R.string.zen_mode_when);
239        mWhen.setDropDownWidth(R.dimen.zen_mode_dropdown_width);
240        mWhen.addItem(R.string.zen_mode_when_every_night, ZenModeConfig.SLEEP_MODE_NIGHTS);
241        mWhen.addItem(R.string.zen_mode_when_weeknights, ZenModeConfig.SLEEP_MODE_WEEKNIGHTS);
242        mWhen.addItem(R.string.zen_mode_when_never, null);
243        mWhen.setCallback(new DropDownPreference.Callback() {
244            @Override
245            public boolean onItemSelected(int pos, Object value) {
246                if (mDisableListeners) return true;
247                final String mode = (String) value;
248                if (Objects.equals(mode, mConfig.sleepMode)) return true;
249                if (DEBUG) Log.d(TAG, "onPrefChange sleepMode=" + mode);
250                final ZenModeConfig newConfig = mConfig.copy();
251                newConfig.sleepMode = mode;
252                return setZenModeConfig(newConfig);
253            }
254        });
255        auto.addPreference(mWhen);
256
257        final FragmentManager mgr = getFragmentManager();
258
259        mStart = new TimePickerPreference(mContext, mgr);
260        mStart.setKey(KEY_START_TIME);
261        mStart.setTitle(R.string.zen_mode_start_time);
262        mStart.setCallback(new TimePickerPreference.Callback() {
263            @Override
264            public boolean onSetTime(int hour, int minute) {
265                if (mDisableListeners) return true;
266                if (!ZenModeConfig.isValidHour(hour)) return false;
267                if (!ZenModeConfig.isValidMinute(minute)) return false;
268                if (hour == mConfig.sleepStartHour && minute == mConfig.sleepStartMinute) {
269                    return true;
270                }
271                if (DEBUG) Log.d(TAG, "onPrefChange sleepStart h=" + hour + " m=" + minute);
272                final ZenModeConfig newConfig = mConfig.copy();
273                newConfig.sleepStartHour = hour;
274                newConfig.sleepStartMinute = minute;
275                return setZenModeConfig(newConfig);
276            }
277        });
278        auto.addPreference(mStart);
279        mStart.setDependency(mWhen.getKey());
280
281        mEnd = new TimePickerPreference(mContext, mgr);
282        mEnd.setKey(KEY_END_TIME);
283        mEnd.setTitle(R.string.zen_mode_end_time);
284        mEnd.setSummaryFormat(R.string.zen_mode_end_time_summary_format);
285        mEnd.setCallback(new TimePickerPreference.Callback() {
286            @Override
287            public boolean onSetTime(int hour, int minute) {
288                if (mDisableListeners) return true;
289                if (!ZenModeConfig.isValidHour(hour)) return false;
290                if (!ZenModeConfig.isValidMinute(minute)) return false;
291                if (hour == mConfig.sleepEndHour && minute == mConfig.sleepEndMinute) {
292                    return true;
293                }
294                if (DEBUG) Log.d(TAG, "onPrefChange sleepEnd h=" + hour + " m=" + minute);
295                final ZenModeConfig newConfig = mConfig.copy();
296                newConfig.sleepEndHour = hour;
297                newConfig.sleepEndMinute = minute;
298                return setZenModeConfig(newConfig);
299            }
300        });
301        auto.addPreference(mEnd);
302        mEnd.setDependency(mWhen.getKey());
303
304        mAutomationCategory = (PreferenceCategory) findPreference(KEY_AUTOMATION);
305        mEntry = findPreference(KEY_ENTRY);
306        mEntry.setOnPreferenceClickListener(new OnPreferenceClickListener() {
307            @Override
308            public boolean onPreferenceClick(Preference preference) {
309                new AlertDialog.Builder(mContext)
310                    .setTitle(R.string.zen_mode_entry_conditions_title)
311                    .setView(new ZenModeAutomaticConditionSelection(mContext))
312                    .setOnDismissListener(new OnDismissListener() {
313                        @Override
314                        public void onDismiss(DialogInterface dialog) {
315                            refreshAutomationSection();
316                        }
317                    })
318                    .setPositiveButton(R.string.dlg_ok, null)
319                    .show();
320                return true;
321            }
322        });
323        mConditionProviders = findPreference(KEY_CONDITION_PROVIDERS);
324
325        updateZenMode();
326        updateControls();
327    }
328
329    private void updateControls() {
330        mDisableListeners = true;
331        if (mCalls != null) {
332            mCalls.setChecked(mConfig.allowCalls);
333        }
334        mMessages.setChecked(mConfig.allowMessages);
335        mStarred.setSelectedValue(mConfig.allowFrom);
336        mWhen.setSelectedValue(mConfig.sleepMode);
337        mStart.setTime(mConfig.sleepStartHour, mConfig.sleepStartMinute);
338        mEnd.setTime(mConfig.sleepEndHour, mConfig.sleepEndMinute);
339        mDisableListeners = false;
340        refreshAutomationSection();
341    }
342
343    private void refreshAutomationSection() {
344        if (mConditionProviders != null) {
345            final int total = ConditionProviderSettings.getProviderCount(mPM);
346            if (total == 0) {
347                getPreferenceScreen().removePreference(mAutomationCategory);
348            } else {
349                final int n = ConditionProviderSettings.getEnabledProviderCount(mContext);
350                if (n == 0) {
351                    mConditionProviders.setSummary(getResources().getString(
352                            R.string.manage_condition_providers_summary_zero));
353                } else {
354                    mConditionProviders.setSummary(String.format(getResources().getQuantityString(
355                            R.plurals.manage_condition_providers_summary_nonzero,
356                            n, n)));
357                }
358                final String entrySummary = getEntryConditionSummary();
359                if (n == 0 || entrySummary == null) {
360                    mEntry.setSummary(R.string.zen_mode_entry_conditions_summary_none);
361                } else {
362                    mEntry.setSummary(entrySummary);
363                }
364            }
365        }
366    }
367
368    private String getEntryConditionSummary() {
369        final INotificationManager nm = INotificationManager.Stub.asInterface(
370                ServiceManager.getService(Context.NOTIFICATION_SERVICE));
371        try {
372            final Condition[] automatic = nm.getAutomaticZenModeConditions();
373            if (automatic == null || automatic.length == 0) {
374                return null;
375            }
376            final String divider = getString(R.string.summary_divider_text);
377            final StringBuilder sb = new StringBuilder();
378            for (int i = 0; i < automatic.length; i++) {
379                if (i > 0) sb.append(divider);
380                sb.append(automatic[i].summary);
381            }
382            return sb.toString();
383        } catch (Exception e) {
384            Log.w(TAG, "Error calling getAutomaticZenModeConditions", e);
385            return null;
386        }
387    }
388
389    @Override
390    public void onResume() {
391        super.onResume();
392        updateControls();
393        updateZenMode();
394        mSettingsObserver.register();
395        mSwitchBar.addOnSwitchChangeListener(this);
396        mSwitchBar.show();
397    }
398
399    @Override
400    public void onPause() {
401        super.onPause();
402        mSettingsObserver.unregister();
403        mSwitchBar.removeOnSwitchChangeListener(this);
404        mSwitchBar.hide();
405    }
406
407    private void updateZenMode() {
408        final boolean zenMode = Global.getInt(getContentResolver(),
409                Global.ZEN_MODE, Global.ZEN_MODE_OFF) != Global.ZEN_MODE_OFF;
410        if (mSwitch.isChecked() != zenMode) {
411            mSwitch.setChecked(zenMode);
412            mIgnoreNext = true;
413        }
414    }
415
416    private void updateZenModeConfig() {
417        final ZenModeConfig config = getZenModeConfig();
418        if (Objects.equals(config, mConfig)) return;
419        mConfig = config;
420        if (DEBUG) Log.d(TAG, "updateZenModeConfig mConfig=" + mConfig);
421        updateControls();
422    }
423
424    private ZenModeConfig getZenModeConfig() {
425        final INotificationManager nm = INotificationManager.Stub.asInterface(
426                ServiceManager.getService(Context.NOTIFICATION_SERVICE));
427        try {
428            return nm.getZenModeConfig();
429        } catch (Exception e) {
430           Log.w(TAG, "Error calling NoMan", e);
431           return new ZenModeConfig();
432        }
433    }
434
435    private boolean setZenModeConfig(ZenModeConfig config) {
436        final INotificationManager nm = INotificationManager.Stub.asInterface(
437                ServiceManager.getService(Context.NOTIFICATION_SERVICE));
438        try {
439            final boolean success = nm.setZenModeConfig(config);
440            if (success) {
441                mConfig = config;
442                if (DEBUG) Log.d(TAG, "Saved mConfig=" + mConfig);
443            }
444            return success;
445        } catch (Exception e) {
446           Log.w(TAG, "Error calling NoMan", e);
447           return false;
448        }
449    }
450
451    protected void putZenModeSetting(int value) {
452        Global.putInt(getContentResolver(), Global.ZEN_MODE, value);
453    }
454
455    protected ZenModeConditionSelection newConditionSelection() {
456        return new ZenModeConditionSelection(mContext);
457    }
458
459    private final Runnable mHideDialog = new Runnable() {
460        @Override
461        public void run() {
462            if (mDialog != null) {
463                mDialog.dismiss();
464                mDialog = null;
465            }
466        }
467    };
468
469    private final Runnable mShowDialog = new Runnable() {
470        @Override
471        public void run() {
472            mDialog = new AlertDialog.Builder(mContext)
473                    .setTitle(R.string.zen_mode_settings_title)
474                    .setView(newConditionSelection())
475                    .setNegativeButton(R.string.dlg_cancel, new DialogInterface.OnClickListener() {
476                        @Override
477                        public void onClick(DialogInterface dialog, int which) {
478                            putZenModeSetting(Global.ZEN_MODE_OFF);
479                        }
480                    })
481                    .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() {
482                        @Override
483                        public void onClick(DialogInterface dialog, int which) {
484                            // noop
485                        }
486                    })
487                    .show();
488        }
489    };
490
491    // Enable indexing of searchable data
492    public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
493        new BaseSearchIndexProvider() {
494            @Override
495            public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
496                final SparseArray<String> keyTitles = allKeyTitles(context);
497                final int N = keyTitles.size();
498                final List<SearchIndexableRaw> result = new ArrayList<SearchIndexableRaw>(N);
499                final Resources res = context.getResources();
500                for (int i = 0; i < N; i++) {
501                    final SearchIndexableRaw data = new SearchIndexableRaw(context);
502                    data.key = keyTitles.valueAt(i);
503                    data.title = res.getString(keyTitles.keyAt(i));
504                    data.screenTitle = res.getString(R.string.zen_mode_settings_title);
505                    result.add(data);
506                }
507                return result;
508            }
509
510            public List<String> getNonIndexableKeys(Context context) {
511                final ArrayList<String> rt = new ArrayList<String>();
512                if (!Utils.isVoiceCapable(context)) {
513                    rt.add(KEY_CALLS);
514                }
515                return rt;
516            }
517        };
518
519    private final class SettingsObserver extends ContentObserver {
520        private final Uri ZEN_MODE_URI = Global.getUriFor(Global.ZEN_MODE);
521        private final Uri ZEN_MODE_CONFIG_ETAG_URI = Global.getUriFor(Global.ZEN_MODE_CONFIG_ETAG);
522
523        public SettingsObserver() {
524            super(mHandler);
525        }
526
527        public void register() {
528            getContentResolver().registerContentObserver(ZEN_MODE_URI, false, this);
529            getContentResolver().registerContentObserver(ZEN_MODE_CONFIG_ETAG_URI, false, this);
530        }
531
532        public void unregister() {
533            getContentResolver().unregisterContentObserver(this);
534        }
535
536        @Override
537        public void onChange(boolean selfChange, Uri uri) {
538            super.onChange(selfChange, uri);
539            if (ZEN_MODE_URI.equals(uri)) {
540                updateZenMode();
541            }
542            if (ZEN_MODE_CONFIG_ETAG_URI.equals(uri)) {
543                updateZenModeConfig();
544            }
545        }
546    }
547
548    private static class TimePickerPreference extends Preference {
549        private final Context mContext;
550
551        private int mSummaryFormat;
552        private int mHourOfDay;
553        private int mMinute;
554        private Callback mCallback;
555
556        public TimePickerPreference(Context context, final FragmentManager mgr) {
557            super(context);
558            mContext = context;
559            setPersistent(false);
560            setOnPreferenceClickListener(new OnPreferenceClickListener(){
561                @Override
562                public boolean onPreferenceClick(Preference preference) {
563                    final TimePickerFragment frag = new TimePickerFragment();
564                    frag.pref = TimePickerPreference.this;
565                    frag.show(mgr, TimePickerPreference.class.getName());
566                    return true;
567                }
568            });
569        }
570
571        public void setCallback(Callback callback) {
572            mCallback = callback;
573        }
574
575        public void setSummaryFormat(int resId) {
576            mSummaryFormat = resId;
577            updateSummary();
578        }
579
580        public void setTime(int hourOfDay, int minute) {
581            if (mCallback != null && !mCallback.onSetTime(hourOfDay, minute)) return;
582            mHourOfDay = hourOfDay;
583            mMinute = minute;
584            updateSummary();
585        }
586
587        private void updateSummary() {
588            final Calendar c = Calendar.getInstance();
589            c.set(Calendar.HOUR_OF_DAY, mHourOfDay);
590            c.set(Calendar.MINUTE, mMinute);
591            String time = DateFormat.getTimeFormat(mContext).format(c.getTime());
592            if (mSummaryFormat != 0) {
593                time = mContext.getResources().getString(mSummaryFormat, time);
594            }
595            setSummary(time);
596        }
597
598        public static class TimePickerFragment extends DialogFragment implements
599                TimePickerDialog.OnTimeSetListener {
600            public TimePickerPreference pref;
601
602            @Override
603            public Dialog onCreateDialog(Bundle savedInstanceState) {
604                final boolean usePref = pref != null && pref.mHourOfDay >= 0 && pref.mMinute >= 0;
605                final Calendar c = Calendar.getInstance();
606                final int hour = usePref ? pref.mHourOfDay : c.get(Calendar.HOUR_OF_DAY);
607                final int minute = usePref ? pref.mMinute : c.get(Calendar.MINUTE);
608                return new TimePickerDialog(getActivity(), this, hour, minute,
609                        DateFormat.is24HourFormat(getActivity()));
610            }
611
612            public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
613                if (pref != null) {
614                    pref.setTime(hourOfDay, minute);
615                }
616            }
617        }
618
619        public interface Callback {
620            boolean onSetTime(int hour, int minute);
621        }
622    }
623}
624