PhoneNumberFormattingTextWatcher.java revision 26a92257b43e8941e1505a5f9df42b00f23a87ee
19066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project/*
29066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Copyright (C) 2008 The Android Open Source Project
39066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project *
49066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Licensed under the Apache License, Version 2.0 (the "License");
59066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * you may not use this file except in compliance with the License.
69066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * You may obtain a copy of the License at
79066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project *
89066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project *      http://www.apache.org/licenses/LICENSE-2.0
99066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project *
109066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Unless required by applicable law or agreed to in writing, software
119066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * distributed under the License is distributed on an "AS IS" BASIS,
129066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
139066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * See the License for the specific language governing permissions and
149066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * limitations under the License.
159066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */
169066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
179066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectpackage android.telephony;
189066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
19e713576292fc72086de47066981b86ad2f27ab0fShaopeng Jiaimport com.android.i18n.phonenumbers.AsYouTypeFormatter;
20e713576292fc72086de47066981b86ad2f27ab0fShaopeng Jiaimport com.android.i18n.phonenumbers.PhoneNumberUtil;
21ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao
22ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Taoimport android.telephony.PhoneNumberUtils;
239066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.text.Editable;
249066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.text.Selection;
259066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.text.TextWatcher;
269066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
279066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.util.Locale;
289066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
299066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project/**
305ddd127d5a38d80c0d8087d1770f41f61f84f048Dianne Hackborn * Watches a {@link android.widget.TextView} and if a phone number is entered
315ddd127d5a38d80c0d8087d1770f41f61f84f048Dianne Hackborn * will format it.
32ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao * <p>
33ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao * Stop formatting when the user
34ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao * <ul>
35ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao * <li>Inputs non-dialable characters</li>
36ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao * <li>Removes the separator in the middle of string.</li>
37ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao * </ul>
38ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao * <p>
39ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao * The formatting will be restarted once the text is cleared.
409066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */
419066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectpublic class PhoneNumberFormattingTextWatcher implements TextWatcher {
429066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
43ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao    /**
44ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao     * Indicates the change was caused by ourselves.
45ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao     */
46ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao    private boolean mSelfChange = false;
47ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao
48ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao    /**
49ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao     * Indicates the formatting has been stopped.
50ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao     */
51ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao    private boolean mStopFormatting;
52ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao
53ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao    private AsYouTypeFormatter mFormatter;
54ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao
55ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao    /**
56ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao     * The formatting is based on the current system locale and future locale changes
57ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao     * may not take effect on this instance.
58ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao     */
599066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public PhoneNumberFormattingTextWatcher() {
601e80299bd4761df8328be15476ca88f2d1f9b1d8Bai Tao        this(Locale.getDefault().getCountry());
61ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao    }
62ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao
63ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao    /**
64ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao     * The formatting is based on the given <code>countryCode</code>.
65ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao     *
66ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao     * @param countryCode the ISO 3166-1 two-letter country code that indicates the country/region
67ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao     * where the phone number is being entered.
68ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao     *
69ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao     * @hide
70ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao     */
71ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao    public PhoneNumberFormattingTextWatcher(String countryCode) {
721e80299bd4761df8328be15476ca88f2d1f9b1d8Bai Tao        if (countryCode == null) throw new IllegalArgumentException();
73ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        mFormatter = PhoneNumberUtil.getInstance().getAsYouTypeFormatter(countryCode);
74ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao    }
75ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao
7626a92257b43e8941e1505a5f9df42b00f23a87eeTodor Kalaydjiev    @Override
77ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao    public void beforeTextChanged(CharSequence s, int start, int count,
78ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            int after) {
79ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        if (mSelfChange || mStopFormatting) {
80ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            return;
81ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        }
8226a92257b43e8941e1505a5f9df42b00f23a87eeTodor Kalaydjiev        // If the user manually deleted any non-dialable characters, stop formatting
8326a92257b43e8941e1505a5f9df42b00f23a87eeTodor Kalaydjiev        if (count > 0 && hasSeparator(s, start, count)) {
8426a92257b43e8941e1505a5f9df42b00f23a87eeTodor Kalaydjiev            stopFormatting();
859066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
869066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
879066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
8826a92257b43e8941e1505a5f9df42b00f23a87eeTodor Kalaydjiev    @Override
89ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao    public void onTextChanged(CharSequence s, int start, int before, int count) {
90ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        if (mSelfChange || mStopFormatting) {
91ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            return;
92ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        }
9326a92257b43e8941e1505a5f9df42b00f23a87eeTodor Kalaydjiev        // If the user inserted any non-dialable characters, stop formatting
9426a92257b43e8941e1505a5f9df42b00f23a87eeTodor Kalaydjiev        if (count > 0 && hasSeparator(s, start, count)) {
95ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            stopFormatting();
96ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        }
97ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao    }
9804e71b3db84fd5f7fc4eefb49a33154ea91ec9fcWink Saville
9926a92257b43e8941e1505a5f9df42b00f23a87eeTodor Kalaydjiev    @Override
100ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao    public synchronized void afterTextChanged(Editable s) {
101ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        if (mStopFormatting) {
102ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            // Restart the formatting when all texts were clear.
103ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            mStopFormatting = !(s.length() == 0);
104ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            return;
105ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        }
106ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        if (mSelfChange) {
107ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            // Ignore the change caused by s.replace().
108ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            return;
109ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        }
110ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        String formatted = reformat(s, Selection.getSelectionEnd(s));
111ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        if (formatted != null) {
112ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            int rememberedPos = mFormatter.getRememberedPosition();
113ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            mSelfChange = true;
114ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            s.replace(0, s.length(), formatted, 0, formatted.length());
115ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            // The text could be changed by other TextWatcher after we changed it. If we found the
116ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            // text is not the one we were expecting, just give up calling setSelection().
117ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            if (formatted.equals(s.toString())) {
118ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao                Selection.setSelection(s, rememberedPos);
119ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            }
120ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            mSelfChange = false;
1219066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
1229066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
1239066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
124ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao    /**
125ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao     * Generate the formatted number by ignoring all non-dialable chars and stick the cursor to the
126ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao     * nearest dialable char to the left. For instance, if the number is  (650) 123-45678 and '4' is
127ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao     * removed then the cursor should be behind '3' instead of '-'.
128ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao     */
129ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao    private String reformat(CharSequence s, int cursor) {
130ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        // The index of char to the leftward of the cursor.
131ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        int curIndex = cursor - 1;
132ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        String formatted = null;
133ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        mFormatter.clear();
134ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        char lastNonSeparator = 0;
135ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        boolean hasCursor = false;
136ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        int len = s.length();
137ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        for (int i = 0; i < len; i++) {
138ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            char c = s.charAt(i);
139ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            if (PhoneNumberUtils.isNonSeparator(c)) {
140ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao                if (lastNonSeparator != 0) {
141ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao                    formatted = getFormattedNumber(lastNonSeparator, hasCursor);
142ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao                    hasCursor = false;
1439066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                }
144ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao                lastNonSeparator = c;
1459066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            }
146ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            if (i == curIndex) {
147ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao                hasCursor = true;
148ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            }
149ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        }
150ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        if (lastNonSeparator != 0) {
151ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            formatted = getFormattedNumber(lastNonSeparator, hasCursor);
15204e71b3db84fd5f7fc4eefb49a33154ea91ec9fcWink Saville        }
153ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        return formatted;
1549066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
1559066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
156ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao    private String getFormattedNumber(char lastNonSeparator, boolean hasCursor) {
157ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        return hasCursor ? mFormatter.inputDigitAndRememberPosition(lastNonSeparator)
158ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao                : mFormatter.inputDigit(lastNonSeparator);
159ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao    }
160ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao
161ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao    private void stopFormatting() {
162ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        mStopFormatting = true;
163ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        mFormatter.clear();
164ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao    }
165ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao
166ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao    private boolean hasSeparator(final CharSequence s, final int start, final int count) {
167ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        for (int i = start; i < start + count; i++) {
168ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            char c = s.charAt(i);
169ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            if (!PhoneNumberUtils.isNonSeparator(c)) {
170ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao                return true;
171ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            }
172ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        }
173ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        return false;
1749066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
1759066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project}
176