package org.holoeverywhere.widget; import java.text.DateFormatSymbols; import java.util.Calendar; import java.util.Locale; import org.holoeverywhere.FontLoader; import org.holoeverywhere.LayoutInflater; import org.holoeverywhere.R; import org.holoeverywhere.internal.NumberPickerEditText; import android.annotation.SuppressLint; import android.content.Context; import android.content.res.Configuration; import android.content.res.TypedArray; import android.os.Build.VERSION; import android.os.Parcel; import android.os.Parcelable; import android.text.format.DateUtils; import android.util.AttributeSet; import android.view.View; import android.view.accessibility.AccessibilityEvent; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.FrameLayout; public class TimePicker extends FrameLayout { public interface OnTimeChangedListener { void onTimeChanged(TimePicker view, int hourOfDay, int minute); } private static class SavedState extends BaseSavedState { @SuppressWarnings("unused") public static final Creator<SavedState> CREATOR = new Creator<SavedState>() { @Override public SavedState createFromParcel(Parcel in) { return new SavedState(in); } @Override public SavedState[] newArray(int size) { return new SavedState[size]; } }; private final int mHour, mMinute; private SavedState(Parcel in) { super(in); mHour = in.readInt(); mMinute = in.readInt(); } private SavedState(Parcelable superState, int hour, int minute) { super(superState); mHour = hour; mMinute = minute; } public int getHour() { return mHour; } public int getMinute() { return mMinute; } @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); dest.writeInt(mHour); dest.writeInt(mMinute); } } private static final boolean DEFAULT_ENABLED_STATE = true; private static final int HOURS_IN_HALF_DAY = 12; private static final OnTimeChangedListener NO_OP_CHANGE_LISTENER = new OnTimeChangedListener() { @Override public void onTimeChanged(TimePicker view, int hourOfDay, int minute) { } }; private static void setContentDescription(View parent, int childId, int textId) { if (parent == null) { return; } View child = parent.findViewById(childId); if (child != null) { child.setContentDescription(parent.getContext().getText(textId)); } } private final Button mAmPmButton; private final String[] mAmPmStrings; private Context mContext; private Locale mCurrentLocale; private final TextView mDivider; private final NumberPicker mHourSpinner, mMinuteSpinner, mAmPmSpinner; private final NumberPickerEditText mHourSpinnerInput, mMinuteSpinnerInput, mAmPmSpinnerInput; private boolean mIs24HourView, mIsAm; private boolean mIsEnabled = TimePicker.DEFAULT_ENABLED_STATE; private OnTimeChangedListener mOnTimeChangedListener; private Calendar mTempCalendar; public TimePicker(Context context) { this(context, null); } public TimePicker(Context context, AttributeSet attrs) { this(context, attrs, R.attr.timePickerStyle); } public TimePicker(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); mContext = context; setCurrentLocale(Locale.getDefault()); TypedArray attributesArray = context.obtainStyledAttributes(attrs, R.styleable.TimePicker, defStyle, 0); int layoutResourceId = attributesArray.getResourceId( R.styleable.TimePicker_internalLayout, R.layout.time_picker_holo); attributesArray.recycle(); LayoutInflater.inflate(mContext, layoutResourceId, this, true); FontLoader.apply(this); mHourSpinner = (NumberPicker) findViewById(R.id.hour); mHourSpinner .setOnValueChangedListener(new NumberPicker.OnValueChangeListener() { @Override public void onValueChange(NumberPicker spinner, int oldVal, int newVal) { updateInputState(); if (!is24HourView()) { if (oldVal == TimePicker.HOURS_IN_HALF_DAY - 1 && newVal == TimePicker.HOURS_IN_HALF_DAY || oldVal == TimePicker.HOURS_IN_HALF_DAY && newVal == TimePicker.HOURS_IN_HALF_DAY - 1) { mIsAm = !mIsAm; updateAmPmControl(); } } onTimeChanged(); } }); mHourSpinnerInput = mHourSpinner.getInputField(); mHourSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT); mDivider = (TextView) findViewById(R.id.divider); if (mDivider != null) { mDivider.setText(R.string.time_picker_separator); } mMinuteSpinner = (NumberPicker) findViewById(R.id.minute); mMinuteSpinner.setMinValue(0); mMinuteSpinner.setMaxValue(59); mMinuteSpinner.setOnLongPressUpdateInterval(100); mMinuteSpinner.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER); mMinuteSpinner .setOnValueChangedListener(new NumberPicker.OnValueChangeListener() { @Override public void onValueChange(NumberPicker spinner, int oldVal, int newVal) { updateInputState(); int minValue = mMinuteSpinner.getMinValue(); int maxValue = mMinuteSpinner.getMaxValue(); if (oldVal == maxValue && newVal == minValue) { int newHour = mHourSpinner.getValue() + 1; if (!is24HourView() && newHour == TimePicker.HOURS_IN_HALF_DAY) { mIsAm = !mIsAm; updateAmPmControl(); } mHourSpinner.setValue(newHour); } else if (oldVal == minValue && newVal == maxValue) { int newHour = mHourSpinner.getValue() - 1; if (!is24HourView() && newHour == TimePicker.HOURS_IN_HALF_DAY - 1) { mIsAm = !mIsAm; updateAmPmControl(); } mHourSpinner.setValue(newHour); } onTimeChanged(); } }); mMinuteSpinnerInput = mMinuteSpinner.getInputField(); mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT); mAmPmStrings = new DateFormatSymbols().getAmPmStrings(); View amPmView = findViewById(R.id.amPm); if (amPmView instanceof Button) { mAmPmSpinner = null; mAmPmSpinnerInput = null; mAmPmButton = (Button) amPmView; mAmPmButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View button) { button.requestFocus(); mIsAm = !mIsAm; updateAmPmControl(); onTimeChanged(); } }); } else { mAmPmButton = null; mAmPmSpinner = (NumberPicker) amPmView; mAmPmSpinner.setMinValue(0); mAmPmSpinner.setMaxValue(1); mAmPmSpinner.setDisplayedValues(mAmPmStrings); mAmPmSpinner .setOnValueChangedListener(new NumberPicker.OnValueChangeListener() { @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { updateInputState(); picker.requestFocus(); mIsAm = !mIsAm; updateAmPmControl(); onTimeChanged(); } }); mAmPmSpinnerInput = mAmPmSpinner.getInputField(); mAmPmSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_DONE); } updateHourControl(); updateAmPmControl(); setOnTimeChangedListener(TimePicker.NO_OP_CHANGE_LISTENER); setCurrentHour(mTempCalendar.get(Calendar.HOUR_OF_DAY)); setCurrentMinute(mTempCalendar.get(Calendar.MINUTE)); if (!isEnabled()) { setEnabled(false); } setContentDescriptions(); } @SuppressLint("NewApi") @Override public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { if (VERSION.SDK_INT >= 14) { onPopulateAccessibilityEvent(event); return true; } else { return super.dispatchPopulateAccessibilityEvent(event); } } @Override public int getBaseline() { return mHourSpinner.getBaseline(); } public Integer getCurrentHour() { int currentHour = mHourSpinner.getValue(); if (is24HourView()) { return currentHour; } else if (mIsAm) { return currentHour % TimePicker.HOURS_IN_HALF_DAY; } else { return currentHour % TimePicker.HOURS_IN_HALF_DAY + TimePicker.HOURS_IN_HALF_DAY; } } public Integer getCurrentMinute() { return mMinuteSpinner.getValue(); } public boolean is24HourView() { return mIs24HourView; } @Override public boolean isEnabled() { return mIsEnabled; } @SuppressLint("NewApi") @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); setCurrentLocale(newConfig.locale); } @SuppressLint("NewApi") @Override public void onPopulateAccessibilityEvent(AccessibilityEvent event) { super.onPopulateAccessibilityEvent(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 selectedDateUtterance = DateUtils.formatDateTime(mContext, mTempCalendar.getTimeInMillis(), flags); event.getText().add(selectedDateUtterance); } @Override protected void onRestoreInstanceState(Parcelable state) { SavedState ss = (SavedState) state; super.onRestoreInstanceState(ss.getSuperState()); setCurrentHour(ss.getHour()); setCurrentMinute(ss.getMinute()); } @Override protected Parcelable onSaveInstanceState() { Parcelable superState = super.onSaveInstanceState(); return new SavedState(superState, getCurrentHour(), getCurrentMinute()); } private void onTimeChanged() { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); if (mOnTimeChangedListener != null) { mOnTimeChangedListener.onTimeChanged(this, getCurrentHour(), getCurrentMinute()); } } private void setContentDescriptions() { TimePicker.setContentDescription(mMinuteSpinner, R.id.increment, R.string.time_picker_increment_minute_button); TimePicker.setContentDescription(mMinuteSpinner, R.id.decrement, R.string.time_picker_decrement_minute_button); TimePicker.setContentDescription(mHourSpinner, R.id.increment, R.string.time_picker_increment_hour_button); TimePicker.setContentDescription(mHourSpinner, R.id.decrement, R.string.time_picker_decrement_hour_button); if (mAmPmSpinner != null) { TimePicker.setContentDescription(mAmPmSpinner, R.id.increment, R.string.time_picker_increment_set_pm_button); TimePicker.setContentDescription(mAmPmSpinner, R.id.decrement, R.string.time_picker_decrement_set_am_button); } } public void setCurrentHour(Integer currentHour) { if (currentHour == null || currentHour == getCurrentHour()) { return; } if (!is24HourView()) { if (currentHour >= TimePicker.HOURS_IN_HALF_DAY) { mIsAm = false; if (currentHour > TimePicker.HOURS_IN_HALF_DAY) { currentHour = currentHour - TimePicker.HOURS_IN_HALF_DAY; } } else { mIsAm = true; if (currentHour == 0) { currentHour = TimePicker.HOURS_IN_HALF_DAY; } } updateAmPmControl(); } mHourSpinner.setValue(currentHour); onTimeChanged(); } private void setCurrentLocale(Locale locale) { if (locale.equals(mCurrentLocale)) { return; } mCurrentLocale = locale; mTempCalendar = Calendar.getInstance(locale); } public void setCurrentMinute(Integer currentMinute) { if (currentMinute == getCurrentMinute()) { return; } mMinuteSpinner.setValue(currentMinute); onTimeChanged(); } @Override public void setEnabled(boolean enabled) { if (mIsEnabled == enabled) { return; } super.setEnabled(enabled); mMinuteSpinner.setEnabled(enabled); if (mDivider != null) { mDivider.setEnabled(enabled); } mHourSpinner.setEnabled(enabled); if (mAmPmSpinner != null) { mAmPmSpinner.setEnabled(enabled); } else { mAmPmButton.setEnabled(enabled); } mIsEnabled = enabled; } public void setIs24HourView(Boolean is24HourView) { if (mIs24HourView == is24HourView) { return; } mIs24HourView = is24HourView; int currentHour = getCurrentHour(); updateHourControl(); setCurrentHour(currentHour); updateAmPmControl(); } public void setOnTimeChangedListener( OnTimeChangedListener onTimeChangedListener) { mOnTimeChangedListener = onTimeChangedListener; } private void updateAmPmControl() { if (is24HourView()) { if (mAmPmSpinner != null) { mAmPmSpinner.setVisibility(View.GONE); } else { mAmPmButton.setVisibility(View.GONE); } } else { int index = mIsAm ? Calendar.AM : Calendar.PM; if (mAmPmSpinner != null) { mAmPmSpinner.setValue(index); mAmPmSpinner.setVisibility(View.VISIBLE); } else { mAmPmButton.setText(mAmPmStrings[index]); mAmPmButton.setVisibility(View.VISIBLE); } } sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); } private void updateHourControl() { if (is24HourView()) { mHourSpinner.setMinValue(0); mHourSpinner.setMaxValue(23); mHourSpinner.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER); } else { mHourSpinner.setMinValue(1); mHourSpinner.setMaxValue(12); mHourSpinner.setFormatter(null); } } private void updateInputState() { InputMethodManager inputMethodManager = (InputMethodManager) mContext .getSystemService(Context.INPUT_METHOD_SERVICE); if (inputMethodManager != null) { if (inputMethodManager.isActive(mHourSpinnerInput)) { mHourSpinnerInput.clearFocus(); inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0); } else if (inputMethodManager.isActive(mMinuteSpinnerInput)) { mMinuteSpinnerInput.clearFocus(); inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0); } else if (inputMethodManager.isActive(mAmPmSpinnerInput)) { mAmPmSpinnerInput.clearFocus(); inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0); } } } }