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