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