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