/* * 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.widget; import android.content.Context; import android.os.LocaleList; import android.text.Editable; import android.text.InputFilter; import android.text.TextUtils; import android.text.TextWatcher; import android.util.AttributeSet; import android.util.MathUtils; import android.view.View; import android.view.accessibility.AccessibilityManager; import com.android.internal.R; /** * View to show text input based time picker with hour and minute fields and an optional AM/PM * spinner. * * @hide */ public class TextInputTimePickerView extends RelativeLayout { public static final int HOURS = 0; public static final int MINUTES = 1; public static final int AMPM = 2; private static final int AM = 0; private static final int PM = 1; private final EditText mHourEditText; private final EditText mMinuteEditText; private final TextView mInputSeparatorView; private final Spinner mAmPmSpinner; private final TextView mErrorLabel; private final TextView mHourLabel; private final TextView mMinuteLabel; private boolean mIs24Hour; private boolean mHourFormatStartsAtZero; private OnValueTypedListener mListener; private boolean mErrorShowing; private boolean mTimeSet; interface OnValueTypedListener { void onValueChanged(int inputType, int newValue); } public TextInputTimePickerView(Context context) { this(context, null); } public TextInputTimePickerView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public TextInputTimePickerView(Context context, AttributeSet attrs, int defStyle) { this(context, attrs, defStyle, 0); } public TextInputTimePickerView(Context context, AttributeSet attrs, int defStyle, int defStyleRes) { super(context, attrs, defStyle, defStyleRes); inflate(context, R.layout.time_picker_text_input_material, this); mHourEditText = findViewById(R.id.input_hour); mMinuteEditText = findViewById(R.id.input_minute); mInputSeparatorView = findViewById(R.id.input_separator); mErrorLabel = findViewById(R.id.label_error); mHourLabel = findViewById(R.id.label_hour); mMinuteLabel = findViewById(R.id.label_minute); mHourEditText.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {} @Override public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {} @Override public void afterTextChanged(Editable editable) { if (parseAndSetHourInternal(editable.toString()) && editable.length() > 1) { AccessibilityManager am = (AccessibilityManager) context.getSystemService( context.ACCESSIBILITY_SERVICE); if (!am.isEnabled()) { mMinuteEditText.requestFocus(); } } } }); mMinuteEditText.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {} @Override public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {} @Override public void afterTextChanged(Editable editable) { parseAndSetMinuteInternal(editable.toString()); } }); mAmPmSpinner = findViewById(R.id.am_pm_spinner); final String[] amPmStrings = TimePicker.getAmPmStrings(context); ArrayAdapter adapter = new ArrayAdapter(context, R.layout.simple_spinner_dropdown_item); adapter.add(TimePickerClockDelegate.obtainVerbatim(amPmStrings[0])); adapter.add(TimePickerClockDelegate.obtainVerbatim(amPmStrings[1])); mAmPmSpinner.setAdapter(adapter); mAmPmSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView adapterView, View view, int position, long id) { if (position == 0) { mListener.onValueChanged(AMPM, AM); } else { mListener.onValueChanged(AMPM, PM); } } @Override public void onNothingSelected(AdapterView adapterView) {} }); } void setListener(OnValueTypedListener listener) { mListener = listener; } void setHourFormat(int maxCharLength) { mHourEditText.setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxCharLength)}); mMinuteEditText.setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxCharLength)}); final LocaleList locales = mContext.getResources().getConfiguration().getLocales(); mHourEditText.setImeHintLocales(locales); mMinuteEditText.setImeHintLocales(locales); } boolean validateInput() { final String hourText = TextUtils.isEmpty(mHourEditText.getText()) ? mHourEditText.getHint().toString() : mHourEditText.getText().toString(); final String minuteText = TextUtils.isEmpty(mMinuteEditText.getText()) ? mMinuteEditText.getHint().toString() : mMinuteEditText.getText().toString(); final boolean inputValid = parseAndSetHourInternal(hourText) && parseAndSetMinuteInternal(minuteText); setError(!inputValid); return inputValid; } void updateSeparator(String separatorText) { mInputSeparatorView.setText(separatorText); } private void setError(boolean enabled) { mErrorShowing = enabled; mErrorLabel.setVisibility(enabled ? View.VISIBLE : View.INVISIBLE); mHourLabel.setVisibility(enabled ? View.INVISIBLE : View.VISIBLE); mMinuteLabel.setVisibility(enabled ? View.INVISIBLE : View.VISIBLE); } private void setTimeSet(boolean timeSet) { mTimeSet = mTimeSet || timeSet; } private boolean isTimeSet() { return mTimeSet; } /** * Computes the display value and updates the text of the view. *

* This method should be called whenever the current value or display * properties (leading zeroes, max digits) change. */ void updateTextInputValues(int localizedHour, int minute, int amOrPm, boolean is24Hour, boolean hourFormatStartsAtZero) { final String hourFormat = "%d"; final String minuteFormat = "%02d"; mIs24Hour = is24Hour; mHourFormatStartsAtZero = hourFormatStartsAtZero; mAmPmSpinner.setVisibility(is24Hour ? View.INVISIBLE : View.VISIBLE); if (amOrPm == AM) { mAmPmSpinner.setSelection(0); } else { mAmPmSpinner.setSelection(1); } if (isTimeSet()) { mHourEditText.setText(String.format(hourFormat, localizedHour)); mMinuteEditText.setText(String.format(minuteFormat, minute)); } else { mHourEditText.setHint(String.format(hourFormat, localizedHour)); mMinuteEditText.setHint(String.format(minuteFormat, minute)); } if (mErrorShowing) { validateInput(); } } private boolean parseAndSetHourInternal(String input) { try { final int hour = Integer.parseInt(input); if (!isValidLocalizedHour(hour)) { final int minHour = mHourFormatStartsAtZero ? 0 : 1; final int maxHour = mIs24Hour ? 23 : 11 + minHour; mListener.onValueChanged(HOURS, getHourOfDayFromLocalizedHour( MathUtils.constrain(hour, minHour, maxHour))); return false; } mListener.onValueChanged(HOURS, getHourOfDayFromLocalizedHour(hour)); setTimeSet(true); return true; } catch (NumberFormatException e) { // Do nothing since we cannot parse the input. return false; } } private boolean parseAndSetMinuteInternal(String input) { try { final int minutes = Integer.parseInt(input); if (minutes < 0 || minutes > 59) { mListener.onValueChanged(MINUTES, MathUtils.constrain(minutes, 0, 59)); return false; } mListener.onValueChanged(MINUTES, minutes); setTimeSet(true); return true; } catch (NumberFormatException e) { // Do nothing since we cannot parse the input. return false; } } private boolean isValidLocalizedHour(int localizedHour) { final int minHour = mHourFormatStartsAtZero ? 0 : 1; final int maxHour = (mIs24Hour ? 23 : 11) + minHour; return localizedHour >= minHour && localizedHour <= maxHour; } private int getHourOfDayFromLocalizedHour(int localizedHour) { int hourOfDay = localizedHour; if (mIs24Hour) { if (!mHourFormatStartsAtZero && localizedHour == 24) { hourOfDay = 0; } } else { if (!mHourFormatStartsAtZero && localizedHour == 12) { hourOfDay = 0; } if (mAmPmSpinner.getSelectedItemPosition() == 1) { hourOfDay += 12; } } return hourOfDay; } }