Reimplement the PhoneNumberFormattingTextWatcher

a. Built the external/libphonenumberutil into the ext.jar. The file size increased 50K, the phone number meta file is 90K before the compression.
b. Used the external/libphonenumberutil to format the phone number for about 200 countries.
c. Beside the phone number formatting, the external/libphonenumberutil will also be used for phonenumber match and international dialing.

Change-Id: Ie5165dc60d66e1eddab7134725a8d1d1c826434a
diff --git a/Android.mk b/Android.mk
index a43e8c8..5fb7366 100644
--- a/Android.mk
+++ b/Android.mk
@@ -569,10 +569,14 @@
 
 ext_dirs := \
 	../../external/apache-http/src \
-	../../external/tagsoup/src
+	../../external/tagsoup/src \
+	../../external/libphonenumber/java/src
 
 ext_src_files := $(call all-java-files-under,$(ext_dirs))
 
+ext_res_dirs := \
+	../../external/libphonenumber/java/src
+
 # ====  the library  =========================================
 include $(CLEAR_VARS)
 
@@ -580,7 +584,7 @@
 
 LOCAL_NO_STANDARD_LIBRARIES := true
 LOCAL_JAVA_LIBRARIES := core
-
+LOCAL_JAVA_RESOURCE_DIRS := $(ext_res_dirs)
 LOCAL_MODULE := ext
 
 LOCAL_NO_EMMA_INSTRUMENT := true
diff --git a/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java b/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java
index 8a47339..5241088b 100644
--- a/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java
+++ b/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java
@@ -16,83 +16,198 @@
 
 package android.telephony;
 
+import com.google.i18n.phonenumbers.AsYouTypeFormatter;
+import com.google.i18n.phonenumbers.PhoneNumberUtil;
+
+import android.telephony.PhoneNumberUtils;
 import android.text.Editable;
 import android.text.Selection;
 import android.text.TextWatcher;
-import android.widget.TextView;
 
 import java.util.Locale;
 
 /**
- * Watches a {@link TextView} and if a phone number is entered will format it using
- * {@link PhoneNumberUtils#formatNumber(Editable, int)}. The formatting is based on
- * the current system locale when this object is created and future locale changes
- * may not take effect on this instance.
+ * Watches a {@link TextView} and if a phone number is entered will format it.
+ * <p>
+ * Stop formatting when the user
+ * <ul>
+ * <li>Inputs non-dialable characters</li>
+ * <li>Removes the separator in the middle of string.</li>
+ * </ul>
+ * <p>
+ * The formatting will be restarted once the text is cleared.
  */
 public class PhoneNumberFormattingTextWatcher implements TextWatcher {
+    /**
+     * One or more characters were removed from the end.
+     */
+    private final static int STATE_REMOVE_LAST = 0;
 
-    static private int sFormatType;
-    static private Locale sCachedLocale;
-    private boolean mFormatting;
-    private boolean mDeletingHyphen;
-    private int mHyphenStart;
-    private boolean mDeletingBackward;
+    /**
+     * One or more characters were appended.
+     */
+    private final static int STATE_APPEND = 1;
 
+    /**
+     * One or more digits were changed in the beginning or the middle of text.
+     */
+    private final static int STATE_MODIFY_DIGITS = 2;
+
+    /**
+     * The changes other than the above.
+     */
+    private final static int STATE_OTHER = 3;
+
+    /**
+     * The state of this change could be one value of the above
+     */
+    private int mState;
+
+    /**
+     * Indicates the change was caused by ourselves.
+     */
+    private boolean mSelfChange = false;
+
+    /**
+     * Indicates the formatting has been stopped.
+     */
+    private boolean mStopFormatting;
+
+    private AsYouTypeFormatter mFormatter;
+
+    /**
+     * The formatting is based on the current system locale and future locale changes
+     * may not take effect on this instance.
+     */
     public PhoneNumberFormattingTextWatcher() {
-        if (sCachedLocale == null || sCachedLocale != Locale.getDefault()) {
-            sCachedLocale = Locale.getDefault();
-            sFormatType = PhoneNumberUtils.getFormatTypeForLocale(sCachedLocale);
-        }
+        this (Locale.getDefault() != null ? Locale.getDefault().getCountry() : "US");
     }
 
-    public synchronized void afterTextChanged(Editable text) {
-        // Make sure to ignore calls to afterTextChanged caused by the work done below
-        if (!mFormatting) {
-            mFormatting = true;
-
-            // If deleting the hyphen, also delete the char before or after that
-            if (mDeletingHyphen && mHyphenStart > 0) {
-                if (mDeletingBackward) {
-                    if (mHyphenStart - 1 < text.length()) {
-                        text.delete(mHyphenStart - 1, mHyphenStart);
-                    }
-                } else if (mHyphenStart < text.length()) {
-                    text.delete(mHyphenStart, mHyphenStart + 1);
-                }
-            }
-
-            PhoneNumberUtils.formatNumber(text, sFormatType);
-
-            mFormatting = false;
-        }
+    /**
+     * The formatting is based on the given <code>countryCode</code>.
+     *
+     * @param countryCode the ISO 3166-1 two-letter country code that indicates the country/region
+     * where the phone number is being entered.
+     *
+     * @hide
+     */
+    public PhoneNumberFormattingTextWatcher(String countryCode) {
+        mFormatter = PhoneNumberUtil.getInstance().getAsYouTypeFormatter(countryCode);
     }
 
-    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-        // Check if the user is deleting a hyphen
-        if (!mFormatting) {
-            // Make sure user is deleting one char, without a selection
-            final int selStart = Selection.getSelectionStart(s);
-            final int selEnd = Selection.getSelectionEnd(s);
-            if (s.length() > 1 // Can delete another character
-                    && count == 1 // Deleting only one character
-                    && after == 0 // Deleting
-                    && s.charAt(start) == '-' // a hyphen
-                    && selStart == selEnd) { // no selection
-                mDeletingHyphen = true;
-                mHyphenStart = start;
-                // Check if the user is deleting forward or backward
-                if (selStart == start + 1) {
-                    mDeletingBackward = true;
-                } else {
-                    mDeletingBackward = false;
-                }
-            } else {
-                mDeletingHyphen = false;
-            }
+    public void beforeTextChanged(CharSequence s, int start, int count,
+            int after) {
+        if (mSelfChange || mStopFormatting) {
+            return;
+        }
+        if (count == 0 && s.length() == start) {
+            // Append one or more new chars
+            mState = STATE_APPEND;
+        } else if (after == 0 && start + count == s.length() && count > 0) {
+            // Remove one or more chars from the end of string.
+            mState = STATE_REMOVE_LAST;
+        } else if (count > 0 && !hasSeparator(s, start, count)) {
+            // Remove the dialable chars in the begin or middle of text.
+            mState = STATE_MODIFY_DIGITS;
+        } else {
+            mState = STATE_OTHER;
         }
     }
 
     public void onTextChanged(CharSequence s, int start, int before, int count) {
-        // Does nothing
+        if (mSelfChange || mStopFormatting) {
+            return;
+        }
+        if (mState == STATE_OTHER) {
+            if (count > 0 && !hasSeparator(s, start, count)) {
+                // User inserted the dialable characters in the middle of text.
+                mState = STATE_MODIFY_DIGITS;
+            }
+        }
+        // Check whether we should stop formatting.
+        if (mState == STATE_APPEND && count > 0 && hasSeparator(s, start, count)) {
+            // User appended the non-dialable character, stop formatting.
+            stopFormatting();
+        } else if (mState == STATE_OTHER) {
+            // User must insert or remove the non-dialable characters in the begin or middle of
+            // number, stop formatting. 
+            stopFormatting();
+        }
+    }
+
+    public synchronized void afterTextChanged(Editable s) {
+        if (mStopFormatting) {
+            // Restart the formatting when all texts were clear.
+            mStopFormatting = !(s.length() == 0);
+            return;
+        }
+        if (mSelfChange) {
+            // Ignore the change caused by s.replace().
+            return;
+        }
+        String formatted = reformat(s, Selection.getSelectionEnd(s));
+        if (formatted != null) {
+            int rememberedPos = mFormatter.getRememberedPosition();
+            mSelfChange = true;
+            s.replace(0, s.length(), formatted, 0, formatted.length());
+            // The text could be changed by other TextWatcher after we changed it. If we found the
+            // text is not the one we were expecting, just give up calling setSelection().
+            if (formatted.equals(s.toString())) {
+                Selection.setSelection(s, rememberedPos);
+            }
+            mSelfChange = false;
+        }
+    }
+
+    /**
+     * Generate the formatted number by ignoring all non-dialable chars and stick the cursor to the
+     * nearest dialable char to the left. For instance, if the number is  (650) 123-45678 and '4' is
+     * removed then the cursor should be behind '3' instead of '-'.
+     */
+    private String reformat(CharSequence s, int cursor) {
+        // The index of char to the leftward of the cursor.
+        int curIndex = cursor - 1;
+        String formatted = null;
+        mFormatter.clear();
+        char lastNonSeparator = 0;
+        boolean hasCursor = false;
+        int len = s.length();
+        for (int i = 0; i < len; i++) {
+            char c = s.charAt(i);
+            if (PhoneNumberUtils.isNonSeparator(c)) {
+                if (lastNonSeparator != 0) {
+                    formatted = getFormattedNumber(lastNonSeparator, hasCursor);
+                    hasCursor = false;
+                }
+                lastNonSeparator = c;
+            }
+            if (i == curIndex) {
+                hasCursor = true;
+            }
+        }
+        if (lastNonSeparator != 0) {
+            formatted = getFormattedNumber(lastNonSeparator, hasCursor);
+        }
+        return formatted;
+    }
+
+    private String getFormattedNumber(char lastNonSeparator, boolean hasCursor) {
+        return hasCursor ? mFormatter.inputDigitAndRememberPosition(lastNonSeparator)
+                : mFormatter.inputDigit(lastNonSeparator);
+    }
+
+    private void stopFormatting() {
+        mStopFormatting = true;
+        mFormatter.clear();
+    }
+
+    private boolean hasSeparator(final CharSequence s, final int start, final int count) {
+        for (int i = start; i < start + count; i++) {
+            char c = s.charAt(i);
+            if (!PhoneNumberUtils.isNonSeparator(c)) {
+                return true;
+            }
+        }
+        return false;
     }
 }
diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberWatcherTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberWatcherTest.java
index 88eaecd..d2e573c 100644
--- a/telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberWatcherTest.java
+++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberWatcherTest.java
@@ -13,53 +13,202 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package com.android.internal.telephony;
 
 import android.telephony.PhoneNumberFormattingTextWatcher;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.test.AndroidTestCase;
+import android.text.Editable;
 import android.text.Selection;
 import android.text.SpannableStringBuilder;
 import android.text.TextWatcher;
 
-import junit.framework.TestCase;
-
-public class PhoneNumberWatcherTest extends TestCase {
-    @SmallTest
-    public void testHyphenation() throws Exception {
+public class PhoneNumberWatcherTest extends AndroidTestCase {
+    public void testAppendChars() {
+        final String multiChars = "65012345";
+        final String formatted1 = "(650) 123-45";
+        TextWatcher textWatcher = getTextWatcher();
         SpannableStringBuilder number = new SpannableStringBuilder();
-        TextWatcher tw = new PhoneNumberFormattingTextWatcher();
-        number.append("555-1212");
-        // Move the cursor to the left edge
-        Selection.setSelection(number, 0);
-        tw.beforeTextChanged(number, 0, 0, 1);
-        // Insert an 8 at the beginning
-        number.insert(0, "8");
-        tw.afterTextChanged(number);
-        assertEquals("855-512-12", number.toString());
+        // Append more than one chars
+        textWatcher.beforeTextChanged(number, 0, 0, multiChars.length());
+        number.append(multiChars);
+        Selection.setSelection(number, number.length());
+        textWatcher.onTextChanged(number, 0, 0, number.length());
+        textWatcher.afterTextChanged(number);
+        assertEquals(formatted1, number.toString());
+        assertEquals(formatted1.length(), Selection.getSelectionEnd(number));
+        // Append one chars
+        final char appendChar = '6';
+        final String formatted2 = "(650) 123-456";
+        int len = number.length();
+        textWatcher.beforeTextChanged(number, number.length(), 0, 1);
+        number.append(appendChar);
+        Selection.setSelection(number, number.length());
+        textWatcher.onTextChanged(number, len, 0, 1);
+        textWatcher.afterTextChanged(number);
+        assertEquals(formatted2, number.toString());
+        assertEquals(formatted2.length(), Selection.getSelectionEnd(number));
     }
-    
-    @SmallTest
-    public void testHyphenDeletion() throws Exception {
-        SpannableStringBuilder number = new SpannableStringBuilder();
-        TextWatcher tw = new PhoneNumberFormattingTextWatcher();
-        number.append("555-1212");
-        // Move the cursor to after the hyphen
-        Selection.setSelection(number, 4);
-        // Delete the hyphen
-        tw.beforeTextChanged(number, 3, 1, 0);
-        number.delete(3, 4);
-        tw.afterTextChanged(number);
-        // Make sure that it deleted the character before the hyphen 
-        assertEquals("551-212", number.toString());
-        
-        // Make sure it deals with left edge boundary case
-        number.insert(0, "-");
-        Selection.setSelection(number, 1);
-        tw.beforeTextChanged(number, 0, 1, 0);
-        number.delete(0, 1);
-        tw.afterTextChanged(number);
-        // Make sure that it deleted the character before the hyphen 
-        assertEquals("551-212", number.toString());
+
+    public void testRemoveLastChars() {
+        final String init = "65012345678";
+        final String result1 = "(650) 123-4567";
+        TextWatcher textWatcher = getTextWatcher();
+        // Remove the last char.
+        SpannableStringBuilder number = new SpannableStringBuilder(init);
+        int len = number.length();
+        textWatcher.beforeTextChanged(number, len - 1, 1, 0);
+        number.delete(len - 1, len);
+        Selection.setSelection(number, number.length());
+        textWatcher.onTextChanged(number, number.length() - 1, 1, 0);
+        textWatcher.afterTextChanged(number);
+        assertEquals(result1, number.toString());
+        assertEquals(result1.length(), Selection.getSelectionEnd(number));
+        // Remove last 5 chars
+        final String result2 = "(650) 123";
+        textWatcher.beforeTextChanged(number, number.length() - 4, 4, 0);
+        number.delete(number.length() - 5, number.length());
+        Selection.setSelection(number, number.length());
+        textWatcher.onTextChanged(number, number.length(), 4, 0);
+        textWatcher.afterTextChanged(number);
+        assertEquals(result2, number.toString());
+        assertEquals(result2.length(), Selection.getSelectionEnd(number));
+    }
+
+    public void testInsertChars() {
+        final String init = "(650) 23";
+        final String expected1 = "(650) 123";
+        TextWatcher textWatcher = getTextWatcher();
+
+        // Insert one char
+        SpannableStringBuilder number = new SpannableStringBuilder(init);
+        textWatcher.beforeTextChanged(number, 4, 0, 1);
+        number.insert(4, "1"); // (6501) 23
+        Selection.setSelection(number, 5); // make the cursor at right of 1
+        textWatcher.onTextChanged(number, 4, 0, 1);
+        textWatcher.afterTextChanged(number);
+        assertEquals(expected1, number.toString());
+        // the cursor should still at the right of '1'
+        assertEquals(7, Selection.getSelectionEnd(number));
+
+        // Insert multiple chars
+        final String expected2 = "(650) 145-6723";
+        textWatcher.beforeTextChanged(number, 7, 0, 4);
+        number.insert(7, "4567"); // change to (650) 1456723
+        Selection.setSelection(number, 11); // the cursor is at the right of '7'.
+        textWatcher.onTextChanged(number, 7, 0, 4);
+        textWatcher.afterTextChanged(number);
+        assertEquals(expected2, number.toString());
+        // the cursor should be still at the right of '7'
+        assertEquals(12, Selection.getSelectionEnd(number));
+    }
+
+    public void testStopFormatting() {
+        final String init = "(650) 123";
+        final String expected1 = "(650) 123 4";
+        TextWatcher textWatcher = getTextWatcher();
+
+        // Append space
+        SpannableStringBuilder number = new SpannableStringBuilder(init);
+        textWatcher.beforeTextChanged(number, 9, 0, 2);
+        number.insert(9, " 4"); // (6501) 23 4
+        Selection.setSelection(number, number.length()); // make the cursor at right of 4
+        textWatcher.onTextChanged(number, 9, 0, 2);
+        textWatcher.afterTextChanged(number);
+        assertEquals(expected1, number.toString());
+        // the cursor should still at the right of '1'
+        assertEquals(expected1.length(), Selection.getSelectionEnd(number));
+
+        // Delete a ')'
+        final String expected2 ="(650 123";
+        textWatcher = getTextWatcher();
+        number = new SpannableStringBuilder(init);
+        textWatcher.beforeTextChanged(number, 4, 1, 0);
+        number.delete(4, 5); // (6501 23 4
+        Selection.setSelection(number, 5); // make the cursor at right of 1
+        textWatcher.onTextChanged(number, 4, 1, 0);
+        textWatcher.afterTextChanged(number);
+        assertEquals(expected2, number.toString());
+        // the cursor should still at the right of '1'
+        assertEquals(5, Selection.getSelectionEnd(number));
+
+        // Insert a hyphen
+        final String expected3 ="(650) 12-3";
+        textWatcher = getTextWatcher();
+        number = new SpannableStringBuilder(init);
+        textWatcher.beforeTextChanged(number, 8, 0, 1);
+        number.insert(8, "-"); // (650) 12-3
+        Selection.setSelection(number, 9); // make the cursor at right of -
+        textWatcher.onTextChanged(number, 8, 0, 1);
+        textWatcher.afterTextChanged(number);
+        assertEquals(expected3, number.toString());
+        // the cursor should still at the right of '-'
+        assertEquals(9, Selection.getSelectionEnd(number));
+    }
+
+    public void testRestartFormatting() {
+        final String init = "(650) 123";
+        final String expected1 = "(650) 123 4";
+        TextWatcher textWatcher = getTextWatcher();
+
+        // Append space
+        SpannableStringBuilder number = new SpannableStringBuilder(init);
+        textWatcher.beforeTextChanged(number, 9, 0, 2);
+        number.insert(9, " 4"); // (650) 123 4
+        Selection.setSelection(number, number.length()); // make the cursor at right of 4
+        textWatcher.onTextChanged(number, 9, 0, 2);
+        textWatcher.afterTextChanged(number);
+        assertEquals(expected1, number.toString());
+        // the cursor should still at the right of '4'
+        assertEquals(expected1.length(), Selection.getSelectionEnd(number));
+
+        // Clear the current string, and start formatting again.
+        int len = number.length();
+        textWatcher.beforeTextChanged(number, 0, len, 0);
+        number.delete(0, len);
+        textWatcher.onTextChanged(number, 0, len, 0);
+        textWatcher.afterTextChanged(number);
+
+        final String expected2 = "(650) 123-4";
+        number = new SpannableStringBuilder(init);
+        textWatcher.beforeTextChanged(number, 9, 0, 1);
+        number.insert(9, "4"); // (650) 1234
+        Selection.setSelection(number, number.length()); // make the cursor at right of 4
+        textWatcher.onTextChanged(number, 9, 0, 1);
+        textWatcher.afterTextChanged(number);
+        assertEquals(expected2, number.toString());
+        // the cursor should still at the right of '4'
+        assertEquals(expected2.length(), Selection.getSelectionEnd(number));
+    }
+
+    public void testTextChangedByOtherTextWatcher() {
+        final TextWatcher cleanupTextWatcher = new TextWatcher() {
+            public void afterTextChanged(Editable s) {
+                s.clear();
+            }
+
+            public void beforeTextChanged(CharSequence s, int start, int count,
+                    int after) {
+            }
+
+            public void onTextChanged(CharSequence s, int start, int before,
+                    int count) {
+            }
+        };
+        final String init = "(650) 123";
+        final String expected1 = "";
+        TextWatcher textWatcher = getTextWatcher();
+
+        SpannableStringBuilder number = new SpannableStringBuilder(init);
+        textWatcher.beforeTextChanged(number, 5, 0, 1);
+        number.insert(5, "4"); // (6504) 123
+        Selection.setSelection(number, 5); // make the cursor at right of 4
+        textWatcher.onTextChanged(number, 5, 0, 1);
+        number.setSpan(cleanupTextWatcher, 0, number.length(), 0);
+        textWatcher.afterTextChanged(number);
+        assertEquals(expected1, number.toString());
+    }
+
+    private TextWatcher getTextWatcher() {
+        return new PhoneNumberFormattingTextWatcher("US");
     }
 }