blob: d4ed860cdafd63a852e27fe5c8305e49cb5f9a8b [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
Artur Satayev74cb7192019-12-10 17:47:56 +000019import android.compat.annotation.UnsupportedAppUsage;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080020import android.text.Editable;
21import android.text.Selection;
22import android.text.TextWatcher;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023
Artur Satayev74cb7192019-12-10 17:47:56 +000024import com.android.i18n.phonenumbers.AsYouTypeFormatter;
25import com.android.i18n.phonenumbers.PhoneNumberUtil;
26
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027import 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
Mathew Inwood323d08f2018-08-16 11:30:29 +010053 @UnsupportedAppUsage
Bai Taoef4fd8e2010-06-07 10:25:53 +080054 private AsYouTypeFormatter mFormatter;
55
56 /**
57 * The formatting is based on the current system locale and future locale changes
58 * may not take effect on this instance.
59 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080060 public PhoneNumberFormattingTextWatcher() {
Bai Tao1e802992010-07-28 17:40:07 -070061 this(Locale.getDefault().getCountry());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080062 }
63
Bai Taoef4fd8e2010-06-07 10:25:53 +080064 /**
65 * The formatting is based on the given <code>countryCode</code>.
66 *
67 * @param countryCode the ISO 3166-1 two-letter country code that indicates the country/region
68 * where the phone number is being entered.
Bai Taoef4fd8e2010-06-07 10:25:53 +080069 */
70 public PhoneNumberFormattingTextWatcher(String countryCode) {
Bai Tao1e802992010-07-28 17:40:07 -070071 if (countryCode == null) throw new IllegalArgumentException();
Bai Taoef4fd8e2010-06-07 10:25:53 +080072 mFormatter = PhoneNumberUtil.getInstance().getAsYouTypeFormatter(countryCode);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080073 }
74
Todor Kalaydjiev26a92252012-03-05 12:10:43 -080075 @Override
Bai Taoef4fd8e2010-06-07 10:25:53 +080076 public void beforeTextChanged(CharSequence s, int start, int count,
77 int after) {
78 if (mSelfChange || mStopFormatting) {
79 return;
80 }
Todor Kalaydjiev26a92252012-03-05 12:10:43 -080081 // If the user manually deleted any non-dialable characters, stop formatting
82 if (count > 0 && hasSeparator(s, start, count)) {
83 stopFormatting();
Wink Saville04e71b32009-04-02 11:00:54 -070084 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080085 }
86
Todor Kalaydjiev26a92252012-03-05 12:10:43 -080087 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080088 public void onTextChanged(CharSequence s, int start, int before, int count) {
Bai Taoef4fd8e2010-06-07 10:25:53 +080089 if (mSelfChange || mStopFormatting) {
90 return;
91 }
Todor Kalaydjiev26a92252012-03-05 12:10:43 -080092 // If the user inserted any non-dialable characters, stop formatting
93 if (count > 0 && hasSeparator(s, start, count)) {
Bai Taoef4fd8e2010-06-07 10:25:53 +080094 stopFormatting();
95 }
96 }
97
Todor Kalaydjiev26a92252012-03-05 12:10:43 -080098 @Override
Bai Taoef4fd8e2010-06-07 10:25:53 +080099 public synchronized void afterTextChanged(Editable s) {
100 if (mStopFormatting) {
101 // Restart the formatting when all texts were clear.
102 mStopFormatting = !(s.length() == 0);
103 return;
104 }
105 if (mSelfChange) {
106 // Ignore the change caused by s.replace().
107 return;
108 }
109 String formatted = reformat(s, Selection.getSelectionEnd(s));
110 if (formatted != null) {
111 int rememberedPos = mFormatter.getRememberedPosition();
112 mSelfChange = true;
113 s.replace(0, s.length(), formatted, 0, formatted.length());
114 // The text could be changed by other TextWatcher after we changed it. If we found the
115 // text is not the one we were expecting, just give up calling setSelection().
116 if (formatted.equals(s.toString())) {
117 Selection.setSelection(s, rememberedPos);
118 }
119 mSelfChange = false;
120 }
Ihab Awad0855e7a2014-12-05 13:38:11 -0800121 PhoneNumberUtils.ttsSpanAsPhoneNumber(s, 0, s.length());
Bai Taoef4fd8e2010-06-07 10:25:53 +0800122 }
123
124 /**
125 * Generate the formatted number by ignoring all non-dialable chars and stick the cursor to the
126 * nearest dialable char to the left. For instance, if the number is (650) 123-45678 and '4' is
127 * removed then the cursor should be behind '3' instead of '-'.
128 */
129 private String reformat(CharSequence s, int cursor) {
130 // The index of char to the leftward of the cursor.
131 int curIndex = cursor - 1;
132 String formatted = null;
133 mFormatter.clear();
134 char lastNonSeparator = 0;
135 boolean hasCursor = false;
136 int len = s.length();
137 for (int i = 0; i < len; i++) {
138 char c = s.charAt(i);
139 if (PhoneNumberUtils.isNonSeparator(c)) {
140 if (lastNonSeparator != 0) {
141 formatted = getFormattedNumber(lastNonSeparator, hasCursor);
142 hasCursor = false;
143 }
144 lastNonSeparator = c;
145 }
146 if (i == curIndex) {
147 hasCursor = true;
148 }
149 }
150 if (lastNonSeparator != 0) {
151 formatted = getFormattedNumber(lastNonSeparator, hasCursor);
152 }
153 return formatted;
154 }
155
156 private String getFormattedNumber(char lastNonSeparator, boolean hasCursor) {
157 return hasCursor ? mFormatter.inputDigitAndRememberPosition(lastNonSeparator)
158 : mFormatter.inputDigit(lastNonSeparator);
159 }
160
161 private void stopFormatting() {
162 mStopFormatting = true;
163 mFormatter.clear();
164 }
165
166 private boolean hasSeparator(final CharSequence s, final int start, final int count) {
167 for (int i = start; i < start + count; i++) {
168 char c = s.charAt(i);
169 if (!PhoneNumberUtils.isNonSeparator(c)) {
170 return true;
171 }
172 }
173 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800174 }
175}