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