package org.holoeverywhere.widget;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Locale;
import org.holoeverywhere.LayoutInflater;
import org.holoeverywhere.R;
import org.holoeverywhere.internal.NumberPickerEditText;
import org.holoeverywhere.util.Arrays;
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.TextUtils;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
public class DatePicker extends FrameLayout {
private final class Callback implements NumberPicker.OnValueChangeListener,
CalendarView.OnDateChangeListener {
@Override
public void onSelectedDayChange(CalendarView view, int year, int month,
int monthDay) {
setDate(year, month, monthDay);
updateSpinners();
notifyDateChanged();
}
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
updateInputState();
tempDate.setTimeInMillis(currentDate.getTimeInMillis());
if (picker == daySpinner) {
int maxDayOfMonth = tempDate
.getActualMaximum(Calendar.DAY_OF_MONTH);
if (oldVal == maxDayOfMonth && newVal == 1) {
tempDate.add(Calendar.DAY_OF_MONTH, 1);
} else if (oldVal == 1 && newVal == maxDayOfMonth) {
tempDate.add(Calendar.DAY_OF_MONTH, -1);
} else {
tempDate.add(Calendar.DAY_OF_MONTH, newVal - oldVal);
}
} else if (picker == monthSpinner) {
if (oldVal == 11 && newVal == 0) {
tempDate.add(Calendar.MONTH, 1);
} else if (oldVal == 0 && newVal == 11) {
tempDate.add(Calendar.MONTH, -1);
} else {
tempDate.add(Calendar.MONTH, newVal - oldVal);
}
} else if (picker == yearSpinner) {
tempDate.set(Calendar.YEAR, newVal);
} else {
return;
}
setDate(tempDate.get(Calendar.YEAR), tempDate.get(Calendar.MONTH),
tempDate.get(Calendar.DAY_OF_MONTH));
updateSpinners();
updateCalendarView();
notifyDateChanged();
}
}
public interface OnDateChangedListener {
void onDateChanged(DatePicker view, int year, int monthOfYear,
int dayOfMonth);
}
private static class SavedState extends BaseSavedState {
@SuppressWarnings("all")
public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
private final int year, month, day;
private SavedState(Parcel in) {
super(in);
year = in.readInt();
month = in.readInt();
day = in.readInt();
}
private SavedState(Parcelable superState, int year, int month, int day) {
super(superState);
this.year = year;
this.month = month;
this.day = day;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(year);
dest.writeInt(month);
dest.writeInt(day);
}
}
private static final String DATE_FORMAT = "MM/dd/yyyy";
private static final String LOG_TAG = DatePicker.class.getSimpleName();
private static Calendar getCalendarForLocale(Calendar oldCalendar,
Locale locale) {
if (oldCalendar == null) {
return Calendar.getInstance(locale);
} else {
final long currentTimeMillis = oldCalendar.getTimeInMillis();
Calendar newCalendar = Calendar.getInstance(locale);
newCalendar.setTimeInMillis(currentTimeMillis);
return newCalendar;
}
}
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 Callback callback = new Callback();
private final java.text.DateFormat dateFormat = new SimpleDateFormat(
DatePicker.DATE_FORMAT);
private final NumberPicker daySpinner, monthSpinner, yearSpinner;
private final InputMethodManager inputMethodManager;
private Locale locale;
private final CalendarView mCalendarView;
private int numberOfMonths;
private OnDateChangedListener onDateChangedListener;
private String[] shortMonths;
private final LinearLayout spinners;
private Calendar tempDate, minDate, maxDate, currentDate;
public DatePicker(Context context) {
this(context, null);
}
public DatePicker(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.datePickerStyle);
}
public DatePicker(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.DatePicker, defStyle, R.style.Holo_DatePicker);
boolean spinnersShown = a.getBoolean(
R.styleable.DatePicker_spinnersShown, true);
boolean calendarViewShown = a.getBoolean(
R.styleable.DatePicker_calendarViewShown, true);
boolean forceShownState = a.getBoolean(
R.styleable.DatePicker_forceShownState, false);
int startYear = a.getInt(R.styleable.DatePicker_startYear, 1900);
int endYear = a.getInt(R.styleable.DatePicker_endYear, 2100);
String minDate = a.getString(R.styleable.DatePicker_minDate);
String maxDate = a.getString(R.styleable.DatePicker_maxDate);
int layoutResourceId = a.getResourceId(R.styleable.DatePicker_layout,
R.layout.date_picker_holo);
a.recycle();
inputMethodManager = (InputMethodManager) context
.getSystemService(Context.INPUT_METHOD_SERVICE);
setLocale(Locale.getDefault());
LayoutInflater.inflate(context, layoutResourceId, this, true);
spinners = (LinearLayout) findViewById(R.id.pickers);
mCalendarView = (CalendarView) findViewById(R.id.calendar_view);
daySpinner = (NumberPicker) findViewById(R.id.day);
monthSpinner = (NumberPicker) findViewById(R.id.month);
yearSpinner = (NumberPicker) findViewById(R.id.year);
if (((AccessibilityManager) getContext().getSystemService(
Context.ACCESSIBILITY_SERVICE)).isEnabled()) {
setContentDescriptions();
}
mCalendarView.setOnDateChangeListener(callback);
daySpinner.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER);
daySpinner.setOnLongPressUpdateInterval(100);
daySpinner.setOnValueChangedListener(callback);
monthSpinner.setMinValue(0);
monthSpinner.setMaxValue(numberOfMonths - 1);
monthSpinner.setDisplayedValues(shortMonths);
monthSpinner.setOnLongPressUpdateInterval(200);
monthSpinner.setOnValueChangedListener(callback);
yearSpinner.setOnLongPressUpdateInterval(100);
yearSpinner.setOnValueChangedListener(callback);
if (spinnersShown || calendarViewShown || forceShownState) {
setSpinnersShown(spinnersShown);
setCalendarViewShown(calendarViewShown);
} else {
setSpinnersShown(true);
setCalendarViewShown(false);
}
tempDate.clear();
if (TextUtils.isEmpty(minDate) || !parseDate(minDate, tempDate)) {
tempDate.set(startYear, 0, 1);
}
setMinDate(tempDate.getTimeInMillis());
tempDate.clear();
if (TextUtils.isEmpty(maxDate) || !parseDate(maxDate, tempDate)) {
tempDate.set(endYear, 11, 31);
}
setMaxDate(tempDate.getTimeInMillis());
currentDate.setTimeInMillis(System.currentTimeMillis());
init(currentDate.get(Calendar.YEAR), currentDate.get(Calendar.MONTH),
currentDate.get(Calendar.DAY_OF_MONTH), null);
reorderSpinners();
}
private void checkInputState(NumberPicker... spinners) {
for (NumberPicker spinner : spinners) {
NumberPickerEditText input = spinner.getInputField();
if (inputMethodManager.isActive(input)) {
input.clearFocus();
inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
}
}
}
@SuppressLint("NewApi")
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
onPopulateAccessibilityEvent(event);
return true;
}
@Override
protected void dispatchRestoreInstanceState(
SparseArray<Parcelable> container) {
dispatchThawSelfOnly(container);
}
public CalendarView getCalendarView() {
return mCalendarView;
}
public boolean getCalendarViewShown() {
return mCalendarView.isShown();
}
public int getDayOfMonth() {
return currentDate.get(Calendar.DAY_OF_MONTH);
}
public long getMaxDate() {
return mCalendarView.getMaxDate();
}
public long getMinDate() {
return mCalendarView.getMinDate();
}
public int getMonth() {
return currentDate.get(Calendar.MONTH);
}
public OnDateChangedListener getOnDateChangedListener() {
return onDateChangedListener;
}
public boolean getSpinnersShown() {
return spinners.isShown();
}
public int getYear() {
return currentDate.get(Calendar.YEAR);
}
public void init(int year, int monthOfYear, int dayOfMonth,
OnDateChangedListener onDateChangedListener) {
setOnDateChangedListener(onDateChangedListener);
setDate(year, monthOfYear, dayOfMonth);
updateSpinners();
updateCalendarView();
}
private boolean isNewDate(int year, int month, int dayOfMonth) {
return currentDate.get(Calendar.YEAR) != year
|| currentDate.get(Calendar.MONTH) != dayOfMonth
|| currentDate.get(Calendar.DAY_OF_MONTH) != month;
}
private void notifyDateChanged() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
if (onDateChangedListener != null) {
onDateChangedListener.onDateChanged(this, getYear(), getMonth(),
getDayOfMonth());
}
}
@Override
@SuppressLint("NewApi")
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
setLocale(newConfig.locale);
}
@SuppressLint("NewApi")
@Override
public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
if (VERSION.SDK_INT >= 14) {
super.onPopulateAccessibilityEvent(event);
}
final int flags = DateUtils.FORMAT_SHOW_DATE
| DateUtils.FORMAT_SHOW_YEAR;
String selectedDateUtterance = DateUtils.formatDateTime(getContext(),
currentDate.getTimeInMillis(), flags);
event.getText().add(selectedDateUtterance);
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
setDate(ss.year, ss.month, ss.day);
updateSpinners();
updateCalendarView();
}
@Override
protected Parcelable onSaveInstanceState() {
return new SavedState(super.onSaveInstanceState(), getYear(),
getMonth(), getDayOfMonth());
}
private boolean parseDate(String date, Calendar outDate) {
try {
outDate.setTime(dateFormat.parse(date));
return true;
} catch (ParseException e) {
Log.w(DatePicker.LOG_TAG, "Date: " + date + " not in format: "
+ DatePicker.DATE_FORMAT);
return false;
}
}
private void pushSpinner(NumberPicker spinner, int spinnerCount, int i) {
if (spinner.getParent() != null
&& spinner.getParent() instanceof ViewGroup) {
ViewGroup parent = (ViewGroup) spinner.getParent();
if (parent.getChildAt(i) != spinner) {
parent.removeView(spinner);
parent.addView(spinner);
setImeOptions(spinner, spinnerCount, i);
}
}
}
private void reorderSpinners() {
char[] order = DateFormat.getDateFormatOrder(getContext());
final int spinnerCount = order.length;
for (int i = 0; i < spinnerCount; i++) {
switch (order[i]) {
case DateFormat.DATE:
pushSpinner(daySpinner, spinnerCount, i);
break;
case DateFormat.MONTH:
pushSpinner(monthSpinner, spinnerCount, i);
break;
case DateFormat.YEAR:
pushSpinner(yearSpinner, spinnerCount, i);
break;
}
}
}
public void setCalendarViewShown(boolean shown) {
mCalendarView.setVisibility(shown ? View.VISIBLE : View.GONE);
}
private void setContentDescriptions() {
DatePicker.setContentDescription(daySpinner, R.id.increment,
R.string.date_picker_increment_day_button);
DatePicker.setContentDescription(daySpinner, R.id.decrement,
R.string.date_picker_decrement_day_button);
DatePicker.setContentDescription(monthSpinner, R.id.increment,
R.string.date_picker_increment_month_button);
DatePicker.setContentDescription(monthSpinner, R.id.decrement,
R.string.date_picker_decrement_month_button);
DatePicker.setContentDescription(yearSpinner, R.id.increment,
R.string.date_picker_increment_year_button);
DatePicker.setContentDescription(yearSpinner, R.id.decrement,
R.string.date_picker_decrement_year_button);
}
private void setDate(int year, int month, int dayOfMonth) {
currentDate.set(year, month, dayOfMonth);
if (currentDate.before(minDate)) {
currentDate.setTimeInMillis(minDate.getTimeInMillis());
} else if (currentDate.after(maxDate)) {
currentDate.setTimeInMillis(maxDate.getTimeInMillis());
}
}
@Override
public void setEnabled(boolean enabled) {
if (isEnabled() == enabled) {
return;
}
super.setEnabled(enabled);
daySpinner.setEnabled(enabled);
monthSpinner.setEnabled(enabled);
yearSpinner.setEnabled(enabled);
mCalendarView.setEnabled(enabled);
}
private void setImeOptions(NumberPicker spinner, int spinnerCount,
int spinnerIndex) {
final int imeOptions;
if (spinnerIndex < spinnerCount - 1) {
imeOptions = EditorInfo.IME_ACTION_NEXT;
} else {
imeOptions = EditorInfo.IME_ACTION_DONE;
}
spinner.getInputField().setImeOptions(imeOptions);
}
public void setLocale(Locale locale) {
if (locale == null || locale.equals(this.locale)) {
return;
}
this.locale = locale;
tempDate = DatePicker.getCalendarForLocale(tempDate, locale);
minDate = DatePicker.getCalendarForLocale(minDate, locale);
maxDate = DatePicker.getCalendarForLocale(maxDate, locale);
currentDate = DatePicker.getCalendarForLocale(currentDate, locale);
numberOfMonths = tempDate.getActualMaximum(Calendar.MONTH) + 1;
shortMonths = new String[numberOfMonths];
for (int i = 0; i < numberOfMonths; i++) {
shortMonths[i] = DateUtils.getMonthString(Calendar.JANUARY + i,
DateUtils.LENGTH_MEDIUM);
}
}
public void setMaxDate(long maxDateL) {
tempDate.setTimeInMillis(maxDateL);
if (tempDate.get(Calendar.YEAR) == maxDate.get(Calendar.YEAR)
&& tempDate.get(Calendar.DAY_OF_YEAR) == maxDate
.get(Calendar.DAY_OF_YEAR)) {
return;
}
maxDate.setTimeInMillis(maxDateL);
mCalendarView.setMaxDate(maxDateL);
if (currentDate.after(maxDate)) {
currentDate.setTimeInMillis(maxDate.getTimeInMillis());
updateCalendarView();
}
updateSpinners();
}
public void setMinDate(long minDateL) {
tempDate.setTimeInMillis(minDateL);
if (tempDate.get(Calendar.YEAR) == minDate.get(Calendar.YEAR)
&& tempDate.get(Calendar.DAY_OF_YEAR) == minDate
.get(Calendar.DAY_OF_YEAR)) {
return;
}
minDate.setTimeInMillis(minDateL);
mCalendarView.setMinDate(minDateL);
if (currentDate.before(minDate)) {
currentDate.setTimeInMillis(minDate.getTimeInMillis());
updateCalendarView();
}
updateSpinners();
}
public void setOnDateChangedListener(
OnDateChangedListener onDateChangedListener) {
this.onDateChangedListener = onDateChangedListener;
}
public void setSpinnersShown(boolean shown) {
spinners.setVisibility(shown ? View.VISIBLE : View.GONE);
}
private void updateCalendarView() {
mCalendarView.setDate(currentDate.getTimeInMillis(), false, false);
}
public void updateDate(int year, int month, int dayOfMonth) {
if (!isNewDate(year, month, dayOfMonth)) {
return;
}
setDate(year, month, dayOfMonth);
updateSpinners();
updateCalendarView();
notifyDateChanged();
}
private void updateInputState() {
if (inputMethodManager != null) {
checkInputState(yearSpinner, monthSpinner, daySpinner);
}
}
private void updateSpinners() {
monthSpinner.setDisplayedValues(null);
if (currentDate.equals(minDate)) {
daySpinner.setMinValue(currentDate.get(Calendar.DAY_OF_MONTH));
daySpinner.setMaxValue(currentDate
.getActualMaximum(Calendar.DAY_OF_MONTH));
daySpinner.setWrapSelectorWheel(false);
monthSpinner.setMinValue(currentDate.get(Calendar.MONTH));
monthSpinner.setMaxValue(currentDate
.getActualMaximum(Calendar.MONTH));
monthSpinner.setWrapSelectorWheel(false);
} else if (currentDate.equals(maxDate)) {
daySpinner.setMinValue(currentDate
.getActualMinimum(Calendar.DAY_OF_MONTH));
daySpinner.setMaxValue(currentDate.get(Calendar.DAY_OF_MONTH));
daySpinner.setWrapSelectorWheel(false);
monthSpinner.setMinValue(currentDate
.getActualMinimum(Calendar.MONTH));
monthSpinner.setMaxValue(currentDate.get(Calendar.MONTH));
monthSpinner.setWrapSelectorWheel(false);
} else {
daySpinner.setMinValue(1);
daySpinner.setMaxValue(currentDate
.getActualMaximum(Calendar.DAY_OF_MONTH));
daySpinner.setWrapSelectorWheel(true);
monthSpinner.setMinValue(0);
monthSpinner.setMaxValue(11);
monthSpinner.setWrapSelectorWheel(true);
}
String[] displayedValues = Arrays.copyOfRange(shortMonths,
monthSpinner.getMinValue(), monthSpinner.getMaxValue() + 1);
monthSpinner.setDisplayedValues(displayedValues);
yearSpinner.setMinValue(minDate.get(Calendar.YEAR));
yearSpinner.setMaxValue(maxDate.get(Calendar.YEAR));
yearSpinner.setWrapSelectorWheel(false);
yearSpinner.setValue(currentDate.get(Calendar.YEAR));
monthSpinner.setValue(currentDate.get(Calendar.MONTH));
daySpinner.setValue(currentDate.get(Calendar.DAY_OF_MONTH));
}
}