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