2 * Copyright (C) 2014 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.settings.notification;
19 import static com.android.settings.notification.ZenModeDowntimeDaysSelection.DAYS;
21 import android.app.AlertDialog;
22 import android.app.Dialog;
23 import android.app.DialogFragment;
24 import android.app.FragmentManager;
25 import android.app.INotificationManager;
26 import android.app.TimePickerDialog;
27 import android.content.Context;
28 import android.content.DialogInterface;
29 import android.content.DialogInterface.OnDismissListener;
30 import android.content.pm.PackageManager;
31 import android.content.res.Resources;
32 import android.database.ContentObserver;
33 import android.net.Uri;
34 import android.os.Bundle;
35 import android.os.Handler;
36 import android.os.ServiceManager;
37 import android.preference.Preference;
38 import android.preference.Preference.OnPreferenceChangeListener;
39 import android.preference.Preference.OnPreferenceClickListener;
40 import android.preference.PreferenceCategory;
41 import android.preference.PreferenceScreen;
42 import android.preference.SwitchPreference;
43 import android.provider.Settings.Global;
44 import android.service.notification.Condition;
45 import android.service.notification.ZenModeConfig;
46 import android.text.format.DateFormat;
47 import android.util.Log;
48 import android.util.SparseArray;
49 import android.widget.ScrollView;
50 import android.widget.TimePicker;
52 import com.android.settings.R;
53 import com.android.settings.SettingsPreferenceFragment;
54 import com.android.settings.Utils;
55 import com.android.settings.notification.DropDownPreference.Callback;
56 import com.android.settings.search.BaseSearchIndexProvider;
57 import com.android.settings.search.Indexable;
58 import com.android.settings.search.SearchIndexableRaw;
60 import java.text.SimpleDateFormat;
61 import java.util.ArrayList;
62 import java.util.Calendar;
63 import java.util.List;
64 import java.util.Objects;
66 public class ZenModeSettings extends SettingsPreferenceFragment implements Indexable {
67 private static final String TAG = "ZenModeSettings";
68 private static final boolean DEBUG = true;
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 private static final String KEY_ALARM_INFO = "alarm_info";
77 private static final String KEY_DOWNTIME = "downtime";
78 private static final String KEY_DAYS = "days";
79 private static final String KEY_START_TIME = "start_time";
80 private static final String KEY_END_TIME = "end_time";
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";
86 private static final SettingPrefWithCallback PREF_ZEN_MODE = new SettingPrefWithCallback(
87 SettingPref.TYPE_GLOBAL, KEY_ZEN_MODE, Global.ZEN_MODE, Global.ZEN_MODE_OFF,
88 Global.ZEN_MODE_OFF, Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
89 Global.ZEN_MODE_NO_INTERRUPTIONS) {
90 protected String getCaption(Resources res, int value) {
92 case Global.ZEN_MODE_NO_INTERRUPTIONS:
93 return res.getString(R.string.zen_mode_option_no_interruptions);
94 case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
95 return res.getString(R.string.zen_mode_option_important_interruptions);
97 return res.getString(R.string.zen_mode_option_off);
102 private static final SimpleDateFormat DAY_FORMAT = new SimpleDateFormat("EEE");
104 private static SparseArray<String> allKeyTitles(Context context) {
105 final SparseArray<String> rt = new SparseArray<String>();
106 rt.put(R.string.zen_mode_important_category, KEY_IMPORTANT);
107 if (Utils.isVoiceCapable(context)) {
108 rt.put(R.string.zen_mode_phone_calls, KEY_CALLS);
109 rt.put(R.string.zen_mode_option_title, KEY_ZEN_MODE);
111 rt.put(R.string.zen_mode_option_title_novoice, KEY_ZEN_MODE);
113 rt.put(R.string.zen_mode_messages, KEY_MESSAGES);
114 rt.put(R.string.zen_mode_from_starred, KEY_STARRED);
115 rt.put(R.string.zen_mode_alarm_info, KEY_ALARM_INFO);
116 rt.put(R.string.zen_mode_downtime_category, KEY_DOWNTIME);
117 rt.put(R.string.zen_mode_downtime_days, KEY_DAYS);
118 rt.put(R.string.zen_mode_start_time, KEY_START_TIME);
119 rt.put(R.string.zen_mode_end_time, KEY_END_TIME);
120 rt.put(R.string.zen_mode_automation_category, KEY_AUTOMATION);
121 rt.put(R.string.manage_condition_providers, KEY_CONDITION_PROVIDERS);
125 private final Handler mHandler = new Handler();
126 private final SettingsObserver mSettingsObserver = new SettingsObserver();
128 private Context mContext;
129 private PackageManager mPM;
130 private ZenModeConfig mConfig;
131 private boolean mDisableListeners;
132 private SwitchPreference mCalls;
133 private SwitchPreference mMessages;
134 private DropDownPreference mStarred;
135 private Preference mDays;
136 private TimePickerPreference mStart;
137 private TimePickerPreference mEnd;
138 private PreferenceCategory mAutomationCategory;
139 private Preference mEntry;
140 private Preference mConditionProviders;
141 private AlertDialog mDialog;
144 public void onCreate(Bundle savedInstanceState) {
145 super.onCreate(savedInstanceState);
146 mContext = getActivity();
147 mPM = mContext.getPackageManager();
149 addPreferencesFromResource(R.xml.zen_mode_settings);
150 final PreferenceScreen root = getPreferenceScreen();
152 mConfig = getZenModeConfig();
153 if (DEBUG) Log.d(TAG, "Loaded mConfig=" + mConfig);
155 final Preference zenMode = PREF_ZEN_MODE.init(this);
156 PREF_ZEN_MODE.setCallback(new SettingPrefWithCallback.Callback() {
158 public void onSettingSelected(int value) {
159 if (value != Global.ZEN_MODE_OFF) {
160 showConditionSelection(value);
164 if (!Utils.isVoiceCapable(mContext)) {
165 zenMode.setTitle(R.string.zen_mode_option_title_novoice);
168 final PreferenceCategory important =
169 (PreferenceCategory) root.findPreference(KEY_IMPORTANT);
171 mCalls = (SwitchPreference) important.findPreference(KEY_CALLS);
172 if (Utils.isVoiceCapable(mContext)) {
173 mCalls.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
175 public boolean onPreferenceChange(Preference preference, Object newValue) {
176 if (mDisableListeners) return true;
177 final boolean val = (Boolean) newValue;
178 if (val == mConfig.allowCalls) return true;
179 if (DEBUG) Log.d(TAG, "onPrefChange allowCalls=" + val);
180 final ZenModeConfig newConfig = mConfig.copy();
181 newConfig.allowCalls = val;
182 return setZenModeConfig(newConfig);
186 important.removePreference(mCalls);
190 mMessages = (SwitchPreference) important.findPreference(KEY_MESSAGES);
191 mMessages.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
193 public boolean onPreferenceChange(Preference preference, Object newValue) {
194 if (mDisableListeners) return true;
195 final boolean val = (Boolean) newValue;
196 if (val == mConfig.allowMessages) return true;
197 if (DEBUG) Log.d(TAG, "onPrefChange allowMessages=" + val);
198 final ZenModeConfig newConfig = mConfig.copy();
199 newConfig.allowMessages = val;
200 return setZenModeConfig(newConfig);
204 mStarred = (DropDownPreference) important.findPreference(KEY_STARRED);
205 mStarred.setDropDownWidth(R.dimen.zen_mode_dropdown_width);
206 mStarred.addItem(R.string.zen_mode_from_anyone, ZenModeConfig.SOURCE_ANYONE);
207 mStarred.addItem(R.string.zen_mode_from_starred, ZenModeConfig.SOURCE_STAR);
208 mStarred.addItem(R.string.zen_mode_from_contacts, ZenModeConfig.SOURCE_CONTACT);
209 mStarred.setCallback(new DropDownPreference.Callback() {
211 public boolean onItemSelected(int pos, Object newValue) {
212 if (mDisableListeners) return true;
213 final int val = (Integer) newValue;
214 if (val == mConfig.allowFrom) return true;
215 if (DEBUG) Log.d(TAG, "onPrefChange allowFrom=" +
216 ZenModeConfig.sourceToString(val));
217 final ZenModeConfig newConfig = mConfig.copy();
218 newConfig.allowFrom = val;
219 return setZenModeConfig(newConfig);
222 important.addPreference(mStarred);
224 final PreferenceCategory downtime = (PreferenceCategory) root.findPreference(KEY_DOWNTIME);
226 mDays = downtime.findPreference(KEY_DAYS);
227 mDays.setOnPreferenceClickListener(new OnPreferenceClickListener() {
229 public boolean onPreferenceClick(Preference preference) {
230 new AlertDialog.Builder(mContext)
231 .setTitle(R.string.zen_mode_downtime_days)
232 .setView(new ZenModeDowntimeDaysSelection(mContext, mConfig.sleepMode) {
234 protected void onChanged(String mode) {
235 if (mDisableListeners) return;
236 if (Objects.equals(mode, mConfig.sleepMode)) return;
237 if (DEBUG) Log.d(TAG, "days.onChanged sleepMode=" + mode);
238 final ZenModeConfig newConfig = mConfig.copy();
239 newConfig.sleepMode = mode;
240 setZenModeConfig(newConfig);
243 .setOnDismissListener(new OnDismissListener() {
245 public void onDismiss(DialogInterface dialog) {
249 .setPositiveButton(R.string.done_button, null)
255 final FragmentManager mgr = getFragmentManager();
257 mStart = new TimePickerPreference(mContext, mgr);
258 mStart.setKey(KEY_START_TIME);
259 mStart.setTitle(R.string.zen_mode_start_time);
260 mStart.setCallback(new TimePickerPreference.Callback() {
262 public boolean onSetTime(int hour, int minute) {
263 if (mDisableListeners) return true;
264 if (!ZenModeConfig.isValidHour(hour)) return false;
265 if (!ZenModeConfig.isValidMinute(minute)) return false;
266 if (hour == mConfig.sleepStartHour && minute == mConfig.sleepStartMinute) {
269 if (DEBUG) Log.d(TAG, "onPrefChange sleepStart h=" + hour + " m=" + minute);
270 final ZenModeConfig newConfig = mConfig.copy();
271 newConfig.sleepStartHour = hour;
272 newConfig.sleepStartMinute = minute;
273 return setZenModeConfig(newConfig);
276 downtime.addPreference(mStart);
277 mStart.setDependency(mDays.getKey());
279 mEnd = new TimePickerPreference(mContext, mgr);
280 mEnd.setKey(KEY_END_TIME);
281 mEnd.setTitle(R.string.zen_mode_end_time);
282 mEnd.setCallback(new TimePickerPreference.Callback() {
284 public boolean onSetTime(int hour, int minute) {
285 if (mDisableListeners) return true;
286 if (!ZenModeConfig.isValidHour(hour)) return false;
287 if (!ZenModeConfig.isValidMinute(minute)) return false;
288 if (hour == mConfig.sleepEndHour && minute == mConfig.sleepEndMinute) {
291 if (DEBUG) Log.d(TAG, "onPrefChange sleepEnd h=" + hour + " m=" + minute);
292 final ZenModeConfig newConfig = mConfig.copy();
293 newConfig.sleepEndHour = hour;
294 newConfig.sleepEndMinute = minute;
295 return setZenModeConfig(newConfig);
298 downtime.addPreference(mEnd);
299 mEnd.setDependency(mDays.getKey());
301 mAutomationCategory = (PreferenceCategory) findPreference(KEY_AUTOMATION);
302 mEntry = findPreference(KEY_ENTRY);
303 mEntry.setOnPreferenceClickListener(new OnPreferenceClickListener() {
305 public boolean onPreferenceClick(Preference preference) {
306 new AlertDialog.Builder(mContext)
307 .setTitle(R.string.zen_mode_entry_conditions_title)
308 .setView(new ZenModeAutomaticConditionSelection(mContext))
309 .setOnDismissListener(new OnDismissListener() {
311 public void onDismiss(DialogInterface dialog) {
312 refreshAutomationSection();
315 .setPositiveButton(R.string.dlg_ok, null)
320 mConditionProviders = findPreference(KEY_CONDITION_PROVIDERS);
325 private void updateDays() {
326 if (mConfig != null) {
327 final int[] days = ZenModeConfig.tryParseDays(mConfig.sleepMode);
328 if (days != null && days.length != 0) {
329 final StringBuilder sb = new StringBuilder();
330 final Calendar c = Calendar.getInstance();
331 for (int i = 0; i < DAYS.length; i++) {
332 final int day = DAYS[i];
333 for (int j = 0; j < days.length; j++) {
334 if (day == days[j]) {
335 c.set(Calendar.DAY_OF_WEEK, day);
336 if (sb.length() > 0) {
337 sb.append(mContext.getString(R.string.summary_divider_text));
339 sb.append(DAY_FORMAT.format(c.getTime()));
344 if (sb.length() > 0) {
345 mDays.setSummary(sb);
346 mDays.notifyDependencyChange(false);
351 mDays.setSummary(R.string.zen_mode_downtime_days_none);
352 mDays.notifyDependencyChange(true);
355 private void updateEndSummary() {
356 final int startMin = 60 * mConfig.sleepStartHour + mConfig.sleepStartMinute;
357 final int endMin = 60 * mConfig.sleepEndHour + mConfig.sleepEndMinute;
358 final boolean nextDay = startMin >= endMin;
359 mEnd.setSummaryFormat(nextDay ? R.string.zen_mode_end_time_summary_format : 0);
362 private void updateControls() {
363 mDisableListeners = true;
364 if (mCalls != null) {
365 mCalls.setChecked(mConfig.allowCalls);
367 mMessages.setChecked(mConfig.allowMessages);
368 mStarred.setSelectedValue(mConfig.allowFrom);
369 updateStarredEnabled();
371 mStart.setTime(mConfig.sleepStartHour, mConfig.sleepStartMinute);
372 mEnd.setTime(mConfig.sleepEndHour, mConfig.sleepEndMinute);
373 mDisableListeners = false;
374 refreshAutomationSection();
378 private void updateStarredEnabled() {
379 mStarred.setEnabled(mConfig.allowCalls || mConfig.allowMessages);
382 private void refreshAutomationSection() {
383 if (mConditionProviders != null) {
384 final int total = ConditionProviderSettings.getProviderCount(mPM);
386 getPreferenceScreen().removePreference(mAutomationCategory);
388 final int n = ConditionProviderSettings.getEnabledProviderCount(mContext);
390 mConditionProviders.setSummary(getResources().getString(
391 R.string.manage_condition_providers_summary_zero));
393 mConditionProviders.setSummary(String.format(getResources().getQuantityString(
394 R.plurals.manage_condition_providers_summary_nonzero,
397 final String entrySummary = getEntryConditionSummary();
398 if (n == 0 || entrySummary == null) {
399 mEntry.setSummary(R.string.zen_mode_entry_conditions_summary_none);
401 mEntry.setSummary(entrySummary);
407 private String getEntryConditionSummary() {
408 final INotificationManager nm = INotificationManager.Stub.asInterface(
409 ServiceManager.getService(Context.NOTIFICATION_SERVICE));
411 final Condition[] automatic = nm.getAutomaticZenModeConditions();
412 if (automatic == null || automatic.length == 0) {
415 final String divider = getString(R.string.summary_divider_text);
416 final StringBuilder sb = new StringBuilder();
417 for (int i = 0; i < automatic.length; i++) {
418 if (i > 0) sb.append(divider);
419 sb.append(automatic[i].summary);
421 return sb.toString();
422 } catch (Exception e) {
423 Log.w(TAG, "Error calling getAutomaticZenModeConditions", e);
429 public void onResume() {
432 mSettingsObserver.register();
436 public void onPause() {
438 mSettingsObserver.unregister();
441 private void updateZenModeConfig() {
442 final ZenModeConfig config = getZenModeConfig();
443 if (Objects.equals(config, mConfig)) return;
445 if (DEBUG) Log.d(TAG, "updateZenModeConfig mConfig=" + mConfig);
449 private ZenModeConfig getZenModeConfig() {
450 final INotificationManager nm = INotificationManager.Stub.asInterface(
451 ServiceManager.getService(Context.NOTIFICATION_SERVICE));
453 return nm.getZenModeConfig();
454 } catch (Exception e) {
455 Log.w(TAG, "Error calling NoMan", e);
456 return new ZenModeConfig();
460 private boolean setZenModeConfig(ZenModeConfig config) {
461 final INotificationManager nm = INotificationManager.Stub.asInterface(
462 ServiceManager.getService(Context.NOTIFICATION_SERVICE));
464 final boolean success = nm.setZenModeConfig(config);
467 if (DEBUG) Log.d(TAG, "Saved mConfig=" + mConfig);
469 updateStarredEnabled();
472 } catch (Exception e) {
473 Log.w(TAG, "Error calling NoMan", e);
478 protected void putZenModeSetting(int value) {
479 Global.putInt(getContentResolver(), Global.ZEN_MODE, value);
482 protected void showConditionSelection(final int newSettingsValue) {
483 if (mDialog != null) return;
485 final ZenModeConditionSelection zenModeConditionSelection =
486 new ZenModeConditionSelection(mContext);
487 DialogInterface.OnClickListener positiveListener = new DialogInterface.OnClickListener() {
489 public void onClick(DialogInterface dialog, int which) {
490 zenModeConditionSelection.confirmCondition();
494 final int oldSettingsValue = PREF_ZEN_MODE.getValue(mContext);
495 ScrollView scrollView = new ScrollView(mContext);
496 scrollView.addView(zenModeConditionSelection);
497 mDialog = new AlertDialog.Builder(getActivity())
498 .setTitle(PREF_ZEN_MODE.getCaption(getResources(), newSettingsValue))
500 .setPositiveButton(R.string.okay, positiveListener)
501 .setNegativeButton(R.string.cancel_all_caps, new DialogInterface.OnClickListener() {
503 public void onClick(DialogInterface dialog, int which) {
504 cancelDialog(oldSettingsValue);
507 .setOnCancelListener(new DialogInterface.OnCancelListener() {
509 public void onCancel(DialogInterface dialog) {
510 cancelDialog(oldSettingsValue);
516 protected void cancelDialog(int oldSettingsValue) {
517 // If not making a decision, reset drop down to current setting.
518 PREF_ZEN_MODE.setValueWithoutCallback(mContext, oldSettingsValue);
522 // Enable indexing of searchable data
523 public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
524 new BaseSearchIndexProvider() {
526 public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
527 final SparseArray<String> keyTitles = allKeyTitles(context);
528 final int N = keyTitles.size();
529 final List<SearchIndexableRaw> result = new ArrayList<SearchIndexableRaw>(N);
530 final Resources res = context.getResources();
531 for (int i = 0; i < N; i++) {
532 final SearchIndexableRaw data = new SearchIndexableRaw(context);
533 data.key = keyTitles.valueAt(i);
534 data.title = res.getString(keyTitles.keyAt(i));
535 data.screenTitle = res.getString(R.string.zen_mode_settings_title);
541 public List<String> getNonIndexableKeys(Context context) {
542 final ArrayList<String> rt = new ArrayList<String>();
543 if (!Utils.isVoiceCapable(context)) {
550 private static class SettingPrefWithCallback extends SettingPref {
552 private Callback mCallback;
555 public SettingPrefWithCallback(int type, String key, String setting, int def,
557 super(type, key, setting, def, values);
560 public void setCallback(Callback callback) {
561 mCallback = callback;
565 public void update(Context context) {
566 // Avoid callbacks from non-user changes.
567 mValue = getValue(context);
568 super.update(context);
572 protected boolean setSetting(Context context, int value) {
573 if (value == mValue) return true;
575 if (mCallback != null) {
576 mCallback.onSettingSelected(value);
578 return super.setSetting(context, value);
582 public Preference init(SettingsPreferenceFragment settings) {
583 Preference ret = super.init(settings);
584 mValue = getValue(settings.getActivity());
589 public boolean setValueWithoutCallback(Context context, int value) {
590 // Set the current value ahead of time, this way we won't trigger a callback.
592 return putInt(mType, context.getContentResolver(), mSetting, value);
595 public int getValue(Context context) {
596 return getInt(mType, context.getContentResolver(), mSetting, mDefault);
599 public interface Callback {
600 void onSettingSelected(int value);
604 private final class SettingsObserver extends ContentObserver {
605 private final Uri ZEN_MODE_URI = Global.getUriFor(Global.ZEN_MODE);
606 private final Uri ZEN_MODE_CONFIG_ETAG_URI = Global.getUriFor(Global.ZEN_MODE_CONFIG_ETAG);
608 public SettingsObserver() {
612 public void register() {
613 getContentResolver().registerContentObserver(ZEN_MODE_URI, false, this);
614 getContentResolver().registerContentObserver(ZEN_MODE_CONFIG_ETAG_URI, false, this);
617 public void unregister() {
618 getContentResolver().unregisterContentObserver(this);
622 public void onChange(boolean selfChange, Uri uri) {
623 super.onChange(selfChange, uri);
624 if (ZEN_MODE_URI.equals(uri)) {
625 PREF_ZEN_MODE.update(mContext);
627 if (ZEN_MODE_CONFIG_ETAG_URI.equals(uri)) {
628 updateZenModeConfig();
633 private static class TimePickerPreference extends Preference {
634 private final Context mContext;
636 private int mSummaryFormat;
637 private int mHourOfDay;
639 private Callback mCallback;
641 public TimePickerPreference(Context context, final FragmentManager mgr) {
644 setPersistent(false);
645 setOnPreferenceClickListener(new OnPreferenceClickListener(){
647 public boolean onPreferenceClick(Preference preference) {
648 final TimePickerFragment frag = new TimePickerFragment();
649 frag.pref = TimePickerPreference.this;
650 frag.show(mgr, TimePickerPreference.class.getName());
656 public void setCallback(Callback callback) {
657 mCallback = callback;
660 public void setSummaryFormat(int resId) {
661 mSummaryFormat = resId;
665 public void setTime(int hourOfDay, int minute) {
666 if (mCallback != null && !mCallback.onSetTime(hourOfDay, minute)) return;
667 mHourOfDay = hourOfDay;
672 private void updateSummary() {
673 final Calendar c = Calendar.getInstance();
674 c.set(Calendar.HOUR_OF_DAY, mHourOfDay);
675 c.set(Calendar.MINUTE, mMinute);
676 String time = DateFormat.getTimeFormat(mContext).format(c.getTime());
677 if (mSummaryFormat != 0) {
678 time = mContext.getResources().getString(mSummaryFormat, time);
683 public static class TimePickerFragment extends DialogFragment implements
684 TimePickerDialog.OnTimeSetListener {
685 public TimePickerPreference pref;
688 public Dialog onCreateDialog(Bundle savedInstanceState) {
689 final boolean usePref = pref != null && pref.mHourOfDay >= 0 && pref.mMinute >= 0;
690 final Calendar c = Calendar.getInstance();
691 final int hour = usePref ? pref.mHourOfDay : c.get(Calendar.HOUR_OF_DAY);
692 final int minute = usePref ? pref.mMinute : c.get(Calendar.MINUTE);
693 return new TimePickerDialog(getActivity(), this, hour, minute,
694 DateFormat.is24HourFormat(getActivity()));
697 public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
699 pref.setTime(hourOfDay, minute);
704 public interface Callback {
705 boolean onSetTime(int hour, int minute);