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