ZenModePanel.java revision 1a3f7db001a75770d888866a760e1308bb1f25fd
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.systemui.volume; 18 19import android.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.content.Context; 22import android.content.Intent; 23import android.content.SharedPreferences; 24import android.content.SharedPreferences.OnSharedPreferenceChangeListener; 25import android.content.res.Resources; 26import android.net.Uri; 27import android.os.Handler; 28import android.os.Looper; 29import android.os.Message; 30import android.provider.Settings; 31import android.provider.Settings.Global; 32import android.service.notification.Condition; 33import android.service.notification.ZenModeConfig; 34import android.util.AttributeSet; 35import android.util.Log; 36import android.util.MathUtils; 37import android.view.LayoutInflater; 38import android.view.View; 39import android.view.animation.AnimationUtils; 40import android.view.animation.Interpolator; 41import android.widget.CompoundButton; 42import android.widget.CompoundButton.OnCheckedChangeListener; 43import android.widget.ImageView; 44import android.widget.LinearLayout; 45import android.widget.RadioButton; 46import android.widget.TextView; 47 48import com.android.systemui.R; 49import com.android.systemui.statusbar.policy.ZenModeController; 50 51import java.util.Arrays; 52import java.util.Objects; 53 54public class ZenModePanel extends LinearLayout { 55 private static final String TAG = "ZenModePanel"; 56 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 57 58 private static final int SECONDS_MS = 1000; 59 private static final int MINUTES_MS = 60 * SECONDS_MS; 60 61 private static final int[] MINUTE_BUCKETS = DEBUG 62 ? new int[] { 0, 1, 2, 5, 15, 30, 45, 60, 120, 180, 240, 480 } 63 : ZenModeConfig.MINUTE_BUCKETS; 64 private static final int MIN_BUCKET_MINUTES = MINUTE_BUCKETS[0]; 65 private static final int MAX_BUCKET_MINUTES = MINUTE_BUCKETS[MINUTE_BUCKETS.length - 1]; 66 private static final int DEFAULT_BUCKET_INDEX = Arrays.binarySearch(MINUTE_BUCKETS, 60); 67 private static final int FOREVER_CONDITION_INDEX = 0; 68 private static final int TIME_CONDITION_INDEX = 1; 69 private static final int FIRST_CONDITION_INDEX = 2; 70 private static final float SILENT_HINT_PULSE_SCALE = 1.1f; 71 72 public static final Intent ZEN_SETTINGS = new Intent(Settings.ACTION_ZEN_MODE_SETTINGS); 73 74 private final Context mContext; 75 private final LayoutInflater mInflater; 76 private final H mHandler = new H(); 77 private final Prefs mPrefs; 78 private final Interpolator mFastOutSlowInInterpolator; 79 private final int mSubheadWarningColor; 80 private final int mSubheadColor; 81 private final ZenToast mZenToast; 82 83 private String mTag = TAG + "/" + Integer.toHexString(System.identityHashCode(this)); 84 85 private SegmentedButtons mZenButtons; 86 private View mZenSubhead; 87 private TextView mZenSubheadCollapsed; 88 private TextView mZenSubheadExpanded; 89 private View mMoreSettings; 90 private LinearLayout mZenConditions; 91 92 private Callback mCallback; 93 private ZenModeController mController; 94 private boolean mRequestingConditions; 95 private Condition mExitCondition; 96 private String mExitConditionText; 97 private int mBucketIndex = -1; 98 private boolean mExpanded; 99 private boolean mHidden = false; 100 private int mSessionZen; 101 private int mAttachedZen; 102 private Condition mSessionExitCondition; 103 private Condition[] mConditions; 104 private Condition mTimeCondition; 105 106 public ZenModePanel(Context context, AttributeSet attrs) { 107 super(context, attrs); 108 mContext = context; 109 mPrefs = new Prefs(); 110 mInflater = LayoutInflater.from(mContext.getApplicationContext()); 111 mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(mContext, 112 android.R.interpolator.fast_out_slow_in); 113 final Resources res = mContext.getResources(); 114 mSubheadWarningColor = res.getColor(R.color.system_warning_color); 115 mSubheadColor = res.getColor(R.color.qs_subhead); 116 mZenToast = new ZenToast(mContext); 117 if (DEBUG) Log.d(mTag, "new ZenModePanel"); 118 } 119 120 @Override 121 protected void onFinishInflate() { 122 super.onFinishInflate(); 123 124 mZenButtons = (SegmentedButtons) findViewById(R.id.zen_buttons); 125 mZenButtons.addButton(R.string.interruption_level_none, Global.ZEN_MODE_NO_INTERRUPTIONS); 126 mZenButtons.addButton(R.string.interruption_level_priority, 127 Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS); 128 mZenButtons.addButton(R.string.interruption_level_all, Global.ZEN_MODE_OFF); 129 mZenButtons.setCallback(mZenButtonsCallback); 130 131 mZenSubhead = findViewById(R.id.zen_subhead); 132 133 mZenSubheadCollapsed = (TextView) findViewById(R.id.zen_subhead_collapsed); 134 mZenSubheadCollapsed.setOnClickListener(new View.OnClickListener() { 135 @Override 136 public void onClick(View v) { 137 setExpanded(true); 138 } 139 }); 140 Interaction.register(mZenSubheadCollapsed, mInteractionCallback); 141 142 mZenSubheadExpanded = (TextView) findViewById(R.id.zen_subhead_expanded); 143 Interaction.register(mZenSubheadExpanded, mInteractionCallback); 144 145 mMoreSettings = findViewById(R.id.zen_more_settings); 146 mMoreSettings.setOnClickListener(new View.OnClickListener() { 147 @Override 148 public void onClick(View v) { 149 fireMoreSettings(); 150 } 151 }); 152 Interaction.register(mMoreSettings, mInteractionCallback); 153 154 mZenConditions = (LinearLayout) findViewById(R.id.zen_conditions); 155 } 156 157 @Override 158 protected void onAttachedToWindow() { 159 super.onAttachedToWindow(); 160 if (DEBUG) Log.d(mTag, "onAttachedToWindow"); 161 mZenToast.hide(); 162 mAttachedZen = getSelectedZen(-1); 163 mSessionZen = mAttachedZen; 164 mSessionExitCondition = copy(mExitCondition); 165 refreshExitConditionText(); 166 updateWidgets(); 167 } 168 169 @Override 170 protected void onDetachedFromWindow() { 171 super.onDetachedFromWindow(); 172 if (DEBUG) Log.d(mTag, "onDetachedFromWindow"); 173 checkForAttachedZenChange(); 174 mAttachedZen = -1; 175 mSessionZen = -1; 176 mSessionExitCondition = null; 177 setExpanded(false); 178 } 179 180 public void setHidden(boolean hidden) { 181 if (mHidden == hidden) return; 182 mHidden = hidden; 183 updateWidgets(); 184 } 185 186 private void checkForAttachedZenChange() { 187 final int selectedZen = getSelectedZen(-1); 188 if (DEBUG) Log.d(mTag, "selectedZen=" + selectedZen); 189 if (selectedZen != mAttachedZen) { 190 if (DEBUG) Log.d(mTag, "attachedZen: " + mAttachedZen + " -> " + selectedZen); 191 if (selectedZen == Global.ZEN_MODE_NO_INTERRUPTIONS) { 192 mPrefs.trackNoneSelected(); 193 } 194 if (selectedZen == Global.ZEN_MODE_NO_INTERRUPTIONS 195 || selectedZen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) { 196 mZenToast.show(selectedZen); 197 } 198 } 199 } 200 201 private void setExpanded(boolean expanded) { 202 if (expanded == mExpanded) return; 203 mExpanded = expanded; 204 updateWidgets(); 205 setRequestingConditions(mExpanded); 206 fireExpanded(); 207 } 208 209 /** Start or stop requesting relevant zen mode exit conditions */ 210 private void setRequestingConditions(boolean requesting) { 211 if (mRequestingConditions == requesting) return; 212 if (DEBUG) Log.d(mTag, "setRequestingConditions " + requesting); 213 mRequestingConditions = requesting; 214 if (mController != null) { 215 mController.requestConditions(mRequestingConditions); 216 } 217 if (mRequestingConditions) { 218 mTimeCondition = parseExistingTimeCondition(mExitCondition); 219 if (mTimeCondition != null) { 220 mBucketIndex = -1; 221 } else { 222 mBucketIndex = DEFAULT_BUCKET_INDEX; 223 mTimeCondition = ZenModeConfig.toTimeCondition(MINUTE_BUCKETS[mBucketIndex]); 224 } 225 if (DEBUG) Log.d(mTag, "Initial bucket index: " + mBucketIndex); 226 mConditions = null; // reset conditions 227 handleUpdateConditions(); 228 } else { 229 mZenConditions.removeAllViews(); 230 } 231 } 232 233 public void init(ZenModeController controller) { 234 mController = controller; 235 setExitCondition(mController.getExitCondition()); 236 refreshExitConditionText(); 237 mSessionZen = getSelectedZen(-1); 238 handleUpdateZen(mController.getZen()); 239 if (DEBUG) Log.d(mTag, "init mExitCondition=" + mExitCondition); 240 mZenConditions.removeAllViews(); 241 mController.addCallback(mZenCallback); 242 } 243 244 public void updateLocale() { 245 mZenButtons.updateLocale(); 246 } 247 248 private void setExitCondition(Condition exitCondition) { 249 if (sameConditionId(mExitCondition, exitCondition)) return; 250 mExitCondition = exitCondition; 251 refreshExitConditionText(); 252 updateWidgets(); 253 } 254 255 private static Uri getConditionId(Condition condition) { 256 return condition != null ? condition.id : null; 257 } 258 259 private static boolean sameConditionId(Condition lhs, Condition rhs) { 260 return lhs == null ? rhs == null : rhs != null && lhs.id.equals(rhs.id); 261 } 262 263 private static Condition copy(Condition condition) { 264 return condition == null ? null : condition.copy(); 265 } 266 267 private void refreshExitConditionText() { 268 final String forever = mContext.getString(com.android.internal.R.string.zen_mode_forever); 269 if (mExitCondition == null) { 270 mExitConditionText = forever; 271 } else if (ZenModeConfig.isValidCountdownConditionId(mExitCondition.id)) { 272 final Condition condition = parseExistingTimeCondition(mExitCondition); 273 mExitConditionText = condition != null ? condition.summary : forever; 274 } else { 275 mExitConditionText = mExitCondition.summary; 276 } 277 } 278 279 public void setCallback(Callback callback) { 280 mCallback = callback; 281 } 282 283 public void showSilentHint() { 284 if (DEBUG) Log.d(mTag, "showSilentHint"); 285 if (mZenButtons == null || mZenButtons.getChildCount() == 0) return; 286 final View noneButton = mZenButtons.getChildAt(0); 287 if (noneButton.getScaleX() != 1) return; // already running 288 noneButton.animate().cancel(); 289 noneButton.animate().scaleX(SILENT_HINT_PULSE_SCALE).scaleY(SILENT_HINT_PULSE_SCALE) 290 .setInterpolator(mFastOutSlowInInterpolator) 291 .setListener(new AnimatorListenerAdapter() { 292 @Override 293 public void onAnimationEnd(Animator animation) { 294 noneButton.animate().scaleX(1).scaleY(1).setListener(null); 295 } 296 }); 297 } 298 299 private void handleUpdateZen(int zen) { 300 if (mSessionZen != -1 && mSessionZen != zen) { 301 setExpanded(zen != Global.ZEN_MODE_OFF); 302 mSessionZen = zen; 303 } 304 mZenButtons.setSelectedValue(zen); 305 updateWidgets(); 306 } 307 308 private int getSelectedZen(int defValue) { 309 final Object zen = mZenButtons.getSelectedValue(); 310 return zen != null ? (Integer) zen : defValue; 311 } 312 313 private void updateWidgets() { 314 final int zen = getSelectedZen(Global.ZEN_MODE_OFF); 315 final boolean zenOff = zen == Global.ZEN_MODE_OFF; 316 final boolean zenImportant = zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; 317 final boolean zenNone = zen == Global.ZEN_MODE_NO_INTERRUPTIONS; 318 final boolean expanded = !mHidden && mExpanded; 319 320 mZenButtons.setVisibility(mHidden ? GONE : VISIBLE); 321 mZenSubhead.setVisibility(!mHidden && !zenOff ? VISIBLE : GONE); 322 mZenSubheadExpanded.setVisibility(expanded ? VISIBLE : GONE); 323 mZenSubheadCollapsed.setVisibility(!expanded ? VISIBLE : GONE); 324 mMoreSettings.setVisibility(zenImportant && expanded ? VISIBLE : GONE); 325 mZenConditions.setVisibility(!zenOff && expanded ? VISIBLE : GONE); 326 327 if (zenNone) { 328 mZenSubheadExpanded.setText(R.string.zen_no_interruptions_with_warning); 329 mZenSubheadCollapsed.setText(mExitConditionText); 330 } else if (zenImportant) { 331 mZenSubheadExpanded.setText(R.string.zen_important_interruptions); 332 mZenSubheadCollapsed.setText(mExitConditionText); 333 } 334 mZenSubheadExpanded.setTextColor(zenNone && mPrefs.isNoneDangerous() 335 ? mSubheadWarningColor : mSubheadColor); 336 } 337 338 private Condition parseExistingTimeCondition(Condition condition) { 339 if (condition == null) return null; 340 final long time = ZenModeConfig.tryParseCountdownConditionId(condition.id); 341 if (time == 0) return null; 342 final long span = time - System.currentTimeMillis(); 343 if (span <= 0 || span > MAX_BUCKET_MINUTES * MINUTES_MS) return null; 344 return ZenModeConfig.toTimeCondition(time, Math.round(span / (float) MINUTES_MS)); 345 } 346 347 private void handleUpdateConditions(Condition[] conditions) { 348 mConditions = conditions; 349 handleUpdateConditions(); 350 } 351 352 private void handleUpdateConditions() { 353 final int conditionCount = mConditions == null ? 0 : mConditions.length; 354 if (DEBUG) Log.d(mTag, "handleUpdateConditions conditionCount=" + conditionCount); 355 for (int i = mZenConditions.getChildCount() - 1; i >= FIRST_CONDITION_INDEX; i--) { 356 mZenConditions.removeViewAt(i); 357 } 358 // forever 359 bind(null, mZenConditions.getChildAt(FOREVER_CONDITION_INDEX)); 360 // countdown 361 bind(mTimeCondition, mZenConditions.getChildAt(TIME_CONDITION_INDEX)); 362 // provider conditions 363 boolean foundDowntime = false; 364 for (int i = 0; i < conditionCount; i++) { 365 bind(mConditions[i], mZenConditions.getChildAt(FIRST_CONDITION_INDEX + i)); 366 foundDowntime |= isDowntime(mConditions[i]); 367 } 368 // ensure downtime exists, if active 369 if (isDowntime(mSessionExitCondition) && !foundDowntime) { 370 bind(mSessionExitCondition, null); 371 } 372 // ensure something is selected 373 checkForDefault(); 374 } 375 376 private static boolean isDowntime(Condition c) { 377 return ZenModeConfig.isValidDowntimeConditionId(getConditionId(c)); 378 } 379 380 private ConditionTag getConditionTagAt(int index) { 381 return (ConditionTag) mZenConditions.getChildAt(index).getTag(); 382 } 383 384 private void checkForDefault() { 385 // are we left without anything selected? if so, set a default 386 for (int i = 0; i < mZenConditions.getChildCount(); i++) { 387 if (getConditionTagAt(i).rb.isChecked()) { 388 if (DEBUG) Log.d(mTag, "Not selecting a default, checked=" 389 + getConditionTagAt(i).condition); 390 return; 391 } 392 } 393 if (DEBUG) Log.d(mTag, "Selecting a default"); 394 final int favoriteIndex = mPrefs.getMinuteIndex(); 395 if (favoriteIndex == -1) { 396 getConditionTagAt(FOREVER_CONDITION_INDEX).rb.setChecked(true); 397 } else { 398 mTimeCondition = ZenModeConfig.toTimeCondition(MINUTE_BUCKETS[favoriteIndex]); 399 mBucketIndex = favoriteIndex; 400 bind(mTimeCondition, mZenConditions.getChildAt(TIME_CONDITION_INDEX)); 401 getConditionTagAt(TIME_CONDITION_INDEX).rb.setChecked(true); 402 } 403 } 404 405 private void handleExitConditionChanged(Condition exitCondition) { 406 setExitCondition(exitCondition); 407 if (DEBUG) Log.d(mTag, "handleExitConditionChanged " + mExitCondition); 408 final int N = mZenConditions.getChildCount(); 409 for (int i = 0; i < N; i++) { 410 final ConditionTag tag = getConditionTagAt(i); 411 tag.rb.setChecked(sameConditionId(tag.condition, mExitCondition)); 412 } 413 } 414 415 private void bind(final Condition condition, View convertView) { 416 final boolean enabled = condition == null || condition.state == Condition.STATE_TRUE; 417 final View row; 418 if (convertView == null) { 419 row = mInflater.inflate(R.layout.zen_mode_condition, this, false); 420 if (DEBUG) Log.d(mTag, "Adding new condition view for: " + condition); 421 mZenConditions.addView(row); 422 } else { 423 row = convertView; 424 } 425 final ConditionTag tag = 426 row.getTag() != null ? (ConditionTag) row.getTag() : new ConditionTag(); 427 row.setTag(tag); 428 if (tag.rb == null) { 429 tag.rb = (RadioButton) row.findViewById(android.R.id.checkbox); 430 } 431 tag.condition = condition; 432 tag.rb.setEnabled(enabled); 433 if (mSessionExitCondition != null 434 && sameConditionId(mSessionExitCondition, tag.condition)) { 435 tag.rb.setChecked(true); 436 } 437 tag.rb.setOnCheckedChangeListener(new OnCheckedChangeListener() { 438 @Override 439 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 440 if (mExpanded && isChecked) { 441 if (DEBUG) Log.d(mTag, "onCheckedChanged " + tag.condition); 442 final int N = mZenConditions.getChildCount(); 443 for (int i = 0; i < N; i++) { 444 ConditionTag childTag = getConditionTagAt(i); 445 if (childTag == tag) continue; 446 childTag.rb.setChecked(false); 447 } 448 select(tag.condition); 449 announceConditionSelection(tag); 450 } 451 } 452 }); 453 454 if (tag.title == null) { 455 tag.title = (TextView) row.findViewById(android.R.id.title); 456 } 457 if (condition == null) { 458 tag.title.setText(mContext.getString(com.android.internal.R.string.zen_mode_forever)); 459 } else { 460 tag.title.setText(condition.summary); 461 } 462 tag.title.setEnabled(enabled); 463 tag.title.setAlpha(enabled ? 1 : .4f); 464 465 final ImageView button1 = (ImageView) row.findViewById(android.R.id.button1); 466 button1.setOnClickListener(new OnClickListener() { 467 @Override 468 public void onClick(View v) { 469 onClickTimeButton(row, tag, false /*down*/); 470 } 471 }); 472 473 final ImageView button2 = (ImageView) row.findViewById(android.R.id.button2); 474 button2.setOnClickListener(new OnClickListener() { 475 @Override 476 public void onClick(View v) { 477 onClickTimeButton(row, tag, true /*up*/); 478 } 479 }); 480 tag.title.setOnClickListener(new OnClickListener() { 481 @Override 482 public void onClick(View v) { 483 tag.rb.setChecked(true); 484 } 485 }); 486 487 final long time = ZenModeConfig.tryParseCountdownConditionId(getConditionId(tag.condition)); 488 if (time > 0) { 489 if (mBucketIndex > -1) { 490 button1.setEnabled(mBucketIndex > 0); 491 button2.setEnabled(mBucketIndex < MINUTE_BUCKETS.length - 1); 492 } else { 493 final long span = time - System.currentTimeMillis(); 494 button1.setEnabled(span > MIN_BUCKET_MINUTES * MINUTES_MS); 495 final Condition maxCondition = ZenModeConfig.toTimeCondition(MAX_BUCKET_MINUTES); 496 button2.setEnabled(!Objects.equals(condition.summary, maxCondition.summary)); 497 } 498 499 button1.setAlpha(button1.isEnabled() ? 1f : .5f); 500 button2.setAlpha(button2.isEnabled() ? 1f : .5f); 501 } else { 502 button1.setVisibility(View.GONE); 503 button2.setVisibility(View.GONE); 504 } 505 // wire up interaction callbacks for newly-added condition rows 506 if (convertView == null) { 507 Interaction.register(tag.rb, mInteractionCallback); 508 Interaction.register(tag.title, mInteractionCallback); 509 Interaction.register(button1, mInteractionCallback); 510 Interaction.register(button2, mInteractionCallback); 511 } 512 } 513 514 private void announceConditionSelection(ConditionTag tag) { 515 final int zen = getSelectedZen(Global.ZEN_MODE_OFF); 516 String modeText; 517 switch(zen) { 518 case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: 519 modeText = mContext.getString(R.string.zen_important_interruptions); 520 break; 521 case Global.ZEN_MODE_NO_INTERRUPTIONS: 522 modeText = mContext.getString(R.string.zen_no_interruptions); 523 break; 524 default: 525 return; 526 } 527 announceForAccessibility(mContext.getString(R.string.zen_mode_and_condition, modeText, 528 tag.title.getText())); 529 } 530 531 private void onClickTimeButton(View row, ConditionTag tag, boolean up) { 532 Condition newCondition = null; 533 final int N = MINUTE_BUCKETS.length; 534 if (mBucketIndex == -1) { 535 // not on a known index, search for the next or prev bucket by time 536 final Uri conditionId = getConditionId(tag.condition); 537 final long time = ZenModeConfig.tryParseCountdownConditionId(conditionId); 538 final long now = System.currentTimeMillis(); 539 for (int i = 0; i < N; i++) { 540 int j = up ? i : N - 1 - i; 541 final int bucketMinutes = MINUTE_BUCKETS[j]; 542 final long bucketTime = now + bucketMinutes * MINUTES_MS; 543 if (up && bucketTime > time || !up && bucketTime < time) { 544 mBucketIndex = j; 545 newCondition = ZenModeConfig.toTimeCondition(bucketTime, bucketMinutes); 546 break; 547 } 548 } 549 if (newCondition == null) { 550 mBucketIndex = DEFAULT_BUCKET_INDEX; 551 newCondition = ZenModeConfig.toTimeCondition(MINUTE_BUCKETS[mBucketIndex]); 552 } 553 } else { 554 // on a known index, simply increment or decrement 555 mBucketIndex = Math.max(0, Math.min(N - 1, mBucketIndex + (up ? 1 : -1))); 556 newCondition = ZenModeConfig.toTimeCondition(MINUTE_BUCKETS[mBucketIndex]); 557 } 558 mTimeCondition = newCondition; 559 bind(mTimeCondition, row); 560 tag.rb.setChecked(true); 561 select(mTimeCondition); 562 announceConditionSelection(tag); 563 } 564 565 private void select(Condition condition) { 566 if (DEBUG) Log.d(mTag, "select " + condition); 567 if (mController != null) { 568 mController.setExitCondition(condition); 569 } 570 setExitCondition(condition); 571 if (condition == null) { 572 mPrefs.setMinuteIndex(-1); 573 } else if (ZenModeConfig.isValidCountdownConditionId(condition.id) && mBucketIndex != -1) { 574 mPrefs.setMinuteIndex(mBucketIndex); 575 } 576 mSessionExitCondition = copy(condition); 577 } 578 579 private void fireMoreSettings() { 580 if (mCallback != null) { 581 mCallback.onMoreSettings(); 582 } 583 } 584 585 private void fireInteraction() { 586 if (mCallback != null) { 587 mCallback.onInteraction(); 588 } 589 } 590 591 private void fireExpanded() { 592 if (mCallback != null) { 593 mCallback.onExpanded(mExpanded); 594 } 595 } 596 597 private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() { 598 @Override 599 public void onZenChanged(int zen) { 600 mHandler.obtainMessage(H.UPDATE_ZEN, zen, 0).sendToTarget(); 601 } 602 @Override 603 public void onConditionsChanged(Condition[] conditions) { 604 mHandler.obtainMessage(H.UPDATE_CONDITIONS, conditions).sendToTarget(); 605 } 606 607 @Override 608 public void onExitConditionChanged(Condition exitCondition) { 609 mHandler.obtainMessage(H.EXIT_CONDITION_CHANGED, exitCondition).sendToTarget(); 610 } 611 }; 612 613 private final class H extends Handler { 614 private static final int UPDATE_CONDITIONS = 1; 615 private static final int EXIT_CONDITION_CHANGED = 2; 616 private static final int UPDATE_ZEN = 3; 617 618 private H() { 619 super(Looper.getMainLooper()); 620 } 621 622 @Override 623 public void handleMessage(Message msg) { 624 if (msg.what == UPDATE_CONDITIONS) { 625 handleUpdateConditions((Condition[]) msg.obj); 626 } else if (msg.what == EXIT_CONDITION_CHANGED) { 627 handleExitConditionChanged((Condition) msg.obj); 628 } else if (msg.what == UPDATE_ZEN) { 629 handleUpdateZen(msg.arg1); 630 } 631 } 632 } 633 634 public interface Callback { 635 void onMoreSettings(); 636 void onInteraction(); 637 void onExpanded(boolean expanded); 638 } 639 640 // used as the view tag on condition rows 641 private static class ConditionTag { 642 RadioButton rb; 643 TextView title; 644 Condition condition; 645 } 646 647 private final class Prefs implements OnSharedPreferenceChangeListener { 648 private static final String KEY_MINUTE_INDEX = "minuteIndex"; 649 private static final String KEY_NONE_SELECTED = "noneSelected"; 650 651 private final int mNoneDangerousThreshold; 652 653 private int mMinuteIndex; 654 private int mNoneSelected; 655 656 private Prefs() { 657 mNoneDangerousThreshold = mContext.getResources() 658 .getInteger(R.integer.zen_mode_alarm_warning_threshold); 659 prefs().registerOnSharedPreferenceChangeListener(this); 660 updateMinuteIndex(); 661 updateNoneSelected(); 662 } 663 664 public boolean isNoneDangerous() { 665 return mNoneSelected < mNoneDangerousThreshold; 666 } 667 668 public void trackNoneSelected() { 669 mNoneSelected = clampNoneSelected(mNoneSelected + 1); 670 if (DEBUG) Log.d(mTag, "Setting none selected: " + mNoneSelected + " threshold=" 671 + mNoneDangerousThreshold); 672 prefs().edit().putInt(KEY_NONE_SELECTED, mNoneSelected).apply(); 673 } 674 675 public int getMinuteIndex() { 676 return mMinuteIndex; 677 } 678 679 public void setMinuteIndex(int minuteIndex) { 680 minuteIndex = clampIndex(minuteIndex); 681 if (minuteIndex == mMinuteIndex) return; 682 mMinuteIndex = clampIndex(minuteIndex); 683 if (DEBUG) Log.d(mTag, "Setting favorite minute index: " + mMinuteIndex); 684 prefs().edit().putInt(KEY_MINUTE_INDEX, mMinuteIndex).apply(); 685 } 686 687 @Override 688 public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { 689 updateMinuteIndex(); 690 updateNoneSelected(); 691 } 692 693 private SharedPreferences prefs() { 694 return mContext.getSharedPreferences(mContext.getPackageName(), 0); 695 } 696 697 private void updateMinuteIndex() { 698 mMinuteIndex = clampIndex(prefs().getInt(KEY_MINUTE_INDEX, DEFAULT_BUCKET_INDEX)); 699 if (DEBUG) Log.d(mTag, "Favorite minute index: " + mMinuteIndex); 700 } 701 702 private int clampIndex(int index) { 703 return MathUtils.constrain(index, -1, MINUTE_BUCKETS.length - 1); 704 } 705 706 private void updateNoneSelected() { 707 mNoneSelected = clampNoneSelected(prefs().getInt(KEY_NONE_SELECTED, 0)); 708 if (DEBUG) Log.d(mTag, "None selected: " + mNoneSelected); 709 } 710 711 private int clampNoneSelected(int noneSelected) { 712 return MathUtils.constrain(noneSelected, 0, Integer.MAX_VALUE); 713 } 714 } 715 716 private final SegmentedButtons.Callback mZenButtonsCallback = new SegmentedButtons.Callback() { 717 @Override 718 public void onSelected(Object value) { 719 if (value != null && mZenButtons.isShown()) { 720 if (DEBUG) Log.d(mTag, "mZenButtonsCallback selected=" + value); 721 mController.setZen((Integer) value); 722 } 723 } 724 725 @Override 726 public void onInteraction() { 727 fireInteraction(); 728 } 729 }; 730 731 private final Interaction.Callback mInteractionCallback = new Interaction.Callback() { 732 @Override 733 public void onInteraction() { 734 fireInteraction(); 735 } 736 }; 737} 738