Home | History | Annotate | Download | only in telephony
      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      */
     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      */
     69     public PhoneNumberFormattingTextWatcher(String countryCode) {
     70         if (countryCode == null) throw new IllegalArgumentException();
     71         mFormatter = PhoneNumberUtil.getInstance().getAsYouTypeFormatter(countryCode);
     72     }
     73 
     74     @Override
     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
     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
     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      */
    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;
    173     }
    174 }
    175