/* * Copyright (C) 2017 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.support.v17.leanback.widget.picker; import android.content.Context; import android.content.res.TypedArray; import android.support.v17.leanback.R; import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Locale; /** * {@link TimePicker} is a direct subclass of {@link Picker}. * <p> * This class is a widget for selecting time and displays it according to the formatting for the * current system locale. The time can be selected by hour, minute, and AM/PM picker columns. * The AM/PM mode is activated only when the 12 hour format is desired by setting * {@code is24HourFormat} attribute to false. Otherwise, TimePicker displays only two columns for a * 24 hour time format. * <p> * This widget can show the current time as the initial value if {@code useCurrentTime} is set to * true. Each individual time picker field can be set set at any time by calling * {@link #setHour(int)}, {@link #setMinute(int)} using 24-hour time format. The time format can * also be changed at any time by calling {@link #setIs24Hour(boolean)}, and the AM/PM picker column * will be activated or deactivated according to the given format. * * @attr ref R.styleable#lbTimePicker_is24HourFormat * @attr ref R.styleable#lbTimePicker_useCurrentTime */ public class TimePicker extends Picker { static final String TAG = "TimePicker"; private static final int AM_INDEX = 0; private static final int PM_INDEX = 1; private static final int HOURS_IN_HALF_DAY = 12; PickerColumn mHourColumn; PickerColumn mMinuteColumn; PickerColumn mAmPmColumn; private ViewGroup mPickerView; private View mAmPmSeparatorView; int mColHourIndex; int mColMinuteIndex; int mColAmPmIndex; private final PickerUtility.TimeConstant mConstant; private boolean mIs24hFormat; private int mCurrentHour; private int mCurrentMinute; private int mCurrentAmPmIndex; /** * Constructor called when inflating a TimePicker widget. This version uses a default style of * 0, so the only attribute values applied are those in the Context's Theme and the given * AttributeSet. * * @param context the context this TimePicker widget is associated with through which we can * access the current theme attributes and resources * @param attrs the attributes of the XML tag that is inflating the TimePicker widget */ public TimePicker(Context context, AttributeSet attrs) { this(context, attrs, 0); } /** * Constructor called when inflating a TimePicker widget. * * @param context the context this TimePicker widget is associated with through which we can * access the current theme attributes and resources * @param attrs the attributes of the XML tag that is inflating the TimePicker widget * @param defStyleAttr An attribute in the current theme that contains a reference to a style * resource that supplies default values for the widget. Can be 0 to not * look for defaults. */ public TimePicker(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mConstant = PickerUtility.getTimeConstantInstance(Locale.getDefault(), context.getResources()); setSeparator(mConstant.timeSeparator); mPickerView = (ViewGroup) findViewById(R.id.picker); final TypedArray attributesArray = context.obtainStyledAttributes(attrs, R.styleable.lbTimePicker); mIs24hFormat = attributesArray.getBoolean(R.styleable.lbTimePicker_is24HourFormat, false); boolean useCurrentTime = attributesArray.getBoolean(R.styleable.lbTimePicker_useCurrentTime, true); updateColumns(getTimePickerFormat()); // The column range for the minute and AM/PM column is static and does not change, whereas // the hour column range can change depending on whether 12 or 24 hour format is set at // any given time. updateHourColumn(false); updateMin(mMinuteColumn, 0); updateMax(mMinuteColumn, 59); updateMin(mAmPmColumn, 0); updateMax(mAmPmColumn, 1); updateAmPmColumn(); if (useCurrentTime) { Calendar currentDate = PickerUtility.getCalendarForLocale(null, mConstant.locale); setHour(currentDate.get(Calendar.HOUR_OF_DAY)); setMinute(currentDate.get(Calendar.MINUTE)); } } private static boolean updateMin(PickerColumn column, int value) { if (value != column.getMinValue()) { column.setMinValue(value); return true; } return false; } private static boolean updateMax(PickerColumn column, int value) { if (value != column.getMaxValue()) { column.setMaxValue(value); return true; } return false; } /** * * @return the time picker format string based on the current system locale and the layout * direction */ private String getTimePickerFormat() { // Obtain the time format string per the current locale (e.g. h:mm a) String hmaPattern = ((SimpleDateFormat) DateFormat .getTimeInstance(DateFormat.SHORT, mConstant.locale)).toPattern(); boolean isRTL = TextUtils.getLayoutDirectionFromLocale(mConstant.locale) == View .LAYOUT_DIRECTION_RTL; boolean isAmPmAtEnd = hmaPattern.indexOf("a") > hmaPattern.indexOf("m"); // Hour will always appear to the left of minutes regardless of layout direction. String timePickerFormat = isRTL ? "mh" : "hm"; return isAmPmAtEnd ? (timePickerFormat + "a") : ("a" + timePickerFormat); } private void updateColumns(String timePickerFormat) { if (TextUtils.isEmpty(timePickerFormat)) { timePickerFormat = "hma"; } timePickerFormat = timePickerFormat.toUpperCase(); mHourColumn = mMinuteColumn = mAmPmColumn = null; mColHourIndex = mColMinuteIndex = mColAmPmIndex = -1; ArrayList<PickerColumn> columns = new ArrayList<>(3); for (int i = 0; i < timePickerFormat.length(); i++) { switch (timePickerFormat.charAt(i)) { case 'H': columns.add(mHourColumn = new PickerColumn()); mHourColumn.setStaticLabels(mConstant.hours24); mColHourIndex = i; break; case 'M': columns.add(mMinuteColumn = new PickerColumn()); mMinuteColumn.setStaticLabels(mConstant.minutes); mColMinuteIndex = i; break; case 'A': columns.add(mAmPmColumn = new PickerColumn()); mAmPmColumn.setStaticLabels(mConstant.ampm); mColAmPmIndex = i; updateMin(mAmPmColumn, 0); updateMax(mAmPmColumn, 1); break; default: throw new IllegalArgumentException("Invalid time picker format."); } } setColumns(columns); mAmPmSeparatorView = mPickerView.getChildAt(mColAmPmIndex == 0 ? 1 : (2 * mColAmPmIndex - 1)); } /** * Updates the range in the hour column and notifies column changed if notifyChanged is true. * Hour column can have either [0-23] or [1-12] depending on whether the 24 hour format is set * or not. * * @param notifyChanged {code true} if we should notify data set changed on the hour column, * {@code false} otherwise. */ private void updateHourColumn(boolean notifyChanged) { updateMin(mHourColumn, mIs24hFormat ? 0 : 1); updateMax(mHourColumn, mIs24hFormat ? 23 : 12); if (notifyChanged) { setColumnAt(mColHourIndex, mHourColumn); } } /** * Updates AM/PM column depending on whether the 24 hour format is set or not. The visibility of * this column is set to {@code GONE} for a 24 hour format, and {@code VISIBLE} in 12 hour * format. This method also updates the value of this column for a 12 hour format. */ private void updateAmPmColumn() { if (mIs24hFormat) { mColumnViews.get(mColAmPmIndex).setVisibility(GONE); mAmPmSeparatorView.setVisibility(GONE); } else { mColumnViews.get(mColAmPmIndex).setVisibility(VISIBLE); mAmPmSeparatorView.setVisibility(VISIBLE); setColumnValue(mColAmPmIndex, mCurrentAmPmIndex, false); } } /** * Sets the currently selected hour using a 24-hour time. * * @param hour the hour to set, in the range (0-23) * @see #getHour() */ public void setHour(int hour) { if (hour < 0 || hour > 23) { throw new IllegalArgumentException("hour: " + hour + " is not in [0-23] range in"); } mCurrentHour = hour; if (!mIs24hFormat) { if (mCurrentHour >= HOURS_IN_HALF_DAY) { mCurrentAmPmIndex = PM_INDEX; if (mCurrentHour > HOURS_IN_HALF_DAY) { mCurrentHour -= HOURS_IN_HALF_DAY; } } else { mCurrentAmPmIndex = AM_INDEX; if (mCurrentHour == 0) { mCurrentHour = HOURS_IN_HALF_DAY; } } updateAmPmColumn(); } setColumnValue(mColHourIndex, mCurrentHour, false); } /** * Returns the currently selected hour using 24-hour time. * * @return the currently selected hour in the range (0-23) * @see #setHour(int) */ public int getHour() { if (mIs24hFormat) { return mCurrentHour; } if (mCurrentAmPmIndex == AM_INDEX) { return mCurrentHour % HOURS_IN_HALF_DAY; } return (mCurrentHour % HOURS_IN_HALF_DAY) + HOURS_IN_HALF_DAY; } /** * Sets the currently selected minute. * * @param minute the minute to set, in the range (0-59) * @see #getMinute() */ public void setMinute(int minute) { if (mCurrentMinute == minute) { return; } if (minute < 0 || minute > 59) { throw new IllegalArgumentException("minute: " + minute + " is not in [0-59] range."); } mCurrentMinute = minute; setColumnValue(mColMinuteIndex, mCurrentMinute, false); } /** * Returns the currently selected minute. * * @return the currently selected minute, in the range (0-59) * @see #setMinute(int) */ public int getMinute() { return mCurrentMinute; } /** * Sets whether this widget displays a 24-hour mode or a 12-hour mode with an AM/PM picker. * * @param is24Hour {@code true} to display in 24-hour mode, * {@code false} ti display in 12-hour mode with AM/PM. * @see #is24Hour() */ public void setIs24Hour(boolean is24Hour) { if (mIs24hFormat == is24Hour) { return; } // the ordering of these statements is important int currentHour = getHour(); mIs24hFormat = is24Hour; updateHourColumn(true); setHour(currentHour); updateAmPmColumn(); } /** * @return {@code true} if this widget displays time in 24-hour mode, * {@code false} otherwise. * * @see #setIs24Hour(boolean) */ public boolean is24Hour() { return mIs24hFormat; } /** * Only meaningful for a 12-hour time. * * @return {@code true} if the currently selected time is in PM, * {@code false} if the currently selected time in in AM. */ public boolean isPm() { return (mCurrentAmPmIndex == PM_INDEX); } @Override public void onColumnValueChanged(int columnIndex, int newValue) { if (columnIndex == mColHourIndex) { mCurrentHour = newValue; } else if (columnIndex == mColMinuteIndex) { mCurrentMinute = newValue; } else if (columnIndex == mColAmPmIndex) { mCurrentAmPmIndex = newValue; } else { throw new IllegalArgumentException("Invalid column index."); } } }