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