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