blob: f7dee99fe6137f61fb793b3192d38509a46b97ea [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
Shaopeng Jiae7135762011-08-12 13:25:41 +020019import com.android.i18n.phonenumbers.AsYouTypeFormatter;
20import com.android.i18n.phonenumbers.PhoneNumberUtil;
Bai Taoef4fd8e2010-06-07 10:25:53 +080021
22import android.telephony.PhoneNumberUtils;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import android.text.Editable;
24import android.text.Selection;
25import android.text.TextWatcher;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026
27import java.util.Locale;
28
29/**
Dianne Hackborn5ddd1272010-06-12 10:15:28 -070030 * Watches a {@link android.widget.TextView} and if a phone number is entered
31 * will format it.
Bai Taoef4fd8e2010-06-07 10:25:53 +080032 * <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.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040 */
41public class PhoneNumberFormattingTextWatcher implements TextWatcher {
Bai Taoef4fd8e2010-06-07 10:25:53 +080042
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 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080059 public PhoneNumberFormattingTextWatcher() {
Bai Tao1e802992010-07-28 17:40:07 -070060 this(Locale.getDefault().getCountry());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080061 }
62
Bai Taoef4fd8e2010-06-07 10:25:53 +080063 /**
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.
Bai Taoef4fd8e2010-06-07 10:25:53 +080068 */
69 public PhoneNumberFormattingTextWatcher(String countryCode) {
Bai Tao1e802992010-07-28 17:40:07 -070070 if (countryCode == null) throw new IllegalArgumentException();
Bai Taoef4fd8e2010-06-07 10:25:53 +080071 mFormatter = PhoneNumberUtil.getInstance().getAsYouTypeFormatter(countryCode);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080072 }
73
Todor Kalaydjiev26a92252012-03-05 12:10:43 -080074 @Override
Bai Taoef4fd8e2010-06-07 10:25:53 +080075 public void beforeTextChanged(CharSequence s, int start, int count,
76 int after) {
77 if (mSelfChange || mStopFormatting) {
78 return;
79 }
Todor Kalaydjiev26a92252012-03-05 12:10:43 -080080 // If the user manually deleted any non-dialable characters, stop formatting
81 if (count > 0 && hasSeparator(s, start, count)) {
82 stopFormatting();
Wink Saville04e71b32009-04-02 11:00:54 -070083 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080084 }
85
Todor Kalaydjiev26a92252012-03-05 12:10:43 -080086 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080087 public void onTextChanged(CharSequence s, int start, int before, int count) {
Bai Taoef4fd8e2010-06-07 10:25:53 +080088 if (mSelfChange || mStopFormatting) {
89 return;
90 }
Todor Kalaydjiev26a92252012-03-05 12:10:43 -080091 // If the user inserted any non-dialable characters, stop formatting
92 if (count > 0 && hasSeparator(s, start, count)) {
Bai Taoef4fd8e2010-06-07 10:25:53 +080093 stopFormatting();
94 }
95 }
96
Todor Kalaydjiev26a92252012-03-05 12:10:43 -080097 @Override
Bai Taoef4fd8e2010-06-07 10:25:53 +080098 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 }
Ihab Awad0855e7a2014-12-05 13:38:11 -0800120 PhoneNumberUtils.ttsSpanAsPhoneNumber(s, 0, s.length());
Bai Taoef4fd8e2010-06-07 10:25:53 +0800121 }
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 */
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
155 private String getFormattedNumber(char lastNonSeparator, boolean hasCursor) {
156 return hasCursor ? mFormatter.inputDigitAndRememberPosition(lastNonSeparator)
157 : mFormatter.inputDigit(lastNonSeparator);
158 }
159
160 private void stopFormatting() {
161 mStopFormatting = true;
162 mFormatter.clear();
163 }
164
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;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800173 }
174}