gecko-dev/mobile/android/base/widget/DateTimePicker.java
Raphael Catolino ac1a0a8144 Bug 730330 - Use android datetime-picker widget. r=wesj
--HG--
rename : mobile/android/base/widget/DatetimePicker.java => mobile/android/base/widget/DateTimePicker.java
2012-08-07 17:09:15 +02:00

611 lines
25 KiB
Java

/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mozilla.gecko.widget;
import android.content.Context;
import android.graphics.Point;
import android.os.Build;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.SparseArray;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.Display;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.OrientationEventListener;
import android.view.WindowManager;
import android.widget.CalendarView;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.FrameLayout.LayoutParams;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.NumberPicker;
import android.widget.TextView;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Locale;
import java.util.TimeZone;
import org.mozilla.gecko.R;
public class DateTimePicker extends FrameLayout {
private static final boolean DEBUG = true;
private static final String LOGTAG = "GeckoDateTimePicker";
private static final String DATE_FORMAT = "MM/dd/yyyy";
private static final int DEFAULT_START_YEAR = 1;
private static final int DEFAULT_END_YEAR = 9999;
// Minimal screen width (in inches) for which we can show the calendar;
private static final int SCREEN_SIZE_THRESHOLD = 5;
private boolean mYearEnabled = true;
private boolean mMonthEnabled = true;
private boolean mWeekEnabled = false;
private boolean mDayEnabled = true;
private boolean mHourEnabled = true;
private boolean mMinuteEnabled = true;
private boolean mCalendarEnabled = false;
// Size of the screen in inches;
private int mScreenWidth;
private int mScreenHeight;
private OnValueChangeListener mOnChangeListener;
private final LinearLayout mPickers;
private final LinearLayout mDateSpinners;
private final LinearLayout mTimeSpinners;
private final LinearLayout mSpinners;
private final NumberPicker mDaySpinner;
private final NumberPicker mMonthSpinner;
private final NumberPicker mWeekSpinner;
private final NumberPicker mYearSpinner;
private final NumberPicker mHourSpinner;
private final NumberPicker mMinuteSpinner;
private final CalendarView mCalendar;
private final EditText mDaySpinnerInput;
private final EditText mMonthSpinnerInput;
private final EditText mWeekSpinnerInput;
private final EditText mYearSpinnerInput;
private final EditText mHourSpinnerInput;
private final EditText mMinuteSpinnerInput;
private Locale mCurrentLocale;
private String[] mShortMonths;
private int mNumberOfMonths;
private Calendar mTempDate;
private Calendar mMinDate;
private Calendar mMaxDate;
private Calendar mCurrentDate;
private pickersState mState;
public static enum pickersState { DATE, MONTH, WEEK, TIME, DATETIME };
public class OnValueChangeListener implements NumberPicker.OnValueChangeListener {
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
updateInputState();
mTempDate.setTimeInMillis(mCurrentDate.getTimeInMillis());
boolean newBehavior = (Build.VERSION.SDK_INT > 10);
if (newBehavior) {
if (DEBUG) Log.d(LOGTAG, "Sdk version > 10, using new behavior");
//The native date picker widget on these sdks increment
//the next field when one field reach the maximum
if (picker == mDaySpinner && mDayEnabled) {
int maxDayOfMonth = mTempDate.getActualMaximum(Calendar.DAY_OF_MONTH);
int old = mTempDate.get(Calendar.DAY_OF_MONTH);
setTempDate(Calendar.DAY_OF_MONTH, old, newVal, 1, maxDayOfMonth);
} else if (picker == mMonthSpinner && mMonthEnabled) {
int old = mTempDate.get(Calendar.MONTH);
setTempDate(Calendar.MONTH, old, newVal, 0, 11);
} else if (picker == mWeekSpinner) {
int old = mTempDate.get(Calendar.WEEK_OF_YEAR);
int maxWeekOfYear = mTempDate.getActualMaximum(Calendar.WEEK_OF_YEAR);
setTempDate(Calendar.WEEK_OF_YEAR, old, newVal, 0, maxWeekOfYear);
} else if (picker == mYearSpinner && mYearEnabled) {
int month=mTempDate.get(Calendar.MONTH);
mTempDate.set(Calendar.YEAR,newVal);
// Changing the year shouldn't change the month. (in case of non-leap year a Feb 29)
// change the day instead;
if (month != mTempDate.get(Calendar.MONTH)){
mTempDate.set(Calendar.MONTH, month);
mTempDate.set(Calendar.DAY_OF_MONTH,
mTempDate.getActualMaximum(Calendar.DAY_OF_MONTH));
}
} else if (picker == mHourSpinner && mHourEnabled) {
setTempDate(Calendar.HOUR_OF_DAY, oldVal, newVal, 0, 23);
} else if (picker == mMinuteSpinner && mMinuteEnabled) {
setTempDate(Calendar.MINUTE, oldVal, newVal, 0, 59);
} else {
throw new IllegalArgumentException();
}
} else {
if (DEBUG) Log.d(LOGTAG,"Sdk version < 10, using old behavior");
if (picker == mDaySpinner && mDayEnabled){
mTempDate.set(Calendar.DAY_OF_MONTH, newVal);
} else if (picker == mMonthSpinner && mMonthEnabled){
mTempDate.set(Calendar.MONTH, newVal);
if (mTempDate.get(Calendar.MONTH) == newVal+1){
mTempDate.set(Calendar.MONTH, newVal);
mTempDate.set(Calendar.DAY_OF_MONTH,
mTempDate.getActualMaximum(Calendar.DAY_OF_MONTH));
}
} else if (picker == mWeekSpinner){
mTempDate.set(Calendar.WEEK_OF_YEAR, newVal);
} else if (picker == mYearSpinner && mYearEnabled){
int month=mTempDate.get(Calendar.MONTH);
mTempDate.set(Calendar.YEAR, newVal);
if (month != mTempDate.get(Calendar.MONTH)){
mTempDate.set(Calendar.MONTH, month);
mTempDate.set(Calendar.DAY_OF_MONTH,
mTempDate.getActualMaximum(Calendar.DAY_OF_MONTH));
}
} else if (picker == mHourSpinner && mHourEnabled){
mTempDate.set(Calendar.HOUR_OF_DAY, newVal);
} else if (picker == mMinuteSpinner && mMinuteEnabled){
mTempDate.set(Calendar.MINUTE, newVal);
} else {
throw new IllegalArgumentException();
}
}
setDate(mTempDate);
if (mDayEnabled) {
mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
}
if(mWeekEnabled) {
mWeekSpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.WEEK_OF_YEAR));
}
updateCalendar();
updateSpinners();
notifyDateChanged();
}
private void setTempDate(int field, int oldVal, int newVal, int min, int max) {
if (oldVal == max && newVal == min ) {
mTempDate.add(field, 1);
} else if (oldVal == min && newVal == max) {
mTempDate.add(field, -1);
} else {
mTempDate.add(field, newVal - oldVal);
}
}
}
private static final NumberPicker.Formatter TWO_DIGIT_FORMATTER = new NumberPicker.Formatter() {
final StringBuilder mBuilder = new StringBuilder();
final java.util.Formatter mFmt = new java.util.Formatter(mBuilder, java.util.Locale.US);
final Object[] mArgs = new Object[1];
public String format(int value) {
mArgs[0] = value;
mBuilder.delete(0, mBuilder.length());
mFmt.format("%02d", mArgs);
return mFmt.toString();
}
};
private void displayPickers() {
setWeekShown(false);
if (mState == pickersState.DATETIME) {
return;
}
setHourShown(false);
setMinuteShown(false);
if (mState == pickersState.WEEK) {
setDayShown(false);
setMonthShown(false);
setWeekShown(true);
} else if (mState == pickersState.MONTH) {
setDayShown(false);
}
}
public DateTimePicker(Context context) {
this(context, "", "", pickersState.DATE);
}
public DateTimePicker(Context context, String dateFormat, String dateTimeValue, pickersState state) {
super(context);
if (Build.VERSION.SDK_INT < 11) {
throw new UnsupportedOperationException("Custom DateTimePicker is only available for SDK > 10");
}
setCurrentLocale(Locale.getDefault());
mMinDate.set(DEFAULT_START_YEAR,1,1);
mMaxDate.set(DEFAULT_END_YEAR,12,31);
mState = state;
LayoutInflater inflater = LayoutInflater.from(context);
inflater.inflate(R.layout.datetime_picker, this, true);
mOnChangeListener = new OnValueChangeListener();
mDateSpinners = (LinearLayout)findViewById(R.id.date_spinners);
mTimeSpinners = (LinearLayout)findViewById(R.id.time_spinners);
mSpinners = (LinearLayout)findViewById(R.id.spinners);
mPickers = (LinearLayout)findViewById(R.id.datetime_picker);
// We will display differently according to the screen size width.
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
DisplayMetrics dm = new DisplayMetrics();
display.getMetrics(dm);
mScreenWidth = display.getWidth() / dm.densityDpi;
mScreenHeight = display.getHeight() / dm.densityDpi;
if (DEBUG) Log.d(LOGTAG, "screen width: " + mScreenWidth + " screen height: " + mScreenHeight);
// If we're displaying a date, the screen is wide enought (and if we're using a sdk where the calendar view exists)
// then display a calendar.
if ((mState == pickersState.DATE || mState == pickersState.DATETIME) &&
Build.VERSION.SDK_INT > 10 && mScreenWidth >= SCREEN_SIZE_THRESHOLD) {
if (DEBUG) Log.d(LOGTAG,"SDK > 10 and screen wide enough, displaying calendar");
mCalendar = new CalendarView(context);
mCalendar.setVisibility(GONE);
LayoutParams layoutParams = new LayoutParams(250,280);
mCalendar.setLayoutParams(layoutParams);
mCalendar.setFocusable(true);
mCalendar.setFocusableInTouchMode(true);
mCalendar.setMaxDate(mMaxDate.getTimeInMillis());
mCalendar.setMinDate(mMinDate.getTimeInMillis());
mCalendar.setOnDateChangeListener(new CalendarView.OnDateChangeListener() {
public void onSelectedDayChange(
CalendarView view, int year, int month, int monthDay) {
mTempDate.set(year, month, monthDay);
setDate(mTempDate);
notifyDateChanged();
}
});
mPickers.addView(mCalendar);
} else {
// If the screen is more wide than high, we are displaying daye and time spinners,
// and if there is no calendar displayed,
// we should display the fields in one row.
if (mScreenWidth > mScreenHeight && mState == pickersState.DATETIME) {
mSpinners.setOrientation(LinearLayout.HORIZONTAL);
}
mCalendar = null;
}
// Find the initial date from the constructor arguments.
try {
if (!dateTimeValue.equals("")) {
mTempDate.setTime(new SimpleDateFormat(dateFormat).parse(dateTimeValue));
} else {
mTempDate.setTimeInMillis(System.currentTimeMillis());
}
} catch (Exception ex) {
Log.e(LOGTAG, "Error parsing format string: " + ex);
mTempDate.setTimeInMillis(System.currentTimeMillis());
}
// Initialize all spinners.
mDaySpinner = setupSpinner(R.id.day, 1,
mTempDate.get(Calendar.DAY_OF_MONTH));
mDaySpinner.setFormatter(TWO_DIGIT_FORMATTER);
mDaySpinnerInput = (EditText) mDaySpinner.getChildAt(1);
mMonthSpinner = setupSpinner(R.id.month, 1,
mTempDate.get(Calendar.MONTH));
mMonthSpinner.setFormatter(TWO_DIGIT_FORMATTER);
mMonthSpinner.setDisplayedValues(mShortMonths);
mMonthSpinnerInput = (EditText) mMonthSpinner.getChildAt(1);
mWeekSpinner = setupSpinner(R.id.week, 1,
mTempDate.get(Calendar.WEEK_OF_YEAR));
mWeekSpinner.setFormatter(TWO_DIGIT_FORMATTER);
mWeekSpinnerInput = (EditText) mWeekSpinner.getChildAt(1);
mYearSpinner = setupSpinner(R.id.year, DEFAULT_START_YEAR,
DEFAULT_END_YEAR);
mYearSpinnerInput = (EditText) mYearSpinner.getChildAt(1);
mHourSpinner = setupSpinner(R.id.hour, 0, 23);
mHourSpinner.setFormatter(TWO_DIGIT_FORMATTER);
mHourSpinnerInput = (EditText) mHourSpinner.getChildAt(1);
mMinuteSpinner = setupSpinner(R.id.minute, 0, 59);
mMinuteSpinner.setFormatter(TWO_DIGIT_FORMATTER);
mMinuteSpinnerInput = (EditText) mMinuteSpinner.getChildAt(1);
// The order in which the spinners are displayed are locale-dependent
reorderDateSpinners();
// Set the date to the initial date. Since this date can come from the user,
// it can fire an exception (out-of-bound date)
try {
updateDate(mTempDate);
} catch (Exception ex) { }
// Display only the pickers needed for the current state.
displayPickers();
}
public NumberPicker setupSpinner(int id, int min, int max) {
NumberPicker mSpinner = (NumberPicker) findViewById(id);
mSpinner.setMinValue(min);
mSpinner.setMaxValue(max);
mSpinner.setOnValueChangedListener(mOnChangeListener);
mSpinner.setOnLongPressUpdateInterval(100);
return mSpinner;
}
public long getTimeInMillis(){
return mCurrentDate.getTimeInMillis();
}
private void reorderDateSpinners() {
mDateSpinners.removeAllViews();
char[] order = DateFormat.getDateFormatOrder(getContext());
final int spinnerCount = order.length;
for (int i = 0; i < spinnerCount; i++) {
switch (order[i]) {
case DateFormat.DATE:
mDateSpinners.addView(mDaySpinner);
break;
case DateFormat.MONTH:
mDateSpinners.addView(mMonthSpinner);
break;
case DateFormat.YEAR:
mDateSpinners.addView(mYearSpinner);
break;
default:
throw new IllegalArgumentException();
}
}
mDateSpinners.addView(mWeekSpinner);
}
private void setDate(Calendar calendar){
mCurrentDate = mTempDate;
if (mCurrentDate.before(mMinDate)) {
mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
} else if (mCurrentDate.after(mMaxDate)) {
mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
}
}
private void updateInputState() {
InputMethodManager inputMethodManager = (InputMethodManager)
getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
if (mYearEnabled && inputMethodManager.isActive(mYearSpinnerInput)) {
mYearSpinnerInput.clearFocus();
inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
} else if (mMonthEnabled && inputMethodManager.isActive(mMonthSpinnerInput)) {
mMonthSpinnerInput.clearFocus();
inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
} else if (mDayEnabled && inputMethodManager.isActive(mDaySpinnerInput)) {
mDaySpinnerInput.clearFocus();
inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
} else if (mHourEnabled && inputMethodManager.isActive(mHourSpinnerInput)) {
mHourSpinnerInput.clearFocus();
inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
} else if (mMinuteEnabled && inputMethodManager.isActive(mMinuteSpinnerInput)) {
mMinuteSpinnerInput.clearFocus();
inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
}
}
private void updateSpinners() {
if (mDayEnabled) {
if (mCurrentDate.equals(mMinDate)) {
mDaySpinner.setMinValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
} else if (mCurrentDate.equals(mMaxDate)) {
mDaySpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.DAY_OF_MONTH));
mDaySpinner.setMaxValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
} else {
mDaySpinner.setMinValue(1);
mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
}
mDaySpinner.setValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
}
if (mWeekEnabled) {
mWeekSpinner.setMinValue(1);
mWeekSpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.WEEK_OF_YEAR));
mWeekSpinner.setValue(mCurrentDate.get(Calendar.WEEK_OF_YEAR));
}
if (mMonthEnabled) {
mMonthSpinner.setDisplayedValues(null);
if (mCurrentDate.equals(mMinDate)) {
mMonthSpinner.setMinValue(mCurrentDate.get(Calendar.MONTH));
mMonthSpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.MONTH));
} else if (mCurrentDate.equals(mMaxDate)) {
mMonthSpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.MONTH));
mMonthSpinner.setMaxValue(mCurrentDate.get(Calendar.MONTH));
} else {
mMonthSpinner.setMinValue(0);
mMonthSpinner.setMaxValue(11);
}
String[] displayedValues = Arrays.copyOfRange(mShortMonths,
mMonthSpinner.getMinValue(), mMonthSpinner.getMaxValue() + 1);
mMonthSpinner.setDisplayedValues(displayedValues);
mMonthSpinner.setValue(mCurrentDate.get(Calendar.MONTH));
}
if (mYearEnabled) {
mYearSpinner.setMinValue(mMinDate.get(Calendar.YEAR));
mYearSpinner.setMaxValue(mMaxDate.get(Calendar.YEAR));
mYearSpinner.setValue(mCurrentDate.get(Calendar.YEAR));
}
if (mHourEnabled) {
mHourSpinner.setValue(mCurrentDate.get(Calendar.HOUR_OF_DAY));
}
if (mMinuteEnabled) {
mMinuteSpinner.setValue(mCurrentDate.get(Calendar.MINUTE));
}
}
private void updateCalendar() {
if (mCalendarEnabled){
mCalendar.setDate(mCurrentDate.getTimeInMillis(), false, false);
}
}
private void notifyDateChanged() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
}
public void toggleCalendar(boolean shown) {
if ((mState != pickersState.DATE && mState != pickersState.DATETIME) ||
Build.VERSION.SDK_INT < 11 || mScreenWidth < SCREEN_SIZE_THRESHOLD) {
if (DEBUG) Log.d(LOGTAG,"Cannot display calendar on this device, in this state" +
": screen width :"+mScreenWidth);
return;
}
if (shown){
mCalendarEnabled = true;
mCalendar.setVisibility(VISIBLE);
setYearShown(false);
setWeekShown(false);
setMonthShown(false);
setDayShown(false);
} else {
mCalendar.setVisibility(GONE);
setYearShown(true);
setMonthShown(true);
setDayShown(true);
mSpinners.setOrientation(LinearLayout.HORIZONTAL);
mCalendarEnabled = false;
}
}
private void setYearShown(boolean shown) {
if (shown) {
toggleCalendar(false);
mYearSpinner.setVisibility(VISIBLE);
mYearEnabled = true;
} else {
mYearSpinner.setVisibility(GONE);
mYearEnabled = false;
}
}
private void setWeekShown(boolean shown) {
if (shown) {
toggleCalendar(false);
mWeekSpinner.setVisibility(VISIBLE);
mWeekEnabled = true;
} else {
mWeekSpinner.setVisibility(GONE);
mWeekEnabled = false;
}
}
private void setMonthShown(boolean shown) {
if (shown) {
toggleCalendar(false);
mMonthSpinner.setVisibility(VISIBLE);
mMonthEnabled = true;
} else {
mMonthSpinner.setVisibility(GONE);
mMonthEnabled = false;
}
}
private void setDayShown(boolean shown) {
if (shown) {
toggleCalendar(false);
mDaySpinner.setVisibility(VISIBLE);
mDayEnabled = true;
} else {
mDaySpinner.setVisibility(GONE);
mDayEnabled = false;
}
}
private void setHourShown(boolean shown) {
if (shown) {
mHourSpinner.setVisibility(VISIBLE);
mHourEnabled= true;
} else {
mHourSpinner.setVisibility(GONE);
mTimeSpinners.setVisibility(GONE);
mHourEnabled = false;
}
}
private void setMinuteShown(boolean shown) {
if (shown) {
mMinuteSpinner.setVisibility(VISIBLE);
mTimeSpinners.findViewById(R.id.mincolon).setVisibility(VISIBLE);
mMinuteEnabled= true;
} else {
mMinuteSpinner.setVisibility(GONE);
mTimeSpinners.findViewById(R.id.mincolon).setVisibility(GONE);
mMinuteEnabled = false;
}
}
private void setCurrentLocale(Locale locale) {
if (locale.equals(mCurrentLocale)) {
return;
}
mCurrentLocale = locale;
mTempDate = getCalendarForLocale(mTempDate, locale);
mMinDate = getCalendarForLocale(mMinDate, locale);
mMaxDate = getCalendarForLocale(mMaxDate, locale);
mCurrentDate = getCalendarForLocale(mCurrentDate, locale);
mNumberOfMonths = mTempDate.getActualMaximum(Calendar.MONTH) + 1;
mShortMonths = new String[mNumberOfMonths];
for (int i = 0; i < mNumberOfMonths; i++) {
mShortMonths[i] = DateUtils.getMonthString(Calendar.JANUARY + i,
DateUtils.LENGTH_MEDIUM);
}
}
private 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;
}
}
public void updateDate(Calendar calendar) {
if (mCurrentDate.equals(calendar)) {
return;
}
mCurrentDate.setTimeInMillis(calendar.getTimeInMillis());
if (mCurrentDate.before(mMinDate)) {
mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
} else if (mCurrentDate.after(mMaxDate)) {
mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
}
updateSpinners();
notifyDateChanged();
}
}