PhoneNumberFormattingTextWatcher.java revision ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbe
1/* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.telephony; 18 19import com.google.i18n.phonenumbers.AsYouTypeFormatter; 20import com.google.i18n.phonenumbers.PhoneNumberUtil; 21 22import android.telephony.PhoneNumberUtils; 23import android.text.Editable; 24import android.text.Selection; 25import android.text.TextWatcher; 26 27import java.util.Locale; 28 29/** 30 * Watches a {@link TextView} and if a phone number is entered will format it. 31 * <p> 32 * Stop formatting when the user 33 * <ul> 34 * <li>Inputs non-dialable characters</li> 35 * <li>Removes the separator in the middle of string.</li> 36 * </ul> 37 * <p> 38 * The formatting will be restarted once the text is cleared. 39 */ 40public class PhoneNumberFormattingTextWatcher implements TextWatcher { 41 /** 42 * One or more characters were removed from the end. 43 */ 44 private final static int STATE_REMOVE_LAST = 0; 45 46 /** 47 * One or more characters were appended. 48 */ 49 private final static int STATE_APPEND = 1; 50 51 /** 52 * One or more digits were changed in the beginning or the middle of text. 53 */ 54 private final static int STATE_MODIFY_DIGITS = 2; 55 56 /** 57 * The changes other than the above. 58 */ 59 private final static int STATE_OTHER = 3; 60 61 /** 62 * The state of this change could be one value of the above 63 */ 64 private int mState; 65 66 /** 67 * Indicates the change was caused by ourselves. 68 */ 69 private boolean mSelfChange = false; 70 71 /** 72 * Indicates the formatting has been stopped. 73 */ 74 private boolean mStopFormatting; 75 76 private AsYouTypeFormatter mFormatter; 77 78 /** 79 * The formatting is based on the current system locale and future locale changes 80 * may not take effect on this instance. 81 */ 82 public PhoneNumberFormattingTextWatcher() { 83 this (Locale.getDefault() != null ? Locale.getDefault().getCountry() : "US"); 84 } 85 86 /** 87 * The formatting is based on the given <code>countryCode</code>. 88 * 89 * @param countryCode the ISO 3166-1 two-letter country code that indicates the country/region 90 * where the phone number is being entered. 91 * 92 * @hide 93 */ 94 public PhoneNumberFormattingTextWatcher(String countryCode) { 95 mFormatter = PhoneNumberUtil.getInstance().getAsYouTypeFormatter(countryCode); 96 } 97 98 public void beforeTextChanged(CharSequence s, int start, int count, 99 int after) { 100 if (mSelfChange || mStopFormatting) { 101 return; 102 } 103 if (count == 0 && s.length() == start) { 104 // Append one or more new chars 105 mState = STATE_APPEND; 106 } else if (after == 0 && start + count == s.length() && count > 0) { 107 // Remove one or more chars from the end of string. 108 mState = STATE_REMOVE_LAST; 109 } else if (count > 0 && !hasSeparator(s, start, count)) { 110 // Remove the dialable chars in the begin or middle of text. 111 mState = STATE_MODIFY_DIGITS; 112 } else { 113 mState = STATE_OTHER; 114 } 115 } 116 117 public void onTextChanged(CharSequence s, int start, int before, int count) { 118 if (mSelfChange || mStopFormatting) { 119 return; 120 } 121 if (mState == STATE_OTHER) { 122 if (count > 0 && !hasSeparator(s, start, count)) { 123 // User inserted the dialable characters in the middle of text. 124 mState = STATE_MODIFY_DIGITS; 125 } 126 } 127 // Check whether we should stop formatting. 128 if (mState == STATE_APPEND && count > 0 && hasSeparator(s, start, count)) { 129 // User appended the non-dialable character, stop formatting. 130 stopFormatting(); 131 } else if (mState == STATE_OTHER) { 132 // User must insert or remove the non-dialable characters in the begin or middle of 133 // number, stop formatting. 134 stopFormatting(); 135 } 136 } 137 138 public synchronized void afterTextChanged(Editable s) { 139 if (mStopFormatting) { 140 // Restart the formatting when all texts were clear. 141 mStopFormatting = !(s.length() == 0); 142 return; 143 } 144 if (mSelfChange) { 145 // Ignore the change caused by s.replace(). 146 return; 147 } 148 String formatted = reformat(s, Selection.getSelectionEnd(s)); 149 if (formatted != null) { 150 int rememberedPos = mFormatter.getRememberedPosition(); 151 mSelfChange = true; 152 s.replace(0, s.length(), formatted, 0, formatted.length()); 153 // The text could be changed by other TextWatcher after we changed it. If we found the 154 // text is not the one we were expecting, just give up calling setSelection(). 155 if (formatted.equals(s.toString())) { 156 Selection.setSelection(s, rememberedPos); 157 } 158 mSelfChange = false; 159 } 160 } 161 162 /** 163 * Generate the formatted number by ignoring all non-dialable chars and stick the cursor to the 164 * nearest dialable char to the left. For instance, if the number is (650) 123-45678 and '4' is 165 * removed then the cursor should be behind '3' instead of '-'. 166 */ 167 private String reformat(CharSequence s, int cursor) { 168 // The index of char to the leftward of the cursor. 169 int curIndex = cursor - 1; 170 String formatted = null; 171 mFormatter.clear(); 172 char lastNonSeparator = 0; 173 boolean hasCursor = false; 174 int len = s.length(); 175 for (int i = 0; i < len; i++) { 176 char c = s.charAt(i); 177 if (PhoneNumberUtils.isNonSeparator(c)) { 178 if (lastNonSeparator != 0) { 179 formatted = getFormattedNumber(lastNonSeparator, hasCursor); 180 hasCursor = false; 181 } 182 lastNonSeparator = c; 183 } 184 if (i == curIndex) { 185 hasCursor = true; 186 } 187 } 188 if (lastNonSeparator != 0) { 189 formatted = getFormattedNumber(lastNonSeparator, hasCursor); 190 } 191 return formatted; 192 } 193 194 private String getFormattedNumber(char lastNonSeparator, boolean hasCursor) { 195 return hasCursor ? mFormatter.inputDigitAndRememberPosition(lastNonSeparator) 196 : mFormatter.inputDigit(lastNonSeparator); 197 } 198 199 private void stopFormatting() { 200 mStopFormatting = true; 201 mFormatter.clear(); 202 } 203 204 private boolean hasSeparator(final CharSequence s, final int start, final int count) { 205 for (int i = start; i < start + count; i++) { 206 char c = s.charAt(i); 207 if (!PhoneNumberUtils.isNonSeparator(c)) { 208 return true; 209 } 210 } 211 return false; 212 } 213} 214