diff --git a/api/current.txt b/api/current.txt index e315958badebd..8ee6ef8af09b7 100644 --- a/api/current.txt +++ b/api/current.txt @@ -50554,6 +50554,7 @@ package android.widget { method public void setIs24HourView(java.lang.Boolean); method public void setMinute(int); method public void setOnTimeChangedListener(android.widget.TimePicker.OnTimeChangedListener); + method public boolean validateInput(); } public static abstract interface TimePicker.OnTimeChangedListener { diff --git a/api/system-current.txt b/api/system-current.txt index ae145dc9b0375..c4505bcd2580d 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -54325,6 +54325,7 @@ package android.widget { method public void setIs24HourView(java.lang.Boolean); method public void setMinute(int); method public void setOnTimeChangedListener(android.widget.TimePicker.OnTimeChangedListener); + method public boolean validateInput(); } public static abstract interface TimePicker.OnTimeChangedListener { diff --git a/api/test-current.txt b/api/test-current.txt index 1e3b6db92185d..b21a4dcc4dcad 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -50879,6 +50879,7 @@ package android.widget { method public void setIs24HourView(java.lang.Boolean); method public void setMinute(int); method public void setOnTimeChangedListener(android.widget.TimePicker.OnTimeChangedListener); + method public boolean validateInput(); field public static final int MODE_CLOCK = 2; // 0x2 field public static final int MODE_SPINNER = 1; // 0x1 } diff --git a/core/java/android/app/TimePickerDialog.java b/core/java/android/app/TimePickerDialog.java index 3f467a01bee2a..b219f2afd016a 100644 --- a/core/java/android/app/TimePickerDialog.java +++ b/core/java/android/app/TimePickerDialog.java @@ -144,15 +144,26 @@ public class TimePickerDialog extends AlertDialog implements OnClickListener, /* do nothing */ } + @Override + public void show() { + super.show(); + getButton(BUTTON_POSITIVE).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (mTimePicker.validateInput()) { + if (mTimeSetListener != null) { + mTimeSetListener.onTimeSet(mTimePicker, mTimePicker.getCurrentHour(), + mTimePicker.getCurrentMinute()); + } + dismiss(); + } + } + }); + } + @Override public void onClick(DialogInterface dialog, int which) { switch (which) { - case BUTTON_POSITIVE: - if (mTimeSetListener != null) { - mTimeSetListener.onTimeSet(mTimePicker, mTimePicker.getCurrentHour(), - mTimePicker.getCurrentMinute()); - } - break; case BUTTON_NEGATIVE: cancel(); break; diff --git a/core/java/android/widget/TextInputTimePickerView.java b/core/java/android/widget/TextInputTimePickerView.java new file mode 100644 index 0000000000000..ef91576c0e275 --- /dev/null +++ b/core/java/android/widget/TextInputTimePickerView.java @@ -0,0 +1,249 @@ +/* + * 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.text.Editable; +import android.text.InputFilter; +import android.text.TextWatcher; +import android.util.AttributeSet; +import android.util.MathUtils; +import android.view.View; + +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; + + 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 = (EditText) findViewById(R.id.input_hour); + mMinuteEditText = (EditText) findViewById(R.id.input_minute); + mInputSeparatorView = (TextView) findViewById(R.id.input_separator); + mErrorLabel = (TextView) findViewById(R.id.label_error); + mHourLabel = (TextView) findViewById(R.id.label_hour); + mMinuteLabel = (TextView) 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) { + parseAndSetHourInternal(editable.toString()); + } + }); + + 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 = (Spinner) 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)}); + } + + boolean validateInput() { + final boolean inputValid = parseAndSetHourInternal(mHourEditText.getText().toString()) + && parseAndSetMinuteInternal(mMinuteEditText.getText().toString()); + 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); + } + + /** + * 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 format = "%d"; + + mIs24Hour = is24Hour; + mHourFormatStartsAtZero = hourFormatStartsAtZero; + + mAmPmSpinner.setVisibility(is24Hour ? View.INVISIBLE : View.VISIBLE); + + mHourEditText.setText(String.format(format, localizedHour)); + mMinuteEditText.setText(String.format(format, minute)); + + if (amOrPm == AM) { + mAmPmSpinner.setSelection(0); + } else { + mAmPmSpinner.setSelection(1); + } + + 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)); + 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); + 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; + } +} diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java index e6cd7987d59a7..9f38b04d4e099 100644 --- a/core/java/android/widget/TimePicker.java +++ b/core/java/android/widget/TimePicker.java @@ -278,6 +278,16 @@ public class TimePicker extends FrameLayout { return mDelegate.getBaseline(); } + /** + * Validates whether current input by the user is a valid time based on the locale. TimePicker + * will show an error message to the user if the time is not valid. + * + * @return {@code true} if the input is valid, {@code false} otherwise + */ + public boolean validateInput() { + return mDelegate.validateInput(); + } + @Override protected Parcelable onSaveInstanceState() { Parcelable superState = super.onSaveInstanceState(); @@ -341,6 +351,8 @@ public class TimePicker extends FrameLayout { void setIs24Hour(boolean is24Hour); boolean is24Hour(); + boolean validateInput(); + void setOnTimeChangedListener(OnTimeChangedListener onTimeChangedListener); void setEnabled(boolean enabled); diff --git a/core/java/android/widget/TimePickerClockDelegate.java b/core/java/android/widget/TimePickerClockDelegate.java index 1d37a21c1e7e8..3a0906393b978 100644 --- a/core/java/android/widget/TimePickerClockDelegate.java +++ b/core/java/android/widget/TimePickerClockDelegate.java @@ -16,12 +16,14 @@ package android.widget; +import android.annotation.IntDef; import android.annotation.Nullable; import android.annotation.TestApi; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.TypedArray; +import android.icu.text.DecimalFormatSymbols; import android.os.Parcelable; import android.text.SpannableStringBuilder; import android.text.format.DateFormat; @@ -40,11 +42,14 @@ import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.widget.RadialTimePickerView.OnValueSelectedListener; +import android.widget.TextInputTimePickerView.OnValueTypedListener; import com.android.internal.R; import com.android.internal.widget.NumericTextView; import com.android.internal.widget.NumericTextView.OnValueChangedListener; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Calendar; /** @@ -58,6 +63,13 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate { */ private static final long DELAY_COMMIT_MILLIS = 2000; + @IntDef({FROM_EXTERNAL_API, FROM_RADIAL_PICKER, FROM_INPUT_PICKER}) + @Retention(RetentionPolicy.SOURCE) + private @interface ChangeSource {} + private static final int FROM_EXTERNAL_API = 0; + private static final int FROM_RADIAL_PICKER = 1; + private static final int FROM_INPUT_PICKER = 2; + // Index used by RadialPickerLayout private static final int HOUR_INDEX = RadialTimePickerView.HOURS; private static final int MINUTE_INDEX = RadialTimePickerView.MINUTES; @@ -78,6 +90,15 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate { private final RadialTimePickerView mRadialTimePickerView; private final TextView mSeparatorView; + private boolean mRadialPickerModeEnabled = true; + private final ImageButton mRadialTimePickerModeButton; + private final String mRadialTimePickerModeEnabledDescription; + private final String mTextInputPickerModeEnabledDescription; + private final View mRadialTimePickerHeader; + private final View mTextInputPickerHeader; + + private final TextInputTimePickerView mTextInputPickerView; + private final Calendar mTempCalendar; // Accessibility strings. @@ -116,8 +137,8 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate { final int layoutResourceId = a.getResourceId(R.styleable.TimePicker_internalLayout, R.layout.time_picker_material); final View mainView = inflater.inflate(layoutResourceId, delegator); - final View headerView = mainView.findViewById(R.id.time_header); - headerView.setOnTouchListener(new NearestTouchDelegate()); + mRadialTimePickerHeader = mainView.findViewById(R.id.time_header); + mRadialTimePickerHeader.setOnTouchListener(new NearestTouchDelegate()); // Set up hour/minute labels. mHourView = (NumericTextView) mainView.findViewById(R.id.hours); @@ -170,6 +191,8 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate { headerTextColor = a.getColorStateList(R.styleable.TimePicker_headerTextColor); } + mTextInputPickerHeader = mainView.findViewById(R.id.input_header); + if (headerTextColor != null) { mHourView.setTextColor(headerTextColor); mSeparatorView.setTextColor(headerTextColor); @@ -180,7 +203,10 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate { // Set up header background, if available. if (a.hasValueOrEmpty(R.styleable.TimePicker_headerBackground)) { - headerView.setBackground(a.getDrawable(R.styleable.TimePicker_headerBackground)); + mRadialTimePickerHeader.setBackground(a.getDrawable( + R.styleable.TimePicker_headerBackground)); + mTextInputPickerHeader.setBackground(a.getDrawable( + R.styleable.TimePicker_headerBackground)); } a.recycle(); @@ -189,6 +215,22 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate { mRadialTimePickerView.applyAttributes(attrs, defStyleAttr, defStyleRes); mRadialTimePickerView.setOnValueSelectedListener(mOnValueSelectedListener); + mTextInputPickerView = (TextInputTimePickerView) mainView.findViewById(R.id.input_mode); + mTextInputPickerView.setListener(mOnValueTypedListener); + + mRadialTimePickerModeButton = + (ImageButton) mainView.findViewById(R.id.toggle_mode); + mRadialTimePickerModeButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + toggleRadialPickerMode(); + } + }); + mRadialTimePickerModeEnabledDescription = context.getResources().getString( + R.string.time_picker_radial_mode_description); + mTextInputPickerModeEnabledDescription = context.getResources().getString( + R.string.time_picker_text_input_mode_description); + mAllowAutoAdvance = true; updateHourFormat(); @@ -200,6 +242,34 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate { initialize(currentHour, currentMinute, mIs24Hour, HOUR_INDEX); } + private void toggleRadialPickerMode() { + if (mRadialPickerModeEnabled) { + mRadialTimePickerView.setVisibility(View.GONE); + mRadialTimePickerHeader.setVisibility(View.GONE); + mTextInputPickerHeader.setVisibility(View.VISIBLE); + mTextInputPickerView.setVisibility(View.VISIBLE); + mRadialTimePickerModeButton.setImageResource(R.drawable.btn_event_material); + mRadialTimePickerModeButton.setContentDescription( + mRadialTimePickerModeEnabledDescription); + mRadialPickerModeEnabled = false; + } else { + mRadialTimePickerView.setVisibility(View.VISIBLE); + mRadialTimePickerHeader.setVisibility(View.VISIBLE); + mTextInputPickerHeader.setVisibility(View.GONE); + mTextInputPickerView.setVisibility(View.GONE); + mRadialTimePickerModeButton.setImageResource(R.drawable.btn_keyboard_key_material); + mRadialTimePickerModeButton.setContentDescription( + mTextInputPickerModeEnabledDescription); + updateTextInputPicker(); + mRadialPickerModeEnabled = true; + } + } + + @Override + public boolean validateInput() { + return mTextInputPickerView.validateInput(); + } + /** * Ensures that a TextView is wide enough to contain its text without * wrapping or clipping. Measures the specified view and sets the minimum @@ -249,9 +319,16 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate { final int maxHour = (mIs24Hour ? 23 : 11) + minHour; mHourView.setRange(minHour, maxHour); mHourView.setShowLeadingZeroes(mHourFormatShowLeadingZero); + + final String[] digits = DecimalFormatSymbols.getInstance(mLocale).getDigitStrings(); + int maxCharLength = 0; + for (int i = 0; i < 10; i++) { + maxCharLength = Math.max(maxCharLength, digits[i].length()); + } + mTextInputPickerView.setHourFormat(maxCharLength * 2); } - private static final CharSequence obtainVerbatim(String text) { + static final CharSequence obtainVerbatim(String text) { return new SpannableStringBuilder().append(text, new TtsSpan.VerbatimBuilder(text).build(), 0); } @@ -333,10 +410,16 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate { updateHeaderSeparator(); updateHeaderMinute(mCurrentMinute, false); updateRadialPicker(index); + updateTextInputPicker(); mDelegator.invalidate(); } + private void updateTextInputPicker() { + mTextInputPickerView.updateTextInputValues(getLocalizedHour(mCurrentHour), mCurrentMinute, + mCurrentHour < 12 ? AM : PM, mIs24Hour, mHourFormatStartsAtZero); + } + private void updateRadialPicker(int index) { mRadialTimePickerView.initialize(mCurrentHour, mCurrentMinute, mIs24Hour); setCurrentItemShowing(index, false, true); @@ -381,10 +464,10 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate { */ @Override public void setHour(int hour) { - setHourInternal(hour, false, true); + setHourInternal(hour, FROM_EXTERNAL_API, true); } - private void setHourInternal(int hour, boolean isFromPicker, boolean announce) { + private void setHourInternal(int hour, @ChangeSource int source, boolean announce) { if (mCurrentHour == hour) { return; } @@ -393,10 +476,13 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate { updateHeaderHour(hour, announce); updateHeaderAmPm(); - if (!isFromPicker) { + if (source != FROM_RADIAL_PICKER) { mRadialTimePickerView.setCurrentHour(hour); mRadialTimePickerView.setAmOrPm(hour < 12 ? AM : PM); } + if (source != FROM_INPUT_PICKER) { + updateTextInputPicker(); + } mDelegator.invalidate(); onTimeChanged(); @@ -424,10 +510,10 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate { */ @Override public void setMinute(int minute) { - setMinuteInternal(minute, false); + setMinuteInternal(minute, FROM_EXTERNAL_API); } - private void setMinuteInternal(int minute, boolean isFromPicker) { + private void setMinuteInternal(int minute, @ChangeSource int source) { if (mCurrentMinute == minute) { return; } @@ -435,9 +521,12 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate { mCurrentMinute = minute; updateHeaderMinute(minute, true); - if (!isFromPicker) { + if (source != FROM_RADIAL_PICKER) { mRadialTimePickerView.setCurrentMinute(minute); } + if (source != FROM_INPUT_PICKER) { + updateTextInputPicker(); + } mDelegator.invalidate(); onTimeChanged(); @@ -661,6 +750,7 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate { separatorText = Character.toString(bestDateTimePattern.charAt(hIndex + 1)); } mSeparatorView.setText(separatorText); + mTextInputPickerView.updateSeparator(separatorText); } static private int lastIndexOfAny(String str, char[] any) { @@ -712,7 +802,7 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate { if (mRadialTimePickerView.setAmOrPm(amOrPm)) { mCurrentHour = getHour(); - + updateTextInputPicker(); if (mOnTimeChangedListener != null) { mOnTimeChangedListener.onTimeChanged(mDelegator, getHour(), getMinute()); } @@ -726,7 +816,7 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate { switch (pickerType) { case RadialTimePickerView.HOURS: final boolean isTransition = mAllowAutoAdvance && autoAdvance; - setHourInternal(newValue, true, !isTransition); + setHourInternal(newValue, FROM_RADIAL_PICKER, !isTransition); if (isTransition) { setCurrentItemShowing(MINUTE_INDEX, true, false); @@ -735,7 +825,7 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate { } break; case RadialTimePickerView.MINUTES: - setMinuteInternal(newValue, true); + setMinuteInternal(newValue, FROM_RADIAL_PICKER); break; } @@ -745,6 +835,23 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate { } }; + private final OnValueTypedListener mOnValueTypedListener = new OnValueTypedListener() { + @Override + public void onValueChanged(int pickerType, int newValue) { + switch (pickerType) { + case TextInputTimePickerView.HOURS: + setHourInternal(newValue, FROM_INPUT_PICKER, false); + break; + case TextInputTimePickerView.MINUTES: + setMinuteInternal(newValue, FROM_INPUT_PICKER); + break; + case TextInputTimePickerView.AMPM: + setAmOrPm(newValue); + break; + } + } + }; + /** Listener for keyboard interaction. */ private final OnValueChangedListener mDigitEnteredListener = new OnValueChangedListener() { @Override diff --git a/core/java/android/widget/TimePickerSpinnerDelegate.java b/core/java/android/widget/TimePickerSpinnerDelegate.java index 6a68f60b2c7aa..7ef54a5c69caf 100644 --- a/core/java/android/widget/TimePickerSpinnerDelegate.java +++ b/core/java/android/widget/TimePickerSpinnerDelegate.java @@ -219,6 +219,11 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate { } } + @Override + public boolean validateInput() { + return true; + } + private void getHourFormatData() { final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mLocale, (mIs24HourView) ? "Hm" : "hm"); diff --git a/core/java/com/android/internal/widget/DrawingSpace.java b/core/java/com/android/internal/widget/DrawingSpace.java deleted file mode 100644 index b8222dba183cd..0000000000000 --- a/core/java/com/android/internal/widget/DrawingSpace.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2015 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 com.android.internal.widget; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.View; - -/** - * Implementation of {@link android.widget.Space} that uses normal View drawing - * rather than a no-op. Useful for dialogs and other places where the base View - * class is too greedy when measured with AT_MOST. - */ -public final class DrawingSpace extends View { - public DrawingSpace(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } - - public DrawingSpace(Context context, AttributeSet attrs, int defStyleAttr) { - this(context, attrs, defStyleAttr, 0); - } - - public DrawingSpace(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public DrawingSpace(Context context) { - this(context, null); - } - - /** - * Compare to: {@link View#getDefaultSize(int, int)} - *

- * If mode is AT_MOST, return the child size instead of the parent size - * (unless it is too big). - */ - private static int getDefaultSizeNonGreedy(int size, int measureSpec) { - int result = size; - int specMode = MeasureSpec.getMode(measureSpec); - int specSize = MeasureSpec.getSize(measureSpec); - - switch (specMode) { - case MeasureSpec.UNSPECIFIED: - result = size; - break; - case MeasureSpec.AT_MOST: - result = Math.min(size, specSize); - break; - case MeasureSpec.EXACTLY: - result = specSize; - break; - } - return result; - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - setMeasuredDimension( - getDefaultSizeNonGreedy(getSuggestedMinimumWidth(), widthMeasureSpec), - getDefaultSizeNonGreedy(getSuggestedMinimumHeight(), heightMeasureSpec)); - } -} diff --git a/core/res/res/values-ldrtl/dimens.xml b/core/res/res/drawable/btn_event_material.xml similarity index 53% rename from core/res/res/values-ldrtl/dimens.xml rename to core/res/res/drawable/btn_event_material.xml index 807c0424235d1..47c49cf2fa8c2 100644 --- a/core/res/res/values-ldrtl/dimens.xml +++ b/core/res/res/drawable/btn_event_material.xml @@ -1,5 +1,6 @@ - - - 1 - 0 - + + + + + \ No newline at end of file diff --git a/core/res/res/drawable/btn_keyboard_key_material.xml b/core/res/res/drawable/btn_keyboard_key_material.xml new file mode 100644 index 0000000000000..14a9492f00239 --- /dev/null +++ b/core/res/res/drawable/btn_keyboard_key_material.xml @@ -0,0 +1,32 @@ + + + + + + + + \ No newline at end of file diff --git a/core/res/res/layout-land/time_picker_material.xml b/core/res/res/layout-land/time_picker_material.xml index 70833d6d757eb..8b95f9f814aa1 100644 --- a/core/res/res/layout-land/time_picker_material.xml +++ b/core/res/res/layout-land/time_picker_material.xml @@ -15,28 +15,17 @@ limitations under the License. --> - + - - - - @@ -44,10 +33,8 @@ android:id="@+id/time_layout" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:orientation="horizontal" - android:layout_centerInParent="true" android:paddingTop="@dimen/timepicker_radial_picker_top_margin" - android:layout_marginBottom="-12dp"> + android:orientation="horizontal"> @@ -125,38 +112,71 @@ android:includeFontPadding="false" android:button="@null" /> - + - + + + android:orientation="vertical"> - + - - + + + + + + + + + + \ No newline at end of file diff --git a/core/res/res/layout/time_picker_material.xml b/core/res/res/layout/time_picker_material.xml index 37a7384608ce5..75973798219ef 100644 --- a/core/res/res/layout/time_picker_material.xml +++ b/core/res/res/layout/time_picker_material.xml @@ -34,4 +34,53 @@ android:layout_marginTop="@dimen/timepicker_radial_picker_top_margin" android:layout_marginStart="@dimen/timepicker_radial_picker_horizontal_margin" android:layout_marginEnd="@dimen/timepicker_radial_picker_horizontal_margin" /> + + + + + + + + + diff --git a/core/res/res/layout/time_picker_text_input_material.xml b/core/res/res/layout/time_picker_text_input_material.xml new file mode 100644 index 0000000000000..f17b80ee2f39a --- /dev/null +++ b/core/res/res/layout/time_picker_text_input_material.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 927988f712832..9824051685231 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -475,9 +475,6 @@ 0dp - 0 - 1 - 15dp 8dp diff --git a/core/res/res/values/dimens_material.xml b/core/res/res/values/dimens_material.xml index ebe577c737581..e3fdcece4ff1f 100644 --- a/core/res/res/values/dimens_material.xml +++ b/core/res/res/values/dimens_material.xml @@ -149,6 +149,7 @@ 296dp 16dp 16dp + 24sp 20dp diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index e00874fe44511..19c5643cdaf8e 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -4471,4 +4471,20 @@ Device storage USB debugging + + + hour + + minute + + Set time + + Enter a valid time + + Type in time + + Switch to text input mode for the time input. + + Switch to clock mode for the time input. + diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml index 1e153485123fe..8f061a3862a34 100644 --- a/core/res/res/values/styles_material.xml +++ b/core/res/res/values/styles_material.xml @@ -426,6 +426,21 @@ please see styles_device_defaults.xml. @color/primary_text_secondary_when_activated_material_inverse + + + + + +