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