• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
beforeTextChanged(CharSequence s, int start, int count, int after)152     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
153         // Do nothing
154     }
155 
156     @Override
onTextChanged(CharSequence input, int start, int before, int changeCount)157     public void onTextChanged(CharSequence input, int start, int before, int changeCount) {
158         maybeChangeHintSize();
159     }
160 
161     @Override
afterTextChanged(Editable input)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
onCreate(Bundle icicle)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
onDestroy()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
onRestoreInstanceState(Bundle icicle)296     protected void onRestoreInstanceState(Bundle icicle) {
297         mLastNumber = icicle.getString(LAST_NUMBER);
298     }
299 
300     @Override
onSaveInstanceState(Bundle outState)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      */
maybeAddNumberFormatting()310     protected void maybeAddNumberFormatting() {
311         // Do nothing.
312     }
313 
314     @Override
onPostCreate(Bundle savedInstanceState)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 
setupKeypad()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
onKeyDown(int keyCode, KeyEvent event)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 
keyPressed(int keyCode)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
onKey(View view, int keyCode, KeyEvent event)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
dispatchTouchEvent(MotionEvent ev)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
onClick(View view)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
onPressed(View view, boolean pressed)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
onLongClick(View view)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
onStart()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
onResume()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
onPause()537     public void onPause() {
538         super.onPause();
539     }
540 
541     @Override
onStop()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      */
updateTheme(boolean supportsDarkText)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      */
placeCall()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      */
playTone(int tone)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 
createErrorMessage(String number)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
onCreateDialog(int id)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
onPrepareDialog(int id, Dialog dialog)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
onOptionsItemSelected(MenuItem item)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      */
updateDialAndDeleteButtonStateEnabledAttr()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      */
removePreviousDigitIfPossible()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      */
updateTtsSpans()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
onColorsChanged(ColorExtractor extractor, int which)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      */
maybeShowWfcEmergencyCallingWarning()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      */
maybeChangeHintSize()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