• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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