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