165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane/*
265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * Copyright (C) 2014 The Android Open Source Project
365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane *
465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * Licensed under the Apache License, Version 2.0 (the "License");
565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * you may not use this file except in compliance with the License.
665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * You may obtain a copy of the License at
765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane *
865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane *      http://www.apache.org/licenses/LICENSE-2.0
965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane *
1065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * Unless required by applicable law or agreed to in writing, software
1165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * distributed under the License is distributed on an "AS IS" BASIS,
1265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * See the License for the specific language governing permissions and
1465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane * limitations under the License.
1565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane */
1665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
17a10ccbdf6a7bdf626ee6e58574d9f8d800d124f5Tony Mantlerpackage com.android.tv.settings.dialog;
1865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
1965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.animation.Animator;
2065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.animation.AnimatorInflater;
2165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.app.Dialog;
223150b12e5df05eec2c3ebb85360661f1d034c188Andrew Wilsonimport android.app.Fragment;
2365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.content.Context;
2465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.content.DialogInterface;
2565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.content.res.Resources;
2665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.os.Bundle;
2765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.os.Handler;
28ddb142fe3e0bd42dc03ca2052f30eaa468151433Tony Mantlerimport android.support.annotation.IntDef;
29ddb142fe3e0bd42dc03ca2052f30eaa468151433Tony Mantlerimport android.support.annotation.UiThread;
3065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.text.TextUtils;
3165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.util.AttributeSet;
3265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.util.Log;
3365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.util.TypedValue;
3465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.view.KeyEvent;
3565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.view.LayoutInflater;
3665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.view.View;
3765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.view.ViewGroup;
3865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.widget.FrameLayout;
3965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.widget.OverScroller;
4065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.widget.TextView;
4165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport android.widget.Toast;
4265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
4365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Laneimport com.android.tv.settings.R;
4465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
45ddb142fe3e0bd42dc03ca2052f30eaa468151433Tony Mantlerimport java.lang.annotation.Retention;
46ddb142fe3e0bd42dc03ca2052f30eaa468151433Tony Mantlerimport java.lang.annotation.RetentionPolicy;
47ddb142fe3e0bd42dc03ca2052f30eaa468151433Tony Mantler
4865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lanepublic abstract class PinDialogFragment extends SafeDismissDialogFragment {
4965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private static final String TAG = "PinDialogFragment";
506e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler    private static final boolean DEBUG = false;
5165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
52c03836a4b5c65e59fc5abc73cc42d0b56176d5e6Tony Mantler    protected static final String ARG_TYPE = "type";
53c03836a4b5c65e59fc5abc73cc42d0b56176d5e6Tony Mantler
54ddb142fe3e0bd42dc03ca2052f30eaa468151433Tony Mantler    @Retention(RetentionPolicy.SOURCE)
55ddb142fe3e0bd42dc03ca2052f30eaa468151433Tony Mantler    @IntDef({PIN_DIALOG_TYPE_UNLOCK_CHANNEL,
56ddb142fe3e0bd42dc03ca2052f30eaa468151433Tony Mantler            PIN_DIALOG_TYPE_UNLOCK_PROGRAM,
57ddb142fe3e0bd42dc03ca2052f30eaa468151433Tony Mantler            PIN_DIALOG_TYPE_ENTER_PIN,
58ddb142fe3e0bd42dc03ca2052f30eaa468151433Tony Mantler            PIN_DIALOG_TYPE_NEW_PIN,
59ddb142fe3e0bd42dc03ca2052f30eaa468151433Tony Mantler            PIN_DIALOG_TYPE_OLD_PIN})
60ddb142fe3e0bd42dc03ca2052f30eaa468151433Tony Mantler    public @interface PinDialogType {}
6165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
6265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * PIN code dialog for unlock channel
6365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
6465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public static final int PIN_DIALOG_TYPE_UNLOCK_CHANNEL = 0;
6565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
6665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
6765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * PIN code dialog for unlock content.
6865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * Only difference between {@code PIN_DIALOG_TYPE_UNLOCK_CHANNEL} is it's title.
6965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
7065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public static final int PIN_DIALOG_TYPE_UNLOCK_PROGRAM = 1;
7165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
7265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
7365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * PIN code dialog for change parental control settings
7465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
7565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public static final int PIN_DIALOG_TYPE_ENTER_PIN = 2;
7665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
7765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    /**
7865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     * PIN code dialog for set new PIN
7965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane     */
8065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public static final int PIN_DIALOG_TYPE_NEW_PIN = 3;
8165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
8265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    // PIN code dialog for checking old PIN. This is intenal only.
8365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private static final int PIN_DIALOG_TYPE_OLD_PIN = 4;
8465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
8565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private static final int PIN_DIALOG_RESULT_SUCCESS = 0;
8665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private static final int PIN_DIALOG_RESULT_FAIL = 1;
8765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
8865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private static final int MAX_WRONG_PIN_COUNT = 5;
8965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private static final int DISABLE_PIN_DURATION_MILLIS = 60 * 1000; // 1 minute
9065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
9165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public interface ResultListener {
92ddb142fe3e0bd42dc03ca2052f30eaa468151433Tony Mantler        void pinFragmentDone(int requestCode, boolean success);
9365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
9465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
9565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public static final String DIALOG_TAG = PinDialogFragment.class.getName();
9665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
9765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private static final int NUMBER_PICKERS_RES_ID[] = {
9865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            R.id.first, R.id.second, R.id.third, R.id.fourth };
9965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
10065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private int mType;
10165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private int mRetCode;
10265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
10365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private TextView mWrongPinView;
10465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private View mEnterPinView;
10565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private TextView mTitleView;
10665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private PinNumberPicker[] mPickers;
10765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private String mPrevPin;
10865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private int mWrongPinCount;
10965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private long mDisablePinUntil;
11065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private final Handler mHandler = new Handler();
11165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
11265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public abstract long getPinDisabledUntil();
11365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public abstract void setPinDisabledUntil(long retryDisableTimeout);
11465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public abstract void setPin(String pin);
11565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public abstract boolean isPinCorrect(String pin);
11665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public abstract boolean isPinSet();
11765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
118c03836a4b5c65e59fc5abc73cc42d0b56176d5e6Tony Mantler    public PinDialogFragment() {
11965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mRetCode = PIN_DIALOG_RESULT_FAIL;
12065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
12165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
12265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    @Override
12365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void onCreate(Bundle savedInstanceState) {
12465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        super.onCreate(savedInstanceState);
12565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        setStyle(STYLE_NO_TITLE, 0);
12665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mDisablePinUntil = getPinDisabledUntil();
127c03836a4b5c65e59fc5abc73cc42d0b56176d5e6Tony Mantler        final Bundle args = getArguments();
128c03836a4b5c65e59fc5abc73cc42d0b56176d5e6Tony Mantler        if (!args.containsKey(ARG_TYPE)) {
129c03836a4b5c65e59fc5abc73cc42d0b56176d5e6Tony Mantler            throw new IllegalStateException("Fragment arguments must specify type");
130c03836a4b5c65e59fc5abc73cc42d0b56176d5e6Tony Mantler        }
131c03836a4b5c65e59fc5abc73cc42d0b56176d5e6Tony Mantler        mType = getArguments().getInt(ARG_TYPE);
13265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
13365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
13465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    @Override
13565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public Dialog onCreateDialog(Bundle savedInstanceState) {
13665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        Dialog dlg = super.onCreateDialog(savedInstanceState);
13765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        dlg.getWindow().getAttributes().windowAnimations = R.style.pin_dialog_animation;
13865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        PinNumberPicker.loadResources(dlg.getContext());
13965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return dlg;
14065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
14165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
14265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    @Override
14365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public View onCreateView(LayoutInflater inflater, ViewGroup container,
14465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            Bundle savedInstanceState) {
14565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        final View v = inflater.inflate(R.layout.pin_dialog, container, false);
14665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
14765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mWrongPinView = (TextView) v.findViewById(R.id.wrong_pin);
14865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mEnterPinView = v.findViewById(R.id.enter_pin);
14965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mTitleView = (TextView) mEnterPinView.findViewById(R.id.title);
15065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (!isPinSet()) {
15165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // If PIN isn't set, user should set a PIN.
15265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // Successfully setting a new set is considered as entering correct PIN.
15365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mType = PIN_DIALOG_TYPE_NEW_PIN;
15465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
15565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        switch (mType) {
15665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            case PIN_DIALOG_TYPE_UNLOCK_CHANNEL:
15765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mTitleView.setText(R.string.pin_enter_unlock_channel);
15865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                break;
15965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            case PIN_DIALOG_TYPE_UNLOCK_PROGRAM:
16065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mTitleView.setText(R.string.pin_enter_unlock_program);
16165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                break;
16265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            case PIN_DIALOG_TYPE_ENTER_PIN:
16365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mTitleView.setText(R.string.pin_enter_pin);
16465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                break;
16565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            case PIN_DIALOG_TYPE_NEW_PIN:
16665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (!isPinSet()) {
16765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    mTitleView.setText(R.string.pin_enter_new_pin);
16865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                } else {
16965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    mTitleView.setText(R.string.pin_enter_old_pin);
17065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    mType = PIN_DIALOG_TYPE_OLD_PIN;
17165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
17265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
17365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
17465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mPickers = new PinNumberPicker[NUMBER_PICKERS_RES_ID.length];
17565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        for (int i = 0; i < NUMBER_PICKERS_RES_ID.length; i++) {
17665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mPickers[i] = (PinNumberPicker) v.findViewById(NUMBER_PICKERS_RES_ID[i]);
17765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mPickers[i].setValueRange(0, 9);
17865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mPickers[i].setPinDialogFragment(this);
17965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mPickers[i].updateFocus();
18065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
18165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        for (int i = 0; i < NUMBER_PICKERS_RES_ID.length - 1; i++) {
18265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mPickers[i].setNextNumberPicker(mPickers[i + 1]);
18365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
18465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
18565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (mType != PIN_DIALOG_TYPE_NEW_PIN) {
18665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            updateWrongPin();
18765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
18865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return v;
18965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
19065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
19165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private final Runnable mUpdateEnterPinRunnable = new Runnable() {
19265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        @Override
19365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public void run() {
19465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            updateWrongPin();
19565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
19665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    };
19765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
19865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void updateWrongPin() {
19965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (getActivity() == null) {
20065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            // The activity is already detached. No need to update.
20165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mHandler.removeCallbacks(null);
20265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return;
20365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
20465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
205134180c96a36d66e5d77fcae90d7326c8e356b79Tony Mantler        final long secondsLeft = (mDisablePinUntil - System.currentTimeMillis()) / 1000;
206134180c96a36d66e5d77fcae90d7326c8e356b79Tony Mantler        final boolean enabled = secondsLeft < 1;
20765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (enabled) {
20865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mWrongPinView.setVisibility(View.INVISIBLE);
20965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mEnterPinView.setVisibility(View.VISIBLE);
21065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mWrongPinCount = 0;
21165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        } else {
21265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mEnterPinView.setVisibility(View.INVISIBLE);
21365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mWrongPinView.setVisibility(View.VISIBLE);
214134180c96a36d66e5d77fcae90d7326c8e356b79Tony Mantler            mWrongPinView.setText(getResources().getString(R.string.pin_enter_wrong_seconds,
215134180c96a36d66e5d77fcae90d7326c8e356b79Tony Mantler                    secondsLeft));
21665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mHandler.postDelayed(mUpdateEnterPinRunnable, 1000);
21765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
21865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
21965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
22065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void exit(int retCode) {
22165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mRetCode = retCode;
22265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        dismiss();
22365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
22465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
22565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    @Override
22665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public void onDismiss(DialogInterface dialog) {
22765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        super.onDismiss(dialog);
22865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (DEBUG) Log.d(TAG, "onDismiss: mRetCode=" + mRetCode);
2293150b12e5df05eec2c3ebb85360661f1d034c188Andrew Wilson
2303150b12e5df05eec2c3ebb85360661f1d034c188Andrew Wilson        boolean result = mRetCode == PIN_DIALOG_RESULT_SUCCESS;
2313150b12e5df05eec2c3ebb85360661f1d034c188Andrew Wilson        Fragment f = getTargetFragment();
2323150b12e5df05eec2c3ebb85360661f1d034c188Andrew Wilson        if (f instanceof ResultListener) {
233ddb142fe3e0bd42dc03ca2052f30eaa468151433Tony Mantler            ((ResultListener) f).pinFragmentDone(getTargetRequestCode(), result);
2343150b12e5df05eec2c3ebb85360661f1d034c188Andrew Wilson        } else if (getActivity() instanceof ResultListener) {
2353150b12e5df05eec2c3ebb85360661f1d034c188Andrew Wilson            final ResultListener listener = (ResultListener) getActivity();
236ddb142fe3e0bd42dc03ca2052f30eaa468151433Tony Mantler            listener.pinFragmentDone(getTargetRequestCode(), result);
23765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
23865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
23965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
24065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void handleWrongPin() {
24165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (++mWrongPinCount >= MAX_WRONG_PIN_COUNT) {
24265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mDisablePinUntil = System.currentTimeMillis() + DISABLE_PIN_DURATION_MILLIS;
24365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            setPinDisabledUntil(mDisablePinUntil);
24465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            updateWrongPin();
24565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        } else {
24665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            showToast(R.string.pin_toast_wrong);
24765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
24865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
24965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
25065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void showToast(int resId) {
25165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        Toast.makeText(getActivity(), resId, Toast.LENGTH_SHORT).show();
25265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
25365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
25465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void done(String pin) {
25565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        if (DEBUG) Log.d(TAG, "done: mType=" + mType + " pin=" + pin);
25665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        switch (mType) {
25765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            case PIN_DIALOG_TYPE_UNLOCK_CHANNEL:
25865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            case PIN_DIALOG_TYPE_UNLOCK_PROGRAM:
25965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            case PIN_DIALOG_TYPE_ENTER_PIN:
26065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                // TODO: Implement limited number of retrials and timeout logic.
26165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (!isPinSet() || isPinCorrect(pin)) {
26265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    exit(PIN_DIALOG_RESULT_SUCCESS);
26365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                } else {
26465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    resetPinInput();
26565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    handleWrongPin();
26665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
26765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                break;
26865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            case PIN_DIALOG_TYPE_NEW_PIN:
26965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                resetPinInput();
27065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (mPrevPin == null) {
27165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    mPrevPin = pin;
27265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    mTitleView.setText(R.string.pin_enter_again);
27365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                } else {
27465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (pin.equals(mPrevPin)) {
27565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        setPin(pin);
27665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        exit(PIN_DIALOG_RESULT_SUCCESS);
27765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    } else {
27865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        mTitleView.setText(R.string.pin_enter_new_pin);
27965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        mPrevPin = null;
28065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        showToast(R.string.pin_toast_not_match);
28165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
28265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
28365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                break;
28465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            case PIN_DIALOG_TYPE_OLD_PIN:
28565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (isPinCorrect(pin)) {
28665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    mType = PIN_DIALOG_TYPE_NEW_PIN;
28765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    resetPinInput();
28865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    mTitleView.setText(R.string.pin_enter_new_pin);
28965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                } else {
29065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    handleWrongPin();
29165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
29265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                break;
29365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
29465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
29565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
29665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public int getType() {
29765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return mType;
29865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
29965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
30065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private String getPinInput() {
30165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        String result = "";
30265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        try {
30365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            for (PinNumberPicker pnp : mPickers) {
30465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                pnp.updateText();
30565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                result += pnp.getValue();
30665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
30765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        } catch (IllegalStateException e) {
30865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            result = "";
30965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
31065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        return result;
31165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
31265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
31365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    private void resetPinInput() {
31465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        for (PinNumberPicker pnp : mPickers) {
31565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            pnp.setValueRange(0, 9);
31665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
31765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        mPickers[0].requestFocus();
31865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
31965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
32065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    public static final class PinNumberPicker extends FrameLayout {
32165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        private static final int NUMBER_VIEWS_RES_ID[] = {
32265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            R.id.previous2_number,
32365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            R.id.previous_number,
32465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            R.id.current_number,
32565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            R.id.next_number,
32665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            R.id.next2_number };
32765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        private static final int CURRENT_NUMBER_VIEW_INDEX = 2;
32865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
32965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        private static Animator sFocusedNumberEnterAnimator;
33065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        private static Animator sFocusedNumberExitAnimator;
33165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        private static Animator sAdjacentNumberEnterAnimator;
33265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        private static Animator sAdjacentNumberExitAnimator;
33365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
33465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        private static float sAlphaForFocusedNumber;
33565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        private static float sAlphaForAdjacentNumber;
33665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
33765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        private int mMinValue;
33865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        private int mMaxValue;
33965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        private int mCurrentValue;
34065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        private int mNextValue;
3416e995161147d9110d77ae1fe38b697e52891d3f2Tony Mantler        private final int mNumberViewHeight;
34265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        private PinDialogFragment mDialog;
34365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        private PinNumberPicker mNextNumberPicker;
34465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        private boolean mCancelAnimation;
34565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
34665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        private final View mNumberViewHolder;
34765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        private final View mBackgroundView;
34865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        private final TextView[] mNumberViews;
34965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        private final OverScroller mScroller;
35065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
35165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public PinNumberPicker(Context context) {
35265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            this(context, null);
35365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
35465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
35565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public PinNumberPicker(Context context, AttributeSet attrs) {
35665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            this(context, attrs, 0);
35765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
35865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
35965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public PinNumberPicker(Context context, AttributeSet attrs, int defStyleAttr) {
36065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            this(context, attrs, defStyleAttr, 0);
36165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
36265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
36365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public PinNumberPicker(Context context, AttributeSet attrs, int defStyleAttr,
36465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                int defStyleRes) {
36565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            super(context, attrs, defStyleAttr, defStyleRes);
36665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            View view = inflate(context, R.layout.pin_number_picker, this);
36765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mNumberViewHolder = view.findViewById(R.id.number_view_holder);
36865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mBackgroundView = view.findViewById(R.id.focused_background);
36965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mNumberViews = new TextView[NUMBER_VIEWS_RES_ID.length];
37065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            for (int i = 0; i < NUMBER_VIEWS_RES_ID.length; ++i) {
37165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mNumberViews[i] = (TextView) view.findViewById(NUMBER_VIEWS_RES_ID[i]);
37265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
37365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            Resources resources = context.getResources();
37465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mNumberViewHeight = resources.getDimensionPixelOffset(
37565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    R.dimen.pin_number_picker_text_view_height);
37665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
37765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mScroller = new OverScroller(context);
37865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
37965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mNumberViewHolder.setOnFocusChangeListener(new OnFocusChangeListener() {
38065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                @Override
38165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                public void onFocusChange(View v, boolean hasFocus) {
38265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    updateFocus();
38365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
38465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            });
38565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
38665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mNumberViewHolder.setOnKeyListener(new OnKeyListener() {
38765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                @Override
38865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                public boolean onKey(View v, int keyCode, KeyEvent event) {
38965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (event.getAction() == KeyEvent.ACTION_DOWN) {
39065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        switch (keyCode) {
39165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            case KeyEvent.KEYCODE_DPAD_UP:
39265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            case KeyEvent.KEYCODE_DPAD_DOWN: {
39365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                if (!mScroller.isFinished() || mCancelAnimation) {
39465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                    endScrollAnimation();
39565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                }
39665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                if (mScroller.isFinished() || mCancelAnimation) {
39765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                    mCancelAnimation = false;
39865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                    if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
39965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                        mNextValue = adjustValueInValidRange(mCurrentValue + 1);
40065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                        startScrollAnimation(true);
40165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                        mScroller.startScroll(0, 0, 0, mNumberViewHeight,
40265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                                getResources().getInteger(
40365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                                        R.integer.pin_number_scroll_duration));
40465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                    } else {
40565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                        mNextValue = adjustValueInValidRange(mCurrentValue - 1);
40665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                        startScrollAnimation(false);
40765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                        mScroller.startScroll(0, 0, 0, -mNumberViewHeight,
40865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                                getResources().getInteger(
40965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                                        R.integer.pin_number_scroll_duration));
41065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                    }
41165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                    updateText();
41265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                    invalidate();
41365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                }
41465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                return true;
41565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            }
41665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        }
41765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    } else if (event.getAction() == KeyEvent.ACTION_UP) {
41865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        switch (keyCode) {
41965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            case KeyEvent.KEYCODE_DPAD_UP:
42065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            case KeyEvent.KEYCODE_DPAD_DOWN: {
42165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                mCancelAnimation = true;
42265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                                return true;
42365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                            }
42465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        }
42565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
42665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    return false;
42765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
42865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            });
42965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mNumberViewHolder.setScrollY(mNumberViewHeight);
43065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
43165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
43265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        static void loadResources(Context context) {
43365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (sFocusedNumberEnterAnimator == null) {
43465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                TypedValue outValue = new TypedValue();
43565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                context.getResources().getValue(
43665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        R.float_type.pin_alpha_for_focused_number, outValue, true);
43765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                sAlphaForFocusedNumber = outValue.getFloat();
43865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                context.getResources().getValue(
43965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        R.float_type.pin_alpha_for_adjacent_number, outValue, true);
44065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                sAlphaForAdjacentNumber = outValue.getFloat();
44165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
44265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                sFocusedNumberEnterAnimator = AnimatorInflater.loadAnimator(context,
44365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        R.animator.pin_focused_number_enter);
44465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                sFocusedNumberExitAnimator = AnimatorInflater.loadAnimator(context,
44565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        R.animator.pin_focused_number_exit);
44665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                sAdjacentNumberEnterAnimator = AnimatorInflater.loadAnimator(context,
44765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        R.animator.pin_adjacent_number_enter);
44865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                sAdjacentNumberExitAnimator = AnimatorInflater.loadAnimator(context,
44965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        R.animator.pin_adjacent_number_exit);
45065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
45165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
45265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
45365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        @Override
45465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public void computeScroll() {
45565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            super.computeScroll();
45665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (mScroller.computeScrollOffset()) {
45765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mNumberViewHolder.setScrollY(mScroller.getCurrY() + mNumberViewHeight);
45865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                updateText();
45965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                invalidate();
46065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            } else if (mCurrentValue != mNextValue) {
46165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mCurrentValue = mNextValue;
46265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
46365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
46465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
46565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        @Override
46665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public boolean dispatchKeyEvent(KeyEvent event) {
46765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (event.getAction() == KeyEvent.ACTION_UP) {
46865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                int keyCode = event.getKeyCode();
46965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) {
47065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    setNextValue(keyCode - KeyEvent.KEYCODE_0);
47165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                } else if (keyCode != KeyEvent.KEYCODE_DPAD_CENTER
47265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        && keyCode != KeyEvent.KEYCODE_ENTER) {
47365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    return super.dispatchKeyEvent(event);
47465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
47565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (mNextNumberPicker == null) {
47665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    String pin = mDialog.getPinInput();
47765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    if (!TextUtils.isEmpty(pin)) {
47865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        mDialog.done(pin);
47965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    }
48065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                } else {
48165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    mNextNumberPicker.requestFocus();
48265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
48365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                return true;
48465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
48565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return super.dispatchKeyEvent(event);
48665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
48765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
48865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        @Override
48965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        public void setEnabled(boolean enabled) {
49065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            super.setEnabled(enabled);
49165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mNumberViewHolder.setFocusable(enabled);
49265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            for (int i = 0; i < NUMBER_VIEWS_RES_ID.length; ++i) {
49365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mNumberViews[i].setEnabled(enabled);
49465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
49565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
49665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
49765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        void startScrollAnimation(boolean scrollUp) {
49865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (scrollUp) {
49965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                sAdjacentNumberExitAnimator.setTarget(mNumberViews[1]);
50065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                sFocusedNumberExitAnimator.setTarget(mNumberViews[2]);
50165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                sFocusedNumberEnterAnimator.setTarget(mNumberViews[3]);
50265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                sAdjacentNumberEnterAnimator.setTarget(mNumberViews[4]);
50365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            } else {
50465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                sAdjacentNumberEnterAnimator.setTarget(mNumberViews[0]);
50565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                sFocusedNumberEnterAnimator.setTarget(mNumberViews[1]);
50665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                sFocusedNumberExitAnimator.setTarget(mNumberViews[2]);
50765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                sAdjacentNumberExitAnimator.setTarget(mNumberViews[3]);
50865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
50965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            sAdjacentNumberExitAnimator.start();
51065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            sFocusedNumberExitAnimator.start();
51165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            sFocusedNumberEnterAnimator.start();
51265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            sAdjacentNumberEnterAnimator.start();
51365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
51465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
51565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        void endScrollAnimation() {
51665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            sAdjacentNumberExitAnimator.end();
51765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            sFocusedNumberExitAnimator.end();
51865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            sFocusedNumberEnterAnimator.end();
51965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            sAdjacentNumberEnterAnimator.end();
52065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mCurrentValue = mNextValue;
52165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mNumberViews[1].setAlpha(sAlphaForAdjacentNumber);
52265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mNumberViews[2].setAlpha(sAlphaForFocusedNumber);
52365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mNumberViews[3].setAlpha(sAlphaForAdjacentNumber);
52465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
52565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
52665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        void setValueRange(int min, int max) {
52765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (min > max) {
52865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                throw new IllegalArgumentException(
52965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        "The min value should be greater than or equal to the max value");
53065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
53165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mMinValue = min;
53265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mMaxValue = max;
53365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mNextValue = mCurrentValue = mMinValue - 1;
53465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            clearText();
53565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mNumberViews[CURRENT_NUMBER_VIEW_INDEX].setText("—");
53665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
53765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
53865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        void setPinDialogFragment(PinDialogFragment dlg) {
53965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mDialog = dlg;
54065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
54165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
54265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        void setNextNumberPicker(PinNumberPicker picker) {
54365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mNextNumberPicker = picker;
54465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
54565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
54665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        int getValue() {
54765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (mCurrentValue < mMinValue || mCurrentValue > mMaxValue) {
54865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                throw new IllegalStateException("Value is not set");
54965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
55065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return mCurrentValue;
55165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
55265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
55365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        // Will take effect when the focus is updated.
55465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        void setNextValue(int value) {
55565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (value < mMinValue || value > mMaxValue) {
55665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                throw new IllegalStateException("Value is not set");
55765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
55865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            mNextValue = adjustValueInValidRange(value);
55965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
56065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
56165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        void updateFocus() {
56265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            endScrollAnimation();
56365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (mNumberViewHolder.isFocused()) {
56465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mBackgroundView.setVisibility(View.VISIBLE);
56565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                updateText();
56665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            } else {
56765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mBackgroundView.setVisibility(View.GONE);
56865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (!mScroller.isFinished()) {
56965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    mCurrentValue = mNextValue;
57065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    mScroller.abortAnimation();
57165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
57265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                clearText();
57365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                mNumberViewHolder.setScrollY(mNumberViewHeight);
57465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
57565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
57665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
57765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        private void clearText() {
57865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            for (int i = 0; i < NUMBER_VIEWS_RES_ID.length; ++i) {
57965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (i != CURRENT_NUMBER_VIEW_INDEX) {
58065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    mNumberViews[i].setText("");
58165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                } else if (mCurrentValue >= mMinValue && mCurrentValue <= mMaxValue) {
5827cdb6b2f0a41e3f584c6c943d5fbf93ba7570eeeTony Mantler                    // Bullet
5837cdb6b2f0a41e3f584c6c943d5fbf93ba7570eeeTony Mantler                    mNumberViews[i].setText("\u2022");
58465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
58565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
58665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
58765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
58865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        private void updateText() {
58965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (mNumberViewHolder.isFocused()) {
59065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                if (mCurrentValue < mMinValue || mCurrentValue > mMaxValue) {
59165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    mNextValue = mCurrentValue = mMinValue;
59265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
59365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                int value = adjustValueInValidRange(mCurrentValue - CURRENT_NUMBER_VIEW_INDEX);
59465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                for (int i = 0; i < NUMBER_VIEWS_RES_ID.length; ++i) {
59565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    mNumberViews[i].setText(String.valueOf(adjustValueInValidRange(value)));
59665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    value = adjustValueInValidRange(value + 1);
59765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                }
59865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
59965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
60065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane
60165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        private int adjustValueInValidRange(int value) {
60265a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            int interval = mMaxValue - mMinValue + 1;
60365a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            if (value < mMinValue - interval || value > mMaxValue + interval) {
60465a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                throw new IllegalArgumentException("The value( " + value
60565a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                        + ") is too small or too big to adjust");
60665a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            }
60765a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane            return (value < mMinValue) ? value + interval
60865a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane                    : (value > mMaxValue) ? value - interval : value;
60965a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane        }
61065a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane    }
61165a5a7d84ad9b5324ae53eda526e39e513473af7Christopher Lane}
612