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