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    public PhoneNumberFormattingTextWatcher(String countryCode) {
701e80299bd4761df8328be15476ca88f2d1f9b1d8Bai Tao        if (countryCode == null) throw new IllegalArgumentException();
71ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        mFormatter = PhoneNumberUtil.getInstance().getAsYouTypeFormatter(countryCode);
72ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao    }
73ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao
7426a92257b43e8941e1505a5f9df42b00f23a87eeTodor Kalaydjiev    @Override
75ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao    public void beforeTextChanged(CharSequence s, int start, int count,
76ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            int after) {
77ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        if (mSelfChange || mStopFormatting) {
78ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            return;
79ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        }
8026a92257b43e8941e1505a5f9df42b00f23a87eeTodor Kalaydjiev        // If the user manually deleted any non-dialable characters, stop formatting
8126a92257b43e8941e1505a5f9df42b00f23a87eeTodor Kalaydjiev        if (count > 0 && hasSeparator(s, start, count)) {
8226a92257b43e8941e1505a5f9df42b00f23a87eeTodor Kalaydjiev            stopFormatting();
839066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
849066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
859066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
8626a92257b43e8941e1505a5f9df42b00f23a87eeTodor Kalaydjiev    @Override
87ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao    public void onTextChanged(CharSequence s, int start, int before, int count) {
88ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        if (mSelfChange || mStopFormatting) {
89ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            return;
90ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        }
9126a92257b43e8941e1505a5f9df42b00f23a87eeTodor Kalaydjiev        // If the user inserted any non-dialable characters, stop formatting
9226a92257b43e8941e1505a5f9df42b00f23a87eeTodor Kalaydjiev        if (count > 0 && hasSeparator(s, start, count)) {
93ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            stopFormatting();
94ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        }
95ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao    }
9604e71b3db84fd5f7fc4eefb49a33154ea91ec9fcWink Saville
9726a92257b43e8941e1505a5f9df42b00f23a87eeTodor Kalaydjiev    @Override
98ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao    public synchronized void afterTextChanged(Editable s) {
99ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        if (mStopFormatting) {
100ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            // Restart the formatting when all texts were clear.
101ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            mStopFormatting = !(s.length() == 0);
102ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            return;
103ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        }
104ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        if (mSelfChange) {
105ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            // Ignore the change caused by s.replace().
106ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            return;
107ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        }
108ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        String formatted = reformat(s, Selection.getSelectionEnd(s));
109ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        if (formatted != null) {
110ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            int rememberedPos = mFormatter.getRememberedPosition();
111ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            mSelfChange = true;
112ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            s.replace(0, s.length(), formatted, 0, formatted.length());
113ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            // The text could be changed by other TextWatcher after we changed it. If we found the
114ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            // text is not the one we were expecting, just give up calling setSelection().
115ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            if (formatted.equals(s.toString())) {
116ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao                Selection.setSelection(s, rememberedPos);
117ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            }
118ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            mSelfChange = false;
1199066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project        }
1200855e7aea22e863f21a546b87acf38b7c3912e2eIhab Awad        PhoneNumberUtils.ttsSpanAsPhoneNumber(s, 0, s.length());
1219066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
1229066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
123ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao    /**
124ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao     * Generate the formatted number by ignoring all non-dialable chars and stick the cursor to the
125ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao     * nearest dialable char to the left. For instance, if the number is  (650) 123-45678 and '4' is
126ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao     * removed then the cursor should be behind '3' instead of '-'.
127ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao     */
128ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao    private String reformat(CharSequence s, int cursor) {
129ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        // The index of char to the leftward of the cursor.
130ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        int curIndex = cursor - 1;
131ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        String formatted = null;
132ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        mFormatter.clear();
133ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        char lastNonSeparator = 0;
134ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        boolean hasCursor = false;
135ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        int len = s.length();
136ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        for (int i = 0; i < len; i++) {
137ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            char c = s.charAt(i);
138ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            if (PhoneNumberUtils.isNonSeparator(c)) {
139ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao                if (lastNonSeparator != 0) {
140ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao                    formatted = getFormattedNumber(lastNonSeparator, hasCursor);
141ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao                    hasCursor = false;
1429066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project                }
143ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao                lastNonSeparator = c;
1449066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project            }
145ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            if (i == curIndex) {
146ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao                hasCursor = true;
147ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            }
148ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        }
149ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        if (lastNonSeparator != 0) {
150ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            formatted = getFormattedNumber(lastNonSeparator, hasCursor);
15104e71b3db84fd5f7fc4eefb49a33154ea91ec9fcWink Saville        }
152ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        return formatted;
1539066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
1549066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
155ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao    private String getFormattedNumber(char lastNonSeparator, boolean hasCursor) {
156ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        return hasCursor ? mFormatter.inputDigitAndRememberPosition(lastNonSeparator)
157ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao                : mFormatter.inputDigit(lastNonSeparator);
158ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao    }
159ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao
160ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao    private void stopFormatting() {
161ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        mStopFormatting = true;
162ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        mFormatter.clear();
163ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao    }
164ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao
165ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao    private boolean hasSeparator(final CharSequence s, final int start, final int count) {
166ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        for (int i = start; i < start + count; i++) {
167ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            char c = s.charAt(i);
168ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            if (!PhoneNumberUtils.isNonSeparator(c)) {
169ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao                return true;
170ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao            }
171ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        }
172ef4fd8e12e1928b1e9cdc03378bb1185d9e05cbeBai Tao        return false;
1739066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    }
1749066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project}
175