Merge "Use text input for various internationalized listeners" into oc-dev
diff --git a/core/java/android/text/method/DateKeyListener.java b/core/java/android/text/method/DateKeyListener.java
index e14cd2c..0accbf6 100644
--- a/core/java/android/text/method/DateKeyListener.java
+++ b/core/java/android/text/method/DateKeyListener.java
@@ -22,6 +22,7 @@
import android.view.KeyEvent;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
import java.util.HashMap;
import java.util.LinkedHashSet;
@@ -37,8 +38,11 @@
public class DateKeyListener extends NumberKeyListener
{
public int getInputType() {
- return InputType.TYPE_CLASS_DATETIME
- | InputType.TYPE_DATETIME_VARIATION_DATE;
+ if (mNeedsAdvancedInput) {
+ return InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL;
+ } else {
+ return InputType.TYPE_CLASS_DATETIME | InputType.TYPE_DATETIME_VARIATION_DATE;
+ }
}
@Override
@@ -65,7 +69,13 @@
final boolean success = NumberKeyListener.addDigits(chars, locale)
&& NumberKeyListener.addFormatCharsFromSkeletons(
chars, locale, SKELETONS, SYMBOLS_TO_IGNORE);
- mCharacters = success ? NumberKeyListener.collectionToArray(chars) : CHARACTERS;
+ if (success) {
+ mCharacters = NumberKeyListener.collectionToArray(chars);
+ mNeedsAdvancedInput = !ArrayUtils.containsAll(CHARACTERS, mCharacters);
+ } else {
+ mCharacters = CHARACTERS;
+ mNeedsAdvancedInput = false;
+ }
}
/**
@@ -110,6 +120,7 @@
};
private final char[] mCharacters;
+ private final boolean mNeedsAdvancedInput;
private static final Object sLock = new Object();
@GuardedBy("sLock")
diff --git a/core/java/android/text/method/DateTimeKeyListener.java b/core/java/android/text/method/DateTimeKeyListener.java
index 62e3ade..551db55 100644
--- a/core/java/android/text/method/DateTimeKeyListener.java
+++ b/core/java/android/text/method/DateTimeKeyListener.java
@@ -22,6 +22,7 @@
import android.view.KeyEvent;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
import java.util.HashMap;
import java.util.LinkedHashSet;
@@ -37,10 +38,13 @@
public class DateTimeKeyListener extends NumberKeyListener
{
public int getInputType() {
- return InputType.TYPE_CLASS_DATETIME
- | InputType.TYPE_DATETIME_VARIATION_NORMAL;
+ if (mNeedsAdvancedInput) {
+ return InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL;
+ } else {
+ return InputType.TYPE_CLASS_DATETIME | InputType.TYPE_DATETIME_VARIATION_NORMAL;
+ }
}
-
+
@Override
@NonNull
protected char[] getAcceptedChars()
@@ -70,7 +74,13 @@
chars, locale, SKELETON_12HOUR, SYMBOLS_TO_IGNORE)
&& NumberKeyListener.addFormatCharsFromSkeleton(
chars, locale, SKELETON_24HOUR, SYMBOLS_TO_IGNORE);
- mCharacters = success ? NumberKeyListener.collectionToArray(chars) : CHARACTERS;
+ if (success) {
+ mCharacters = NumberKeyListener.collectionToArray(chars);
+ mNeedsAdvancedInput = !ArrayUtils.containsAll(CHARACTERS, mCharacters);
+ } else {
+ mCharacters = CHARACTERS;
+ mNeedsAdvancedInput = false;
+ }
}
/**
@@ -114,6 +124,7 @@
};
private final char[] mCharacters;
+ private final boolean mNeedsAdvancedInput;
private static final Object sLock = new Object();
@GuardedBy("sLock")
diff --git a/core/java/android/text/method/DigitsKeyListener.java b/core/java/android/text/method/DigitsKeyListener.java
index 26c69ab..d9f2dcf 100644
--- a/core/java/android/text/method/DigitsKeyListener.java
+++ b/core/java/android/text/method/DigitsKeyListener.java
@@ -27,6 +27,7 @@
import android.view.KeyEvent;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
import java.util.HashMap;
import java.util.LinkedHashSet;
@@ -42,8 +43,12 @@
public class DigitsKeyListener extends NumberKeyListener
{
private char[] mAccepted;
+ private boolean mNeedsAdvancedInput;
private final boolean mSign;
private final boolean mDecimal;
+ private final boolean mStringMode;
+ @Nullable
+ private final Locale mLocale;
private static final String DEFAULT_DECIMAL_POINT_CHARS = ".";
private static final String DEFAULT_SIGN_CHARS = "-+";
@@ -112,11 +117,17 @@
this(locale, false, false);
}
- private void setToCompat(boolean sign, boolean decimal) {
+ private void setToCompat() {
mDecimalPointChars = DEFAULT_DECIMAL_POINT_CHARS;
mSignChars = DEFAULT_SIGN_CHARS;
- final int kind = (sign ? SIGN : 0) | (decimal ? DECIMAL : 0);
+ final int kind = (mSign ? SIGN : 0) | (mDecimal ? DECIMAL : 0);
mAccepted = COMPATIBILITY_CHARACTERS[kind];
+ mNeedsAdvancedInput = false;
+ }
+
+ private void calculateNeedForAdvancedInput() {
+ final int kind = (mSign ? SIGN : 0) | (mDecimal ? DECIMAL : 0);
+ mNeedsAdvancedInput = !ArrayUtils.containsAll(COMPATIBILITY_CHARACTERS[kind], mAccepted);
}
// Takes a sign string and strips off its bidi controls, if any.
@@ -144,14 +155,16 @@
public DigitsKeyListener(@Nullable Locale locale, boolean sign, boolean decimal) {
mSign = sign;
mDecimal = decimal;
+ mStringMode = false;
+ mLocale = locale;
if (locale == null) {
- setToCompat(sign, decimal);
+ setToCompat();
return;
}
LinkedHashSet<Character> chars = new LinkedHashSet<>();
final boolean success = NumberKeyListener.addDigits(chars, locale);
if (!success) {
- setToCompat(sign, decimal);
+ setToCompat();
return;
}
if (sign || decimal) {
@@ -161,7 +174,7 @@
final String plusString = stripBidiControls(symbols.getPlusSignString());
if (minusString.length() > 1 || plusString.length() > 1) {
// non-BMP and multi-character signs are not supported.
- setToCompat(sign, decimal);
+ setToCompat();
return;
}
final char minus = minusString.charAt(0);
@@ -181,7 +194,7 @@
final String separatorString = symbols.getDecimalSeparatorString();
if (separatorString.length() > 1) {
// non-BMP and multi-character decimal separators are not supported.
- setToCompat(sign, decimal);
+ setToCompat();
return;
}
final Character separatorChar = Character.valueOf(separatorString.charAt(0));
@@ -190,13 +203,19 @@
}
}
mAccepted = NumberKeyListener.collectionToArray(chars);
+ calculateNeedForAdvancedInput();
}
private DigitsKeyListener(@NonNull final String accepted) {
mSign = false;
mDecimal = false;
+ mStringMode = true;
+ mLocale = null;
mAccepted = new char[accepted.length()];
accepted.getChars(0, accepted.length(), mAccepted, 0);
+ // Theoretically we may need advanced input, but for backward compatibility, we don't change
+ // the input type.
+ mNeedsAdvancedInput = false;
}
/**
@@ -280,13 +299,38 @@
return result;
}
- public int getInputType() {
- int contentType = InputType.TYPE_CLASS_NUMBER;
- if (mSign) {
- contentType |= InputType.TYPE_NUMBER_FLAG_SIGNED;
+ /**
+ * Returns a DigitsKeyListener based on an the settings of a existing DigitsKeyListener, with
+ * the locale modified.
+ *
+ * @hide
+ */
+ @NonNull
+ public static DigitsKeyListener getInstance(
+ @Nullable Locale locale,
+ @NonNull DigitsKeyListener listener) {
+ if (listener.mStringMode) {
+ return listener; // string-mode DigitsKeyListeners have no locale.
+ } else {
+ return getInstance(locale, listener.mSign, listener.mDecimal);
}
- if (mDecimal) {
- contentType |= InputType.TYPE_NUMBER_FLAG_DECIMAL;
+ }
+
+ /**
+ * Returns the input type for the listener.
+ */
+ public int getInputType() {
+ int contentType;
+ if (mNeedsAdvancedInput) {
+ contentType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL;
+ } else {
+ contentType = InputType.TYPE_CLASS_NUMBER;
+ if (mSign) {
+ contentType |= InputType.TYPE_NUMBER_FLAG_SIGNED;
+ }
+ if (mDecimal) {
+ contentType |= InputType.TYPE_NUMBER_FLAG_DECIMAL;
+ }
}
return contentType;
}
diff --git a/core/java/android/text/method/TimeKeyListener.java b/core/java/android/text/method/TimeKeyListener.java
index c9f9f9f..5b1db11 100644
--- a/core/java/android/text/method/TimeKeyListener.java
+++ b/core/java/android/text/method/TimeKeyListener.java
@@ -22,6 +22,7 @@
import android.view.KeyEvent;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
import java.util.HashMap;
import java.util.LinkedHashSet;
@@ -37,8 +38,11 @@
public class TimeKeyListener extends NumberKeyListener
{
public int getInputType() {
- return InputType.TYPE_CLASS_DATETIME
- | InputType.TYPE_DATETIME_VARIATION_TIME;
+ if (mNeedsAdvancedInput) {
+ return InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL;
+ } else {
+ return InputType.TYPE_CLASS_DATETIME | InputType.TYPE_DATETIME_VARIATION_TIME;
+ }
}
@Override
@@ -70,7 +74,13 @@
chars, locale, SKELETON_12HOUR, SYMBOLS_TO_IGNORE)
&& NumberKeyListener.addFormatCharsFromSkeleton(
chars, locale, SKELETON_24HOUR, SYMBOLS_TO_IGNORE);
- mCharacters = success ? NumberKeyListener.collectionToArray(chars) : CHARACTERS;
+ if (success) {
+ mCharacters = NumberKeyListener.collectionToArray(chars);
+ mNeedsAdvancedInput = !ArrayUtils.containsAll(CHARACTERS, mCharacters);
+ } else {
+ mCharacters = CHARACTERS;
+ mNeedsAdvancedInput = false;
+ }
}
/**
@@ -114,6 +124,7 @@
};
private final char[] mCharacters;
+ private final boolean mNeedsAdvancedInput;
private static final Object sLock = new Object();
@GuardedBy("sLock")
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 1b60ebc..7d9253b 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -598,6 +598,11 @@
private Layout mLayout;
private boolean mLocalesChanged = false;
+ // True if setKeyListener() has been explicitly called
+ private boolean mListenerChanged = false;
+ // True if internationalized input should be used for numbers and date and time.
+ private final boolean mUseInternationalizedInput;
+
@ViewDebug.ExportedProperty(category = "text")
private int mGravity = Gravity.TOP | Gravity.START;
private boolean mHorizontallyScrolling;
@@ -1356,6 +1361,9 @@
final boolean numberPasswordInputType = variation
== (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
+ mUseInternationalizedInput =
+ context.getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O;
+
if (inputMethod != null) {
Class<?> c;
@@ -1398,15 +1406,11 @@
mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
} else if (numeric != 0) {
createEditorIfNeeded();
- mEditor.mKeyListener = DigitsKeyListener.getInstance((numeric & SIGNED) != 0,
- (numeric & DECIMAL) != 0);
- inputType = EditorInfo.TYPE_CLASS_NUMBER;
- if ((numeric & SIGNED) != 0) {
- inputType |= EditorInfo.TYPE_NUMBER_FLAG_SIGNED;
- }
- if ((numeric & DECIMAL) != 0) {
- inputType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL;
- }
+ mEditor.mKeyListener = DigitsKeyListener.getInstance(
+ mUseInternationalizedInput ? getTextLocale() : null,
+ (numeric & SIGNED) != 0,
+ (numeric & DECIMAL) != 0);
+ inputType = mEditor.mKeyListener.getInputType();
mEditor.mInputType = inputType;
} else if (autotext || autocap != -1) {
TextKeyListener.Capitalize cap;
@@ -2308,19 +2312,13 @@
* @attr ref android.R.styleable#TextView_autoText
*/
public void setKeyListener(KeyListener input) {
+ mListenerChanged = true;
setKeyListenerOnly(input);
fixFocusableAndClickableSettings();
if (input != null) {
createEditorIfNeeded();
- try {
- mEditor.mInputType = mEditor.mKeyListener.getInputType();
- } catch (IncompatibleClassChangeError e) {
- mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
- }
- // Change inputType, without affecting transformation.
- // No need to applySingleLine since mSingleLine is unchanged.
- setInputTypeSingleLine(mSingleLine);
+ setInputTypeFromEditor();
} else {
if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL;
}
@@ -2329,6 +2327,17 @@
if (imm != null) imm.restartInput(this);
}
+ private void setInputTypeFromEditor() {
+ try {
+ mEditor.mInputType = mEditor.mKeyListener.getInputType();
+ } catch (IncompatibleClassChangeError e) {
+ mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
+ }
+ // Change inputType, without affecting transformation.
+ // No need to applySingleLine since mSingleLine is unchanged.
+ setInputTypeSingleLine(mSingleLine);
+ }
+
private void setKeyListenerOnly(KeyListener input) {
if (mEditor == null && input == null) return; // null is the default value
@@ -3390,6 +3399,29 @@
return mTextPaint.getTextLocales();
}
+ private void changeListenerLocaleTo(@NonNull Locale locale) {
+ if (mListenerChanged) {
+ // If a listener has been explicitly set, don't change it. We may break something.
+ return;
+ }
+ if (mEditor != null) {
+ KeyListener listener = mEditor.mKeyListener;
+ if (listener instanceof DigitsKeyListener) {
+ listener = DigitsKeyListener.getInstance(locale, (DigitsKeyListener) listener);
+ } else if (listener instanceof DateKeyListener) {
+ listener = DateKeyListener.getInstance(locale);
+ } else if (listener instanceof TimeKeyListener) {
+ listener = TimeKeyListener.getInstance(locale);
+ } else if (listener instanceof DateTimeKeyListener) {
+ listener = DateTimeKeyListener.getInstance(locale);
+ } else {
+ return;
+ }
+ setKeyListenerOnly(listener);
+ setInputTypeFromEditor();
+ }
+ }
+
/**
* Set the default {@link LocaleList} of the text in this TextView to a one-member list
* containing just the given value.
@@ -3401,6 +3433,7 @@
public void setTextLocale(@NonNull Locale locale) {
mLocalesChanged = true;
mTextPaint.setTextLocale(locale);
+ changeListenerLocaleTo(locale);
if (mLayout != null) {
nullLayouts();
requestLayout();
@@ -3422,6 +3455,7 @@
public void setTextLocales(@NonNull @Size(min = 1) LocaleList locales) {
mLocalesChanged = true;
mTextPaint.setTextLocales(locales);
+ changeListenerLocaleTo(locales.get(0));
if (mLayout != null) {
nullLayouts();
requestLayout();
@@ -3433,7 +3467,9 @@
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (!mLocalesChanged) {
- mTextPaint.setTextLocales(LocaleList.getDefault());
+ final LocaleList locales = LocaleList.getDefault();
+ mTextPaint.setTextLocales(locales);
+ changeListenerLocaleTo(locales.get(0));
if (mLayout != null) {
nullLayouts();
requestLayout();
@@ -5567,26 +5603,35 @@
input = TextKeyListener.getInstance(autotext, cap);
} else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
input = DigitsKeyListener.getInstance(
+ mUseInternationalizedInput ? getTextLocale() : null,
(type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
(type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
+ if (mUseInternationalizedInput) {
+ type = input.getInputType(); // Override type, if necessary for i18n.
+ }
} else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
+ final Locale locale = mUseInternationalizedInput ? getTextLocale() : null;
switch (type & EditorInfo.TYPE_MASK_VARIATION) {
case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
- input = DateKeyListener.getInstance();
+ input = DateKeyListener.getInstance(locale);
break;
case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
- input = TimeKeyListener.getInstance();
+ input = TimeKeyListener.getInstance(locale);
break;
default:
- input = DateTimeKeyListener.getInstance();
+ input = DateTimeKeyListener.getInstance(locale);
break;
}
+ if (mUseInternationalizedInput) {
+ type = input.getInputType(); // Override type, if necessary for i18n.
+ }
} else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
input = DialerKeyListener.getInstance();
} else {
input = TextKeyListener.getInstance();
}
setRawInputType(type);
+ mListenerChanged = false;
if (direct) {
createEditorIfNeeded();
mEditor.mKeyListener = input;
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index f4dd5a6..2c8e4e0 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -236,6 +236,29 @@
return false;
}
+ public static boolean contains(@Nullable char[] array, char value) {
+ if (array == null) return false;
+ for (char element : array) {
+ if (element == value) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Test if all {@code check} items are contained in {@code array}.
+ */
+ public static <T> boolean containsAll(@Nullable char[] array, char[] check) {
+ if (check == null) return true;
+ for (char checkItem : check) {
+ if (!contains(array, checkItem)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
public static long total(@Nullable long[] array) {
long total = 0;
if (array != null) {