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.animation.Animator; 22 import android.animation.AnimatorListenerAdapter; 23 import android.app.Activity; 24 import android.app.AlertDialog; 25 import android.app.Dialog; 26 import android.app.WallpaperManager; 27 import android.content.BroadcastReceiver; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.IntentFilter; 31 import android.database.DataSetObserver; 32 import android.graphics.Color; 33 import android.graphics.Point; 34 import android.media.AudioManager; 35 import android.media.ToneGenerator; 36 import android.net.Uri; 37 import android.os.AsyncTask; 38 import android.os.Bundle; 39 import android.os.PersistableBundle; 40 import android.provider.Settings; 41 import android.telecom.ParcelableCallAnalytics; 42 import android.telecom.PhoneAccount; 43 import android.telecom.TelecomManager; 44 import android.telephony.CarrierConfigManager; 45 import android.telephony.PhoneNumberUtils; 46 import android.telephony.Rlog; 47 import android.telephony.ServiceState; 48 import android.telephony.SubscriptionManager; 49 import android.telephony.TelephonyManager; 50 import android.text.Editable; 51 import android.text.InputType; 52 import android.text.Spannable; 53 import android.text.SpannableString; 54 import android.text.TextUtils; 55 import android.text.TextWatcher; 56 import android.text.method.DialerKeyListener; 57 import android.text.style.TtsSpan; 58 import android.util.Log; 59 import android.util.TypedValue; 60 import android.view.HapticFeedbackConstants; 61 import android.view.KeyEvent; 62 import android.view.MenuItem; 63 import android.view.MotionEvent; 64 import android.view.View; 65 import android.view.View.AccessibilityDelegate; 66 import android.view.ViewGroup; 67 import android.view.WindowManager; 68 import android.view.accessibility.AccessibilityEvent; 69 import android.widget.TextView; 70 71 import com.android.internal.colorextraction.ColorExtractor; 72 import com.android.internal.colorextraction.ColorExtractor.GradientColors; 73 import com.android.internal.colorextraction.drawable.ScrimDrawable; 74 import com.android.phone.EmergencyDialerMetricsLogger.DialedFrom; 75 import com.android.phone.EmergencyDialerMetricsLogger.PhoneNumberType; 76 import com.android.phone.EmergencyDialerMetricsLogger.UiModeErrorCode; 77 import com.android.phone.common.dialpad.DialpadKeyButton; 78 import com.android.phone.common.util.ViewUtil; 79 import com.android.phone.common.widget.ResizingTextEditText; 80 81 import java.util.ArrayList; 82 import java.util.List; 83 import java.util.Locale; 84 85 /** 86 * EmergencyDialer is a special dialer that is used ONLY for dialing emergency calls. 87 * 88 * It's a simplified version of the regular dialer (i.e. the TwelveKeyDialer 89 * activity from apps/Contacts) that: 90 * 1. Allows ONLY emergency calls to be dialed 91 * 2. Disallows voicemail functionality 92 * 3. Allows this activity to stay in front of the keyguard. 93 * 94 * TODO: Even though this is an ultra-simplified version of the normal 95 * dialer, there's still lots of code duplication between this class and 96 * the TwelveKeyDialer class from apps/Contacts. Could the common code be 97 * moved into a shared base class that would live in the framework? 98 * Or could we figure out some way to move *this* class into apps/Contacts 99 * also? 100 */ 101 public class EmergencyDialer extends Activity implements View.OnClickListener, 102 View.OnLongClickListener, View.OnKeyListener, TextWatcher, 103 DialpadKeyButton.OnPressedListener, ColorExtractor.OnColorsChangedListener, 104 EmergencyShortcutButton.OnConfirmClickListener, 105 EmergencyInfoGroup.OnConfirmClickListener { 106 107 // Keys used with onSaveInstanceState(). 108 private static final String LAST_NUMBER = "lastNumber"; 109 110 // Intent action for this activity. 111 public static final String ACTION_DIAL = "com.android.phone.EmergencyDialer.DIAL"; 112 113 /** 114 * Extra included in {@link #ACTION_DIAL} to indicate the entry type that user starts 115 * the emergency dialer. 116 */ 117 public static final String EXTRA_ENTRY_TYPE = 118 "com.android.phone.EmergencyDialer.extra.ENTRY_TYPE"; 119 120 // Constants indicating the entry type that user opened emergency dialer. 121 // This info is sent from system UI with EXTRA_ENTRY_TYPE. Please make them being 122 // in sync with those in com.android.systemui.util.EmergencyDialerConstants. 123 public static final int ENTRY_TYPE_UNKNOWN = 0; 124 public static final int ENTRY_TYPE_LOCKSCREEN_BUTTON = 1; 125 public static final int ENTRY_TYPE_POWER_MENU = 2; 126 127 // List of dialer button IDs. 128 private static final int[] DIALER_KEYS = new int[]{ 129 R.id.one, R.id.two, R.id.three, 130 R.id.four, R.id.five, R.id.six, 131 R.id.seven, R.id.eight, R.id.nine, 132 R.id.star, R.id.zero, R.id.pound}; 133 134 // Debug constants. 135 private static final boolean DBG = false; 136 private static final String LOG_TAG = "EmergencyDialer"; 137 138 /** The length of DTMF tones in milliseconds */ 139 private static final int TONE_LENGTH_MS = 150; 140 141 /** The DTMF tone volume relative to other sounds in the stream */ 142 private static final int TONE_RELATIVE_VOLUME = 80; 143 144 /** Stream type used to play the DTMF tones off call, and mapped to the volume control keys */ 145 private static final int DIAL_TONE_STREAM_TYPE = AudioManager.STREAM_DTMF; 146 147 private static final int BAD_EMERGENCY_NUMBER_DIALOG = 0; 148 149 /** 90% opacity, different from other gradients **/ 150 private static final int BACKGROUND_GRADIENT_ALPHA = 230; 151 152 /** 85% opacity for black background **/ 153 private static final int BLACK_BACKGROUND_GRADIENT_ALPHA = 217; 154 155 /** Size limit of emergency shortcut buttons container. **/ 156 private static final int SHORTCUT_SIZE_LIMIT = 3; 157 158 ResizingTextEditText mDigits; 159 private View mDialButton; 160 private View mDelete; 161 private View mEmergencyShortcutView; 162 private View mDialpadView; 163 164 private List<EmergencyShortcutButton> mEmergencyShortcutButtonList; 165 private EccShortcutAdapter mShortcutAdapter; 166 private DataSetObserver mShortcutDataSetObserver = null; 167 168 private ToneGenerator mToneGenerator; 169 private Object mToneGeneratorLock = new Object(); 170 171 // determines if we want to playback local DTMF tones. 172 private boolean mDTMFToneEnabled; 173 174 private EmergencyActionGroup mEmergencyActionGroup; 175 176 private EmergencyInfoGroup mEmergencyInfoGroup; 177 178 // close activity when screen turns off 179 private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 180 @Override 181 public void onReceive(Context context, Intent intent) { 182 if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) { 183 finishAndRemoveTask(); 184 } 185 } 186 }; 187 188 /** 189 * Customize accessibility methods in View. 190 */ 191 private AccessibilityDelegate mAccessibilityDelegate = new AccessibilityDelegate() { 192 193 /** 194 * Stop AccessiblityService from reading the title of a hidden View. 195 * 196 * <p>The crossfade animation will set the visibility of fade out view to {@link View.GONE} 197 * in the animation end. The view with an accessibility pane title would call the 198 * {@link AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED} event, which would trigger the 199 * accessibility service to read the pane title of fade out view instead of pane title of 200 * fade in view. So it need to filter out the event called by vanished pane. 201 */ 202 @Override 203 public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) { 204 if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED 205 && host.getVisibility() == View.GONE) { 206 return; 207 } 208 super.onPopulateAccessibilityEvent(host, event); 209 } 210 }; 211 212 private String mLastNumber; // last number we tried to dial. Used to restore error dialog. 213 214 // Background gradient 215 private ColorExtractor mColorExtractor; 216 private ScrimDrawable mBackgroundDrawable; 217 private boolean mSupportsDarkText; 218 219 private boolean mIsWfcEmergencyCallingWarningEnabled; 220 private float mDefaultDigitsTextSize; 221 222 private int mEntryType; 223 private ShortcutViewUtils.Config mShortcutViewConfig; 224 225 private EmergencyDialerMetricsLogger mMetricsLogger; 226 227 @Override beforeTextChanged(CharSequence s, int start, int count, int after)228 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 229 // Do nothing 230 } 231 232 @Override onTextChanged(CharSequence input, int start, int before, int changeCount)233 public void onTextChanged(CharSequence input, int start, int before, int changeCount) { 234 maybeChangeHintSize(); 235 } 236 237 @Override afterTextChanged(Editable input)238 public void afterTextChanged(Editable input) { 239 // Check for special sequences, in particular the "**04" or "**05" 240 // sequences that allow you to enter PIN or PUK-related codes. 241 // 242 // But note we *don't* allow most other special sequences here, 243 // like "secret codes" (*#*#<code>#*#*) or IMEI display ("*#06#"), 244 // since those shouldn't be available if the device is locked. 245 // 246 // So we call SpecialCharSequenceMgr.handleCharsForLockedDevice() 247 // here, not the regular handleChars() method. 248 if (SpecialCharSequenceMgr.handleCharsForLockedDevice(this, input.toString(), this)) { 249 // A special sequence was entered, clear the digits 250 mDigits.getText().clear(); 251 } 252 253 updateDialAndDeleteButtonStateEnabledAttr(); 254 updateTtsSpans(); 255 } 256 257 @Override onCreate(Bundle icicle)258 protected void onCreate(Bundle icicle) { 259 super.onCreate(icicle); 260 261 mMetricsLogger = new EmergencyDialerMetricsLogger(this); 262 263 mEntryType = getIntent().getIntExtra(EXTRA_ENTRY_TYPE, ENTRY_TYPE_UNKNOWN); 264 Log.d(LOG_TAG, "Launched from " + entryTypeToString(mEntryType)); 265 266 // Allow this activity to be displayed in front of the keyguard / lockscreen. 267 setShowWhenLocked(true); 268 // Allow turning screen on 269 setTurnScreenOn(true); 270 271 CarrierConfigManager configMgr = getSystemService(CarrierConfigManager.class); 272 PersistableBundle carrierConfig = 273 configMgr.getConfigForSubId(SubscriptionManager.getDefaultVoiceSubscriptionId()); 274 275 mShortcutViewConfig = new ShortcutViewUtils.Config(this, carrierConfig, mEntryType); 276 Log.d(LOG_TAG, "Enable emergency dialer shortcut: " 277 + mShortcutViewConfig.isEnabled()); 278 279 mColorExtractor = new ColorExtractor(this); 280 281 if (mShortcutViewConfig.isEnabled()) { 282 // Shortcut view doesn't support dark text theme. 283 updateTheme(false); 284 } else { 285 GradientColors lockScreenColors = mColorExtractor.getColors(WallpaperManager.FLAG_LOCK, 286 ColorExtractor.TYPE_EXTRA_DARK); 287 updateTheme(lockScreenColors.supportsDarkText()); 288 } 289 290 setContentView(R.layout.emergency_dialer); 291 292 mDigits = (ResizingTextEditText) findViewById(R.id.digits); 293 mDigits.setKeyListener(DialerKeyListener.getInstance()); 294 mDigits.setOnClickListener(this); 295 mDigits.setOnKeyListener(this); 296 mDigits.setLongClickable(false); 297 mDigits.setInputType(InputType.TYPE_NULL); 298 mDefaultDigitsTextSize = mDigits.getScaledTextSize(); 299 maybeAddNumberFormatting(); 300 301 mBackgroundDrawable = new ScrimDrawable(); 302 Point displaySize = new Point(); 303 ((WindowManager) getSystemService(Context.WINDOW_SERVICE)) 304 .getDefaultDisplay().getSize(displaySize); 305 mBackgroundDrawable.setAlpha(mShortcutViewConfig.isEnabled() 306 ? BLACK_BACKGROUND_GRADIENT_ALPHA : BACKGROUND_GRADIENT_ALPHA); 307 getWindow().setBackgroundDrawable(mBackgroundDrawable); 308 309 // Check for the presence of the keypad 310 View view = findViewById(R.id.one); 311 if (view != null) { 312 setupKeypad(); 313 } 314 315 mDelete = findViewById(R.id.deleteButton); 316 mDelete.setOnClickListener(this); 317 mDelete.setOnLongClickListener(this); 318 319 mDialButton = findViewById(R.id.floating_action_button); 320 321 // Check whether we should show the onscreen "Dial" button and co. 322 // Read carrier config through the public API because PhoneGlobals is not available when we 323 // run as a secondary user. 324 if (carrierConfig.getBoolean(CarrierConfigManager.KEY_SHOW_ONSCREEN_DIAL_BUTTON_BOOL)) { 325 mDialButton.setOnClickListener(this); 326 } else { 327 mDialButton.setVisibility(View.GONE); 328 } 329 mIsWfcEmergencyCallingWarningEnabled = carrierConfig.getInt( 330 CarrierConfigManager.KEY_EMERGENCY_NOTIFICATION_DELAY_INT) > -1; 331 maybeShowWfcEmergencyCallingWarning(); 332 333 ViewUtil.setupFloatingActionButton(mDialButton, getResources()); 334 335 if (icicle != null) { 336 super.onRestoreInstanceState(icicle); 337 } 338 339 // Extract phone number from intent 340 Uri data = getIntent().getData(); 341 if (data != null && (PhoneAccount.SCHEME_TEL.equals(data.getScheme()))) { 342 String number = PhoneNumberUtils.getNumberFromIntent(getIntent(), this); 343 if (number != null) { 344 mDigits.setText(number); 345 } 346 } 347 348 // if the mToneGenerator creation fails, just continue without it. It is 349 // a local audio signal, and is not as important as the dtmf tone itself. 350 synchronized (mToneGeneratorLock) { 351 if (mToneGenerator == null) { 352 try { 353 mToneGenerator = new ToneGenerator(DIAL_TONE_STREAM_TYPE, TONE_RELATIVE_VOLUME); 354 } catch (RuntimeException e) { 355 Log.w(LOG_TAG, "Exception caught while creating local tone generator: " + e); 356 mToneGenerator = null; 357 } 358 } 359 } 360 361 final IntentFilter intentFilter = new IntentFilter(); 362 intentFilter.addAction(Intent.ACTION_SCREEN_OFF); 363 registerReceiver(mBroadcastReceiver, intentFilter); 364 365 mEmergencyActionGroup = (EmergencyActionGroup) findViewById(R.id.emergency_action_group); 366 367 mEmergencyInfoGroup = (EmergencyInfoGroup) findViewById(R.id.emergency_info_button); 368 369 if (mShortcutViewConfig.isEnabled()) { 370 setupEmergencyShortcutsView(); 371 } 372 } 373 374 @Override onDestroy()375 protected void onDestroy() { 376 super.onDestroy(); 377 synchronized (mToneGeneratorLock) { 378 if (mToneGenerator != null) { 379 mToneGenerator.release(); 380 mToneGenerator = null; 381 } 382 } 383 unregisterReceiver(mBroadcastReceiver); 384 if (mShortcutAdapter != null && mShortcutDataSetObserver != null) { 385 mShortcutAdapter.unregisterDataSetObserver(mShortcutDataSetObserver); 386 mShortcutDataSetObserver = null; 387 } 388 } 389 390 @Override onRestoreInstanceState(Bundle icicle)391 protected void onRestoreInstanceState(Bundle icicle) { 392 mLastNumber = icicle.getString(LAST_NUMBER); 393 } 394 395 @Override onSaveInstanceState(Bundle outState)396 protected void onSaveInstanceState(Bundle outState) { 397 super.onSaveInstanceState(outState); 398 outState.putString(LAST_NUMBER, mLastNumber); 399 } 400 401 /** 402 * Explicitly turn off number formatting, since it gets in the way of the emergency 403 * number detector 404 */ maybeAddNumberFormatting()405 protected void maybeAddNumberFormatting() { 406 // Do nothing. 407 } 408 409 @Override onPostCreate(Bundle savedInstanceState)410 protected void onPostCreate(Bundle savedInstanceState) { 411 super.onPostCreate(savedInstanceState); 412 413 // This can't be done in onCreate(), since the auto-restoring of the digits 414 // will play DTMF tones for all the old digits if it is when onRestoreSavedInstanceState() 415 // is called. This method will be called every time the activity is created, and 416 // will always happen after onRestoreSavedInstanceState(). 417 mDigits.addTextChangedListener(this); 418 } 419 setupKeypad()420 private void setupKeypad() { 421 // Setup the listeners for the buttons 422 for (int id : DIALER_KEYS) { 423 final DialpadKeyButton key = (DialpadKeyButton) findViewById(id); 424 key.setOnPressedListener(this); 425 } 426 427 View view = findViewById(R.id.zero); 428 view.setOnLongClickListener(this); 429 } 430 431 @Override onBackPressed()432 public void onBackPressed() { 433 // If shortcut view is enabled and Dialpad view is visible, pressing the back key will 434 // back to display EmergencyShortcutView view. Otherwise, it would finish the activity. 435 if (mShortcutViewConfig.isEnabled() && mDialpadView != null 436 && mDialpadView.getVisibility() == View.VISIBLE) { 437 switchView(mEmergencyShortcutView, mDialpadView, true); 438 return; 439 } 440 super.onBackPressed(); 441 } 442 443 /** 444 * handle key events 445 */ 446 @Override onKeyDown(int keyCode, KeyEvent event)447 public boolean onKeyDown(int keyCode, KeyEvent event) { 448 switch (keyCode) { 449 // Happen when there's a "Call" hard button. 450 case KeyEvent.KEYCODE_CALL: { 451 if (TextUtils.isEmpty(mDigits.getText().toString())) { 452 // if we are adding a call from the InCallScreen and the phone 453 // number entered is empty, we just close the dialer to expose 454 // the InCallScreen under it. 455 finish(); 456 } else { 457 // otherwise, we place the call. 458 placeCall(); 459 } 460 return true; 461 } 462 } 463 return super.onKeyDown(keyCode, event); 464 } 465 keyPressed(int keyCode)466 private void keyPressed(int keyCode) { 467 mDigits.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); 468 KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode); 469 mDigits.onKeyDown(keyCode, event); 470 } 471 472 @Override onKey(View view, int keyCode, KeyEvent event)473 public boolean onKey(View view, int keyCode, KeyEvent event) { 474 switch (view.getId()) { 475 case R.id.digits: 476 // Happen when "Done" button of the IME is pressed. This can happen when this 477 // Activity is forced into landscape mode due to a desk dock. 478 if (keyCode == KeyEvent.KEYCODE_ENTER 479 && event.getAction() == KeyEvent.ACTION_UP) { 480 placeCall(); 481 return true; 482 } 483 break; 484 } 485 return false; 486 } 487 488 @Override dispatchTouchEvent(MotionEvent ev)489 public boolean dispatchTouchEvent(MotionEvent ev) { 490 onPreTouchEvent(ev); 491 boolean handled = super.dispatchTouchEvent(ev); 492 onPostTouchEvent(ev); 493 return handled; 494 } 495 496 @Override onConfirmClick(EmergencyShortcutButton button)497 public void onConfirmClick(EmergencyShortcutButton button) { 498 if (button == null) return; 499 String phoneNumber = button.getPhoneNumber(); 500 501 if (!TextUtils.isEmpty(phoneNumber)) { 502 if (DBG) Log.d(LOG_TAG, "dial emergency number: " + Rlog.pii(LOG_TAG, phoneNumber)); 503 504 // Write metrics when user has intention to make a call from a shortcut button. 505 mMetricsLogger.logPlaceCall(DialedFrom.SHORTCUT, PhoneNumberType.HAS_SHORTCUT, 506 mShortcutViewConfig.getPhoneInfo()); 507 508 placeCall(phoneNumber, ParcelableCallAnalytics.CALL_SOURCE_EMERGENCY_SHORTCUT, 509 mShortcutViewConfig.getPhoneInfo()); 510 } else { 511 Log.d(LOG_TAG, "emergency number is empty"); 512 } 513 } 514 515 @Override onConfirmClick(EmergencyInfoGroup button)516 public void onConfirmClick(EmergencyInfoGroup button) { 517 if (button == null) return; 518 519 Intent intent = (Intent) button.getTag(R.id.tag_intent); 520 if (intent != null) { 521 startActivity(intent); 522 } 523 } 524 525 @Override onClick(View view)526 public void onClick(View view) { 527 switch (view.getId()) { 528 case R.id.deleteButton: { 529 keyPressed(KeyEvent.KEYCODE_DEL); 530 return; 531 } 532 case R.id.floating_action_button: { 533 view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); 534 placeCall(); 535 return; 536 } 537 case R.id.digits: { 538 if (mDigits.length() != 0) { 539 mDigits.setCursorVisible(true); 540 } 541 return; 542 } 543 case R.id.floating_action_button_dialpad: { 544 mDigits.getText().clear(); 545 switchView(mDialpadView, mEmergencyShortcutView, true); 546 return; 547 } 548 } 549 } 550 551 @Override onPressed(View view, boolean pressed)552 public void onPressed(View view, boolean pressed) { 553 if (!pressed) { 554 return; 555 } 556 switch (view.getId()) { 557 case R.id.one: { 558 playTone(ToneGenerator.TONE_DTMF_1); 559 keyPressed(KeyEvent.KEYCODE_1); 560 return; 561 } 562 case R.id.two: { 563 playTone(ToneGenerator.TONE_DTMF_2); 564 keyPressed(KeyEvent.KEYCODE_2); 565 return; 566 } 567 case R.id.three: { 568 playTone(ToneGenerator.TONE_DTMF_3); 569 keyPressed(KeyEvent.KEYCODE_3); 570 return; 571 } 572 case R.id.four: { 573 playTone(ToneGenerator.TONE_DTMF_4); 574 keyPressed(KeyEvent.KEYCODE_4); 575 return; 576 } 577 case R.id.five: { 578 playTone(ToneGenerator.TONE_DTMF_5); 579 keyPressed(KeyEvent.KEYCODE_5); 580 return; 581 } 582 case R.id.six: { 583 playTone(ToneGenerator.TONE_DTMF_6); 584 keyPressed(KeyEvent.KEYCODE_6); 585 return; 586 } 587 case R.id.seven: { 588 playTone(ToneGenerator.TONE_DTMF_7); 589 keyPressed(KeyEvent.KEYCODE_7); 590 return; 591 } 592 case R.id.eight: { 593 playTone(ToneGenerator.TONE_DTMF_8); 594 keyPressed(KeyEvent.KEYCODE_8); 595 return; 596 } 597 case R.id.nine: { 598 playTone(ToneGenerator.TONE_DTMF_9); 599 keyPressed(KeyEvent.KEYCODE_9); 600 return; 601 } 602 case R.id.zero: { 603 playTone(ToneGenerator.TONE_DTMF_0); 604 keyPressed(KeyEvent.KEYCODE_0); 605 return; 606 } 607 case R.id.pound: { 608 playTone(ToneGenerator.TONE_DTMF_P); 609 keyPressed(KeyEvent.KEYCODE_POUND); 610 return; 611 } 612 case R.id.star: { 613 playTone(ToneGenerator.TONE_DTMF_S); 614 keyPressed(KeyEvent.KEYCODE_STAR); 615 return; 616 } 617 } 618 } 619 620 /** 621 * called for long touch events 622 */ 623 @Override onLongClick(View view)624 public boolean onLongClick(View view) { 625 int id = view.getId(); 626 switch (id) { 627 case R.id.deleteButton: { 628 mDigits.getText().clear(); 629 return true; 630 } 631 case R.id.zero: { 632 removePreviousDigitIfPossible(); 633 keyPressed(KeyEvent.KEYCODE_PLUS); 634 return true; 635 } 636 } 637 return false; 638 } 639 640 @Override onStart()641 protected void onStart() { 642 super.onStart(); 643 644 mMetricsLogger.logLaunchEmergencyDialer(mEntryType, 645 mShortcutViewConfig.isEnabled() ? UiModeErrorCode.SUCCESS 646 : UiModeErrorCode.UNSPECIFIED_ERROR); 647 648 if (mShortcutViewConfig.isEnabled()) { 649 // Shortcut view doesn't support dark text theme. 650 mBackgroundDrawable.setColor(Color.BLACK, false); 651 updateTheme(false); 652 } else { 653 mColorExtractor.addOnColorsChangedListener(this); 654 GradientColors lockScreenColors = mColorExtractor.getColors(WallpaperManager.FLAG_LOCK, 655 ColorExtractor.TYPE_EXTRA_DARK); 656 // Do not animate when view isn't visible yet, just set an initial state. 657 mBackgroundDrawable.setColor(lockScreenColors.getMainColor(), false); 658 updateTheme(lockScreenColors.supportsDarkText()); 659 } 660 661 if (mShortcutViewConfig.isEnabled()) { 662 updateLocationAndEccInfo(); 663 } 664 } 665 666 @Override onResume()667 protected void onResume() { 668 super.onResume(); 669 670 // retrieve the DTMF tone play back setting. 671 mDTMFToneEnabled = Settings.System.getInt(getContentResolver(), 672 Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1; 673 674 // if the mToneGenerator creation fails, just continue without it. It is 675 // a local audio signal, and is not as important as the dtmf tone itself. 676 synchronized (mToneGeneratorLock) { 677 if (mToneGenerator == null) { 678 try { 679 mToneGenerator = new ToneGenerator(AudioManager.STREAM_DTMF, 680 TONE_RELATIVE_VOLUME); 681 } catch (RuntimeException e) { 682 Log.w(LOG_TAG, "Exception caught while creating local tone generator: " + e); 683 mToneGenerator = null; 684 } 685 } 686 } 687 688 updateDialAndDeleteButtonStateEnabledAttr(); 689 } 690 691 @Override onPause()692 public void onPause() { 693 super.onPause(); 694 } 695 696 @Override onStop()697 protected void onStop() { 698 super.onStop(); 699 mColorExtractor.removeOnColorsChangedListener(this); 700 } 701 702 /** 703 * Sets theme based on gradient colors 704 * 705 * @param supportsDarkText true if gradient supports dark text 706 */ updateTheme(boolean supportsDarkText)707 private void updateTheme(boolean supportsDarkText) { 708 if (mSupportsDarkText == supportsDarkText) { 709 return; 710 } 711 mSupportsDarkText = supportsDarkText; 712 713 // We can't change themes after inflation, in this case we'll have to recreate 714 // the whole activity. 715 if (mBackgroundDrawable != null) { 716 recreate(); 717 return; 718 } 719 720 int vis = getWindow().getDecorView().getSystemUiVisibility(); 721 if (supportsDarkText) { 722 vis |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; 723 vis |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; 724 setTheme(R.style.EmergencyDialerThemeDark); 725 } else { 726 vis &= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; 727 vis &= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; 728 setTheme(R.style.EmergencyDialerTheme); 729 } 730 getWindow().getDecorView().setSystemUiVisibility(vis); 731 } 732 733 /** 734 * place the call, but check to make sure it is a viable number. 735 */ placeCall()736 private void placeCall() { 737 mLastNumber = mDigits.getText().toString(); 738 739 // Convert into emergency number according to emergency conversion map. 740 // If conversion map is not defined (this is default), this method does 741 // nothing and just returns input number. 742 mLastNumber = PhoneNumberUtils.convertToEmergencyNumber(this, mLastNumber); 743 744 @DialedFrom final int dialedFrom = 745 mShortcutViewConfig.isEnabled() ? DialedFrom.FASTER_LAUNCHER_DIALPAD 746 : DialedFrom.TRADITIONAL_DIALPAD; 747 @PhoneNumberType final int phoneNumberType; 748 749 boolean isEmergencyNumber; 750 ShortcutViewUtils.PhoneInfo phoneToMakeCall = null; 751 if (mShortcutAdapter != null && mShortcutAdapter.hasShortcut(mLastNumber)) { 752 isEmergencyNumber = true; 753 phoneToMakeCall = mShortcutViewConfig.getPhoneInfo(); 754 phoneNumberType = PhoneNumberType.HAS_SHORTCUT; 755 } else if (mShortcutViewConfig.hasPromotedEmergencyNumber(mLastNumber)) { 756 // If a number from SIM/network/... is categoried as police/ambulance/fire, 757 // hasPromotedEmergencyNumber() will return true, but it maybe not promoted as a 758 // shortcut button because a number provided by database has higher priority. 759 isEmergencyNumber = true; 760 phoneToMakeCall = mShortcutViewConfig.getPhoneInfo(); 761 phoneNumberType = PhoneNumberType.NO_SHORTCUT; 762 } else { 763 isEmergencyNumber = getSystemService(TelephonyManager.class) 764 .isEmergencyNumber(mLastNumber); 765 phoneNumberType = PhoneNumberType.NO_SHORTCUT; 766 } 767 768 if (isEmergencyNumber) { 769 if (DBG) Log.d(LOG_TAG, "placing call to " + mLastNumber); 770 771 // place the call if it is a valid number 772 if (mLastNumber == null || !TextUtils.isGraphic(mLastNumber)) { 773 // There is no number entered. 774 playTone(ToneGenerator.TONE_PROP_NACK); 775 return; 776 } 777 778 // Write metrics when user has intention to make a call from dialpad 779 mMetricsLogger.logPlaceCall(dialedFrom, phoneNumberType, phoneToMakeCall); 780 781 placeCall(mLastNumber, ParcelableCallAnalytics.CALL_SOURCE_EMERGENCY_DIALPAD, 782 phoneToMakeCall); 783 } else { 784 if (DBG) Log.d(LOG_TAG, "rejecting bad requested number " + mLastNumber); 785 786 // Write metrics when user has intention to make a call of a non-emergency number, even 787 // this number is rejected. 788 mMetricsLogger.logPlaceCall(dialedFrom, PhoneNumberType.NOT_EMERGENCY_NUMBER, 789 phoneToMakeCall); 790 791 showDialog(BAD_EMERGENCY_NUMBER_DIALOG); 792 } 793 mDigits.getText().delete(0, mDigits.getText().length()); 794 } 795 placeCall(String number, int callSource, ShortcutViewUtils.PhoneInfo phone)796 private void placeCall(String number, int callSource, ShortcutViewUtils.PhoneInfo phone) { 797 Log.d(LOG_TAG, "Place emergency call from " + callSourceToString(callSource) 798 + ", entry = " + entryTypeToString(mEntryType)); 799 800 Bundle extras = new Bundle(); 801 extras.putInt(TelecomManager.EXTRA_CALL_SOURCE, callSource); 802 /** 803 * This is used for Telecom and Telephony to tell modem user's intent is emergency call, 804 * when the dialed number is ambiguous and identified as both emergency number and any 805 * other non-emergency number; e.g. in some situation, 611 could be both an emergency 806 * number in a country and a non-emergency number of a carrier's customer service hotline. 807 */ 808 extras.putBoolean(TelecomManager.EXTRA_IS_USER_INTENT_EMERGENCY_CALL, true); 809 810 if (phone != null && phone.getPhoneAccountHandle() != null) { 811 // Requests to dial through the specified phone. 812 extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, 813 phone.getPhoneAccountHandle()); 814 } 815 816 TelecomManager tm = this.getSystemService(TelecomManager.class); 817 tm.placeCall(Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null), extras); 818 } 819 820 /** 821 * Plays the specified tone for TONE_LENGTH_MS milliseconds. 822 * 823 * The tone is played locally, using the audio stream for phone calls. 824 * Tones are played only if the "Audible touch tones" user preference 825 * is checked, and are NOT played if the device is in silent mode. 826 * 827 * @param tone a tone code from {@link ToneGenerator} 828 */ playTone(int tone)829 void playTone(int tone) { 830 // if local tone playback is disabled, just return. 831 if (!mDTMFToneEnabled) { 832 return; 833 } 834 835 // Also do nothing if the phone is in silent mode. 836 // We need to re-check the ringer mode for *every* playTone() 837 // call, rather than keeping a local flag that's updated in 838 // onResume(), since it's possible to toggle silent mode without 839 // leaving the current activity (via the ENDCALL-longpress menu.) 840 AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 841 int ringerMode = audioManager.getRingerMode(); 842 if ((ringerMode == AudioManager.RINGER_MODE_SILENT) 843 || (ringerMode == AudioManager.RINGER_MODE_VIBRATE)) { 844 return; 845 } 846 847 synchronized (mToneGeneratorLock) { 848 if (mToneGenerator == null) { 849 Log.w(LOG_TAG, "playTone: mToneGenerator == null, tone: " + tone); 850 return; 851 } 852 853 // Start the new tone (will stop any playing tone) 854 mToneGenerator.startTone(tone, TONE_LENGTH_MS); 855 } 856 } 857 createErrorMessage(String number)858 private CharSequence createErrorMessage(String number) { 859 if (!TextUtils.isEmpty(number)) { 860 String errorString = getString(R.string.dial_emergency_error, number); 861 int startingPosition = errorString.indexOf(number); 862 int endingPosition = startingPosition + number.length(); 863 Spannable result = new SpannableString(errorString); 864 PhoneNumberUtils.addTtsSpan(result, startingPosition, endingPosition); 865 return result; 866 } else { 867 return getText(R.string.dial_emergency_empty_error).toString(); 868 } 869 } 870 871 @Override onCreateDialog(int id)872 protected Dialog onCreateDialog(int id) { 873 AlertDialog dialog = null; 874 if (id == BAD_EMERGENCY_NUMBER_DIALOG) { 875 // construct dialog 876 dialog = new AlertDialog.Builder(this, R.style.EmergencyDialerAlertDialogTheme) 877 .setTitle(getText(R.string.emergency_enable_radio_dialog_title)) 878 .setMessage(createErrorMessage(mLastNumber)) 879 .setPositiveButton(R.string.ok, null) 880 .setCancelable(true).create(); 881 882 // blur stuff behind the dialog 883 dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND); 884 setShowWhenLocked(true); 885 } 886 return dialog; 887 } 888 889 @Override onPrepareDialog(int id, Dialog dialog)890 protected void onPrepareDialog(int id, Dialog dialog) { 891 super.onPrepareDialog(id, dialog); 892 if (id == BAD_EMERGENCY_NUMBER_DIALOG) { 893 AlertDialog alert = (AlertDialog) dialog; 894 alert.setMessage(createErrorMessage(mLastNumber)); 895 } 896 } 897 898 @Override onOptionsItemSelected(MenuItem item)899 public boolean onOptionsItemSelected(MenuItem item) { 900 final int itemId = item.getItemId(); 901 if (itemId == android.R.id.home) { 902 onBackPressed(); 903 return true; 904 } 905 return super.onOptionsItemSelected(item); 906 } 907 908 /** 909 * Update the enabledness of the "Dial" and "Backspace" buttons if applicable. 910 */ updateDialAndDeleteButtonStateEnabledAttr()911 private void updateDialAndDeleteButtonStateEnabledAttr() { 912 final boolean notEmpty = mDigits.length() != 0; 913 914 mDelete.setEnabled(notEmpty); 915 } 916 917 /** 918 * Remove the digit just before the current position. Used by various long pressed callbacks 919 * to remove the digit that was populated as a result of the short click. 920 */ removePreviousDigitIfPossible()921 private void removePreviousDigitIfPossible() { 922 final int currentPosition = mDigits.getSelectionStart(); 923 if (currentPosition > 0) { 924 mDigits.setSelection(currentPosition); 925 mDigits.getText().delete(currentPosition - 1, currentPosition); 926 } 927 } 928 929 /** 930 * Update the text-to-speech annotations in the edit field. 931 */ updateTtsSpans()932 private void updateTtsSpans() { 933 for (Object o : mDigits.getText().getSpans(0, mDigits.getText().length(), TtsSpan.class)) { 934 mDigits.getText().removeSpan(o); 935 } 936 PhoneNumberUtils.ttsSpanAsPhoneNumber(mDigits.getText(), 0, mDigits.getText().length()); 937 } 938 939 @Override onColorsChanged(ColorExtractor extractor, int which)940 public void onColorsChanged(ColorExtractor extractor, int which) { 941 if ((which & WallpaperManager.FLAG_LOCK) != 0) { 942 GradientColors colors = extractor.getColors(WallpaperManager.FLAG_LOCK, 943 ColorExtractor.TYPE_EXTRA_DARK); 944 mBackgroundDrawable.setColor(colors.getMainColor(), true /* animated */); 945 updateTheme(colors.supportsDarkText()); 946 } 947 } 948 949 /** 950 * Where a carrier requires a warning that emergency calling is not available while on WFC, 951 * add hint text above the dial pad which warns the user of this case. 952 */ maybeShowWfcEmergencyCallingWarning()953 private void maybeShowWfcEmergencyCallingWarning() { 954 if (!mIsWfcEmergencyCallingWarningEnabled) { 955 Log.i(LOG_TAG, "maybeShowWfcEmergencyCallingWarning: warning disabled by carrier."); 956 return; 957 } 958 959 // Use an async task rather than calling into Telephony on UI thread. 960 AsyncTask<Void, Void, Boolean> showWfcWarningTask = new AsyncTask<Void, Void, Boolean>() { 961 @Override 962 protected Boolean doInBackground(Void... voids) { 963 TelephonyManager tm = getSystemService(TelephonyManager.class); 964 boolean isWfcAvailable = tm.isWifiCallingAvailable(); 965 ServiceState ss = tm.getServiceState(); 966 boolean isCellAvailable = 967 ss.getRilVoiceRadioTechnology() != RIL_RADIO_TECHNOLOGY_UNKNOWN; 968 Log.i(LOG_TAG, "showWfcWarningTask: isWfcAvailable=" + isWfcAvailable 969 + " isCellAvailable=" + isCellAvailable 970 + "(rat=" + ss.getRilVoiceRadioTechnology() + ")"); 971 return isWfcAvailable && !isCellAvailable; 972 } 973 974 @Override 975 protected void onPostExecute(Boolean result) { 976 if (result.booleanValue()) { 977 Log.i(LOG_TAG, "showWfcWarningTask: showing ecall warning"); 978 mDigits.setHint(R.string.dial_emergency_calling_not_available); 979 } else { 980 Log.i(LOG_TAG, "showWfcWarningTask: hiding ecall warning"); 981 mDigits.setHint(""); 982 } 983 maybeChangeHintSize(); 984 } 985 }; 986 showWfcWarningTask.execute((Void) null); 987 } 988 989 /** 990 * Where a hint is applied and there are no digits dialed, disable autoresize of the dial digits 991 * edit view and set the font size to a smaller size appropriate for the emergency calling 992 * warning. 993 */ maybeChangeHintSize()994 private void maybeChangeHintSize() { 995 if (TextUtils.isEmpty(mDigits.getHint()) 996 || !TextUtils.isEmpty(mDigits.getText().toString())) { 997 // No hint or there are dialed digits, so use default size. 998 mDigits.setTextSize(TypedValue.COMPLEX_UNIT_SP, mDefaultDigitsTextSize); 999 // By default, the digits view auto-resizes to fit the text it contains, so 1000 // enable that now. 1001 mDigits.setResizeEnabled(true); 1002 Log.i(LOG_TAG, "no hint - setting to " + mDigits.getScaledTextSize()); 1003 } else { 1004 // Hint present and no dialed digits, set custom font size appropriate for the warning. 1005 mDigits.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimensionPixelSize( 1006 R.dimen.emergency_call_warning_size)); 1007 // Since we're populating this with a static text string, disable auto-resize. 1008 mDigits.setResizeEnabled(false); 1009 Log.i(LOG_TAG, "hint - setting to " + mDigits.getScaledTextSize()); 1010 } 1011 } 1012 setupEmergencyShortcutsView()1013 private void setupEmergencyShortcutsView() { 1014 mEmergencyShortcutView = findViewById(R.id.emergency_dialer_shortcuts); 1015 mDialpadView = findViewById(R.id.emergency_dialer); 1016 1017 mEmergencyShortcutView.setAccessibilityDelegate(mAccessibilityDelegate); 1018 mDialpadView.setAccessibilityDelegate(mAccessibilityDelegate); 1019 1020 final View dialpadButton = findViewById(R.id.floating_action_button_dialpad); 1021 dialpadButton.setOnClickListener(this); 1022 1023 mEmergencyInfoGroup.setOnConfirmClickListener(this); 1024 1025 mEmergencyShortcutButtonList = new ArrayList<>(); 1026 setupEmergencyCallShortcutButton(); 1027 1028 updateLocationAndEccInfo(); 1029 1030 switchView(mEmergencyShortcutView, mDialpadView, false); 1031 } 1032 setLocationInfo()1033 private void setLocationInfo() { 1034 final View locationInfo = findViewById(R.id.location_info); 1035 1036 String countryIso = mShortcutViewConfig.getCountryIso(); 1037 String countryName = null; 1038 if (!TextUtils.isEmpty(countryIso)) { 1039 Locale locale = Locale.getDefault(); 1040 countryName = new Locale(locale.getLanguage(), countryIso, locale.getVariant()) 1041 .getDisplayCountry(); 1042 } 1043 if (TextUtils.isEmpty(countryName)) { 1044 locationInfo.setVisibility(View.INVISIBLE); 1045 } else { 1046 final TextView location = (TextView) locationInfo.findViewById(R.id.location_text); 1047 location.setText(countryName); 1048 locationInfo.setVisibility(View.VISIBLE); 1049 } 1050 } 1051 setupEmergencyCallShortcutButton()1052 private void setupEmergencyCallShortcutButton() { 1053 final ViewGroup shortcutButtonContainer = findViewById( 1054 R.id.emergency_shortcut_buttons_container); 1055 shortcutButtonContainer.setClipToOutline(true); 1056 final TextView emergencyNumberTitle = findViewById(R.id.emergency_number_title); 1057 1058 mShortcutAdapter = new EccShortcutAdapter(this) { 1059 @Override 1060 public View inflateView(View convertView, ViewGroup parent, CharSequence number, 1061 CharSequence description, int iconRes) { 1062 EmergencyShortcutButton button = (EmergencyShortcutButton) getLayoutInflater() 1063 .inflate(R.layout.emergency_shortcut_button, parent, false); 1064 button.setPhoneNumber(number); 1065 button.setPhoneDescription(description); 1066 button.setPhoneTypeIcon(iconRes); 1067 button.setOnConfirmClickListener(EmergencyDialer.this); 1068 return button; 1069 } 1070 }; 1071 mShortcutDataSetObserver = new DataSetObserver() { 1072 @Override 1073 public void onChanged() { 1074 super.onChanged(); 1075 updateLayout(); 1076 } 1077 1078 @Override 1079 public void onInvalidated() { 1080 super.onInvalidated(); 1081 updateLayout(); 1082 } 1083 1084 private void updateLayout() { 1085 // clear previous added buttons 1086 shortcutButtonContainer.removeAllViews(); 1087 mEmergencyShortcutButtonList.clear(); 1088 1089 for (int i = 0; i < mShortcutAdapter.getCount() && i < SHORTCUT_SIZE_LIMIT; ++i) { 1090 EmergencyShortcutButton button = (EmergencyShortcutButton) 1091 mShortcutAdapter.getView(i, null, shortcutButtonContainer); 1092 mEmergencyShortcutButtonList.add(button); 1093 shortcutButtonContainer.addView(button); 1094 } 1095 1096 // Update emergency numbers title for numerous buttons. 1097 if (mEmergencyShortcutButtonList.size() > 1) { 1098 emergencyNumberTitle.setText(getString( 1099 R.string.numerous_emergency_numbers_title)); 1100 // Update mEmergencyInfoGroup margin to avoid UI overlay when 1101 // emergency shortcut button more than 2. 1102 if (mEmergencyShortcutButtonList.size() > 2) { 1103 mEmergencyInfoGroup.updateLayoutMargin(); 1104 } 1105 } else { 1106 emergencyNumberTitle.setText(getText(R.string.single_emergency_number_title)); 1107 } 1108 } 1109 }; 1110 mShortcutAdapter.registerDataSetObserver(mShortcutDataSetObserver); 1111 } 1112 updateLocationAndEccInfo()1113 private void updateLocationAndEccInfo() { 1114 if (!isFinishing() && !isDestroyed()) { 1115 setLocationInfo(); 1116 if (mShortcutAdapter != null) { 1117 mShortcutAdapter.updateCountryEccInfo(this, mShortcutViewConfig.getPhoneInfo()); 1118 } 1119 } 1120 } 1121 1122 /** 1123 * Called by the activity before a touch event is dispatched to the view hierarchy. 1124 */ onPreTouchEvent(MotionEvent event)1125 private void onPreTouchEvent(MotionEvent event) { 1126 mEmergencyActionGroup.onPreTouchEvent(event); 1127 mEmergencyInfoGroup.onPreTouchEvent(event); 1128 1129 if (mEmergencyShortcutButtonList != null) { 1130 for (EmergencyShortcutButton button : mEmergencyShortcutButtonList) { 1131 button.onPreTouchEvent(event); 1132 } 1133 } 1134 } 1135 1136 /** 1137 * Called by the activity after a touch event is dispatched to the view hierarchy. 1138 */ onPostTouchEvent(MotionEvent event)1139 private void onPostTouchEvent(MotionEvent event) { 1140 mEmergencyActionGroup.onPostTouchEvent(event); 1141 mEmergencyInfoGroup.onPostTouchEvent(event); 1142 1143 if (mEmergencyShortcutButtonList != null) { 1144 for (EmergencyShortcutButton button : mEmergencyShortcutButtonList) { 1145 button.onPostTouchEvent(event); 1146 } 1147 } 1148 } 1149 1150 /** 1151 * Switch two view. 1152 * 1153 * @param displayView the view would be displayed. 1154 * @param hideView the view would be hidden. 1155 * @param hasAnimation is {@code true} when the view should be displayed with animation. 1156 */ switchView(View displayView, View hideView, boolean hasAnimation)1157 private void switchView(View displayView, View hideView, boolean hasAnimation) { 1158 if (displayView == null || hideView == null) { 1159 return; 1160 } 1161 1162 if (displayView.getVisibility() == View.VISIBLE) { 1163 return; 1164 } 1165 1166 if (hasAnimation) { 1167 crossfade(hideView, displayView); 1168 } else { 1169 hideView.setVisibility(View.GONE); 1170 displayView.setVisibility(View.VISIBLE); 1171 } 1172 } 1173 1174 /** 1175 * Fade out and fade in animation between two view transition. 1176 */ crossfade(View fadeOutView, View fadeInView)1177 private void crossfade(View fadeOutView, View fadeInView) { 1178 if (fadeOutView == null || fadeInView == null) { 1179 return; 1180 } 1181 final int shortAnimationDuration = getResources().getInteger( 1182 android.R.integer.config_shortAnimTime); 1183 1184 fadeInView.setAlpha(0f); 1185 fadeInView.setVisibility(View.VISIBLE); 1186 1187 fadeInView.animate() 1188 .alpha(1f) 1189 .setDuration(shortAnimationDuration) 1190 .setListener(null); 1191 1192 fadeOutView.animate() 1193 .alpha(0f) 1194 .setDuration(shortAnimationDuration) 1195 .setListener(new AnimatorListenerAdapter() { 1196 @Override 1197 public void onAnimationEnd(Animator animation) { 1198 fadeOutView.setVisibility(View.GONE); 1199 } 1200 }); 1201 } 1202 isShortcutNumber(String number)1203 private boolean isShortcutNumber(String number) { 1204 if (TextUtils.isEmpty(number) || mEmergencyShortcutButtonList == null) { 1205 return false; 1206 } 1207 1208 boolean isShortcut = false; 1209 for (EmergencyShortcutButton button : mEmergencyShortcutButtonList) { 1210 if (button != null && number.equals(button.getPhoneNumber())) { 1211 isShortcut = true; 1212 break; 1213 } 1214 } 1215 return isShortcut; 1216 } 1217 entryTypeToString(int entryType)1218 private String entryTypeToString(int entryType) { 1219 switch (entryType) { 1220 case ENTRY_TYPE_LOCKSCREEN_BUTTON: 1221 return "LockScreen"; 1222 case ENTRY_TYPE_POWER_MENU: 1223 return "PowerMenu"; 1224 default: 1225 return "Unknown-" + entryType; 1226 } 1227 } 1228 callSourceToString(int callSource)1229 private String callSourceToString(int callSource) { 1230 switch (callSource) { 1231 case ParcelableCallAnalytics.CALL_SOURCE_EMERGENCY_DIALPAD: 1232 return "DialPad"; 1233 case ParcelableCallAnalytics.CALL_SOURCE_EMERGENCY_SHORTCUT: 1234 return "Shortcut"; 1235 default: 1236 return "Unknown-" + callSource; 1237 } 1238 } 1239 } 1240