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 static android.telephony.ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN;
     20 
     21 import android.app.Activity;
     22 import android.app.AlertDialog;
     23 import android.app.Dialog;
     24 import android.app.WallpaperManager;
     25 import android.content.BroadcastReceiver;
     26 import android.content.Context;
     27 import android.content.Intent;
     28 import android.content.IntentFilter;
     29 import android.graphics.Point;
     30 import android.media.AudioManager;
     31 import android.media.ToneGenerator;
     32 import android.net.Uri;
     33 import android.os.AsyncTask;
     34 import android.os.Bundle;
     35 import android.os.PersistableBundle;
     36 import android.provider.Settings;
     37 import android.telecom.PhoneAccount;
     38 import android.telecom.TelecomManager;
     39 import android.telephony.CarrierConfigManager;
     40 import android.telephony.PhoneNumberUtils;
     41 import android.telephony.ServiceState;
     42 import android.telephony.SubscriptionManager;
     43 import android.telephony.TelephonyManager;
     44 import android.text.Editable;
     45 import android.text.InputType;
     46 import android.text.Spannable;
     47 import android.text.SpannableString;
     48 import android.text.TextUtils;
     49 import android.text.TextWatcher;
     50 import android.text.method.DialerKeyListener;
     51 import android.text.style.TtsSpan;
     52 import android.util.Log;
     53 import android.util.TypedValue;
     54 import android.view.HapticFeedbackConstants;
     55 import android.view.KeyEvent;
     56 import android.view.MenuItem;
     57 import android.view.MotionEvent;
     58 import android.view.View;
     59 import android.view.WindowManager;
     60 
     61 import com.android.internal.colorextraction.ColorExtractor;
     62 import com.android.internal.colorextraction.ColorExtractor.GradientColors;
     63 import com.android.internal.colorextraction.drawable.GradientDrawable;
     64 import com.android.phone.common.dialpad.DialpadKeyButton;
     65 import com.android.phone.common.util.ViewUtil;
     66 import com.android.phone.common.widget.ResizingTextEditText;
     67 
     68 /**
     69  * EmergencyDialer is a special dialer that is used ONLY for dialing emergency calls.
     70  *
     71  * It's a simplified version of the regular dialer (i.e. the TwelveKeyDialer
     72  * activity from apps/Contacts) that:
     73  *   1. Allows ONLY emergency calls to be dialed
     74  *   2. Disallows voicemail functionality
     75  *   3. Uses the FLAG_SHOW_WHEN_LOCKED window manager flag to allow this
     76  *      activity to stay in front of the keyguard.
     77  *
     78  * TODO: Even though this is an ultra-simplified version of the normal
     79  * dialer, there's still lots of code duplication between this class and
     80  * the TwelveKeyDialer class from apps/Contacts.  Could the common code be
     81  * moved into a shared base class that would live in the framework?
     82  * Or could we figure out some way to move *this* class into apps/Contacts
     83  * also?
     84  */
     85 public class EmergencyDialer extends Activity implements View.OnClickListener,
     86         View.OnLongClickListener, View.OnKeyListener, TextWatcher,
     87         DialpadKeyButton.OnPressedListener, ColorExtractor.OnColorsChangedListener {
     88     // Keys used with onSaveInstanceState().
     89     private static final String LAST_NUMBER = "lastNumber";
     90 
     91     // Intent action for this activity.
     92     public static final String ACTION_DIAL = "com.android.phone.EmergencyDialer.DIAL";
     93 
     94     // List of dialer button IDs.
     95     private static final int[] DIALER_KEYS = new int[] {
     96             R.id.one, R.id.two, R.id.three,
     97             R.id.four, R.id.five, R.id.six,
     98             R.id.seven, R.id.eight, R.id.nine,
     99             R.id.star, R.id.zero, R.id.pound };
    100 
    101     // Debug constants.
    102     private static final boolean DBG = false;
    103     private static final String LOG_TAG = "EmergencyDialer";
    104 
    105     /** The length of DTMF tones in milliseconds */
    106     private static final int TONE_LENGTH_MS = 150;
    107 
    108     /** The DTMF tone volume relative to other sounds in the stream */
    109     private static final int TONE_RELATIVE_VOLUME = 80;
    110 
    111     /** Stream type used to play the DTMF tones off call, and mapped to the volume control keys */
    112     private static final int DIAL_TONE_STREAM_TYPE = AudioManager.STREAM_DTMF;
    113 
    114     private static final int BAD_EMERGENCY_NUMBER_DIALOG = 0;
    115 
    116     /** 90% opacity, different from other gradients **/
    117     private static final int BACKGROUND_GRADIENT_ALPHA = 230;
    118 
    119     ResizingTextEditText mDigits;
    120     private View mDialButton;
    121     private View mDelete;
    122 
    123     private ToneGenerator mToneGenerator;
    124     private Object mToneGeneratorLock = new Object();
    125 
    126     // determines if we want to playback local DTMF tones.
    127     private boolean mDTMFToneEnabled;
    128 
    129     private EmergencyActionGroup mEmergencyActionGroup;
    130 
    131     // close activity when screen turns off
    132     private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
    133         @Override
    134         public void onReceive(Context context, Intent intent) {
    135             if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
    136                 finishAndRemoveTask();
    137             }
    138         }
    139     };
    140 
    141     private String mLastNumber; // last number we tried to dial. Used to restore error dialog.
    142 
    143     // Background gradient
    144     private ColorExtractor mColorExtractor;
    145     private GradientDrawable mBackgroundGradient;
    146     private boolean mSupportsDarkText;
    147 
    148     private boolean mIsWfcEmergencyCallingWarningEnabled;
    149     private float mDefaultDigitsTextSize;
    150 
    151     @Override
    152     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    153         // Do nothing
    154     }
    155 
    156     @Override
    157     public void onTextChanged(CharSequence input, int start, int before, int changeCount) {
    158         maybeChangeHintSize();
    159     }
    160 
    161     @Override
    162     public void afterTextChanged(Editable input) {
    163         // Check for special sequences, in particular the "**04" or "**05"
    164         // sequences that allow you to enter PIN or PUK-related codes.
    165         //
    166         // But note we *don't* allow most other special sequences here,
    167         // like "secret codes" (*#*#<code>#*#*) or IMEI display ("*#06#"),
    168         // since those shouldn't be available if the device is locked.
    169         //
    170         // So we call SpecialCharSequenceMgr.handleCharsForLockedDevice()
    171         // here, not the regular handleChars() method.
    172         if (SpecialCharSequenceMgr.handleCharsForLockedDevice(this, input.toString(), this)) {
    173             // A special sequence was entered, clear the digits
    174             mDigits.getText().clear();
    175         }
    176 
    177         updateDialAndDeleteButtonStateEnabledAttr();
    178         updateTtsSpans();
    179     }
    180 
    181     @Override
    182     protected void onCreate(Bundle icicle) {
    183         super.onCreate(icicle);
    184 
    185         // Allow this activity to be displayed in front of the keyguard / lockscreen.
    186         WindowManager.LayoutParams lp = getWindow().getAttributes();
    187         lp.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
    188 
    189         // When no proximity sensor is available, use a shorter timeout.
    190         // TODO: Do we enable this for non proximity devices any more?
    191         // lp.userActivityTimeout = USER_ACTIVITY_TIMEOUT_WHEN_NO_PROX_SENSOR;
    192 
    193         getWindow().setAttributes(lp);
    194 
    195         mColorExtractor = new ColorExtractor(this);
    196         GradientColors lockScreenColors = mColorExtractor.getColors(WallpaperManager.FLAG_LOCK,
    197                 ColorExtractor.TYPE_EXTRA_DARK);
    198         updateTheme(lockScreenColors.supportsDarkText());
    199 
    200         setContentView(R.layout.emergency_dialer);
    201 
    202         mDigits = (ResizingTextEditText) findViewById(R.id.digits);
    203         mDigits.setKeyListener(DialerKeyListener.getInstance());
    204         mDigits.setOnClickListener(this);
    205         mDigits.setOnKeyListener(this);
    206         mDigits.setLongClickable(false);
    207         mDigits.setInputType(InputType.TYPE_NULL);
    208         mDefaultDigitsTextSize = mDigits.getScaledTextSize();
    209         maybeAddNumberFormatting();
    210 
    211         mBackgroundGradient = new GradientDrawable(this);
    212         Point displaySize = new Point();
    213         ((WindowManager) getSystemService(Context.WINDOW_SERVICE))
    214                 .getDefaultDisplay().getSize(displaySize);
    215         mBackgroundGradient.setScreenSize(displaySize.x, displaySize.y);
    216         mBackgroundGradient.setAlpha(BACKGROUND_GRADIENT_ALPHA);
    217         getWindow().setBackgroundDrawable(mBackgroundGradient);
    218 
    219         // Check for the presence of the keypad
    220         View view = findViewById(R.id.one);
    221         if (view != null) {
    222             setupKeypad();
    223         }
    224 
    225         mDelete = findViewById(R.id.deleteButton);
    226         mDelete.setOnClickListener(this);
    227         mDelete.setOnLongClickListener(this);
    228 
    229         mDialButton = findViewById(R.id.floating_action_button);
    230 
    231         // Check whether we should show the onscreen "Dial" button and co.
    232         // Read carrier config through the public API because PhoneGlobals is not available when we
    233         // run as a secondary user.
    234         CarrierConfigManager configMgr =
    235                 (CarrierConfigManager) getSystemService(Context.CARRIER_CONFIG_SERVICE);
    236         PersistableBundle carrierConfig =
    237                 configMgr.getConfigForSubId(SubscriptionManager.getDefaultVoiceSubscriptionId());
    238 
    239         if (carrierConfig.getBoolean(CarrierConfigManager.KEY_SHOW_ONSCREEN_DIAL_BUTTON_BOOL)) {
    240             mDialButton.setOnClickListener(this);
    241         } else {
    242             mDialButton.setVisibility(View.GONE);
    243         }
    244         mIsWfcEmergencyCallingWarningEnabled = carrierConfig.getInt(
    245                 CarrierConfigManager.KEY_EMERGENCY_NOTIFICATION_DELAY_INT) > -1;
    246         maybeShowWfcEmergencyCallingWarning();
    247 
    248         ViewUtil.setupFloatingActionButton(mDialButton, getResources());
    249 
    250         if (icicle != null) {
    251             super.onRestoreInstanceState(icicle);
    252         }
    253 
    254         // Extract phone number from intent
    255         Uri data = getIntent().getData();
    256         if (data != null && (PhoneAccount.SCHEME_TEL.equals(data.getScheme()))) {
    257             String number = PhoneNumberUtils.getNumberFromIntent(getIntent(), this);
    258             if (number != null) {
    259                 mDigits.setText(number);
    260             }
    261         }
    262 
    263         // if the mToneGenerator creation fails, just continue without it.  It is
    264         // a local audio signal, and is not as important as the dtmf tone itself.
    265         synchronized (mToneGeneratorLock) {
    266             if (mToneGenerator == null) {
    267                 try {
    268                     mToneGenerator = new ToneGenerator(DIAL_TONE_STREAM_TYPE, TONE_RELATIVE_VOLUME);
    269                 } catch (RuntimeException e) {
    270                     Log.w(LOG_TAG, "Exception caught while creating local tone generator: " + e);
    271                     mToneGenerator = null;
    272                 }
    273             }
    274         }
    275 
    276         final IntentFilter intentFilter = new IntentFilter();
    277         intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
    278         registerReceiver(mBroadcastReceiver, intentFilter);
    279 
    280         mEmergencyActionGroup = (EmergencyActionGroup) findViewById(R.id.emergency_action_group);
    281     }
    282 
    283     @Override
    284     protected void onDestroy() {
    285         super.onDestroy();
    286         synchronized (mToneGeneratorLock) {
    287             if (mToneGenerator != null) {
    288                 mToneGenerator.release();
    289                 mToneGenerator = null;
    290             }
    291         }
    292         unregisterReceiver(mBroadcastReceiver);
    293     }
    294 
    295     @Override
    296     protected void onRestoreInstanceState(Bundle icicle) {
    297         mLastNumber = icicle.getString(LAST_NUMBER);
    298     }
    299 
    300     @Override
    301     protected void onSaveInstanceState(Bundle outState) {
    302         super.onSaveInstanceState(outState);
    303         outState.putString(LAST_NUMBER, mLastNumber);
    304     }
    305 
    306     /**
    307      * Explicitly turn off number formatting, since it gets in the way of the emergency
    308      * number detector
    309      */
    310     protected void maybeAddNumberFormatting() {
    311         // Do nothing.
    312     }
    313 
    314     @Override
    315     protected void onPostCreate(Bundle savedInstanceState) {
    316         super.onPostCreate(savedInstanceState);
    317 
    318         // This can't be done in onCreate(), since the auto-restoring of the digits
    319         // will play DTMF tones for all the old digits if it is when onRestoreSavedInstanceState()
    320         // is called. This method will be called every time the activity is created, and
    321         // will always happen after onRestoreSavedInstanceState().
    322         mDigits.addTextChangedListener(this);
    323     }
    324 
    325     private void setupKeypad() {
    326         // Setup the listeners for the buttons
    327         for (int id : DIALER_KEYS) {
    328             final DialpadKeyButton key = (DialpadKeyButton) findViewById(id);
    329             key.setOnPressedListener(this);
    330         }
    331 
    332         View view = findViewById(R.id.zero);
    333         view.setOnLongClickListener(this);
    334     }
    335 
    336     /**
    337      * handle key events
    338      */
    339     @Override
    340     public boolean onKeyDown(int keyCode, KeyEvent event) {
    341         switch (keyCode) {
    342             // Happen when there's a "Call" hard button.
    343             case KeyEvent.KEYCODE_CALL: {
    344                 if (TextUtils.isEmpty(mDigits.getText().toString())) {
    345                     // if we are adding a call from the InCallScreen and the phone
    346                     // number entered is empty, we just close the dialer to expose
    347                     // the InCallScreen under it.
    348                     finish();
    349                 } else {
    350                     // otherwise, we place the call.
    351                     placeCall();
    352                 }
    353                 return true;
    354             }
    355         }
    356         return super.onKeyDown(keyCode, event);
    357     }
    358 
    359     private void keyPressed(int keyCode) {
    360         mDigits.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
    361         KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
    362         mDigits.onKeyDown(keyCode, event);
    363     }
    364 
    365     @Override
    366     public boolean onKey(View view, int keyCode, KeyEvent event) {
    367         switch (view.getId()) {
    368             case R.id.digits:
    369                 // Happen when "Done" button of the IME is pressed. This can happen when this
    370                 // Activity is forced into landscape mode due to a desk dock.
    371                 if (keyCode == KeyEvent.KEYCODE_ENTER
    372                         && event.getAction() == KeyEvent.ACTION_UP) {
    373                     placeCall();
    374                     return true;
    375                 }
    376                 break;
    377         }
    378         return false;
    379     }
    380 
    381     @Override
    382     public boolean dispatchTouchEvent(MotionEvent ev) {
    383         mEmergencyActionGroup.onPreTouchEvent(ev);
    384         boolean handled = super.dispatchTouchEvent(ev);
    385         mEmergencyActionGroup.onPostTouchEvent(ev);
    386         return handled;
    387     }
    388 
    389     @Override
    390     public void onClick(View view) {
    391         switch (view.getId()) {
    392             case R.id.deleteButton: {
    393                 keyPressed(KeyEvent.KEYCODE_DEL);
    394                 return;
    395             }
    396             case R.id.floating_action_button: {
    397                 view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
    398                 placeCall();
    399                 return;
    400             }
    401             case R.id.digits: {
    402                 if (mDigits.length() != 0) {
    403                     mDigits.setCursorVisible(true);
    404                 }
    405                 return;
    406             }
    407         }
    408     }
    409 
    410     @Override
    411     public void onPressed(View view, boolean pressed) {
    412         if (!pressed) {
    413             return;
    414         }
    415         switch (view.getId()) {
    416             case R.id.one: {
    417                 playTone(ToneGenerator.TONE_DTMF_1);
    418                 keyPressed(KeyEvent.KEYCODE_1);
    419                 return;
    420             }
    421             case R.id.two: {
    422                 playTone(ToneGenerator.TONE_DTMF_2);
    423                 keyPressed(KeyEvent.KEYCODE_2);
    424                 return;
    425             }
    426             case R.id.three: {
    427                 playTone(ToneGenerator.TONE_DTMF_3);
    428                 keyPressed(KeyEvent.KEYCODE_3);
    429                 return;
    430             }
    431             case R.id.four: {
    432                 playTone(ToneGenerator.TONE_DTMF_4);
    433                 keyPressed(KeyEvent.KEYCODE_4);
    434                 return;
    435             }
    436             case R.id.five: {
    437                 playTone(ToneGenerator.TONE_DTMF_5);
    438                 keyPressed(KeyEvent.KEYCODE_5);
    439                 return;
    440             }
    441             case R.id.six: {
    442                 playTone(ToneGenerator.TONE_DTMF_6);
    443                 keyPressed(KeyEvent.KEYCODE_6);
    444                 return;
    445             }
    446             case R.id.seven: {
    447                 playTone(ToneGenerator.TONE_DTMF_7);
    448                 keyPressed(KeyEvent.KEYCODE_7);
    449                 return;
    450             }
    451             case R.id.eight: {
    452                 playTone(ToneGenerator.TONE_DTMF_8);
    453                 keyPressed(KeyEvent.KEYCODE_8);
    454                 return;
    455             }
    456             case R.id.nine: {
    457                 playTone(ToneGenerator.TONE_DTMF_9);
    458                 keyPressed(KeyEvent.KEYCODE_9);
    459                 return;
    460             }
    461             case R.id.zero: {
    462                 playTone(ToneGenerator.TONE_DTMF_0);
    463                 keyPressed(KeyEvent.KEYCODE_0);
    464                 return;
    465             }
    466             case R.id.pound: {
    467                 playTone(ToneGenerator.TONE_DTMF_P);
    468                 keyPressed(KeyEvent.KEYCODE_POUND);
    469                 return;
    470             }
    471             case R.id.star: {
    472                 playTone(ToneGenerator.TONE_DTMF_S);
    473                 keyPressed(KeyEvent.KEYCODE_STAR);
    474                 return;
    475             }
    476         }
    477     }
    478 
    479     /**
    480      * called for long touch events
    481      */
    482     @Override
    483     public boolean onLongClick(View view) {
    484         int id = view.getId();
    485         switch (id) {
    486             case R.id.deleteButton: {
    487                 mDigits.getText().clear();
    488                 return true;
    489             }
    490             case R.id.zero: {
    491                 removePreviousDigitIfPossible();
    492                 keyPressed(KeyEvent.KEYCODE_PLUS);
    493                 return true;
    494             }
    495         }
    496         return false;
    497     }
    498 
    499     @Override
    500     protected void onStart() {
    501         super.onStart();
    502 
    503         mColorExtractor.addOnColorsChangedListener(this);
    504         GradientColors lockScreenColors = mColorExtractor.getColors(WallpaperManager.FLAG_LOCK,
    505                 ColorExtractor.TYPE_EXTRA_DARK);
    506         // Do not animate when view isn't visible yet, just set an initial state.
    507         mBackgroundGradient.setColors(lockScreenColors, false);
    508         updateTheme(lockScreenColors.supportsDarkText());
    509     }
    510 
    511     @Override
    512     protected void onResume() {
    513         super.onResume();
    514 
    515         // retrieve the DTMF tone play back setting.
    516         mDTMFToneEnabled = Settings.System.getInt(getContentResolver(),
    517                 Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1;
    518 
    519         // if the mToneGenerator creation fails, just continue without it.  It is
    520         // a local audio signal, and is not as important as the dtmf tone itself.
    521         synchronized (mToneGeneratorLock) {
    522             if (mToneGenerator == null) {
    523                 try {
    524                     mToneGenerator = new ToneGenerator(AudioManager.STREAM_DTMF,
    525                             TONE_RELATIVE_VOLUME);
    526                 } catch (RuntimeException e) {
    527                     Log.w(LOG_TAG, "Exception caught while creating local tone generator: " + e);
    528                     mToneGenerator = null;
    529                 }
    530             }
    531         }
    532 
    533         updateDialAndDeleteButtonStateEnabledAttr();
    534     }
    535 
    536     @Override
    537     public void onPause() {
    538         super.onPause();
    539     }
    540 
    541     @Override
    542     protected void onStop() {
    543         super.onStop();
    544 
    545         mColorExtractor.removeOnColorsChangedListener(this);
    546     }
    547 
    548     /**
    549      * Sets theme based on gradient colors
    550      * @param supportsDarkText true if gradient supports dark text
    551      */
    552     private void updateTheme(boolean supportsDarkText) {
    553         if (mSupportsDarkText == supportsDarkText) {
    554             return;
    555         }
    556         mSupportsDarkText = supportsDarkText;
    557 
    558         // We can't change themes after inflation, in this case we'll have to recreate
    559         // the whole activity.
    560         if (mBackgroundGradient != null) {
    561             recreate();
    562             return;
    563         }
    564 
    565         int vis = getWindow().getDecorView().getSystemUiVisibility();
    566         if (supportsDarkText) {
    567             vis |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
    568             vis |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
    569             setTheme(R.style.EmergencyDialerThemeDark);
    570         } else {
    571             vis &= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
    572             vis &= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
    573             setTheme(R.style.EmergencyDialerTheme);
    574         }
    575         getWindow().getDecorView().setSystemUiVisibility(vis);
    576     }
    577 
    578     /**
    579      * place the call, but check to make sure it is a viable number.
    580      */
    581     private void placeCall() {
    582         mLastNumber = mDigits.getText().toString();
    583 
    584         // Convert into emergency number according to emergency conversion map.
    585         // If conversion map is not defined (this is default), this method does
    586         // nothing and just returns input number.
    587         mLastNumber = PhoneNumberUtils.convertToEmergencyNumber(this, mLastNumber);
    588 
    589         if (PhoneNumberUtils.isLocalEmergencyNumber(this, mLastNumber)) {
    590             if (DBG) Log.d(LOG_TAG, "placing call to " + mLastNumber);
    591 
    592             // place the call if it is a valid number
    593             if (mLastNumber == null || !TextUtils.isGraphic(mLastNumber)) {
    594                 // There is no number entered.
    595                 playTone(ToneGenerator.TONE_PROP_NACK);
    596                 return;
    597             }
    598             TelecomManager tm = (TelecomManager) getSystemService(TELECOM_SERVICE);
    599             tm.placeCall(Uri.fromParts(PhoneAccount.SCHEME_TEL, mLastNumber, null), null);
    600         } else {
    601             if (DBG) Log.d(LOG_TAG, "rejecting bad requested number " + mLastNumber);
    602 
    603             showDialog(BAD_EMERGENCY_NUMBER_DIALOG);
    604         }
    605         mDigits.getText().delete(0, mDigits.getText().length());
    606     }
    607 
    608     /**
    609      * Plays the specified tone for TONE_LENGTH_MS milliseconds.
    610      *
    611      * The tone is played locally, using the audio stream for phone calls.
    612      * Tones are played only if the "Audible touch tones" user preference
    613      * is checked, and are NOT played if the device is in silent mode.
    614      *
    615      * @param tone a tone code from {@link ToneGenerator}
    616      */
    617     void playTone(int tone) {
    618         // if local tone playback is disabled, just return.
    619         if (!mDTMFToneEnabled) {
    620             return;
    621         }
    622 
    623         // Also do nothing if the phone is in silent mode.
    624         // We need to re-check the ringer mode for *every* playTone()
    625         // call, rather than keeping a local flag that's updated in
    626         // onResume(), since it's possible to toggle silent mode without
    627         // leaving the current activity (via the ENDCALL-longpress menu.)
    628         AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
    629         int ringerMode = audioManager.getRingerMode();
    630         if ((ringerMode == AudioManager.RINGER_MODE_SILENT)
    631             || (ringerMode == AudioManager.RINGER_MODE_VIBRATE)) {
    632             return;
    633         }
    634 
    635         synchronized (mToneGeneratorLock) {
    636             if (mToneGenerator == null) {
    637                 Log.w(LOG_TAG, "playTone: mToneGenerator == null, tone: " + tone);
    638                 return;
    639             }
    640 
    641             // Start the new tone (will stop any playing tone)
    642             mToneGenerator.startTone(tone, TONE_LENGTH_MS);
    643         }
    644     }
    645 
    646     private CharSequence createErrorMessage(String number) {
    647         if (!TextUtils.isEmpty(number)) {
    648             String errorString = getString(R.string.dial_emergency_error, number);
    649             int startingPosition = errorString.indexOf(number);
    650             int endingPosition = startingPosition + number.length();
    651             Spannable result = new SpannableString(errorString);
    652             PhoneNumberUtils.addTtsSpan(result, startingPosition, endingPosition);
    653             return result;
    654         } else {
    655             return getText(R.string.dial_emergency_empty_error).toString();
    656         }
    657     }
    658 
    659     @Override
    660     protected Dialog onCreateDialog(int id) {
    661         AlertDialog dialog = null;
    662         if (id == BAD_EMERGENCY_NUMBER_DIALOG) {
    663             // construct dialog
    664             dialog = new AlertDialog.Builder(this, R.style.EmergencyDialerAlertDialogTheme)
    665                     .setTitle(getText(R.string.emergency_enable_radio_dialog_title))
    666                     .setMessage(createErrorMessage(mLastNumber))
    667                     .setPositiveButton(R.string.ok, null)
    668                     .setCancelable(true).create();
    669 
    670             // blur stuff behind the dialog
    671             dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
    672             setShowWhenLocked(true);
    673         }
    674         return dialog;
    675     }
    676 
    677     @Override
    678     protected void onPrepareDialog(int id, Dialog dialog) {
    679         super.onPrepareDialog(id, dialog);
    680         if (id == BAD_EMERGENCY_NUMBER_DIALOG) {
    681             AlertDialog alert = (AlertDialog) dialog;
    682             alert.setMessage(createErrorMessage(mLastNumber));
    683         }
    684     }
    685 
    686     @Override
    687     public boolean onOptionsItemSelected(MenuItem item) {
    688         final int itemId = item.getItemId();
    689         if (itemId == android.R.id.home) {
    690             onBackPressed();
    691             return true;
    692         }
    693         return super.onOptionsItemSelected(item);
    694     }
    695 
    696     /**
    697      * Update the enabledness of the "Dial" and "Backspace" buttons if applicable.
    698      */
    699     private void updateDialAndDeleteButtonStateEnabledAttr() {
    700         final boolean notEmpty = mDigits.length() != 0;
    701 
    702         mDelete.setEnabled(notEmpty);
    703     }
    704 
    705     /**
    706      * Remove the digit just before the current position. Used by various long pressed callbacks
    707      * to remove the digit that was populated as a result of the short click.
    708      */
    709     private void removePreviousDigitIfPossible() {
    710         final int currentPosition = mDigits.getSelectionStart();
    711         if (currentPosition > 0) {
    712             mDigits.setSelection(currentPosition);
    713             mDigits.getText().delete(currentPosition - 1, currentPosition);
    714         }
    715     }
    716 
    717     /**
    718      * Update the text-to-speech annotations in the edit field.
    719      */
    720     private void updateTtsSpans() {
    721         for (Object o : mDigits.getText().getSpans(0, mDigits.getText().length(), TtsSpan.class)) {
    722             mDigits.getText().removeSpan(o);
    723         }
    724         PhoneNumberUtils.ttsSpanAsPhoneNumber(mDigits.getText(), 0, mDigits.getText().length());
    725     }
    726 
    727     @Override
    728     public void onColorsChanged(ColorExtractor extractor, int which) {
    729         if ((which & WallpaperManager.FLAG_LOCK) != 0) {
    730             GradientColors colors = extractor.getColors(WallpaperManager.FLAG_LOCK,
    731                     ColorExtractor.TYPE_EXTRA_DARK);
    732             mBackgroundGradient.setColors(colors);
    733             updateTheme(colors.supportsDarkText());
    734         }
    735     }
    736 
    737     /**
    738      * Where a carrier requires a warning that emergency calling is not available while on WFC,
    739      * add hint text above the dial pad which warns the user of this case.
    740      */
    741     private void maybeShowWfcEmergencyCallingWarning() {
    742         if (!mIsWfcEmergencyCallingWarningEnabled) {
    743             Log.i(LOG_TAG, "maybeShowWfcEmergencyCallingWarning: warning disabled by carrier.");
    744             return;
    745         }
    746 
    747         // Use an async task rather than calling into Telephony on UI thread.
    748         AsyncTask<Void, Void, Boolean> showWfcWarningTask = new AsyncTask<Void, Void, Boolean>() {
    749             @Override
    750             protected Boolean doInBackground(Void... voids) {
    751                 TelephonyManager tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
    752                 boolean isWfcAvailable = tm.isWifiCallingAvailable();
    753                 ServiceState ss = tm.getServiceState();
    754                 boolean isCellAvailable =
    755                         ss.getRilVoiceRadioTechnology() != RIL_RADIO_TECHNOLOGY_UNKNOWN;
    756                 Log.i(LOG_TAG, "showWfcWarningTask: isWfcAvailable=" + isWfcAvailable
    757                                 + " isCellAvailable=" + isCellAvailable
    758                                 + "(rat=" + ss.getRilVoiceRadioTechnology() + ")");
    759                 return isWfcAvailable && !isCellAvailable;
    760             }
    761 
    762             @Override
    763             protected void onPostExecute(Boolean result) {
    764                 if (result.booleanValue()) {
    765                     Log.i(LOG_TAG, "showWfcWarningTask: showing ecall warning");
    766                     mDigits.setHint(R.string.dial_emergency_calling_not_available);
    767                 } else {
    768                     Log.i(LOG_TAG, "showWfcWarningTask: hiding ecall warning");
    769                     mDigits.setHint("");
    770                 }
    771                 maybeChangeHintSize();
    772             }
    773         };
    774         showWfcWarningTask.execute((Void) null);
    775     }
    776 
    777     /**
    778      * Where a hint is applied and there are no digits dialed, disable autoresize of the dial digits
    779      * edit view and set the font size to a smaller size appropriate for the emergency calling
    780      * warning.
    781      */
    782     private void maybeChangeHintSize() {
    783         if (TextUtils.isEmpty(mDigits.getHint())
    784                 || !TextUtils.isEmpty(mDigits.getText().toString())) {
    785             // No hint or there are dialed digits, so use default size.
    786             mDigits.setTextSize(TypedValue.COMPLEX_UNIT_SP, mDefaultDigitsTextSize);
    787             // By default, the digits view auto-resizes to fit the text it contains, so
    788             // enable that now.
    789             mDigits.setResizeEnabled(true);
    790             Log.i(LOG_TAG, "no hint - setting to " + mDigits.getScaledTextSize());
    791         } else {
    792             // Hint present and no dialed digits, set custom font size appropriate for the warning.
    793             mDigits.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimensionPixelSize(
    794                     R.dimen.emergency_call_warning_size));
    795             // Since we're populating this with a static text string, disable auto-resize.
    796             mDigits.setResizeEnabled(false);
    797             Log.i(LOG_TAG, "hint - setting to " + mDigits.getScaledTextSize());
    798         }
    799     }
    800 }
    801