Skip to content

Commit 0c204b8

Browse files
committed
[MaterialTimePicker] Fix NumberFormatException
PiperOrigin-RevId: 482810027
1 parent 8510596 commit 0c204b8

File tree

4 files changed

+79
-13
lines changed

4 files changed

+79
-13
lines changed

lib/java/com/google/android/material/timepicker/ChipTextInputComboView.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import com.google.android.material.R;
2020

21+
import static android.text.TextUtils.isEmpty;
2122
import static com.google.android.material.timepicker.TimePickerView.GENERIC_VIEW_ACCESSIBILITY_CLASS_NAME;
2223

2324
import android.content.Context;
@@ -27,7 +28,6 @@
2728
import android.os.LocaleList;
2829
import android.text.Editable;
2930
import android.text.InputFilter;
30-
import android.text.TextUtils;
3131
import android.text.TextWatcher;
3232
import android.util.AttributeSet;
3333
import android.view.LayoutInflater;
@@ -37,6 +37,7 @@
3737
import android.widget.TextView;
3838
import androidx.annotation.NonNull;
3939
import androidx.annotation.Nullable;
40+
import androidx.annotation.VisibleForTesting;
4041
import androidx.core.view.AccessibilityDelegateCompat;
4142
import androidx.core.view.ViewCompat;
4243
import com.google.android.material.chip.Chip;
@@ -117,13 +118,18 @@ public void toggle() {
117118
public void setText(CharSequence text) {
118119
String formattedText = formatText(text);
119120
chip.setText(formattedText);
120-
if (!TextUtils.isEmpty(formattedText)) {
121+
if (!isEmpty(formattedText)) {
121122
editText.removeTextChangedListener(watcher);
122123
editText.setText(formattedText);
123124
editText.addTextChangedListener(watcher);
124125
}
125126
}
126127

128+
@VisibleForTesting
129+
CharSequence getChipText() {
130+
return chip.getText();
131+
}
132+
127133
private String formatText(CharSequence text) {
128134
return TimeModel.formatText(getResources(), text);
129135
}
@@ -167,12 +173,12 @@ private class TextFormatter extends TextWatcherAdapter {
167173

168174
@Override
169175
public void afterTextChanged(Editable editable) {
170-
if (TextUtils.isEmpty(editable)) {
176+
if (isEmpty(editable)) {
171177
chip.setText(formatText(DEFAULT_TEXT));
172178
return;
173179
}
174-
175-
chip.setText(formatText(editable));
180+
String formattedText = formatText(editable);
181+
chip.setText(isEmpty(formattedText) ? formatText(DEFAULT_TEXT) : formattedText);
176182
}
177183
}
178184

lib/java/com/google/android/material/timepicker/TimeModel.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import android.os.Parcel;
2929
import android.os.Parcelable;
3030
import androidx.annotation.IntRange;
31+
import androidx.annotation.Nullable;
3132
import androidx.annotation.StringRes;
3233
import com.google.android.material.timepicker.TimePickerControls.ActiveSelection;
3334
import com.google.android.material.timepicker.TimePickerControls.ClockPeriod;
@@ -186,14 +187,18 @@ public void setPeriod(@ClockPeriod int period) {
186187
}
187188
}
188189

190+
@Nullable
189191
public static String formatText(Resources resources, CharSequence text) {
190192
return formatText(resources, text, ZERO_LEADING_NUMBER_FORMAT);
191193
}
192194

195+
@Nullable
193196
public static String formatText(Resources resources, CharSequence text, String format) {
194-
return String.format(
195-
resources.getConfiguration().locale,
196-
format,
197-
Integer.parseInt(String.valueOf(text)));
197+
try {
198+
return String.format(
199+
resources.getConfiguration().locale, format, Integer.parseInt(String.valueOf(text)));
200+
} catch (NumberFormatException e) {
201+
return null;
202+
}
198203
}
199204
}

lib/javatests/com/google/android/material/timepicker/TimeModelTest.java

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import static com.google.android.material.timepicker.TimeFormat.CLOCK_24H;
2323
import static com.google.common.truth.Truth.assertThat;
2424

25+
import androidx.test.core.app.ApplicationProvider;
2526
import org.junit.Test;
2627
import org.junit.runner.RunWith;
2728
import org.robolectric.RobolectricTestRunner;
@@ -31,33 +32,49 @@
3132
public class TimeModelTest {
3233

3334
@Test
34-
public void timeModel_with12HFormat_hasCorrectValidators() {
35+
public void with12HFormat_hasCorrectValidators() {
3536
TimeModel timeModel = new TimeModel(CLOCK_12H);
3637

3738
assertThat(timeModel.getHourInputValidator().getMax()).isEqualTo(12);
3839
assertThat(timeModel.getMinuteInputValidator().getMax()).isEqualTo(59);
3940
}
4041

4142
@Test
42-
public void timeModel_with24HFormat_hasCorrectValidators() {
43+
public void with24HFormat_hasCorrectValidators() {
4344
TimeModel timeModel = new TimeModel(CLOCK_24H);
4445

4546
assertThat(timeModel.getHourInputValidator().getMax()).isEqualTo(24);
4647
assertThat(timeModel.getMinuteInputValidator().getMax()).isEqualTo(59);
4748
}
4849

4950
@Test
50-
public void timeModel_with12HFormat_returnsCorrectHourContentDescription() {
51+
public void with12HFormat_returnsCorrectHourContentDescription() {
5152
TimeModel timeModel = new TimeModel(CLOCK_12H);
5253

5354
assertThat(timeModel.getHourContentDescriptionResId()).isEqualTo(R.string.material_hour_suffix);
5455
}
5556

5657
@Test
57-
public void timeModel_with24HFormat_returnsCorrectHourContentDescription() {
58+
public void with24HFormat_returnsCorrectHourContentDescription() {
5859
TimeModel timeModel = new TimeModel(CLOCK_24H);
5960

6061
assertThat(timeModel.getHourContentDescriptionResId())
6162
.isEqualTo(R.string.material_hour_24h_suffix);
6263
}
64+
65+
@Test
66+
public void formatText_validInput_returnsFormattedText() {
67+
String formattedText =
68+
TimeModel.formatText(ApplicationProvider.getApplicationContext().getResources(), "1");
69+
70+
assertThat(formattedText).isEqualTo("01");
71+
}
72+
73+
@Test
74+
public void formatText_invalidInput_returnsNull() {
75+
String formattedText =
76+
TimeModel.formatText(ApplicationProvider.getApplicationContext().getResources(), "+");
77+
78+
assertThat(formattedText).isNull();
79+
}
6380
}

lib/javatests/com/google/android/material/timepicker/TimePickerTextInputKeyControllerTest.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,44 @@ public void controller_clearPrefilledText_shouldNotClearWhenPartialText() {
121121
assertThat(editText.getText().length()).isEqualTo(1);
122122
}
123123

124+
@Test
125+
public void afterTextChanged_validHourInput_formatsText() {
126+
EditText editText = hourInput.getTextInput().getEditText();
127+
editText.setText("1");
128+
shadowOf(getMainLooper()).idle();
129+
130+
assertThat(hourInput.getChipText().toString()).isEqualTo("01");
131+
}
132+
133+
@Test
134+
public void afterTextChanged_invalidHourInput_resetsToDefault() {
135+
EditText editText = hourInput.getTextInput().getEditText();
136+
editText.setText("1");
137+
editText.setText("+");
138+
shadowOf(getMainLooper()).idle();
139+
140+
assertThat(hourInput.getChipText().toString()).isEqualTo("00");
141+
}
142+
143+
@Test
144+
public void afterTextChanged_validMinuteInput_formatsText() {
145+
EditText editText = minuteInput.getTextInput().getEditText();
146+
editText.setText("1");
147+
shadowOf(getMainLooper()).idle();
148+
149+
assertThat(minuteInput.getChipText().toString()).isEqualTo("01");
150+
}
151+
152+
@Test
153+
public void afterTextChanged_invalidMinuteInput_resetsToDefault() {
154+
EditText editText = minuteInput.getTextInput().getEditText();
155+
editText.setText("1");
156+
editText.setText("+");
157+
shadowOf(getMainLooper()).idle();
158+
159+
assertThat(minuteInput.getChipText().toString()).isEqualTo("00");
160+
}
161+
124162
private static void pressKeys(EditText editText, int... keycodes) {
125163
for (int key : keycodes) {
126164
editText.dispatchKeyEvent(new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, key, 0));

0 commit comments

Comments
 (0)