FileDocCategorySizeDatePackage
TimePickerClockDelegate.javaAPI DocAndroid 5.1 API48076Thu Mar 12 22:22:10 GMT 2015android.widget

TimePickerClockDelegate

public class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate implements RadialTimePickerView.OnValueSelectedListener
A delegate implementing the radial clock-based TimePicker.

Fields Summary
private static final String
TAG
private static final int
HOUR_INDEX
private static final int
MINUTE_INDEX
private static final int
AMPM_INDEX
private static final int
ENABLE_PICKER_INDEX
static final int
AM
static final int
PM
private static final boolean
DEFAULT_ENABLED_STATE
private boolean
mIsEnabled
private static final int
HOURS_IN_HALF_DAY
private final android.view.View
mHeaderView
private final TextView
mHourView
private final TextView
mMinuteView
private final android.view.View
mAmPmLayout
private final CheckedTextView
mAmLabel
private final CheckedTextView
mPmLabel
private final RadialTimePickerView
mRadialTimePickerView
private final TextView
mSeparatorView
private final String
mAmText
private final String
mPmText
private final float
mDisabledAlpha
private boolean
mAllowAutoAdvance
private int
mInitialHourOfDay
private int
mInitialMinute
private boolean
mIs24HourView
private char
mPlaceholderText
private String
mDoublePlaceholderText
private String
mDeletedKeyFormat
private boolean
mInKbMode
private ArrayList
mTypedTimes
private Node
mLegalTimesTree
private int
mAmKeyCode
private int
mPmKeyCode
private String
mSelectHours
private String
mSelectMinutes
private CharSequence
mLastAnnouncedText
private boolean
mLastAnnouncedIsHour
private Calendar
mTempCalendar
private final View.OnClickListener
mClickListener
private final View.OnKeyListener
mKeyListener
private final View.OnFocusChangeListener
mFocusListener
Constructors Summary
public TimePickerClockDelegate(TimePicker delegator, android.content.Context context, android.util.AttributeSet attrs, int defStyleAttr, int defStyleRes)


          
                
        super(delegator, context);

        // process style attributes
        final TypedArray a = mContext.obtainStyledAttributes(attrs,
                R.styleable.TimePicker, defStyleAttr, defStyleRes);
        final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
                Context.LAYOUT_INFLATER_SERVICE);
        final Resources res = mContext.getResources();

        mSelectHours = res.getString(R.string.select_hours);
        mSelectMinutes = res.getString(R.string.select_minutes);

        String[] amPmStrings = TimePickerSpinnerDelegate.getAmPmStrings(context);
        mAmText = amPmStrings[0];
        mPmText = amPmStrings[1];

        final int layoutResourceId = a.getResourceId(R.styleable.TimePicker_internalLayout,
                R.layout.time_picker_holo);
        final View mainView = inflater.inflate(layoutResourceId, delegator);

        mHeaderView = mainView.findViewById(R.id.time_header);
        mHeaderView.setBackground(a.getDrawable(R.styleable.TimePicker_headerBackground));

        // Set up hour/minute labels.
        mHourView = (TextView) mHeaderView.findViewById(R.id.hours);
        mHourView.setOnClickListener(mClickListener);
        mHourView.setAccessibilityDelegate(
                new ClickActionDelegate(context, R.string.select_hours));
        mSeparatorView = (TextView) mHeaderView.findViewById(R.id.separator);
        mMinuteView = (TextView) mHeaderView.findViewById(R.id.minutes);
        mMinuteView.setOnClickListener(mClickListener);
        mMinuteView.setAccessibilityDelegate(
                new ClickActionDelegate(context, R.string.select_minutes));

        final int headerTimeTextAppearance = a.getResourceId(
                R.styleable.TimePicker_headerTimeTextAppearance, 0);
        if (headerTimeTextAppearance != 0) {
            mHourView.setTextAppearance(context, headerTimeTextAppearance);
            mSeparatorView.setTextAppearance(context, headerTimeTextAppearance);
            mMinuteView.setTextAppearance(context, headerTimeTextAppearance);
        }

        // Now that we have text appearances out of the way, make sure the hour
        // and minute views are correctly sized.
        mHourView.setMinWidth(computeStableWidth(mHourView, 24));
        mMinuteView.setMinWidth(computeStableWidth(mMinuteView, 60));

        // TODO: This can be removed once we support themed color state lists.
        final int headerSelectedTextColor = a.getColor(
                R.styleable.TimePicker_headerSelectedTextColor,
                res.getColor(R.color.timepicker_default_selector_color_material));
        mHourView.setTextColor(ColorStateList.addFirstIfMissing(mHourView.getTextColors(),
                R.attr.state_selected, headerSelectedTextColor));
        mMinuteView.setTextColor(ColorStateList.addFirstIfMissing(mMinuteView.getTextColors(),
                R.attr.state_selected, headerSelectedTextColor));

        // Set up AM/PM labels.
        mAmPmLayout = mHeaderView.findViewById(R.id.ampm_layout);
        mAmLabel = (CheckedTextView) mAmPmLayout.findViewById(R.id.am_label);
        mAmLabel.setText(amPmStrings[0]);
        mAmLabel.setOnClickListener(mClickListener);
        mPmLabel = (CheckedTextView) mAmPmLayout.findViewById(R.id.pm_label);
        mPmLabel.setText(amPmStrings[1]);
        mPmLabel.setOnClickListener(mClickListener);

        final int headerAmPmTextAppearance = a.getResourceId(
                R.styleable.TimePicker_headerAmPmTextAppearance, 0);
        if (headerAmPmTextAppearance != 0) {
            mAmLabel.setTextAppearance(context, headerAmPmTextAppearance);
            mPmLabel.setTextAppearance(context, headerAmPmTextAppearance);
        }

        a.recycle();

        // Pull disabled alpha from theme.
        final TypedValue outValue = new TypedValue();
        context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, outValue, true);
        mDisabledAlpha = outValue.getFloat();

        mRadialTimePickerView = (RadialTimePickerView) mainView.findViewById(
                R.id.radial_picker);

        setupListeners();

        mAllowAutoAdvance = true;

        // Set up for keyboard mode.
        mDoublePlaceholderText = res.getString(R.string.time_placeholder);
        mDeletedKeyFormat = res.getString(R.string.deleted_key);
        mPlaceholderText = mDoublePlaceholderText.charAt(0);
        mAmKeyCode = mPmKeyCode = -1;
        generateLegalTimesTree();

        // Initialize with current time
        final Calendar calendar = Calendar.getInstance(mCurrentLocale);
        final int currentHour = calendar.get(Calendar.HOUR_OF_DAY);
        final int currentMinute = calendar.get(Calendar.MINUTE);
        initialize(currentHour, currentMinute, false /* 12h */, HOUR_INDEX);
    
Methods Summary
private booleanaddKeyIfLegal(int keyCode)

        // If we're in 24hour mode, we'll need to check if the input is full. If in AM/PM mode,
        // we'll need to see if AM/PM have been typed.
        if ((mIs24HourView && mTypedTimes.size() == 4) ||
                (!mIs24HourView && isTypedTimeFullyLegal())) {
            return false;
        }

        mTypedTimes.add(keyCode);
        if (!isTypedTimeLegalSoFar()) {
            deleteLastTypedKey();
            return false;
        }

        int val = getValFromKeyCode(keyCode);
        mDelegator.announceForAccessibility(String.format("%d", val));
        // Automatically fill in 0's if AM or PM was legally entered.
        if (isTypedTimeFullyLegal()) {
            if (!mIs24HourView && mTypedTimes.size() <= 3) {
                mTypedTimes.add(mTypedTimes.size() - 1, KeyEvent.KEYCODE_0);
                mTypedTimes.add(mTypedTimes.size() - 1, KeyEvent.KEYCODE_0);
            }
            onValidationChanged(true);
        }

        return true;
    
private intcomputeStableWidth(TextView v, int maxNumber)

        int maxWidth = 0;

        for (int i = 0; i < maxNumber; i++) {
            final String text = String.format("%02d", i);
            v.setText(text);
            v.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);

            final int width = v.getMeasuredWidth();
            if (width > maxWidth) {
                maxWidth = width;
            }
        }

        return maxWidth;
    
private intdeleteLastTypedKey()

        int deleted = mTypedTimes.remove(mTypedTimes.size() - 1);
        if (!isTypedTimeFullyLegal()) {
            onValidationChanged(false);
        }
        return deleted;
    
public booleandispatchPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent event)

        onPopulateAccessibilityEvent(event);
        return true;
    
private voidfinishKbMode()
Get out of keyboard mode. If there is nothing in typedTimes, revert to TimePicker's time.

        mInKbMode = false;
        if (!mTypedTimes.isEmpty()) {
            int values[] = getEnteredTime(null);
            mRadialTimePickerView.setCurrentHour(values[0]);
            mRadialTimePickerView.setCurrentMinute(values[1]);
            if (!mIs24HourView) {
                mRadialTimePickerView.setAmOrPm(values[2]);
            }
            mTypedTimes.clear();
        }
        updateDisplay(false);
        mRadialTimePickerView.setInputEnabled(true);
    
private voidgenerateLegalTimesTree()
Create a tree for deciding what keys can legally be typed.

        // Create a quick cache of numbers to their keycodes.
        final int k0 = KeyEvent.KEYCODE_0;
        final int k1 = KeyEvent.KEYCODE_1;
        final int k2 = KeyEvent.KEYCODE_2;
        final int k3 = KeyEvent.KEYCODE_3;
        final int k4 = KeyEvent.KEYCODE_4;
        final int k5 = KeyEvent.KEYCODE_5;
        final int k6 = KeyEvent.KEYCODE_6;
        final int k7 = KeyEvent.KEYCODE_7;
        final int k8 = KeyEvent.KEYCODE_8;
        final int k9 = KeyEvent.KEYCODE_9;

        // The root of the tree doesn't contain any numbers.
        mLegalTimesTree = new Node();
        if (mIs24HourView) {
            // We'll be re-using these nodes, so we'll save them.
            Node minuteFirstDigit = new Node(k0, k1, k2, k3, k4, k5);
            Node minuteSecondDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
            // The first digit must be followed by the second digit.
            minuteFirstDigit.addChild(minuteSecondDigit);

            // The first digit may be 0-1.
            Node firstDigit = new Node(k0, k1);
            mLegalTimesTree.addChild(firstDigit);

            // When the first digit is 0-1, the second digit may be 0-5.
            Node secondDigit = new Node(k0, k1, k2, k3, k4, k5);
            firstDigit.addChild(secondDigit);
            // We may now be followed by the first minute digit. E.g. 00:09, 15:58.
            secondDigit.addChild(minuteFirstDigit);

            // When the first digit is 0-1, and the second digit is 0-5, the third digit may be 6-9.
            Node thirdDigit = new Node(k6, k7, k8, k9);
            // The time must now be finished. E.g. 0:55, 1:08.
            secondDigit.addChild(thirdDigit);

            // When the first digit is 0-1, the second digit may be 6-9.
            secondDigit = new Node(k6, k7, k8, k9);
            firstDigit.addChild(secondDigit);
            // We must now be followed by the first minute digit. E.g. 06:50, 18:20.
            secondDigit.addChild(minuteFirstDigit);

            // The first digit may be 2.
            firstDigit = new Node(k2);
            mLegalTimesTree.addChild(firstDigit);

            // When the first digit is 2, the second digit may be 0-3.
            secondDigit = new Node(k0, k1, k2, k3);
            firstDigit.addChild(secondDigit);
            // We must now be followed by the first minute digit. E.g. 20:50, 23:09.
            secondDigit.addChild(minuteFirstDigit);

            // When the first digit is 2, the second digit may be 4-5.
            secondDigit = new Node(k4, k5);
            firstDigit.addChild(secondDigit);
            // We must now be followd by the last minute digit. E.g. 2:40, 2:53.
            secondDigit.addChild(minuteSecondDigit);

            // The first digit may be 3-9.
            firstDigit = new Node(k3, k4, k5, k6, k7, k8, k9);
            mLegalTimesTree.addChild(firstDigit);
            // We must now be followed by the first minute digit. E.g. 3:57, 8:12.
            firstDigit.addChild(minuteFirstDigit);
        } else {
            // We'll need to use the AM/PM node a lot.
            // Set up AM and PM to respond to "a" and "p".
            Node ampm = new Node(getAmOrPmKeyCode(AM), getAmOrPmKeyCode(PM));

            // The first hour digit may be 1.
            Node firstDigit = new Node(k1);
            mLegalTimesTree.addChild(firstDigit);
            // We'll allow quick input of on-the-hour times. E.g. 1pm.
            firstDigit.addChild(ampm);

            // When the first digit is 1, the second digit may be 0-2.
            Node secondDigit = new Node(k0, k1, k2);
            firstDigit.addChild(secondDigit);
            // Also for quick input of on-the-hour times. E.g. 10pm, 12am.
            secondDigit.addChild(ampm);

            // When the first digit is 1, and the second digit is 0-2, the third digit may be 0-5.
            Node thirdDigit = new Node(k0, k1, k2, k3, k4, k5);
            secondDigit.addChild(thirdDigit);
            // The time may be finished now. E.g. 1:02pm, 1:25am.
            thirdDigit.addChild(ampm);

            // When the first digit is 1, the second digit is 0-2, and the third digit is 0-5,
            // the fourth digit may be 0-9.
            Node fourthDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
            thirdDigit.addChild(fourthDigit);
            // The time must be finished now. E.g. 10:49am, 12:40pm.
            fourthDigit.addChild(ampm);

            // When the first digit is 1, and the second digit is 0-2, the third digit may be 6-9.
            thirdDigit = new Node(k6, k7, k8, k9);
            secondDigit.addChild(thirdDigit);
            // The time must be finished now. E.g. 1:08am, 1:26pm.
            thirdDigit.addChild(ampm);

            // When the first digit is 1, the second digit may be 3-5.
            secondDigit = new Node(k3, k4, k5);
            firstDigit.addChild(secondDigit);

            // When the first digit is 1, and the second digit is 3-5, the third digit may be 0-9.
            thirdDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
            secondDigit.addChild(thirdDigit);
            // The time must be finished now. E.g. 1:39am, 1:50pm.
            thirdDigit.addChild(ampm);

            // The hour digit may be 2-9.
            firstDigit = new Node(k2, k3, k4, k5, k6, k7, k8, k9);
            mLegalTimesTree.addChild(firstDigit);
            // We'll allow quick input of on-the-hour-times. E.g. 2am, 5pm.
            firstDigit.addChild(ampm);

            // When the first digit is 2-9, the second digit may be 0-5.
            secondDigit = new Node(k0, k1, k2, k3, k4, k5);
            firstDigit.addChild(secondDigit);

            // When the first digit is 2-9, and the second digit is 0-5, the third digit may be 0-9.
            thirdDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
            secondDigit.addChild(thirdDigit);
            // The time must be finished now. E.g. 2:57am, 9:30pm.
            thirdDigit.addChild(ampm);
        }
    
private intgetAmOrPmKeyCode(int amOrPm)
Get the keycode value for AM and PM in the current language.

        // Cache the codes.
        if (mAmKeyCode == -1 || mPmKeyCode == -1) {
            // Find the first character in the AM/PM text that is unique.
            final KeyCharacterMap kcm = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
            final CharSequence amText = mAmText.toLowerCase(mCurrentLocale);
            final CharSequence pmText = mPmText.toLowerCase(mCurrentLocale);
            final int N = Math.min(amText.length(), pmText.length());
            for (int i = 0; i < N; i++) {
                final char amChar = amText.charAt(i);
                final char pmChar = pmText.charAt(i);
                if (amChar != pmChar) {
                    // There should be 4 events: a down and up for both AM and PM.
                    final KeyEvent[] events = kcm.getEvents(new char[] { amChar, pmChar });
                    if (events != null && events.length == 4) {
                        mAmKeyCode = events[0].getKeyCode();
                        mPmKeyCode = events[2].getKeyCode();
                    } else {
                        Log.e(TAG, "Unable to find keycodes for AM and PM.");
                    }
                    break;
                }
            }
        }

        if (amOrPm == AM) {
            return mAmKeyCode;
        } else if (amOrPm == PM) {
            return mPmKeyCode;
        }

        return -1;
    
public intgetBaseline()

        // does not support baseline alignment
        return -1;
    
public java.lang.IntegergetCurrentHour()

return
The current hour in the range (0-23).

        int currentHour = mRadialTimePickerView.getCurrentHour();
        if (mIs24HourView) {
            return currentHour;
        } else {
            switch(mRadialTimePickerView.getAmOrPm()) {
                case PM:
                    return (currentHour % HOURS_IN_HALF_DAY) + HOURS_IN_HALF_DAY;
                case AM:
                default:
                    return currentHour % HOURS_IN_HALF_DAY;
            }
        }
    
private intgetCurrentItemShowing()

return
the index of the current item showing

        return mRadialTimePickerView.getCurrentItemShowing();
    
public java.lang.IntegergetCurrentMinute()

return
The current minute.

        return mRadialTimePickerView.getCurrentMinute();
    
private int[]getEnteredTime(boolean[] enteredZeros)
Get the currently-entered time, as integer values of the hours and minutes typed.

param
enteredZeros A size-2 boolean array, which the caller should initialize, and which may then be used for the caller to know whether zeros had been explicitly entered as either hours of minutes. This is helpful for deciding whether to show the dashes, or actual 0's.
return
A size-3 int array. The first value will be the hours, the second value will be the minutes, and the third will be either AM or PM.

        int amOrPm = -1;
        int startIndex = 1;
        if (!mIs24HourView && isTypedTimeFullyLegal()) {
            int keyCode = mTypedTimes.get(mTypedTimes.size() - 1);
            if (keyCode == getAmOrPmKeyCode(AM)) {
                amOrPm = AM;
            } else if (keyCode == getAmOrPmKeyCode(PM)){
                amOrPm = PM;
            }
            startIndex = 2;
        }
        int minute = -1;
        int hour = -1;
        for (int i = startIndex; i <= mTypedTimes.size(); i++) {
            int val = getValFromKeyCode(mTypedTimes.get(mTypedTimes.size() - i));
            if (i == startIndex) {
                minute = val;
            } else if (i == startIndex+1) {
                minute += 10 * val;
                if (enteredZeros != null && val == 0) {
                    enteredZeros[1] = true;
                }
            } else if (i == startIndex+2) {
                hour = val;
            } else if (i == startIndex+3) {
                hour += 10 * val;
                if (enteredZeros != null && val == 0) {
                    enteredZeros[0] = true;
                }
            }
        }

        return new int[] { hour, minute, amOrPm };
    
private java.util.ArrayListgetTypedTimes()

return
an array of typed times

        return mTypedTimes;
    
private intgetValFromKeyCode(int keyCode)

        switch (keyCode) {
            case KeyEvent.KEYCODE_0:
                return 0;
            case KeyEvent.KEYCODE_1:
                return 1;
            case KeyEvent.KEYCODE_2:
                return 2;
            case KeyEvent.KEYCODE_3:
                return 3;
            case KeyEvent.KEYCODE_4:
                return 4;
            case KeyEvent.KEYCODE_5:
                return 5;
            case KeyEvent.KEYCODE_6:
                return 6;
            case KeyEvent.KEYCODE_7:
                return 7;
            case KeyEvent.KEYCODE_8:
                return 8;
            case KeyEvent.KEYCODE_9:
                return 9;
            default:
                return -1;
        }
    
private booleaninKbMode()

return
true if in keyboard mode

        return mInKbMode;
    
private voidinitialize(int hourOfDay, int minute, boolean is24HourView, int index)

        mInitialHourOfDay = hourOfDay;
        mInitialMinute = minute;
        mIs24HourView = is24HourView;
        mInKbMode = false;
        updateUI(index);
    
public booleanis24HourView()

return
true if this is in 24 hour view else false.

        return mIs24HourView;
    
public booleanisEnabled()

        return mIsEnabled;
    
private booleanisTypedTimeFullyLegal()
Check if the time that has been typed so far is completely legal, as is.

        if (mIs24HourView) {
            // For 24-hour mode, the time is legal if the hours and minutes are each legal. Note:
            // getEnteredTime() will ONLY call isTypedTimeFullyLegal() when NOT in 24hour mode.
            int[] values = getEnteredTime(null);
            return (values[0] >= 0 && values[1] >= 0 && values[1] < 60);
        } else {
            // For AM/PM mode, the time is legal if it contains an AM or PM, as those can only be
            // legally added at specific times based on the tree's algorithm.
            return (mTypedTimes.contains(getAmOrPmKeyCode(AM)) ||
                    mTypedTimes.contains(getAmOrPmKeyCode(PM)));
        }
    
private booleanisTypedTimeLegalSoFar()
Traverse the tree to see if the keys that have been typed so far are legal as is, or may become legal as more keys are typed (excluding backspace).

        Node node = mLegalTimesTree;
        for (int keyCode : mTypedTimes) {
            node = node.canReach(keyCode);
            if (node == null) {
                return false;
            }
        }
        return true;
    
private static intlastIndexOfAny(java.lang.String str, char[] any)

        final int lengthAny = any.length;
        if (lengthAny > 0) {
            for (int i = str.length() - 1; i >= 0; i--) {
                char c = str.charAt(i);
                for (int j = 0; j < lengthAny; j++) {
                    if (c == any[j]) {
                        return i;
                    }
                }
            }
        }
        return -1;
    
private static intmodulo12(int n, boolean startWithZero)

        int value = n % 12;
        if (value == 0 && !startWithZero) {
            value = 12;
        }
        return value;
    
public voidonConfigurationChanged(android.content.res.Configuration newConfig)

        updateUI(mRadialTimePickerView.getCurrentItemShowing());
    
public voidonInitializeAccessibilityEvent(android.view.accessibility.AccessibilityEvent event)

        event.setClassName(TimePicker.class.getName());
    
public voidonInitializeAccessibilityNodeInfo(android.view.accessibility.AccessibilityNodeInfo info)

        info.setClassName(TimePicker.class.getName());
    
public voidonPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent event)

        int flags = DateUtils.FORMAT_SHOW_TIME;
        if (mIs24HourView) {
            flags |= DateUtils.FORMAT_24HOUR;
        } else {
            flags |= DateUtils.FORMAT_12HOUR;
        }
        mTempCalendar.set(Calendar.HOUR_OF_DAY, getCurrentHour());
        mTempCalendar.set(Calendar.MINUTE, getCurrentMinute());
        String selectedDate = DateUtils.formatDateTime(mContext,
                mTempCalendar.getTimeInMillis(), flags);
        event.getText().add(selectedDate);
    
public voidonRestoreInstanceState(android.os.Parcelable state)

        SavedState ss = (SavedState) state;
        setInKbMode(ss.inKbMode());
        setTypedTimes(ss.getTypesTimes());
        initialize(ss.getHour(), ss.getMinute(), ss.is24HourMode(), ss.getCurrentItemShowing());
        mRadialTimePickerView.invalidate();
        if (mInKbMode) {
            tryStartingKbMode(-1);
            mHourView.invalidate();
        }
    
public android.os.ParcelableonSaveInstanceState(android.os.Parcelable superState)

        return new SavedState(superState, getCurrentHour(), getCurrentMinute(),
                is24HourView(), inKbMode(), getTypedTimes(), getCurrentItemShowing());
    
private voidonTimeChanged()
Propagate the time change

        mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
        if (mOnTimeChangedListener != null) {
            mOnTimeChangedListener.onTimeChanged(mDelegator,
                    getCurrentHour(), getCurrentMinute());
        }
    
public voidonValueSelected(int pickerIndex, int newValue, boolean autoAdvance)
Called by the picker for updating the header display.

        switch (pickerIndex) {
            case HOUR_INDEX:
                if (mAllowAutoAdvance && autoAdvance) {
                    updateHeaderHour(newValue, false);
                    setCurrentItemShowing(MINUTE_INDEX, true, false);
                    mDelegator.announceForAccessibility(newValue + ". " + mSelectMinutes);
                } else {
                    updateHeaderHour(newValue, true);
                }
                break;
            case MINUTE_INDEX:
                updateHeaderMinute(newValue, true);
                break;
            case AMPM_INDEX:
                updateAmPmLabelStates(newValue);
                break;
            case ENABLE_PICKER_INDEX:
                if (!isTypedTimeFullyLegal()) {
                    mTypedTimes.clear();
                }
                finishKbMode();
                break;
        }

        if (mOnTimeChangedListener != null) {
            mOnTimeChangedListener.onTimeChanged(mDelegator, getCurrentHour(), getCurrentMinute());
        }
    
private booleanprocessKeyUp(int keyCode)
For keyboard mode, processes key events.

param
keyCode the pressed key.
return
true if the key was successfully processed, false otherwise.

        if (keyCode == KeyEvent.KEYCODE_DEL) {
            if (mInKbMode) {
                if (!mTypedTimes.isEmpty()) {
                    int deleted = deleteLastTypedKey();
                    String deletedKeyStr;
                    if (deleted == getAmOrPmKeyCode(AM)) {
                        deletedKeyStr = mAmText;
                    } else if (deleted == getAmOrPmKeyCode(PM)) {
                        deletedKeyStr = mPmText;
                    } else {
                        deletedKeyStr = String.format("%d", getValFromKeyCode(deleted));
                    }
                    mDelegator.announceForAccessibility(
                            String.format(mDeletedKeyFormat, deletedKeyStr));
                    updateDisplay(true);
                }
            }
        } else if (keyCode == KeyEvent.KEYCODE_0 || keyCode == KeyEvent.KEYCODE_1
                || keyCode == KeyEvent.KEYCODE_2 || keyCode == KeyEvent.KEYCODE_3
                || keyCode == KeyEvent.KEYCODE_4 || keyCode == KeyEvent.KEYCODE_5
                || keyCode == KeyEvent.KEYCODE_6 || keyCode == KeyEvent.KEYCODE_7
                || keyCode == KeyEvent.KEYCODE_8 || keyCode == KeyEvent.KEYCODE_9
                || (!mIs24HourView &&
                (keyCode == getAmOrPmKeyCode(AM) || keyCode == getAmOrPmKeyCode(PM)))) {
            if (!mInKbMode) {
                if (mRadialTimePickerView == null) {
                    // Something's wrong, because time picker should definitely not be null.
                    Log.e(TAG, "Unable to initiate keyboard mode, TimePicker was null.");
                    return true;
                }
                mTypedTimes.clear();
                tryStartingKbMode(keyCode);
                return true;
            }
            // We're already in keyboard mode.
            if (addKeyIfLegal(keyCode)) {
                updateDisplay(false);
            }
            return true;
        }
        return false;
    
private voidsetAmOrPm(int amOrPm)

        updateAmPmLabelStates(amOrPm);
        mRadialTimePickerView.setAmOrPm(amOrPm);
    
public voidsetCurrentHour(java.lang.Integer currentHour)
Set the current hour.

        if (mInitialHourOfDay == currentHour) {
            return;
        }
        mInitialHourOfDay = currentHour;
        updateHeaderHour(currentHour, true);
        updateHeaderAmPm();
        mRadialTimePickerView.setCurrentHour(currentHour);
        mRadialTimePickerView.setAmOrPm(mInitialHourOfDay < 12 ? AM : PM);
        mDelegator.invalidate();
        onTimeChanged();
    
private voidsetCurrentItemShowing(int index, boolean animateCircle, boolean announce)
Show either Hours or Minutes.

        mRadialTimePickerView.setCurrentItemShowing(index, animateCircle);

        if (index == HOUR_INDEX) {
            if (announce) {
                mDelegator.announceForAccessibility(mSelectHours);
            }
        } else {
            if (announce) {
                mDelegator.announceForAccessibility(mSelectMinutes);
            }
        }

        mHourView.setSelected(index == HOUR_INDEX);
        mMinuteView.setSelected(index == MINUTE_INDEX);
    
public voidsetCurrentLocale(java.util.Locale locale)

        super.setCurrentLocale(locale);
        mTempCalendar = Calendar.getInstance(locale);
    
public voidsetCurrentMinute(java.lang.Integer currentMinute)
Set the current minute (0-59).

        if (mInitialMinute == currentMinute) {
            return;
        }
        mInitialMinute = currentMinute;
        updateHeaderMinute(currentMinute, true);
        mRadialTimePickerView.setCurrentMinute(currentMinute);
        mDelegator.invalidate();
        onTimeChanged();
    
public voidsetEnabled(boolean enabled)

        mHourView.setEnabled(enabled);
        mMinuteView.setEnabled(enabled);
        mAmLabel.setEnabled(enabled);
        mPmLabel.setEnabled(enabled);
        mRadialTimePickerView.setEnabled(enabled);
        mIsEnabled = enabled;
    
private voidsetInKbMode(boolean inKbMode)
Set whether in keyboard mode or not.

param
inKbMode True means in keyboard mode.

        mInKbMode = inKbMode;
    
public voidsetIs24HourView(java.lang.Boolean is24HourView)
Set whether in 24 hour or AM/PM mode.

param
is24HourView True = 24 hour mode. False = AM/PM.

        if (is24HourView == mIs24HourView) {
            return;
        }
        mIs24HourView = is24HourView;
        generateLegalTimesTree();
        int hour = mRadialTimePickerView.getCurrentHour();
        mInitialHourOfDay = hour;
        updateHeaderHour(hour, false);
        updateHeaderAmPm();
        updateRadialPicker(mRadialTimePickerView.getCurrentItemShowing());
        mDelegator.invalidate();
    
public voidsetOnTimeChangedListener(TimePicker.OnTimeChangedListener callback)

        mOnTimeChangedListener = callback;
    
private voidsetTypedTimes(java.util.ArrayList typeTimes)

        mTypedTimes = typeTimes;
    
private voidsetupListeners()

        mHeaderView.setOnKeyListener(mKeyListener);
        mHeaderView.setOnFocusChangeListener(mFocusListener);
        mHeaderView.setFocusable(true);

        mRadialTimePickerView.setOnValueSelectedListener(this);
    
private voidtryAnnounceForAccessibility(java.lang.CharSequence text, boolean isHour)

        if (mLastAnnouncedIsHour != isHour || !text.equals(mLastAnnouncedText)) {
            // TODO: Find a better solution, potentially live regions?
            mDelegator.announceForAccessibility(text);
            mLastAnnouncedText = text;
            mLastAnnouncedIsHour = isHour;
        }
    
private voidtryStartingKbMode(int keyCode)
Try to start keyboard mode with the specified key.

param
keyCode The key to use as the first press. Keyboard mode will not be started if the key is not legal to start with. Or, pass in -1 to get into keyboard mode without a starting key.

        if (keyCode == -1 || addKeyIfLegal(keyCode)) {
            mInKbMode = true;
            onValidationChanged(false);
            updateDisplay(false);
            mRadialTimePickerView.setInputEnabled(false);
        }
    
private voidtryVibrate()

    

       
        mDelegator.performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK);
    
private voidupdateAmPmLabelStates(int amOrPm)

        final boolean isAm = amOrPm == AM;
        mAmLabel.setChecked(isAm);
        mAmLabel.setAlpha(isAm ? 1 : mDisabledAlpha);

        final boolean isPm = amOrPm == PM;
        mPmLabel.setChecked(isPm);
        mPmLabel.setAlpha(isPm ? 1 : mDisabledAlpha);
    
private voidupdateDisplay(boolean allowEmptyDisplay)
Update the hours, minutes, and AM/PM displays with the typed times. If the typedTimes is empty, either show an empty display (filled with the placeholder text), or update from the timepicker's values.

param
allowEmptyDisplay if true, then if the typedTimes is empty, use the placeholder text. Otherwise, revert to the timepicker's values.

        if (!allowEmptyDisplay && mTypedTimes.isEmpty()) {
            int hour = mRadialTimePickerView.getCurrentHour();
            int minute = mRadialTimePickerView.getCurrentMinute();
            updateHeaderHour(hour, false);
            updateHeaderMinute(minute, false);
            if (!mIs24HourView) {
                updateAmPmLabelStates(hour < 12 ? AM : PM);
            }
            setCurrentItemShowing(mRadialTimePickerView.getCurrentItemShowing(), true, true);
            onValidationChanged(true);
        } else {
            boolean[] enteredZeros = {false, false};
            int[] values = getEnteredTime(enteredZeros);
            String hourFormat = enteredZeros[0] ? "%02d" : "%2d";
            String minuteFormat = (enteredZeros[1]) ? "%02d" : "%2d";
            String hourStr = (values[0] == -1) ? mDoublePlaceholderText :
                    String.format(hourFormat, values[0]).replace(' ", mPlaceholderText);
            String minuteStr = (values[1] == -1) ? mDoublePlaceholderText :
                    String.format(minuteFormat, values[1]).replace(' ", mPlaceholderText);
            mHourView.setText(hourStr);
            mHourView.setSelected(false);
            mMinuteView.setText(minuteStr);
            mMinuteView.setSelected(false);
            if (!mIs24HourView) {
                updateAmPmLabelStates(values[2]);
            }
        }
    
private voidupdateHeaderAmPm()

        if (mIs24HourView) {
            mAmPmLayout.setVisibility(View.GONE);
        } else {
            // Ensure that AM/PM layout is in the correct position.
            final String dateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale, "hm");
            final boolean amPmAtStart = dateTimePattern.startsWith("a");
            final ViewGroup parent = (ViewGroup) mAmPmLayout.getParent();
            final int targetIndex = amPmAtStart ? 0 : parent.getChildCount() - 1;
            final int currentIndex = parent.indexOfChild(mAmPmLayout);
            if (targetIndex != currentIndex) {
                parent.removeView(mAmPmLayout);
                parent.addView(mAmPmLayout, targetIndex);
            }

            updateAmPmLabelStates(mInitialHourOfDay < 12 ? AM : PM);
        }
    
private voidupdateHeaderHour(int value, boolean announce)

        final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale,
                (mIs24HourView) ? "Hm" : "hm");
        final int lengthPattern = bestDateTimePattern.length();
        boolean hourWithTwoDigit = false;
        char hourFormat = '\0";
        // Check if the returned pattern is single or double 'H', 'h', 'K', 'k'. We also save
        // the hour format that we found.
        for (int i = 0; i < lengthPattern; i++) {
            final char c = bestDateTimePattern.charAt(i);
            if (c == 'H" || c == 'h" || c == 'K" || c == 'k") {
                hourFormat = c;
                if (i + 1 < lengthPattern && c == bestDateTimePattern.charAt(i + 1)) {
                    hourWithTwoDigit = true;
                }
                break;
            }
        }
        final String format;
        if (hourWithTwoDigit) {
            format = "%02d";
        } else {
            format = "%d";
        }
        if (mIs24HourView) {
            // 'k' means 1-24 hour
            if (hourFormat == 'k" && value == 0) {
                value = 24;
            }
        } else {
            // 'K' means 0-11 hour
            value = modulo12(value, hourFormat == 'K");
        }
        CharSequence text = String.format(format, value);
        mHourView.setText(text);
        if (announce) {
            tryAnnounceForAccessibility(text, true);
        }
    
private voidupdateHeaderMinute(int value, boolean announceForAccessibility)

        if (value == 60) {
            value = 0;
        }
        final CharSequence text = String.format(mCurrentLocale, "%02d", value);
        mMinuteView.setText(text);
        if (announceForAccessibility) {
            tryAnnounceForAccessibility(text, false);
        }
    
private voidupdateHeaderSeparator()
The time separator is defined in the Unicode CLDR and cannot be supposed to be ":". See http://unicode.org/cldr/trac/browser/trunk/common/main We pass the correct "skeleton" depending on 12 or 24 hours view and then extract the separator as the character which is just after the hour marker in the returned pattern.

        final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale,
                (mIs24HourView) ? "Hm" : "hm");
        final String separatorText;
        // See http://www.unicode.org/reports/tr35/tr35-dates.html for hour formats
        final char[] hourFormats = {'H", 'h", 'K", 'k"};
        int hIndex = lastIndexOfAny(bestDateTimePattern, hourFormats);
        if (hIndex == -1) {
            // Default case
            separatorText = ":";
        } else {
            separatorText = Character.toString(bestDateTimePattern.charAt(hIndex + 1));
        }
        mSeparatorView.setText(separatorText);
    
private voidupdateRadialPicker(int index)

        mRadialTimePickerView.initialize(mInitialHourOfDay, mInitialMinute, mIs24HourView);
        setCurrentItemShowing(index, false, true);
    
private voidupdateUI(int index)

        // Update RadialPicker values
        updateRadialPicker(index);
        // Enable or disable the AM/PM view.
        updateHeaderAmPm();
        // Update Hour and Minutes
        updateHeaderHour(mInitialHourOfDay, false);
        // Update time separator
        updateHeaderSeparator();
        // Update Minutes
        updateHeaderMinute(mInitialMinute, false);
        // Invalidate everything
        mDelegator.invalidate();