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.tv.settings.dialog; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorInflater; 21 import android.app.Dialog; 22 import android.app.Fragment; 23 import android.content.Context; 24 import android.content.DialogInterface; 25 import android.content.res.Resources; 26 import android.os.Bundle; 27 import android.os.Handler; 28 import android.support.annotation.IntDef; 29 import android.text.TextUtils; 30 import android.util.AttributeSet; 31 import android.util.Log; 32 import android.util.TypedValue; 33 import android.view.KeyEvent; 34 import android.view.LayoutInflater; 35 import android.view.View; 36 import android.view.ViewGroup; 37 import android.widget.FrameLayout; 38 import android.widget.OverScroller; 39 import android.widget.TextView; 40 import android.widget.Toast; 41 42 import com.android.tv.settings.R; 43 44 import java.lang.annotation.Retention; 45 import java.lang.annotation.RetentionPolicy; 46 47 public abstract class PinDialogFragment extends SafeDismissDialogFragment { 48 private static final String TAG = "PinDialogFragment"; 49 private static final boolean DEBUG = false; 50 51 protected static final String ARG_TYPE = "type"; 52 53 @Retention(RetentionPolicy.SOURCE) 54 @IntDef({PIN_DIALOG_TYPE_UNLOCK_CHANNEL, 55 PIN_DIALOG_TYPE_UNLOCK_PROGRAM, 56 PIN_DIALOG_TYPE_ENTER_PIN, 57 PIN_DIALOG_TYPE_NEW_PIN, 58 PIN_DIALOG_TYPE_OLD_PIN, 59 PIN_DIALOG_TYPE_DELETE_PIN}) 60 public @interface PinDialogType {} 61 /** 62 * PIN code dialog for unlock channel 63 */ 64 public static final int PIN_DIALOG_TYPE_UNLOCK_CHANNEL = 0; 65 66 /** 67 * PIN code dialog for unlock content. 68 * Only difference between {@code PIN_DIALOG_TYPE_UNLOCK_CHANNEL} is it's title. 69 */ 70 public static final int PIN_DIALOG_TYPE_UNLOCK_PROGRAM = 1; 71 72 /** 73 * PIN code dialog for change parental control settings 74 */ 75 public static final int PIN_DIALOG_TYPE_ENTER_PIN = 2; 76 77 /** 78 * PIN code dialog for set new PIN 79 */ 80 public static final int PIN_DIALOG_TYPE_NEW_PIN = 3; 81 82 // PIN code dialog for checking old PIN. This is intenal only. 83 private static final int PIN_DIALOG_TYPE_OLD_PIN = 4; 84 85 /** 86 * PIN code dialog for deleting the PIN 87 */ 88 public static final int PIN_DIALOG_TYPE_DELETE_PIN = 5; 89 90 private static final int PIN_DIALOG_RESULT_SUCCESS = 0; 91 private static final int PIN_DIALOG_RESULT_FAIL = 1; 92 93 private static final int MAX_WRONG_PIN_COUNT = 5; 94 private static final int DISABLE_PIN_DURATION_MILLIS = 60 * 1000; // 1 minute 95 96 public interface ResultListener { 97 void pinFragmentDone(int requestCode, boolean success); 98 } 99 100 public static final String DIALOG_TAG = PinDialogFragment.class.getName(); 101 102 private static final int NUMBER_PICKERS_RES_ID[] = { 103 R.id.first, R.id.second, R.id.third, R.id.fourth }; 104 105 private int mType; 106 private int mRetCode; 107 108 private TextView mWrongPinView; 109 private View mEnterPinView; 110 private TextView mTitleView; 111 private PinNumberPicker[] mPickers; 112 private String mOriginalPin; 113 private String mPrevPin; 114 private int mWrongPinCount; 115 private long mDisablePinUntil; 116 private final Handler mHandler = new Handler(); 117 118 /** 119 * Get the bad PIN retry time 120 * @return Retry time 121 */ 122 public abstract long getPinDisabledUntil(); 123 124 /** 125 * Set the bad PIN retry time 126 * @param retryDisableTimeout Retry time 127 */ 128 public abstract void setPinDisabledUntil(long retryDisableTimeout); 129 130 /** 131 * Set PIN password for the profile 132 * @param pin New PIN password 133 */ 134 public abstract void setPin(String pin, String originalPin); 135 136 /** 137 * Delete PIN password for the profile 138 * @param oldPin Old PIN password (required) 139 */ 140 public abstract void deletePin(String oldPin); 141 142 /** 143 * Validate PIN password for the profile 144 * @param pin Password to check 145 * @return {@code True} if password is correct 146 */ 147 public abstract boolean isPinCorrect(String pin); 148 149 /** 150 * Check if there is a PIN password set on the profile 151 * @return {@code True} if password is set 152 */ 153 public abstract boolean isPinSet(); 154 155 public PinDialogFragment() { 156 mRetCode = PIN_DIALOG_RESULT_FAIL; 157 } 158 159 @Override 160 public void onCreate(Bundle savedInstanceState) { 161 super.onCreate(savedInstanceState); 162 setStyle(STYLE_NO_TITLE, 0); 163 mDisablePinUntil = getPinDisabledUntil(); 164 final Bundle args = getArguments(); 165 if (!args.containsKey(ARG_TYPE)) { 166 throw new IllegalStateException("Fragment arguments must specify type"); 167 } 168 mType = getArguments().getInt(ARG_TYPE); 169 } 170 171 @Override 172 public Dialog onCreateDialog(Bundle savedInstanceState) { 173 Dialog dlg = super.onCreateDialog(savedInstanceState); 174 dlg.getWindow().getAttributes().windowAnimations = R.style.pin_dialog_animation; 175 PinNumberPicker.loadResources(dlg.getContext()); 176 return dlg; 177 } 178 179 @Override 180 public View onCreateView(LayoutInflater inflater, ViewGroup container, 181 Bundle savedInstanceState) { 182 final View v = inflater.inflate(R.layout.pin_dialog, container, false); 183 184 mWrongPinView = v.findViewById(R.id.wrong_pin); 185 mEnterPinView = v.findViewById(R.id.enter_pin); 186 if (mEnterPinView == null) { 187 throw new IllegalStateException("R.id.enter_pin missing!"); 188 } 189 mTitleView = mEnterPinView.findViewById(R.id.title); 190 if (!isPinSet()) { 191 // If PIN isn't set, user should set a PIN. 192 // Successfully setting a new set is considered as entering correct PIN. 193 mType = PIN_DIALOG_TYPE_NEW_PIN; 194 } 195 switch (mType) { 196 case PIN_DIALOG_TYPE_UNLOCK_CHANNEL: 197 mTitleView.setText(R.string.pin_enter_unlock_channel); 198 break; 199 case PIN_DIALOG_TYPE_UNLOCK_PROGRAM: 200 mTitleView.setText(R.string.pin_enter_unlock_program); 201 break; 202 case PIN_DIALOG_TYPE_ENTER_PIN: 203 case PIN_DIALOG_TYPE_DELETE_PIN: 204 mTitleView.setText(R.string.pin_enter_pin); 205 break; 206 case PIN_DIALOG_TYPE_NEW_PIN: 207 if (!isPinSet()) { 208 mTitleView.setText(R.string.pin_enter_new_pin); 209 } else { 210 mTitleView.setText(R.string.pin_enter_old_pin); 211 mType = PIN_DIALOG_TYPE_OLD_PIN; 212 } 213 } 214 215 mPickers = new PinNumberPicker[NUMBER_PICKERS_RES_ID.length]; 216 for (int i = 0; i < NUMBER_PICKERS_RES_ID.length; i++) { 217 mPickers[i] = v.findViewById(NUMBER_PICKERS_RES_ID[i]); 218 mPickers[i].setValueRange(0, 9); 219 mPickers[i].setPinDialogFragment(this); 220 mPickers[i].updateFocus(); 221 } 222 for (int i = 0; i < NUMBER_PICKERS_RES_ID.length - 1; i++) { 223 mPickers[i].setNextNumberPicker(mPickers[i + 1]); 224 } 225 226 if (mType != PIN_DIALOG_TYPE_NEW_PIN) { 227 updateWrongPin(); 228 } 229 230 if (savedInstanceState == null) { 231 mPickers[0].requestFocus(); 232 } 233 return v; 234 } 235 236 private final Runnable mUpdateEnterPinRunnable = this::updateWrongPin; 237 238 private void updateWrongPin() { 239 if (getActivity() == null) { 240 // The activity is already detached. No need to update. 241 mHandler.removeCallbacks(null); 242 return; 243 } 244 245 final long secondsLeft = (mDisablePinUntil - System.currentTimeMillis()) / 1000; 246 final boolean enabled = secondsLeft < 1; 247 if (enabled) { 248 mWrongPinView.setVisibility(View.GONE); 249 mEnterPinView.setVisibility(View.VISIBLE); 250 mWrongPinCount = 0; 251 } else { 252 mEnterPinView.setVisibility(View.GONE); 253 mWrongPinView.setVisibility(View.VISIBLE); 254 mWrongPinView.setText(getResources().getString(R.string.pin_enter_wrong_seconds, 255 secondsLeft)); 256 mHandler.postDelayed(mUpdateEnterPinRunnable, 1000); 257 } 258 } 259 260 private void exit(int retCode) { 261 mRetCode = retCode; 262 dismiss(); 263 } 264 265 @Override 266 public void onDismiss(DialogInterface dialog) { 267 super.onDismiss(dialog); 268 if (DEBUG) Log.d(TAG, "onDismiss: mRetCode=" + mRetCode); 269 270 boolean result = mRetCode == PIN_DIALOG_RESULT_SUCCESS; 271 Fragment f = getTargetFragment(); 272 if (f instanceof ResultListener) { 273 ((ResultListener) f).pinFragmentDone(getTargetRequestCode(), result); 274 } else if (getActivity() instanceof ResultListener) { 275 final ResultListener listener = (ResultListener) getActivity(); 276 listener.pinFragmentDone(getTargetRequestCode(), result); 277 } 278 } 279 280 private void handleWrongPin() { 281 if (++mWrongPinCount >= MAX_WRONG_PIN_COUNT) { 282 mDisablePinUntil = System.currentTimeMillis() + DISABLE_PIN_DURATION_MILLIS; 283 setPinDisabledUntil(mDisablePinUntil); 284 updateWrongPin(); 285 } else { 286 showToast(R.string.pin_toast_wrong); 287 } 288 } 289 290 private void showToast(int resId) { 291 Toast.makeText(getActivity(), resId, Toast.LENGTH_SHORT).show(); 292 } 293 294 private void done(String pin) { 295 if (DEBUG) Log.d(TAG, "done: mType=" + mType + " pin=" + pin); 296 switch (mType) { 297 case PIN_DIALOG_TYPE_UNLOCK_CHANNEL: 298 case PIN_DIALOG_TYPE_UNLOCK_PROGRAM: 299 case PIN_DIALOG_TYPE_ENTER_PIN: 300 case PIN_DIALOG_TYPE_DELETE_PIN: 301 // TODO: Implement limited number of retrials and timeout logic. 302 if (!isPinSet() || isPinCorrect(pin)) { 303 if (mType == PIN_DIALOG_TYPE_DELETE_PIN) { 304 deletePin(pin); 305 } 306 exit(PIN_DIALOG_RESULT_SUCCESS); 307 } else { 308 resetPinInput(); 309 handleWrongPin(); 310 } 311 break; 312 case PIN_DIALOG_TYPE_NEW_PIN: 313 resetPinInput(); 314 if (mPrevPin == null) { 315 mPrevPin = pin; 316 mTitleView.setText(R.string.pin_enter_again); 317 } else { 318 if (pin.equals(mPrevPin)) { 319 setPin(pin, mOriginalPin); 320 exit(PIN_DIALOG_RESULT_SUCCESS); 321 } else { 322 mTitleView.setText(R.string.pin_enter_new_pin); 323 mPrevPin = null; 324 showToast(R.string.pin_toast_not_match); 325 } 326 } 327 break; 328 case PIN_DIALOG_TYPE_OLD_PIN: 329 resetPinInput(); 330 if (isPinCorrect(pin)) { 331 mOriginalPin = pin; 332 mType = PIN_DIALOG_TYPE_NEW_PIN; 333 mTitleView.setText(R.string.pin_enter_new_pin); 334 } else { 335 handleWrongPin(); 336 } 337 break; 338 } 339 } 340 341 public int getType() { 342 return mType; 343 } 344 345 private String getPinInput() { 346 String result = ""; 347 try { 348 for (PinNumberPicker pnp : mPickers) { 349 pnp.updateText(); 350 result += pnp.getValue(); 351 } 352 } catch (IllegalStateException e) { 353 result = ""; 354 } 355 return result; 356 } 357 358 private void resetPinInput() { 359 for (PinNumberPicker pnp : mPickers) { 360 pnp.setValueRange(0, 9); 361 } 362 mPickers[0].requestFocus(); 363 } 364 365 public static final class PinNumberPicker extends FrameLayout { 366 private static final int NUMBER_VIEWS_RES_ID[] = { 367 R.id.previous2_number, 368 R.id.previous_number, 369 R.id.current_number, 370 R.id.next_number, 371 R.id.next2_number }; 372 private static final int CURRENT_NUMBER_VIEW_INDEX = 2; 373 374 private static Animator sFocusedNumberEnterAnimator; 375 private static Animator sFocusedNumberExitAnimator; 376 private static Animator sAdjacentNumberEnterAnimator; 377 private static Animator sAdjacentNumberExitAnimator; 378 379 private static float sAlphaForFocusedNumber; 380 private static float sAlphaForAdjacentNumber; 381 382 private int mMinValue; 383 private int mMaxValue; 384 private int mCurrentValue; 385 private int mNextValue; 386 private final int mNumberViewHeight; 387 private PinDialogFragment mDialog; 388 private PinNumberPicker mNextNumberPicker; 389 private boolean mCancelAnimation; 390 391 private final View mNumberViewHolder; 392 private final View mBackgroundView; 393 private final TextView[] mNumberViews; 394 private final OverScroller mScroller; 395 396 public PinNumberPicker(Context context) { 397 this(context, null); 398 } 399 400 public PinNumberPicker(Context context, AttributeSet attrs) { 401 this(context, attrs, 0); 402 } 403 404 public PinNumberPicker(Context context, AttributeSet attrs, int defStyleAttr) { 405 this(context, attrs, defStyleAttr, 0); 406 } 407 408 public PinNumberPicker(Context context, AttributeSet attrs, int defStyleAttr, 409 int defStyleRes) { 410 super(context, attrs, defStyleAttr, defStyleRes); 411 View view = inflate(context, R.layout.pin_number_picker, this); 412 mNumberViewHolder = view.findViewById(R.id.number_view_holder); 413 if (mNumberViewHolder == null) { 414 throw new IllegalStateException("R.id.number_view_holder missing!"); 415 } 416 mBackgroundView = view.findViewById(R.id.focused_background); 417 mNumberViews = new TextView[NUMBER_VIEWS_RES_ID.length]; 418 for (int i = 0; i < NUMBER_VIEWS_RES_ID.length; ++i) { 419 mNumberViews[i] = view.findViewById(NUMBER_VIEWS_RES_ID[i]); 420 } 421 Resources resources = context.getResources(); 422 mNumberViewHeight = resources.getDimensionPixelOffset( 423 R.dimen.pin_number_picker_text_view_height); 424 425 mScroller = new OverScroller(context); 426 427 mNumberViewHolder.setOnFocusChangeListener((v, hasFocus) -> updateFocus()); 428 429 mNumberViewHolder.setOnKeyListener((v, keyCode, event) -> { 430 if (event.getAction() == KeyEvent.ACTION_DOWN) { 431 switch (keyCode) { 432 case KeyEvent.KEYCODE_DPAD_UP: 433 case KeyEvent.KEYCODE_DPAD_DOWN: { 434 if (!mScroller.isFinished() || mCancelAnimation) { 435 endScrollAnimation(); 436 } 437 if (mScroller.isFinished() || mCancelAnimation) { 438 mCancelAnimation = false; 439 if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) { 440 mNextValue = adjustValueInValidRange(mCurrentValue + 1); 441 startScrollAnimation(true); 442 mScroller.startScroll(0, 0, 0, mNumberViewHeight, 443 getResources().getInteger( 444 R.integer.pin_number_scroll_duration)); 445 } else { 446 mNextValue = adjustValueInValidRange(mCurrentValue - 1); 447 startScrollAnimation(false); 448 mScroller.startScroll(0, 0, 0, -mNumberViewHeight, 449 getResources().getInteger( 450 R.integer.pin_number_scroll_duration)); 451 } 452 updateText(); 453 invalidate(); 454 } 455 return true; 456 } 457 } 458 } else if (event.getAction() == KeyEvent.ACTION_UP) { 459 switch (keyCode) { 460 case KeyEvent.KEYCODE_DPAD_UP: 461 case KeyEvent.KEYCODE_DPAD_DOWN: { 462 mCancelAnimation = true; 463 return true; 464 } 465 } 466 } 467 return false; 468 }); 469 mNumberViewHolder.setScrollY(mNumberViewHeight); 470 } 471 472 static void loadResources(Context context) { 473 if (sFocusedNumberEnterAnimator == null) { 474 TypedValue outValue = new TypedValue(); 475 context.getResources().getValue( 476 R.dimen.pin_alpha_for_focused_number, outValue, true); 477 sAlphaForFocusedNumber = outValue.getFloat(); 478 context.getResources().getValue( 479 R.dimen.pin_alpha_for_adjacent_number, outValue, true); 480 sAlphaForAdjacentNumber = outValue.getFloat(); 481 482 sFocusedNumberEnterAnimator = AnimatorInflater.loadAnimator(context, 483 R.animator.pin_focused_number_enter); 484 sFocusedNumberExitAnimator = AnimatorInflater.loadAnimator(context, 485 R.animator.pin_focused_number_exit); 486 sAdjacentNumberEnterAnimator = AnimatorInflater.loadAnimator(context, 487 R.animator.pin_adjacent_number_enter); 488 sAdjacentNumberExitAnimator = AnimatorInflater.loadAnimator(context, 489 R.animator.pin_adjacent_number_exit); 490 } 491 } 492 493 @Override 494 public void computeScroll() { 495 super.computeScroll(); 496 if (mScroller.computeScrollOffset()) { 497 mNumberViewHolder.setScrollY(mScroller.getCurrY() + mNumberViewHeight); 498 updateText(); 499 invalidate(); 500 } else if (mCurrentValue != mNextValue) { 501 mCurrentValue = mNextValue; 502 } 503 } 504 505 @Override 506 public boolean dispatchKeyEvent(KeyEvent event) { 507 if (event.getAction() == KeyEvent.ACTION_UP) { 508 int keyCode = event.getKeyCode(); 509 if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) { 510 jumpNextValue(keyCode - KeyEvent.KEYCODE_0); 511 } else if (keyCode != KeyEvent.KEYCODE_DPAD_CENTER 512 && keyCode != KeyEvent.KEYCODE_ENTER) { 513 return super.dispatchKeyEvent(event); 514 } 515 if (mNextNumberPicker == null) { 516 String pin = mDialog.getPinInput(); 517 if (!TextUtils.isEmpty(pin)) { 518 mDialog.done(pin); 519 } 520 } else { 521 mNextNumberPicker.requestFocus(); 522 } 523 return true; 524 } 525 return super.dispatchKeyEvent(event); 526 } 527 528 @Override 529 public void setEnabled(boolean enabled) { 530 super.setEnabled(enabled); 531 mNumberViewHolder.setFocusable(enabled); 532 for (int i = 0; i < NUMBER_VIEWS_RES_ID.length; ++i) { 533 mNumberViews[i].setEnabled(enabled); 534 } 535 } 536 537 void startScrollAnimation(boolean scrollUp) { 538 if (scrollUp) { 539 sAdjacentNumberExitAnimator.setTarget(mNumberViews[1]); 540 sFocusedNumberExitAnimator.setTarget(mNumberViews[2]); 541 sFocusedNumberEnterAnimator.setTarget(mNumberViews[3]); 542 sAdjacentNumberEnterAnimator.setTarget(mNumberViews[4]); 543 } else { 544 sAdjacentNumberEnterAnimator.setTarget(mNumberViews[0]); 545 sFocusedNumberEnterAnimator.setTarget(mNumberViews[1]); 546 sFocusedNumberExitAnimator.setTarget(mNumberViews[2]); 547 sAdjacentNumberExitAnimator.setTarget(mNumberViews[3]); 548 } 549 sAdjacentNumberExitAnimator.start(); 550 sFocusedNumberExitAnimator.start(); 551 sFocusedNumberEnterAnimator.start(); 552 sAdjacentNumberEnterAnimator.start(); 553 } 554 555 void endScrollAnimation() { 556 sAdjacentNumberExitAnimator.end(); 557 sFocusedNumberExitAnimator.end(); 558 sFocusedNumberEnterAnimator.end(); 559 sAdjacentNumberEnterAnimator.end(); 560 mCurrentValue = mNextValue; 561 mNumberViews[1].setAlpha(sAlphaForAdjacentNumber); 562 mNumberViews[2].setAlpha(sAlphaForFocusedNumber); 563 mNumberViews[3].setAlpha(sAlphaForAdjacentNumber); 564 } 565 566 void setValueRange(int min, int max) { 567 if (min > max) { 568 throw new IllegalArgumentException( 569 "The min value should be greater than or equal to the max value"); 570 } 571 mMinValue = min; 572 mMaxValue = max; 573 mNextValue = mCurrentValue = mMinValue - 1; 574 clearText(); 575 mNumberViews[CURRENT_NUMBER_VIEW_INDEX].setText(""); 576 } 577 578 void setPinDialogFragment(PinDialogFragment dlg) { 579 mDialog = dlg; 580 } 581 582 void setNextNumberPicker(PinNumberPicker picker) { 583 mNextNumberPicker = picker; 584 } 585 586 int getValue() { 587 if (mCurrentValue < mMinValue || mCurrentValue > mMaxValue) { 588 throw new IllegalStateException("Value is not set"); 589 } 590 return mCurrentValue; 591 } 592 593 void jumpNextValue(int value) { 594 if (value < mMinValue || value > mMaxValue) { 595 throw new IllegalStateException("Value is not set"); 596 } 597 mNextValue = mCurrentValue = adjustValueInValidRange(value); 598 updateText(); 599 } 600 601 void updateFocus() { 602 endScrollAnimation(); 603 if (mNumberViewHolder.isFocused()) { 604 mBackgroundView.setVisibility(View.VISIBLE); 605 updateText(); 606 } else { 607 mBackgroundView.setVisibility(View.GONE); 608 if (!mScroller.isFinished()) { 609 mCurrentValue = mNextValue; 610 mScroller.abortAnimation(); 611 } 612 clearText(); 613 mNumberViewHolder.setScrollY(mNumberViewHeight); 614 } 615 } 616 617 private void clearText() { 618 for (int i = 0; i < NUMBER_VIEWS_RES_ID.length; ++i) { 619 if (i != CURRENT_NUMBER_VIEW_INDEX) { 620 mNumberViews[i].setText(""); 621 } else if (mCurrentValue >= mMinValue && mCurrentValue <= mMaxValue) { 622 // Bullet 623 mNumberViews[i].setText("\u2022"); 624 } 625 } 626 } 627 628 private void updateText() { 629 if (mNumberViewHolder.isFocused()) { 630 if (mCurrentValue < mMinValue || mCurrentValue > mMaxValue) { 631 mNextValue = mCurrentValue = mMinValue; 632 } 633 int value = adjustValueInValidRange(mCurrentValue - CURRENT_NUMBER_VIEW_INDEX); 634 for (int i = 0; i < NUMBER_VIEWS_RES_ID.length; ++i) { 635 mNumberViews[i].setText(String.valueOf(adjustValueInValidRange(value))); 636 value = adjustValueInValidRange(value + 1); 637 } 638 } 639 } 640 641 private int adjustValueInValidRange(int value) { 642 int interval = mMaxValue - mMinValue + 1; 643 if (value < mMinValue - interval || value > mMaxValue + interval) { 644 throw new IllegalArgumentException("The value( " + value 645 + ") is too small or too big to adjust"); 646 } 647 return (value < mMinValue) ? value + interval 648 : (value > mMaxValue) ? value - interval : value; 649 } 650 } 651 } 652