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 17 package android.telephony; 18 19 import com.android.i18n.phonenumbers.AsYouTypeFormatter; 20 import com.android.i18n.phonenumbers.PhoneNumberUtil; 21 22 import android.telephony.PhoneNumberUtils; 23 import android.text.Editable; 24 import android.text.Selection; 25 import android.text.TextWatcher; 26 27 import 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 */ 41 public class PhoneNumberFormattingTextWatcher implements TextWatcher { 42 43 /** 44 * Indicates the change was caused by ourselves. 45 */ 46 private boolean mSelfChange = false; 47 48 /** 49 * Indicates the formatting has been stopped. 50 */ 51 private boolean mStopFormatting; 52 53 private AsYouTypeFormatter mFormatter; 54 55 /** 56 * The formatting is based on the current system locale and future locale changes 57 * may not take effect on this instance. 58 */ PhoneNumberFormattingTextWatcher()59 public PhoneNumberFormattingTextWatcher() { 60 this(Locale.getDefault().getCountry()); 61 } 62 63 /** 64 * The formatting is based on the given <code>countryCode</code>. 65 * 66 * @param countryCode the ISO 3166-1 two-letter country code that indicates the country/region 67 * where the phone number is being entered. 68 */ PhoneNumberFormattingTextWatcher(String countryCode)69 public PhoneNumberFormattingTextWatcher(String countryCode) { 70 if (countryCode == null) throw new IllegalArgumentException(); 71 mFormatter = PhoneNumberUtil.getInstance().getAsYouTypeFormatter(countryCode); 72 } 73 74 @Override beforeTextChanged(CharSequence s, int start, int count, int after)75 public void beforeTextChanged(CharSequence s, int start, int count, 76 int after) { 77 if (mSelfChange || mStopFormatting) { 78 return; 79 } 80 // If the user manually deleted any non-dialable characters, stop formatting 81 if (count > 0 && hasSeparator(s, start, count)) { 82 stopFormatting(); 83 } 84 } 85 86 @Override onTextChanged(CharSequence s, int start, int before, int count)87 public void onTextChanged(CharSequence s, int start, int before, int count) { 88 if (mSelfChange || mStopFormatting) { 89 return; 90 } 91 // If the user inserted any non-dialable characters, stop formatting 92 if (count > 0 && hasSeparator(s, start, count)) { 93 stopFormatting(); 94 } 95 } 96 97 @Override afterTextChanged(Editable s)98 public synchronized void afterTextChanged(Editable s) { 99 if (mStopFormatting) { 100 // Restart the formatting when all texts were clear. 101 mStopFormatting = !(s.length() == 0); 102 return; 103 } 104 if (mSelfChange) { 105 // Ignore the change caused by s.replace(). 106 return; 107 } 108 String formatted = reformat(s, Selection.getSelectionEnd(s)); 109 if (formatted != null) { 110 int rememberedPos = mFormatter.getRememberedPosition(); 111 mSelfChange = true; 112 s.replace(0, s.length(), formatted, 0, formatted.length()); 113 // The text could be changed by other TextWatcher after we changed it. If we found the 114 // text is not the one we were expecting, just give up calling setSelection(). 115 if (formatted.equals(s.toString())) { 116 Selection.setSelection(s, rememberedPos); 117 } 118 mSelfChange = false; 119 } 120 PhoneNumberUtils.ttsSpanAsPhoneNumber(s, 0, s.length()); 121 } 122 123 /** 124 * Generate the formatted number by ignoring all non-dialable chars and stick the cursor to the 125 * nearest dialable char to the left. For instance, if the number is (650) 123-45678 and '4' is 126 * removed then the cursor should be behind '3' instead of '-'. 127 */ reformat(CharSequence s, int cursor)128 private String reformat(CharSequence s, int cursor) { 129 // The index of char to the leftward of the cursor. 130 int curIndex = cursor - 1; 131 String formatted = null; 132 mFormatter.clear(); 133 char lastNonSeparator = 0; 134 boolean hasCursor = false; 135 int len = s.length(); 136 for (int i = 0; i < len; i++) { 137 char c = s.charAt(i); 138 if (PhoneNumberUtils.isNonSeparator(c)) { 139 if (lastNonSeparator != 0) { 140 formatted = getFormattedNumber(lastNonSeparator, hasCursor); 141 hasCursor = false; 142 } 143 lastNonSeparator = c; 144 } 145 if (i == curIndex) { 146 hasCursor = true; 147 } 148 } 149 if (lastNonSeparator != 0) { 150 formatted = getFormattedNumber(lastNonSeparator, hasCursor); 151 } 152 return formatted; 153 } 154 getFormattedNumber(char lastNonSeparator, boolean hasCursor)155 private String getFormattedNumber(char lastNonSeparator, boolean hasCursor) { 156 return hasCursor ? mFormatter.inputDigitAndRememberPosition(lastNonSeparator) 157 : mFormatter.inputDigit(lastNonSeparator); 158 } 159 stopFormatting()160 private void stopFormatting() { 161 mStopFormatting = true; 162 mFormatter.clear(); 163 } 164 hasSeparator(final CharSequence s, final int start, final int count)165 private boolean hasSeparator(final CharSequence s, final int start, final int count) { 166 for (int i = start; i < start + count; i++) { 167 char c = s.charAt(i); 168 if (!PhoneNumberUtils.isNonSeparator(c)) { 169 return true; 170 } 171 } 172 return false; 173 } 174 } 175