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