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