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