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