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