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 17 package com.android.systemui.volume; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.SharedPreferences; 24 import android.content.SharedPreferences.OnSharedPreferenceChangeListener; 25 import android.content.res.Resources; 26 import android.net.Uri; 27 import android.os.Handler; 28 import android.os.Looper; 29 import android.os.Message; 30 import android.provider.Settings; 31 import android.provider.Settings.Global; 32 import android.service.notification.Condition; 33 import android.service.notification.ZenModeConfig; 34 import android.util.AttributeSet; 35 import android.util.Log; 36 import android.util.MathUtils; 37 import android.view.LayoutInflater; 38 import android.view.View; 39 import android.view.animation.AnimationUtils; 40 import android.view.animation.Interpolator; 41 import android.widget.CompoundButton; 42 import android.widget.CompoundButton.OnCheckedChangeListener; 43 import android.widget.ImageView; 44 import android.widget.LinearLayout; 45 import android.widget.RadioButton; 46 import android.widget.TextView; 47 48 import com.android.systemui.R; 49 import com.android.systemui.statusbar.policy.ZenModeController; 50 51 import java.util.Arrays; 52 import java.util.Objects; 53 54 public 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 ZenModePanel(Context context, AttributeSet attrs)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 onFinishInflate()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 onAttachedToWindow()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 onDetachedFromWindow()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 setHidden(boolean hidden)180 public void setHidden(boolean hidden) { 181 if (mHidden == hidden) return; 182 mHidden = hidden; 183 updateWidgets(); 184 } 185 checkForAttachedZenChange()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 setExpanded(boolean expanded)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 */ setRequestingConditions(boolean requesting)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 init(ZenModeController controller)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 updateLocale()244 public void updateLocale() { 245 mZenButtons.updateLocale(); 246 } 247 setExitCondition(Condition exitCondition)248 private void setExitCondition(Condition exitCondition) { 249 if (sameConditionId(mExitCondition, exitCondition)) return; 250 mExitCondition = exitCondition; 251 refreshExitConditionText(); 252 updateWidgets(); 253 } 254 getConditionId(Condition condition)255 private static Uri getConditionId(Condition condition) { 256 return condition != null ? condition.id : null; 257 } 258 sameConditionId(Condition lhs, Condition rhs)259 private static boolean sameConditionId(Condition lhs, Condition rhs) { 260 return lhs == null ? rhs == null : rhs != null && lhs.id.equals(rhs.id); 261 } 262 copy(Condition condition)263 private static Condition copy(Condition condition) { 264 return condition == null ? null : condition.copy(); 265 } 266 refreshExitConditionText()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 setCallback(Callback callback)279 public void setCallback(Callback callback) { 280 mCallback = callback; 281 } 282 showSilentHint()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 handleUpdateZen(int zen)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 getSelectedZen(int defValue)308 private int getSelectedZen(int defValue) { 309 final Object zen = mZenButtons.getSelectedValue(); 310 return zen != null ? (Integer) zen : defValue; 311 } 312 updateWidgets()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 parseExistingTimeCondition(Condition condition)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 handleUpdateConditions(Condition[] conditions)347 private void handleUpdateConditions(Condition[] conditions) { 348 mConditions = conditions; 349 handleUpdateConditions(); 350 } 351 handleUpdateConditions()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 isDowntime(Condition c)376 private static boolean isDowntime(Condition c) { 377 return ZenModeConfig.isValidDowntimeConditionId(getConditionId(c)); 378 } 379 getConditionTagAt(int index)380 private ConditionTag getConditionTagAt(int index) { 381 return (ConditionTag) mZenConditions.getChildAt(index).getTag(); 382 } 383 checkForDefault()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 handleExitConditionChanged(Condition exitCondition)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 bind(final Condition condition, View convertView)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 (sameConditionId(mSessionExitCondition, tag.condition)) { 434 tag.rb.setChecked(true); 435 } 436 tag.rb.setOnCheckedChangeListener(new OnCheckedChangeListener() { 437 @Override 438 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 439 if (mExpanded && isChecked) { 440 if (DEBUG) Log.d(mTag, "onCheckedChanged " + tag.condition); 441 final int N = mZenConditions.getChildCount(); 442 for (int i = 0; i < N; i++) { 443 ConditionTag childTag = getConditionTagAt(i); 444 if (childTag == tag) continue; 445 childTag.rb.setChecked(false); 446 } 447 select(tag.condition); 448 announceConditionSelection(tag); 449 } 450 } 451 }); 452 453 if (tag.title == null) { 454 tag.title = (TextView) row.findViewById(android.R.id.title); 455 } 456 if (condition == null) { 457 tag.title.setText(mContext.getString(com.android.internal.R.string.zen_mode_forever)); 458 } else { 459 tag.title.setText(condition.summary); 460 } 461 tag.title.setEnabled(enabled); 462 tag.title.setAlpha(enabled ? 1 : .4f); 463 464 final ImageView button1 = (ImageView) row.findViewById(android.R.id.button1); 465 button1.setOnClickListener(new OnClickListener() { 466 @Override 467 public void onClick(View v) { 468 onClickTimeButton(row, tag, false /*down*/); 469 } 470 }); 471 472 final ImageView button2 = (ImageView) row.findViewById(android.R.id.button2); 473 button2.setOnClickListener(new OnClickListener() { 474 @Override 475 public void onClick(View v) { 476 onClickTimeButton(row, tag, true /*up*/); 477 } 478 }); 479 tag.title.setOnClickListener(new OnClickListener() { 480 @Override 481 public void onClick(View v) { 482 tag.rb.setChecked(true); 483 } 484 }); 485 486 final long time = ZenModeConfig.tryParseCountdownConditionId(getConditionId(tag.condition)); 487 if (time > 0) { 488 if (mBucketIndex > -1) { 489 button1.setEnabled(mBucketIndex > 0); 490 button2.setEnabled(mBucketIndex < MINUTE_BUCKETS.length - 1); 491 } else { 492 final long span = time - System.currentTimeMillis(); 493 button1.setEnabled(span > MIN_BUCKET_MINUTES * MINUTES_MS); 494 final Condition maxCondition = ZenModeConfig.toTimeCondition(MAX_BUCKET_MINUTES); 495 button2.setEnabled(!Objects.equals(condition.summary, maxCondition.summary)); 496 } 497 498 button1.setAlpha(button1.isEnabled() ? 1f : .5f); 499 button2.setAlpha(button2.isEnabled() ? 1f : .5f); 500 } else { 501 button1.setVisibility(View.GONE); 502 button2.setVisibility(View.GONE); 503 } 504 // wire up interaction callbacks for newly-added condition rows 505 if (convertView == null) { 506 Interaction.register(tag.rb, mInteractionCallback); 507 Interaction.register(tag.title, mInteractionCallback); 508 Interaction.register(button1, mInteractionCallback); 509 Interaction.register(button2, mInteractionCallback); 510 } 511 } 512 announceConditionSelection(ConditionTag tag)513 private void announceConditionSelection(ConditionTag tag) { 514 final int zen = getSelectedZen(Global.ZEN_MODE_OFF); 515 String modeText; 516 switch(zen) { 517 case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: 518 modeText = mContext.getString(R.string.zen_important_interruptions); 519 break; 520 case Global.ZEN_MODE_NO_INTERRUPTIONS: 521 modeText = mContext.getString(R.string.zen_no_interruptions); 522 break; 523 default: 524 return; 525 } 526 announceForAccessibility(mContext.getString(R.string.zen_mode_and_condition, modeText, 527 tag.title.getText())); 528 } 529 onClickTimeButton(View row, ConditionTag tag, boolean up)530 private void onClickTimeButton(View row, ConditionTag tag, boolean up) { 531 Condition newCondition = null; 532 final int N = MINUTE_BUCKETS.length; 533 if (mBucketIndex == -1) { 534 // not on a known index, search for the next or prev bucket by time 535 final Uri conditionId = getConditionId(tag.condition); 536 final long time = ZenModeConfig.tryParseCountdownConditionId(conditionId); 537 final long now = System.currentTimeMillis(); 538 for (int i = 0; i < N; i++) { 539 int j = up ? i : N - 1 - i; 540 final int bucketMinutes = MINUTE_BUCKETS[j]; 541 final long bucketTime = now + bucketMinutes * MINUTES_MS; 542 if (up && bucketTime > time || !up && bucketTime < time) { 543 mBucketIndex = j; 544 newCondition = ZenModeConfig.toTimeCondition(bucketTime, bucketMinutes); 545 break; 546 } 547 } 548 if (newCondition == null) { 549 mBucketIndex = DEFAULT_BUCKET_INDEX; 550 newCondition = ZenModeConfig.toTimeCondition(MINUTE_BUCKETS[mBucketIndex]); 551 } 552 } else { 553 // on a known index, simply increment or decrement 554 mBucketIndex = Math.max(0, Math.min(N - 1, mBucketIndex + (up ? 1 : -1))); 555 newCondition = ZenModeConfig.toTimeCondition(MINUTE_BUCKETS[mBucketIndex]); 556 } 557 mTimeCondition = newCondition; 558 bind(mTimeCondition, row); 559 tag.rb.setChecked(true); 560 select(mTimeCondition); 561 announceConditionSelection(tag); 562 } 563 select(Condition condition)564 private void select(Condition condition) { 565 if (DEBUG) Log.d(mTag, "select " + condition); 566 if (mController != null) { 567 mController.setExitCondition(condition); 568 } 569 setExitCondition(condition); 570 if (condition == null) { 571 mPrefs.setMinuteIndex(-1); 572 } else if (ZenModeConfig.isValidCountdownConditionId(condition.id) && mBucketIndex != -1) { 573 mPrefs.setMinuteIndex(mBucketIndex); 574 } 575 mSessionExitCondition = copy(condition); 576 } 577 fireMoreSettings()578 private void fireMoreSettings() { 579 if (mCallback != null) { 580 mCallback.onMoreSettings(); 581 } 582 } 583 fireInteraction()584 private void fireInteraction() { 585 if (mCallback != null) { 586 mCallback.onInteraction(); 587 } 588 } 589 fireExpanded()590 private void fireExpanded() { 591 if (mCallback != null) { 592 mCallback.onExpanded(mExpanded); 593 } 594 } 595 596 private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() { 597 @Override 598 public void onZenChanged(int zen) { 599 mHandler.obtainMessage(H.UPDATE_ZEN, zen, 0).sendToTarget(); 600 } 601 @Override 602 public void onConditionsChanged(Condition[] conditions) { 603 mHandler.obtainMessage(H.UPDATE_CONDITIONS, conditions).sendToTarget(); 604 } 605 606 @Override 607 public void onExitConditionChanged(Condition exitCondition) { 608 mHandler.obtainMessage(H.EXIT_CONDITION_CHANGED, exitCondition).sendToTarget(); 609 } 610 }; 611 612 private final class H extends Handler { 613 private static final int UPDATE_CONDITIONS = 1; 614 private static final int EXIT_CONDITION_CHANGED = 2; 615 private static final int UPDATE_ZEN = 3; 616 H()617 private H() { 618 super(Looper.getMainLooper()); 619 } 620 621 @Override handleMessage(Message msg)622 public void handleMessage(Message msg) { 623 if (msg.what == UPDATE_CONDITIONS) { 624 handleUpdateConditions((Condition[]) msg.obj); 625 } else if (msg.what == EXIT_CONDITION_CHANGED) { 626 handleExitConditionChanged((Condition) msg.obj); 627 } else if (msg.what == UPDATE_ZEN) { 628 handleUpdateZen(msg.arg1); 629 } 630 } 631 } 632 633 public interface Callback { onMoreSettings()634 void onMoreSettings(); onInteraction()635 void onInteraction(); onExpanded(boolean expanded)636 void onExpanded(boolean expanded); 637 } 638 639 // used as the view tag on condition rows 640 private static class ConditionTag { 641 RadioButton rb; 642 TextView title; 643 Condition condition; 644 } 645 646 private final class Prefs implements OnSharedPreferenceChangeListener { 647 private static final String KEY_MINUTE_INDEX = "minuteIndex"; 648 private static final String KEY_NONE_SELECTED = "noneSelected"; 649 650 private final int mNoneDangerousThreshold; 651 652 private int mMinuteIndex; 653 private int mNoneSelected; 654 Prefs()655 private Prefs() { 656 mNoneDangerousThreshold = mContext.getResources() 657 .getInteger(R.integer.zen_mode_alarm_warning_threshold); 658 prefs().registerOnSharedPreferenceChangeListener(this); 659 updateMinuteIndex(); 660 updateNoneSelected(); 661 } 662 isNoneDangerous()663 public boolean isNoneDangerous() { 664 return mNoneSelected < mNoneDangerousThreshold; 665 } 666 trackNoneSelected()667 public void trackNoneSelected() { 668 mNoneSelected = clampNoneSelected(mNoneSelected + 1); 669 if (DEBUG) Log.d(mTag, "Setting none selected: " + mNoneSelected + " threshold=" 670 + mNoneDangerousThreshold); 671 prefs().edit().putInt(KEY_NONE_SELECTED, mNoneSelected).apply(); 672 } 673 getMinuteIndex()674 public int getMinuteIndex() { 675 return mMinuteIndex; 676 } 677 setMinuteIndex(int minuteIndex)678 public void setMinuteIndex(int minuteIndex) { 679 minuteIndex = clampIndex(minuteIndex); 680 if (minuteIndex == mMinuteIndex) return; 681 mMinuteIndex = clampIndex(minuteIndex); 682 if (DEBUG) Log.d(mTag, "Setting favorite minute index: " + mMinuteIndex); 683 prefs().edit().putInt(KEY_MINUTE_INDEX, mMinuteIndex).apply(); 684 } 685 686 @Override onSharedPreferenceChanged(SharedPreferences prefs, String key)687 public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { 688 updateMinuteIndex(); 689 updateNoneSelected(); 690 } 691 prefs()692 private SharedPreferences prefs() { 693 return mContext.getSharedPreferences(ZenModePanel.class.getSimpleName(), 0); 694 } 695 updateMinuteIndex()696 private void updateMinuteIndex() { 697 mMinuteIndex = clampIndex(prefs().getInt(KEY_MINUTE_INDEX, DEFAULT_BUCKET_INDEX)); 698 if (DEBUG) Log.d(mTag, "Favorite minute index: " + mMinuteIndex); 699 } 700 clampIndex(int index)701 private int clampIndex(int index) { 702 return MathUtils.constrain(index, -1, MINUTE_BUCKETS.length - 1); 703 } 704 updateNoneSelected()705 private void updateNoneSelected() { 706 mNoneSelected = clampNoneSelected(prefs().getInt(KEY_NONE_SELECTED, 0)); 707 if (DEBUG) Log.d(mTag, "None selected: " + mNoneSelected); 708 } 709 clampNoneSelected(int noneSelected)710 private int clampNoneSelected(int noneSelected) { 711 return MathUtils.constrain(noneSelected, 0, Integer.MAX_VALUE); 712 } 713 } 714 715 private final SegmentedButtons.Callback mZenButtonsCallback = new SegmentedButtons.Callback() { 716 @Override 717 public void onSelected(Object value) { 718 if (value != null && mZenButtons.isShown()) { 719 if (DEBUG) Log.d(mTag, "mZenButtonsCallback selected=" + value); 720 mController.setZen((Integer) value); 721 } 722 } 723 724 @Override 725 public void onInteraction() { 726 fireInteraction(); 727 } 728 }; 729 730 private final Interaction.Callback mInteractionCallback = new Interaction.Callback() { 731 @Override 732 public void onInteraction() { 733 fireInteraction(); 734 } 735 }; 736 } 737