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