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