/* * 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 android.widget; import android.annotation.Widget; import android.content.Context; import android.content.res.Configuration; import android.content.res.TypedArray; import android.os.Parcel; import android.os.Parcelable; import android.text.format.DateFormat; import android.text.format.DateUtils; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.NumberPicker.OnValueChangeListener; import com.android.internal.R; import java.text.DateFormatSymbols; import java.util.Calendar; import java.util.Locale; /** * A view for selecting the time of day, in either 24 hour or AM/PM mode. The * hour, each minute digit, and AM/PM (if applicable) can be conrolled by * vertical spinners. The hour can be entered by keyboard input. Entering in two * digit hours can be accomplished by hitting two digits within a timeout of * about a second (e.g. '1' then '2' to select 12). The minutes can be entered * by entering single digits. Under AM/PM mode, the user can hit 'a', 'A", 'p' * or 'P' to pick. For a dialog using this view, see * {@link android.app.TimePickerDialog}. *
* See the Pickers * guide. *
*/ @Widget public class TimePicker extends FrameLayout { private static final boolean DEFAULT_ENABLED_STATE = true; private static final int HOURS_IN_HALF_DAY = 12; /** * A no-op callback used in the constructor to avoid null checks later in * the code. */ private static final OnTimeChangedListener NO_OP_CHANGE_LISTENER = new OnTimeChangedListener() { public void onTimeChanged(TimePicker view, int hourOfDay, int minute) { } }; // state private boolean mIs24HourView; private boolean mIsAm; // ui components private final NumberPicker mHourSpinner; private final NumberPicker mMinuteSpinner; private final NumberPicker mAmPmSpinner; private final EditText mHourSpinnerInput; private final EditText mMinuteSpinnerInput; private final EditText mAmPmSpinnerInput; private final TextView mDivider; // Note that the legacy implementation of the TimePicker is // using a button for toggling between AM/PM while the new // version uses a NumberPicker spinner. Therefore the code // accommodates these two cases to be backwards compatible. private final Button mAmPmButton; private final String[] mAmPmStrings; private boolean mIsEnabled = DEFAULT_ENABLED_STATE; // callbacks private OnTimeChangedListener mOnTimeChangedListener; private Calendar mTempCalendar; private Locale mCurrentLocale; private boolean mHourWithTwoDigit; private char mHourFormat; /** * The callback interface used to indicate the time has been adjusted. */ public interface OnTimeChangedListener { /** * @param view The view associated with this listener. * @param hourOfDay The current hour. * @param minute The current minute. */ void onTimeChanged(TimePicker view, int hourOfDay, int minute); } 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); // initialization based on locale setCurrentLocale(Locale.getDefault()); // process style attributes TypedArray attributesArray = context.obtainStyledAttributes( attrs, R.styleable.TimePicker, defStyle, 0); int layoutResourceId = attributesArray.getResourceId( R.styleable.TimePicker_internalLayout, R.layout.time_picker); attributesArray.recycle(); LayoutInflater inflater = (LayoutInflater) context.getSystemService( Context.LAYOUT_INFLATER_SERVICE); inflater.inflate(layoutResourceId, this, true); // hour mHourSpinner = (NumberPicker) findViewById(R.id.hour); mHourSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() { public void onValueChange(NumberPicker spinner, int oldVal, int newVal) { updateInputState(); if (!is24HourView()) { if ((oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) || (oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1)) { mIsAm = !mIsAm; updateAmPmControl(); } } onTimeChanged(); } }); mHourSpinnerInput = (EditText) mHourSpinner.findViewById(R.id.numberpicker_input); mHourSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT); // divider (only for the new widget style) mDivider = (TextView) findViewById(R.id.divider); if (mDivider != null) { setDividerText(); } // minute mMinuteSpinner = (NumberPicker) findViewById(R.id.minute); mMinuteSpinner.setMinValue(0); mMinuteSpinner.setMaxValue(59); mMinuteSpinner.setOnLongPressUpdateInterval(100); mMinuteSpinner.setFormatter(NumberPicker.getTwoDigitFormatter()); mMinuteSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() { 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 == HOURS_IN_HALF_DAY) { mIsAm = !mIsAm; updateAmPmControl(); } mHourSpinner.setValue(newHour); } else if (oldVal == minValue && newVal == maxValue) { int newHour = mHourSpinner.getValue() - 1; if (!is24HourView() && newHour == HOURS_IN_HALF_DAY - 1) { mIsAm = !mIsAm; updateAmPmControl(); } mHourSpinner.setValue(newHour); } onTimeChanged(); } }); mMinuteSpinnerInput = (EditText) mMinuteSpinner.findViewById(R.id.numberpicker_input); mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT); /* Get the localized am/pm strings and use them in the spinner */ mAmPmStrings = new DateFormatSymbols().getAmPmStrings(); // am/pm View amPmView = findViewById(R.id.amPm); if (amPmView instanceof Button) { mAmPmSpinner = null; mAmPmSpinnerInput = null; mAmPmButton = (Button) amPmView; mAmPmButton.setOnClickListener(new OnClickListener() { 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 OnValueChangeListener() { public void onValueChange(NumberPicker picker, int oldVal, int newVal) { updateInputState(); picker.requestFocus(); mIsAm = !mIsAm; updateAmPmControl(); onTimeChanged(); } }); mAmPmSpinnerInput = (EditText) mAmPmSpinner.findViewById(R.id.numberpicker_input); mAmPmSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_DONE); } if (isAmPmAtStart()) { // Move the am/pm view to the beginning ViewGroup amPmParent = (ViewGroup) findViewById(R.id.timePickerLayout); amPmParent.removeView(amPmView); amPmParent.addView(amPmView, 0); // Swap layout margins if needed. They may be not symmetrical (Old Standard Theme for // example and not for Holo Theme) ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) amPmView.getLayoutParams(); final int startMargin = lp.getMarginStart(); final int endMargin = lp.getMarginEnd(); if (startMargin != endMargin) { lp.setMarginStart(endMargin); lp.setMarginEnd(startMargin); } } getHourFormatData(); // update controls to initial state updateHourControl(); updateMinuteControl(); updateAmPmControl(); setOnTimeChangedListener(NO_OP_CHANGE_LISTENER); // set to current time setCurrentHour(mTempCalendar.get(Calendar.HOUR_OF_DAY)); setCurrentMinute(mTempCalendar.get(Calendar.MINUTE)); if (!isEnabled()) { setEnabled(false); } // set the content descriptions setContentDescriptions(); // If not explicitly specified this view is important for accessibility. if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); } } private void getHourFormatData() { final Locale defaultLocale = Locale.getDefault(); final String bestDateTimePattern = DateFormat.getBestDateTimePattern(defaultLocale, (mIs24HourView) ? "Hm" : "hm"); final int lengthPattern = bestDateTimePattern.length(); mHourWithTwoDigit = 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') { mHourFormat = c; if (i + 1 < lengthPattern && c == bestDateTimePattern.charAt(i + 1)) { mHourWithTwoDigit = true; } break; } } } private boolean isAmPmAtStart() { final Locale defaultLocale = Locale.getDefault(); final String bestDateTimePattern = DateFormat.getBestDateTimePattern(defaultLocale, "hm" /* skeleton */); return bestDateTimePattern.startsWith("a"); } @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; } @Override public boolean isEnabled() { return mIsEnabled; } @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); setCurrentLocale(newConfig.locale); } /** * Sets the current locale. * * @param locale The current locale. */ private void setCurrentLocale(Locale locale) { if (locale.equals(mCurrentLocale)) { return; } mCurrentLocale = locale; mTempCalendar = Calendar.getInstance(locale); } /** * Used to save / restore state of time picker */ private static class SavedState extends BaseSavedState { private final int mHour; private final int mMinute; private SavedState(Parcelable superState, int hour, int minute) { super(superState); mHour = hour; mMinute = minute; } private SavedState(Parcel in) { super(in); mHour = in.readInt(); mMinute = in.readInt(); } 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); } @SuppressWarnings({"unused", "hiding"}) public static final Parcelable.Creator