• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.widget;
18 
19 import android.content.Context;
20 import android.os.LocaleList;
21 import android.text.Editable;
22 import android.text.InputFilter;
23 import android.text.TextWatcher;
24 import android.util.AttributeSet;
25 import android.util.MathUtils;
26 import android.view.View;
27 
28 import com.android.internal.R;
29 
30 /**
31  * View to show text input based time picker with hour and minute fields and an optional AM/PM
32  * spinner.
33  *
34  * @hide
35  */
36 public class TextInputTimePickerView extends RelativeLayout {
37     public static final int HOURS = 0;
38     public static final int MINUTES = 1;
39     public static final int AMPM = 2;
40 
41     private static final int AM = 0;
42     private static final int PM = 1;
43 
44     private final EditText mHourEditText;
45     private final EditText mMinuteEditText;
46     private final TextView mInputSeparatorView;
47     private final Spinner mAmPmSpinner;
48     private final TextView mErrorLabel;
49     private final TextView mHourLabel;
50     private final TextView mMinuteLabel;
51 
52     private boolean mIs24Hour;
53     private boolean mHourFormatStartsAtZero;
54     private OnValueTypedListener mListener;
55 
56     private boolean mErrorShowing;
57 
58     interface OnValueTypedListener {
onValueChanged(int inputType, int newValue)59         void onValueChanged(int inputType, int newValue);
60     }
61 
TextInputTimePickerView(Context context)62     public TextInputTimePickerView(Context context) {
63         this(context, null);
64     }
65 
TextInputTimePickerView(Context context, AttributeSet attrs)66     public TextInputTimePickerView(Context context, AttributeSet attrs) {
67         this(context, attrs, 0);
68     }
69 
TextInputTimePickerView(Context context, AttributeSet attrs, int defStyle)70     public TextInputTimePickerView(Context context, AttributeSet attrs, int defStyle) {
71         this(context, attrs, defStyle, 0);
72     }
73 
TextInputTimePickerView(Context context, AttributeSet attrs, int defStyle, int defStyleRes)74     public TextInputTimePickerView(Context context, AttributeSet attrs, int defStyle,
75             int defStyleRes) {
76         super(context, attrs, defStyle, defStyleRes);
77 
78         inflate(context, R.layout.time_picker_text_input_material, this);
79 
80         mHourEditText = findViewById(R.id.input_hour);
81         mMinuteEditText = findViewById(R.id.input_minute);
82         mInputSeparatorView = findViewById(R.id.input_separator);
83         mErrorLabel = findViewById(R.id.label_error);
84         mHourLabel = findViewById(R.id.label_hour);
85         mMinuteLabel = findViewById(R.id.label_minute);
86 
87         mHourEditText.addTextChangedListener(new TextWatcher() {
88             @Override
89             public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
90 
91             @Override
92             public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
93 
94             @Override
95             public void afterTextChanged(Editable editable) {
96                 parseAndSetHourInternal(editable.toString());
97             }
98         });
99 
100         mMinuteEditText.addTextChangedListener(new TextWatcher() {
101             @Override
102             public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
103 
104             @Override
105             public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
106 
107             @Override
108             public void afterTextChanged(Editable editable) {
109                 parseAndSetMinuteInternal(editable.toString());
110             }
111         });
112 
113         mAmPmSpinner = findViewById(R.id.am_pm_spinner);
114         final String[] amPmStrings = TimePicker.getAmPmStrings(context);
115         ArrayAdapter<CharSequence> adapter =
116                 new ArrayAdapter<CharSequence>(context, R.layout.simple_spinner_dropdown_item);
117         adapter.add(TimePickerClockDelegate.obtainVerbatim(amPmStrings[0]));
118         adapter.add(TimePickerClockDelegate.obtainVerbatim(amPmStrings[1]));
119         mAmPmSpinner.setAdapter(adapter);
120         mAmPmSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
121             @Override
122             public void onItemSelected(AdapterView<?> adapterView, View view, int position,
123                     long id) {
124                 if (position == 0) {
125                     mListener.onValueChanged(AMPM, AM);
126                 } else {
127                     mListener.onValueChanged(AMPM, PM);
128                 }
129             }
130 
131             @Override
132             public void onNothingSelected(AdapterView<?> adapterView) {}
133         });
134     }
135 
setListener(OnValueTypedListener listener)136     void setListener(OnValueTypedListener listener) {
137         mListener = listener;
138     }
139 
setHourFormat(int maxCharLength)140     void setHourFormat(int maxCharLength) {
141         mHourEditText.setFilters(new InputFilter[] {
142                 new InputFilter.LengthFilter(maxCharLength)});
143         mMinuteEditText.setFilters(new InputFilter[] {
144                 new InputFilter.LengthFilter(maxCharLength)});
145         final LocaleList locales = mContext.getResources().getConfiguration().getLocales();
146         mHourEditText.setImeHintLocales(locales);
147         mMinuteEditText.setImeHintLocales(locales);
148     }
149 
validateInput()150     boolean validateInput() {
151         final boolean inputValid = parseAndSetHourInternal(mHourEditText.getText().toString())
152                 && parseAndSetMinuteInternal(mMinuteEditText.getText().toString());
153         setError(!inputValid);
154         return inputValid;
155     }
156 
updateSeparator(String separatorText)157     void updateSeparator(String separatorText) {
158         mInputSeparatorView.setText(separatorText);
159     }
160 
setError(boolean enabled)161     private void setError(boolean enabled) {
162         mErrorShowing = enabled;
163 
164         mErrorLabel.setVisibility(enabled ? View.VISIBLE : View.INVISIBLE);
165         mHourLabel.setVisibility(enabled ? View.INVISIBLE : View.VISIBLE);
166         mMinuteLabel.setVisibility(enabled ? View.INVISIBLE : View.VISIBLE);
167     }
168 
169     /**
170      * Computes the display value and updates the text of the view.
171      * <p>
172      * This method should be called whenever the current value or display
173      * properties (leading zeroes, max digits) change.
174      */
updateTextInputValues(int localizedHour, int minute, int amOrPm, boolean is24Hour, boolean hourFormatStartsAtZero)175     void updateTextInputValues(int localizedHour, int minute, int amOrPm, boolean is24Hour,
176             boolean hourFormatStartsAtZero) {
177         final String hourFormat = "%d";
178         final String minuteFormat = "%02d";
179 
180         mIs24Hour = is24Hour;
181         mHourFormatStartsAtZero = hourFormatStartsAtZero;
182 
183         mAmPmSpinner.setVisibility(is24Hour ? View.INVISIBLE : View.VISIBLE);
184 
185         if (amOrPm == AM) {
186             mAmPmSpinner.setSelection(0);
187         } else {
188             mAmPmSpinner.setSelection(1);
189         }
190 
191         mHourEditText.setText(String.format(hourFormat, localizedHour));
192         mMinuteEditText.setText(String.format(minuteFormat, minute));
193 
194         if (mErrorShowing) {
195             validateInput();
196         }
197     }
198 
parseAndSetHourInternal(String input)199     private boolean parseAndSetHourInternal(String input) {
200         try {
201             final int hour = Integer.parseInt(input);
202             if (!isValidLocalizedHour(hour)) {
203                 final int minHour = mHourFormatStartsAtZero ? 0 : 1;
204                 final int maxHour = mIs24Hour ? 23 : 11 + minHour;
205                 mListener.onValueChanged(HOURS, getHourOfDayFromLocalizedHour(
206                         MathUtils.constrain(hour, minHour, maxHour)));
207                 return false;
208             }
209             mListener.onValueChanged(HOURS, getHourOfDayFromLocalizedHour(hour));
210             return true;
211         } catch (NumberFormatException e) {
212             // Do nothing since we cannot parse the input.
213             return false;
214         }
215     }
216 
parseAndSetMinuteInternal(String input)217     private boolean parseAndSetMinuteInternal(String input) {
218         try {
219             final int minutes = Integer.parseInt(input);
220             if (minutes < 0 || minutes > 59) {
221                 mListener.onValueChanged(MINUTES, MathUtils.constrain(minutes, 0, 59));
222                 return false;
223             }
224             mListener.onValueChanged(MINUTES, minutes);
225             return true;
226         } catch (NumberFormatException e) {
227             // Do nothing since we cannot parse the input.
228             return false;
229         }
230     }
231 
isValidLocalizedHour(int localizedHour)232     private boolean isValidLocalizedHour(int localizedHour) {
233         final int minHour = mHourFormatStartsAtZero ? 0 : 1;
234         final int maxHour = (mIs24Hour ? 23 : 11) + minHour;
235         return localizedHour >= minHour && localizedHour <= maxHour;
236     }
237 
getHourOfDayFromLocalizedHour(int localizedHour)238     private int getHourOfDayFromLocalizedHour(int localizedHour) {
239         int hourOfDay = localizedHour;
240         if (mIs24Hour) {
241             if (!mHourFormatStartsAtZero && localizedHour == 24) {
242                 hourOfDay = 0;
243             }
244         } else {
245             if (!mHourFormatStartsAtZero && localizedHour == 12) {
246                 hourOfDay = 0;
247             }
248             if (mAmPmSpinner.getSelectedItemPosition() == 1) {
249                 hourOfDay += 12;
250             }
251         }
252         return hourOfDay;
253     }
254 }
255