Home | History | Annotate | Download | only in phone
      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 com.android.phone;
     18 
     19 import android.app.Activity;
     20 import android.app.AlertDialog;
     21 import android.app.Dialog;
     22 import android.app.StatusBarManager;
     23 import android.content.BroadcastReceiver;
     24 import android.content.Context;
     25 import android.content.Intent;
     26 import android.content.IntentFilter;
     27 import android.content.res.Resources;
     28 import android.media.AudioManager;
     29 import android.media.ToneGenerator;
     30 import android.net.Uri;
     31 import android.os.Bundle;
     32 import android.provider.Settings;
     33 import android.telecom.PhoneAccount;
     34 import android.telephony.PhoneNumberUtils;
     35 import android.text.Editable;
     36 import android.text.TextUtils;
     37 import android.text.TextWatcher;
     38 import android.text.method.DialerKeyListener;
     39 import android.text.style.TtsSpan;
     40 import android.util.Log;
     41 import android.view.KeyEvent;
     42 import android.view.MenuItem;
     43 import android.view.View;
     44 import android.view.WindowManager;
     45 import android.view.accessibility.AccessibilityManager;
     46 import android.widget.EditText;
     47 
     48 import com.android.phone.common.HapticFeedback;
     49 import com.android.phone.common.dialpad.DialpadKeyButton;
     50 import com.android.phone.common.util.ViewUtil;
     51 
     52 
     53 /**
     54  * EmergencyDialer is a special dialer that is used ONLY for dialing emergency calls.
     55  *
     56  * It's a simplified version of the regular dialer (i.e. the TwelveKeyDialer
     57  * activity from apps/Contacts) that:
     58  *   1. Allows ONLY emergency calls to be dialed
     59  *   2. Disallows voicemail functionality
     60  *   3. Uses the FLAG_SHOW_WHEN_LOCKED window manager flag to allow this
     61  *      activity to stay in front of the keyguard.
     62  *
     63  * TODO: Even though this is an ultra-simplified version of the normal
     64  * dialer, there's still lots of code duplication between this class and
     65  * the TwelveKeyDialer class from apps/Contacts.  Could the common code be
     66  * moved into a shared base class that would live in the framework?
     67  * Or could we figure out some way to move *this* class into apps/Contacts
     68  * also?
     69  */
     70 public class EmergencyDialer extends Activity implements View.OnClickListener,
     71         View.OnLongClickListener, View.OnKeyListener, TextWatcher,
     72         DialpadKeyButton.OnPressedListener {
     73     // Keys used with onSaveInstanceState().
     74     private static final String LAST_NUMBER = "lastNumber";
     75 
     76     // Intent action for this activity.
     77     public static final String ACTION_DIAL = "com.android.phone.EmergencyDialer.DIAL";
     78 
     79     // List of dialer button IDs.
     80     private static final int[] DIALER_KEYS = new int[] {
     81             R.id.one, R.id.two, R.id.three,
     82             R.id.four, R.id.five, R.id.six,
     83             R.id.seven, R.id.eight, R.id.nine,
     84             R.id.star, R.id.zero, R.id.pound };
     85 
     86     // Debug constants.
     87     private static final boolean DBG = false;
     88     private static final String LOG_TAG = "EmergencyDialer";
     89 
     90     private StatusBarManager mStatusBarManager;
     91 
     92     /** The length of DTMF tones in milliseconds */
     93     private static final int TONE_LENGTH_MS = 150;
     94 
     95     /** The DTMF tone volume relative to other sounds in the stream */
     96     private static final int TONE_RELATIVE_VOLUME = 80;
     97 
     98     /** Stream type used to play the DTMF tones off call, and mapped to the volume control keys */
     99     private static final int DIAL_TONE_STREAM_TYPE = AudioManager.STREAM_DTMF;
    100 
    101     private static final int BAD_EMERGENCY_NUMBER_DIALOG = 0;
    102 
    103     // private static final int USER_ACTIVITY_TIMEOUT_WHEN_NO_PROX_SENSOR = 15000; // millis
    104 
    105     EditText mDigits;
    106     private View mDialButton;
    107     private View mDelete;
    108 
    109     private ToneGenerator mToneGenerator;
    110     private Object mToneGeneratorLock = new Object();
    111 
    112     // determines if we want to playback local DTMF tones.
    113     private boolean mDTMFToneEnabled;
    114 
    115     // Haptic feedback (vibration) for dialer key presses.
    116     private HapticFeedback mHaptic = new HapticFeedback();
    117 
    118     // close activity when screen turns off
    119     private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
    120         @Override
    121         public void onReceive(Context context, Intent intent) {
    122             if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
    123                 finish();
    124             }
    125         }
    126     };
    127 
    128     private String mLastNumber; // last number we tried to dial. Used to restore error dialog.
    129 
    130     @Override
    131     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    132         // Do nothing
    133     }
    134 
    135     @Override
    136     public void onTextChanged(CharSequence input, int start, int before, int changeCount) {
    137         // Do nothing
    138     }
    139 
    140     @Override
    141     public void afterTextChanged(Editable input) {
    142         // Check for special sequences, in particular the "**04" or "**05"
    143         // sequences that allow you to enter PIN or PUK-related codes.
    144         //
    145         // But note we *don't* allow most other special sequences here,
    146         // like "secret codes" (*#*#<code>#*#*) or IMEI display ("*#06#"),
    147         // since those shouldn't be available if the device is locked.
    148         //
    149         // So we call SpecialCharSequenceMgr.handleCharsForLockedDevice()
    150         // here, not the regular handleChars() method.
    151         if (SpecialCharSequenceMgr.handleCharsForLockedDevice(this, input.toString(), this)) {
    152             // A special sequence was entered, clear the digits
    153             mDigits.getText().clear();
    154         }
    155 
    156         updateDialAndDeleteButtonStateEnabledAttr();
    157         updateTtsSpans();
    158     }
    159 
    160     @Override
    161     protected void onCreate(Bundle icicle) {
    162         super.onCreate(icicle);
    163 
    164         mStatusBarManager = (StatusBarManager) getSystemService(Context.STATUS_BAR_SERVICE);
    165 
    166         // Allow this activity to be displayed in front of the keyguard / lockscreen.
    167         WindowManager.LayoutParams lp = getWindow().getAttributes();
    168         lp.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
    169 
    170         // When no proximity sensor is available, use a shorter timeout.
    171         // TODO: Do we enable this for non proximity devices any more?
    172         // lp.userActivityTimeout = USER_ACTIVITY_TIMEOUT_WHEN_NO_PROX_SENSOR;
    173 
    174         getWindow().setAttributes(lp);
    175 
    176         setContentView(R.layout.emergency_dialer);
    177 
    178         mDigits = (EditText) findViewById(R.id.digits);
    179         mDigits.setKeyListener(DialerKeyListener.getInstance());
    180         mDigits.setOnClickListener(this);
    181         mDigits.setOnKeyListener(this);
    182         mDigits.setLongClickable(false);
    183         maybeAddNumberFormatting();
    184 
    185         // Check for the presence of the keypad
    186         View view = findViewById(R.id.one);
    187         if (view != null) {
    188             setupKeypad();
    189         }
    190 
    191         mDelete = findViewById(R.id.deleteButton);
    192         mDelete.setOnClickListener(this);
    193         mDelete.setOnLongClickListener(this);
    194 
    195         mDialButton = findViewById(R.id.floating_action_button);
    196 
    197         // Check whether we should show the onscreen "Dial" button and co.
    198         Resources res = getResources();
    199         if (res.getBoolean(R.bool.config_show_onscreen_dial_button)) {
    200             mDialButton.setOnClickListener(this);
    201         } else {
    202             mDialButton.setVisibility(View.GONE);
    203         }
    204         View floatingActionButtonContainer = findViewById(R.id.floating_action_button_container);
    205         ViewUtil.setupFloatingActionButton(floatingActionButtonContainer, getResources());
    206 
    207         if (icicle != null) {
    208             super.onRestoreInstanceState(icicle);
    209         }
    210 
    211         // Extract phone number from intent
    212         Uri data = getIntent().getData();
    213         if (data != null && (PhoneAccount.SCHEME_TEL.equals(data.getScheme()))) {
    214             String number = PhoneNumberUtils.getNumberFromIntent(getIntent(), this);
    215             if (number != null) {
    216                 mDigits.setText(number);
    217             }
    218         }
    219 
    220         // if the mToneGenerator creation fails, just continue without it.  It is
    221         // a local audio signal, and is not as important as the dtmf tone itself.
    222         synchronized (mToneGeneratorLock) {
    223             if (mToneGenerator == null) {
    224                 try {
    225                     mToneGenerator = new ToneGenerator(DIAL_TONE_STREAM_TYPE, TONE_RELATIVE_VOLUME);
    226                 } catch (RuntimeException e) {
    227                     Log.w(LOG_TAG, "Exception caught while creating local tone generator: " + e);
    228                     mToneGenerator = null;
    229                 }
    230             }
    231         }
    232 
    233         final IntentFilter intentFilter = new IntentFilter();
    234         intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
    235         registerReceiver(mBroadcastReceiver, intentFilter);
    236 
    237         try {
    238             mHaptic.init(this, res.getBoolean(R.bool.config_enable_dialer_key_vibration));
    239         } catch (Resources.NotFoundException nfe) {
    240              Log.e(LOG_TAG, "Vibrate control bool missing.", nfe);
    241         }
    242     }
    243 
    244     @Override
    245     protected void onDestroy() {
    246         super.onDestroy();
    247         synchronized (mToneGeneratorLock) {
    248             if (mToneGenerator != null) {
    249                 mToneGenerator.release();
    250                 mToneGenerator = null;
    251             }
    252         }
    253         unregisterReceiver(mBroadcastReceiver);
    254     }
    255 
    256     @Override
    257     protected void onRestoreInstanceState(Bundle icicle) {
    258         mLastNumber = icicle.getString(LAST_NUMBER);
    259     }
    260 
    261     @Override
    262     protected void onSaveInstanceState(Bundle outState) {
    263         super.onSaveInstanceState(outState);
    264         outState.putString(LAST_NUMBER, mLastNumber);
    265     }
    266 
    267     /**
    268      * Explicitly turn off number formatting, since it gets in the way of the emergency
    269      * number detector
    270      */
    271     protected void maybeAddNumberFormatting() {
    272         // Do nothing.
    273     }
    274 
    275     @Override
    276     protected void onPostCreate(Bundle savedInstanceState) {
    277         super.onPostCreate(savedInstanceState);
    278 
    279         // This can't be done in onCreate(), since the auto-restoring of the digits
    280         // will play DTMF tones for all the old digits if it is when onRestoreSavedInstanceState()
    281         // is called. This method will be called every time the activity is created, and
    282         // will always happen after onRestoreSavedInstanceState().
    283         mDigits.addTextChangedListener(this);
    284     }
    285 
    286     private void setupKeypad() {
    287         // Setup the listeners for the buttons
    288         for (int id : DIALER_KEYS) {
    289             final DialpadKeyButton key = (DialpadKeyButton) findViewById(id);
    290             key.setOnPressedListener(this);
    291         }
    292 
    293         View view = findViewById(R.id.zero);
    294         view.setOnLongClickListener(this);
    295     }
    296 
    297     /**
    298      * handle key events
    299      */
    300     @Override
    301     public boolean onKeyDown(int keyCode, KeyEvent event) {
    302         switch (keyCode) {
    303             // Happen when there's a "Call" hard button.
    304             case KeyEvent.KEYCODE_CALL: {
    305                 if (TextUtils.isEmpty(mDigits.getText().toString())) {
    306                     // if we are adding a call from the InCallScreen and the phone
    307                     // number entered is empty, we just close the dialer to expose
    308                     // the InCallScreen under it.
    309                     finish();
    310                 } else {
    311                     // otherwise, we place the call.
    312                     placeCall();
    313                 }
    314                 return true;
    315             }
    316         }
    317         return super.onKeyDown(keyCode, event);
    318     }
    319 
    320     private void keyPressed(int keyCode) {
    321         mHaptic.vibrate();
    322         KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
    323         mDigits.onKeyDown(keyCode, event);
    324     }
    325 
    326     @Override
    327     public boolean onKey(View view, int keyCode, KeyEvent event) {
    328         switch (view.getId()) {
    329             case R.id.digits:
    330                 // Happen when "Done" button of the IME is pressed. This can happen when this
    331                 // Activity is forced into landscape mode due to a desk dock.
    332                 if (keyCode == KeyEvent.KEYCODE_ENTER
    333                         && event.getAction() == KeyEvent.ACTION_UP) {
    334                     placeCall();
    335                     return true;
    336                 }
    337                 break;
    338         }
    339         return false;
    340     }
    341 
    342     @Override
    343     public void onClick(View view) {
    344         switch (view.getId()) {
    345             case R.id.deleteButton: {
    346                 keyPressed(KeyEvent.KEYCODE_DEL);
    347                 return;
    348             }
    349             case R.id.floating_action_button: {
    350                 mHaptic.vibrate();  // Vibrate here too, just like we do for the regular keys
    351                 placeCall();
    352                 return;
    353             }
    354             case R.id.digits: {
    355                 if (mDigits.length() != 0) {
    356                     mDigits.setCursorVisible(true);
    357                 }
    358                 return;
    359             }
    360         }
    361     }
    362 
    363     @Override
    364     public void onPressed(View view, boolean pressed) {
    365         if (!pressed) {
    366             return;
    367         }
    368         switch (view.getId()) {
    369             case R.id.one: {
    370                 playTone(ToneGenerator.TONE_DTMF_1);
    371                 keyPressed(KeyEvent.KEYCODE_1);
    372                 return;
    373             }
    374             case R.id.two: {
    375                 playTone(ToneGenerator.TONE_DTMF_2);
    376                 keyPressed(KeyEvent.KEYCODE_2);
    377                 return;
    378             }
    379             case R.id.three: {
    380                 playTone(ToneGenerator.TONE_DTMF_3);
    381                 keyPressed(KeyEvent.KEYCODE_3);
    382                 return;
    383             }
    384             case R.id.four: {
    385                 playTone(ToneGenerator.TONE_DTMF_4);
    386                 keyPressed(KeyEvent.KEYCODE_4);
    387                 return;
    388             }
    389             case R.id.five: {
    390                 playTone(ToneGenerator.TONE_DTMF_5);
    391                 keyPressed(KeyEvent.KEYCODE_5);
    392                 return;
    393             }
    394             case R.id.six: {
    395                 playTone(ToneGenerator.TONE_DTMF_6);
    396                 keyPressed(KeyEvent.KEYCODE_6);
    397                 return;
    398             }
    399             case R.id.seven: {
    400                 playTone(ToneGenerator.TONE_DTMF_7);
    401                 keyPressed(KeyEvent.KEYCODE_7);
    402                 return;
    403             }
    404             case R.id.eight: {
    405                 playTone(ToneGenerator.TONE_DTMF_8);
    406                 keyPressed(KeyEvent.KEYCODE_8);
    407                 return;
    408             }
    409             case R.id.nine: {
    410                 playTone(ToneGenerator.TONE_DTMF_9);
    411                 keyPressed(KeyEvent.KEYCODE_9);
    412                 return;
    413             }
    414             case R.id.zero: {
    415                 playTone(ToneGenerator.TONE_DTMF_0);
    416                 keyPressed(KeyEvent.KEYCODE_0);
    417                 return;
    418             }
    419             case R.id.pound: {
    420                 playTone(ToneGenerator.TONE_DTMF_P);
    421                 keyPressed(KeyEvent.KEYCODE_POUND);
    422                 return;
    423             }
    424             case R.id.star: {
    425                 playTone(ToneGenerator.TONE_DTMF_S);
    426                 keyPressed(KeyEvent.KEYCODE_STAR);
    427                 return;
    428             }
    429         }
    430     }
    431 
    432     /**
    433      * called for long touch events
    434      */
    435     @Override
    436     public boolean onLongClick(View view) {
    437         int id = view.getId();
    438         switch (id) {
    439             case R.id.deleteButton: {
    440                 mDigits.getText().clear();
    441                 return true;
    442             }
    443             case R.id.zero: {
    444                 removePreviousDigitIfPossible();
    445                 keyPressed(KeyEvent.KEYCODE_PLUS);
    446                 return true;
    447             }
    448         }
    449         return false;
    450     }
    451 
    452     @Override
    453     protected void onResume() {
    454         super.onResume();
    455 
    456         // retrieve the DTMF tone play back setting.
    457         mDTMFToneEnabled = Settings.System.getInt(getContentResolver(),
    458                 Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1;
    459 
    460         // Retrieve the haptic feedback setting.
    461         mHaptic.checkSystemSetting();
    462 
    463         // if the mToneGenerator creation fails, just continue without it.  It is
    464         // a local audio signal, and is not as important as the dtmf tone itself.
    465         synchronized (mToneGeneratorLock) {
    466             if (mToneGenerator == null) {
    467                 try {
    468                     mToneGenerator = new ToneGenerator(AudioManager.STREAM_DTMF,
    469                             TONE_RELATIVE_VOLUME);
    470                 } catch (RuntimeException e) {
    471                     Log.w(LOG_TAG, "Exception caught while creating local tone generator: " + e);
    472                     mToneGenerator = null;
    473                 }
    474             }
    475         }
    476 
    477         // Disable the status bar and set the poke lock timeout to medium.
    478         // There is no need to do anything with the wake lock.
    479         if (DBG) Log.d(LOG_TAG, "disabling status bar, set to long timeout");
    480         mStatusBarManager.disable(StatusBarManager.DISABLE_EXPAND);
    481 
    482         updateDialAndDeleteButtonStateEnabledAttr();
    483     }
    484 
    485     @Override
    486     public void onPause() {
    487         // Reenable the status bar and set the poke lock timeout to default.
    488         // There is no need to do anything with the wake lock.
    489         if (DBG) Log.d(LOG_TAG, "reenabling status bar and closing the dialer");
    490         mStatusBarManager.disable(StatusBarManager.DISABLE_NONE);
    491 
    492         super.onPause();
    493 
    494         synchronized (mToneGeneratorLock) {
    495             if (mToneGenerator != null) {
    496                 mToneGenerator.release();
    497                 mToneGenerator = null;
    498             }
    499         }
    500     }
    501 
    502     /**
    503      * place the call, but check to make sure it is a viable number.
    504      */
    505     private void placeCall() {
    506         mLastNumber = mDigits.getText().toString();
    507         if (PhoneNumberUtils.isLocalEmergencyNumber(this, mLastNumber)) {
    508             if (DBG) Log.d(LOG_TAG, "placing call to " + mLastNumber);
    509 
    510             // place the call if it is a valid number
    511             if (mLastNumber == null || !TextUtils.isGraphic(mLastNumber)) {
    512                 // There is no number entered.
    513                 playTone(ToneGenerator.TONE_PROP_NACK);
    514                 return;
    515             }
    516             Intent intent = new Intent(Intent.ACTION_CALL_EMERGENCY);
    517             intent.setData(Uri.fromParts(PhoneAccount.SCHEME_TEL, mLastNumber, null));
    518             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    519             startActivity(intent);
    520         } else {
    521             if (DBG) Log.d(LOG_TAG, "rejecting bad requested number " + mLastNumber);
    522 
    523             showDialog(BAD_EMERGENCY_NUMBER_DIALOG);
    524         }
    525         mDigits.getText().delete(0, mDigits.getText().length());
    526     }
    527 
    528     /**
    529      * Plays the specified tone for TONE_LENGTH_MS milliseconds.
    530      *
    531      * The tone is played locally, using the audio stream for phone calls.
    532      * Tones are played only if the "Audible touch tones" user preference
    533      * is checked, and are NOT played if the device is in silent mode.
    534      *
    535      * @param tone a tone code from {@link ToneGenerator}
    536      */
    537     void playTone(int tone) {
    538         // if local tone playback is disabled, just return.
    539         if (!mDTMFToneEnabled) {
    540             return;
    541         }
    542 
    543         // Also do nothing if the phone is in silent mode.
    544         // We need to re-check the ringer mode for *every* playTone()
    545         // call, rather than keeping a local flag that's updated in
    546         // onResume(), since it's possible to toggle silent mode without
    547         // leaving the current activity (via the ENDCALL-longpress menu.)
    548         AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
    549         int ringerMode = audioManager.getRingerMode();
    550         if ((ringerMode == AudioManager.RINGER_MODE_SILENT)
    551             || (ringerMode == AudioManager.RINGER_MODE_VIBRATE)) {
    552             return;
    553         }
    554 
    555         synchronized (mToneGeneratorLock) {
    556             if (mToneGenerator == null) {
    557                 Log.w(LOG_TAG, "playTone: mToneGenerator == null, tone: " + tone);
    558                 return;
    559             }
    560 
    561             // Start the new tone (will stop any playing tone)
    562             mToneGenerator.startTone(tone, TONE_LENGTH_MS);
    563         }
    564     }
    565 
    566     private CharSequence createErrorMessage(String number) {
    567         if (!TextUtils.isEmpty(number)) {
    568             return getString(R.string.dial_emergency_error, mLastNumber);
    569         } else {
    570             return getText(R.string.dial_emergency_empty_error).toString();
    571         }
    572     }
    573 
    574     @Override
    575     protected Dialog onCreateDialog(int id) {
    576         AlertDialog dialog = null;
    577         if (id == BAD_EMERGENCY_NUMBER_DIALOG) {
    578             // construct dialog
    579             dialog = new AlertDialog.Builder(this)
    580                     .setTitle(getText(R.string.emergency_enable_radio_dialog_title))
    581                     .setMessage(createErrorMessage(mLastNumber))
    582                     .setPositiveButton(R.string.ok, null)
    583                     .setCancelable(true).create();
    584 
    585             // blur stuff behind the dialog
    586             dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
    587             dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
    588         }
    589         return dialog;
    590     }
    591 
    592     @Override
    593     protected void onPrepareDialog(int id, Dialog dialog) {
    594         super.onPrepareDialog(id, dialog);
    595         if (id == BAD_EMERGENCY_NUMBER_DIALOG) {
    596             AlertDialog alert = (AlertDialog) dialog;
    597             alert.setMessage(createErrorMessage(mLastNumber));
    598         }
    599     }
    600 
    601     @Override
    602     public boolean onOptionsItemSelected(MenuItem item) {
    603         final int itemId = item.getItemId();
    604         if (itemId == android.R.id.home) {
    605             onBackPressed();
    606             return true;
    607         }
    608         return super.onOptionsItemSelected(item);
    609     }
    610 
    611     /**
    612      * Update the enabledness of the "Dial" and "Backspace" buttons if applicable.
    613      */
    614     private void updateDialAndDeleteButtonStateEnabledAttr() {
    615         final boolean notEmpty = mDigits.length() != 0;
    616 
    617         mDelete.setEnabled(notEmpty);
    618     }
    619 
    620     /**
    621      * Remove the digit just before the current position. Used by various long pressed callbacks
    622      * to remove the digit that was populated as a result of the short click.
    623      */
    624     private void removePreviousDigitIfPossible() {
    625         final int currentPosition = mDigits.getSelectionStart();
    626         if (currentPosition > 0) {
    627             mDigits.setSelection(currentPosition);
    628             mDigits.getText().delete(currentPosition - 1, currentPosition);
    629         }
    630     }
    631 
    632     /**
    633      * Update the text-to-speech annotations in the edit field.
    634      */
    635     private void updateTtsSpans() {
    636         for (Object o : mDigits.getText().getSpans(0, mDigits.getText().length(), TtsSpan.class)) {
    637             mDigits.getText().removeSpan(o);
    638         }
    639         PhoneNumberUtils.ttsSpanAsPhoneNumber(mDigits.getText(), 0, mDigits.getText().length());
    640     }
    641 }
    642