• 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.content.BroadcastReceiver;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.IntentFilter;
27 import android.content.res.Resources;
28 import android.media.AudioManager;
29 import android.media.ToneGenerator;
30 import android.net.Uri;
31 import android.os.Bundle;
32 import android.provider.Settings;
33 import android.telephony.PhoneNumberUtils;
34 import android.text.Editable;
35 import android.text.TextUtils;
36 import android.text.TextWatcher;
37 import android.text.method.DialerKeyListener;
38 import android.util.Log;
39 import android.view.KeyEvent;
40 import android.view.View;
41 import android.view.WindowManager;
42 import android.widget.EditText;
43 
44 
45 /**
46  * EmergencyDialer is a special dialer that is used ONLY for dialing emergency calls.
47  *
48  * It's a simplified version of the regular dialer (i.e. the TwelveKeyDialer
49  * activity from apps/Contacts) that:
50  *   1. Allows ONLY emergency calls to be dialed
51  *   2. Disallows voicemail functionality
52  *   3. Uses the FLAG_SHOW_WHEN_LOCKED window manager flag to allow this
53  *      activity to stay in front of the keyguard.
54  *
55  * TODO: Even though this is an ultra-simplified version of the normal
56  * dialer, there's still lots of code duplication between this class and
57  * the TwelveKeyDialer class from apps/Contacts.  Could the common code be
58  * moved into a shared base class that would live in the framework?
59  * Or could we figure out some way to move *this* class into apps/Contacts
60  * also?
61  */
62 public class EmergencyDialer extends Activity
63         implements View.OnClickListener, View.OnLongClickListener,
64         View.OnKeyListener, TextWatcher {
65     // Keys used with onSaveInstanceState().
66     private static final String LAST_NUMBER = "lastNumber";
67 
68     // Intent action for this activity.
69     public static final String ACTION_DIAL = "com.android.phone.EmergencyDialer.DIAL";
70 
71     // Debug constants.
72     private static final boolean DBG = false;
73     private static final String LOG_TAG = "EmergencyDialer";
74 
75     private PhoneApp mApp;
76     private StatusBarManager mStatusBarManager;
77 
78     /** The length of DTMF tones in milliseconds */
79     private static final int TONE_LENGTH_MS = 150;
80 
81     /** The DTMF tone volume relative to other sounds in the stream */
82     private static final int TONE_RELATIVE_VOLUME = 80;
83 
84     /** Stream type used to play the DTMF tones off call, and mapped to the volume control keys */
85     private static final int DIAL_TONE_STREAM_TYPE = AudioManager.STREAM_DTMF;
86 
87     private static final int BAD_EMERGENCY_NUMBER_DIALOG = 0;
88 
89     EditText mDigits;
90     private View mDialButton;
91     private View mDelete;
92 
93     private ToneGenerator mToneGenerator;
94     private Object mToneGeneratorLock = new Object();
95 
96     // determines if we want to playback local DTMF tones.
97     private boolean mDTMFToneEnabled;
98 
99     // Haptic feedback (vibration) for dialer key presses.
100     private HapticFeedback mHaptic = new HapticFeedback();
101 
102     // close activity when screen turns off
103     private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
104         @Override
105         public void onReceive(Context context, Intent intent) {
106             if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
107                 finish();
108             }
109         }
110     };
111 
112     private String mLastNumber; // last number we tried to dial. Used to restore error dialog.
113 
114     @Override
beforeTextChanged(CharSequence s, int start, int count, int after)115     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
116         // Do nothing
117     }
118 
119     @Override
onTextChanged(CharSequence input, int start, int before, int changeCount)120     public void onTextChanged(CharSequence input, int start, int before, int changeCount) {
121         // Do nothing
122     }
123 
124     @Override
afterTextChanged(Editable input)125     public void afterTextChanged(Editable input) {
126         // Check for special sequences, in particular the "**04" or "**05"
127         // sequences that allow you to enter PIN or PUK-related codes.
128         //
129         // But note we *don't* allow most other special sequences here,
130         // like "secret codes" (*#*#<code>#*#*) or IMEI display ("*#06#"),
131         // since those shouldn't be available if the device is locked.
132         //
133         // So we call SpecialCharSequenceMgr.handleCharsForLockedDevice()
134         // here, not the regular handleChars() method.
135         if (SpecialCharSequenceMgr.handleCharsForLockedDevice(this, input.toString(), this)) {
136             // A special sequence was entered, clear the digits
137             mDigits.getText().clear();
138         }
139 
140         updateDialAndDeleteButtonStateEnabledAttr();
141     }
142 
143     @Override
onCreate(Bundle icicle)144     protected void onCreate(Bundle icicle) {
145         super.onCreate(icicle);
146 
147         mApp = PhoneApp.getInstance();
148         mStatusBarManager = (StatusBarManager) getSystemService(Context.STATUS_BAR_SERVICE);
149 
150         // Allow this activity to be displayed in front of the keyguard / lockscreen.
151         getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
152 
153         setContentView(R.layout.emergency_dialer);
154 
155         mDigits = (EditText) findViewById(R.id.digits);
156         mDigits.setKeyListener(DialerKeyListener.getInstance());
157         mDigits.setOnClickListener(this);
158         mDigits.setOnKeyListener(this);
159         mDigits.setLongClickable(false);
160         maybeAddNumberFormatting();
161 
162         // Check for the presence of the keypad
163         View view = findViewById(R.id.one);
164         if (view != null) {
165             setupKeypad();
166         }
167 
168         mDelete = findViewById(R.id.deleteButton);
169         mDelete.setOnClickListener(this);
170         mDelete.setOnLongClickListener(this);
171 
172         mDialButton = findViewById(R.id.dialButton);
173 
174         // Check whether we should show the onscreen "Dial" button and co.
175         Resources res = getResources();
176         if (res.getBoolean(R.bool.config_show_onscreen_dial_button)) {
177             mDialButton.setOnClickListener(this);
178         } else {
179             mDialButton.setVisibility(View.GONE);
180         }
181 
182         if (icicle != null) {
183             super.onRestoreInstanceState(icicle);
184         }
185 
186         // Extract phone number from intent
187         Uri data = getIntent().getData();
188         if (data != null && (Constants.SCHEME_TEL.equals(data.getScheme()))) {
189             String number = PhoneNumberUtils.getNumberFromIntent(getIntent(), this);
190             if (number != null) {
191                 mDigits.setText(number);
192             }
193         }
194 
195         // if the mToneGenerator creation fails, just continue without it.  It is
196         // a local audio signal, and is not as important as the dtmf tone itself.
197         synchronized (mToneGeneratorLock) {
198             if (mToneGenerator == null) {
199                 try {
200                     mToneGenerator = new ToneGenerator(DIAL_TONE_STREAM_TYPE, TONE_RELATIVE_VOLUME);
201                 } catch (RuntimeException e) {
202                     Log.w(LOG_TAG, "Exception caught while creating local tone generator: " + e);
203                     mToneGenerator = null;
204                 }
205             }
206         }
207 
208         final IntentFilter intentFilter = new IntentFilter();
209         intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
210         registerReceiver(mBroadcastReceiver, intentFilter);
211 
212         try {
213             mHaptic.init(this, res.getBoolean(R.bool.config_enable_dialer_key_vibration));
214         } catch (Resources.NotFoundException nfe) {
215              Log.e(LOG_TAG, "Vibrate control bool missing.", nfe);
216         }
217     }
218 
219     @Override
onDestroy()220     protected void onDestroy() {
221         super.onDestroy();
222         synchronized (mToneGeneratorLock) {
223             if (mToneGenerator != null) {
224                 mToneGenerator.release();
225                 mToneGenerator = null;
226             }
227         }
228         unregisterReceiver(mBroadcastReceiver);
229     }
230 
231     @Override
onRestoreInstanceState(Bundle icicle)232     protected void onRestoreInstanceState(Bundle icicle) {
233         mLastNumber = icicle.getString(LAST_NUMBER);
234     }
235 
236     @Override
onSaveInstanceState(Bundle outState)237     protected void onSaveInstanceState(Bundle outState) {
238         super.onSaveInstanceState(outState);
239         outState.putString(LAST_NUMBER, mLastNumber);
240     }
241 
242     /**
243      * Explicitly turn off number formatting, since it gets in the way of the emergency
244      * number detector
245      */
maybeAddNumberFormatting()246     protected void maybeAddNumberFormatting() {
247         // Do nothing.
248     }
249 
250     @Override
onPostCreate(Bundle savedInstanceState)251     protected void onPostCreate(Bundle savedInstanceState) {
252         super.onPostCreate(savedInstanceState);
253 
254         // This can't be done in onCreate(), since the auto-restoring of the digits
255         // will play DTMF tones for all the old digits if it is when onRestoreSavedInstanceState()
256         // is called. This method will be called every time the activity is created, and
257         // will always happen after onRestoreSavedInstanceState().
258         mDigits.addTextChangedListener(this);
259     }
260 
setupKeypad()261     private void setupKeypad() {
262         // Setup the listeners for the buttons
263         findViewById(R.id.one).setOnClickListener(this);
264         findViewById(R.id.two).setOnClickListener(this);
265         findViewById(R.id.three).setOnClickListener(this);
266         findViewById(R.id.four).setOnClickListener(this);
267         findViewById(R.id.five).setOnClickListener(this);
268         findViewById(R.id.six).setOnClickListener(this);
269         findViewById(R.id.seven).setOnClickListener(this);
270         findViewById(R.id.eight).setOnClickListener(this);
271         findViewById(R.id.nine).setOnClickListener(this);
272         findViewById(R.id.star).setOnClickListener(this);
273 
274         View view = findViewById(R.id.zero);
275         view.setOnClickListener(this);
276         view.setOnLongClickListener(this);
277 
278         findViewById(R.id.pound).setOnClickListener(this);
279     }
280 
281     /**
282      * handle key events
283      */
284     @Override
onKeyDown(int keyCode, KeyEvent event)285     public boolean onKeyDown(int keyCode, KeyEvent event) {
286         switch (keyCode) {
287             // Happen when there's a "Call" hard button.
288             case KeyEvent.KEYCODE_CALL: {
289                 if (TextUtils.isEmpty(mDigits.getText().toString())) {
290                     // if we are adding a call from the InCallScreen and the phone
291                     // number entered is empty, we just close the dialer to expose
292                     // the InCallScreen under it.
293                     finish();
294                 } else {
295                     // otherwise, we place the call.
296                     placeCall();
297                 }
298                 return true;
299             }
300         }
301         return super.onKeyDown(keyCode, event);
302     }
303 
keyPressed(int keyCode)304     private void keyPressed(int keyCode) {
305         mHaptic.vibrate();
306         KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
307         mDigits.onKeyDown(keyCode, event);
308     }
309 
310     @Override
onKey(View view, int keyCode, KeyEvent event)311     public boolean onKey(View view, int keyCode, KeyEvent event) {
312         switch (view.getId()) {
313             case R.id.digits:
314                 // Happen when "Done" button of the IME is pressed. This can happen when this
315                 // Activity is forced into landscape mode due to a desk dock.
316                 if (keyCode == KeyEvent.KEYCODE_ENTER
317                         && event.getAction() == KeyEvent.ACTION_UP) {
318                     placeCall();
319                     return true;
320                 }
321                 break;
322         }
323         return false;
324     }
325 
326     @Override
onClick(View view)327     public void onClick(View view) {
328         switch (view.getId()) {
329             case R.id.one: {
330                 playTone(ToneGenerator.TONE_DTMF_1);
331                 keyPressed(KeyEvent.KEYCODE_1);
332                 return;
333             }
334             case R.id.two: {
335                 playTone(ToneGenerator.TONE_DTMF_2);
336                 keyPressed(KeyEvent.KEYCODE_2);
337                 return;
338             }
339             case R.id.three: {
340                 playTone(ToneGenerator.TONE_DTMF_3);
341                 keyPressed(KeyEvent.KEYCODE_3);
342                 return;
343             }
344             case R.id.four: {
345                 playTone(ToneGenerator.TONE_DTMF_4);
346                 keyPressed(KeyEvent.KEYCODE_4);
347                 return;
348             }
349             case R.id.five: {
350                 playTone(ToneGenerator.TONE_DTMF_5);
351                 keyPressed(KeyEvent.KEYCODE_5);
352                 return;
353             }
354             case R.id.six: {
355                 playTone(ToneGenerator.TONE_DTMF_6);
356                 keyPressed(KeyEvent.KEYCODE_6);
357                 return;
358             }
359             case R.id.seven: {
360                 playTone(ToneGenerator.TONE_DTMF_7);
361                 keyPressed(KeyEvent.KEYCODE_7);
362                 return;
363             }
364             case R.id.eight: {
365                 playTone(ToneGenerator.TONE_DTMF_8);
366                 keyPressed(KeyEvent.KEYCODE_8);
367                 return;
368             }
369             case R.id.nine: {
370                 playTone(ToneGenerator.TONE_DTMF_9);
371                 keyPressed(KeyEvent.KEYCODE_9);
372                 return;
373             }
374             case R.id.zero: {
375                 playTone(ToneGenerator.TONE_DTMF_0);
376                 keyPressed(KeyEvent.KEYCODE_0);
377                 return;
378             }
379             case R.id.pound: {
380                 playTone(ToneGenerator.TONE_DTMF_P);
381                 keyPressed(KeyEvent.KEYCODE_POUND);
382                 return;
383             }
384             case R.id.star: {
385                 playTone(ToneGenerator.TONE_DTMF_S);
386                 keyPressed(KeyEvent.KEYCODE_STAR);
387                 return;
388             }
389             case R.id.deleteButton: {
390                 keyPressed(KeyEvent.KEYCODE_DEL);
391                 return;
392             }
393             case R.id.dialButton: {
394                 mHaptic.vibrate();  // Vibrate here too, just like we do for the regular keys
395                 placeCall();
396                 return;
397             }
398             case R.id.digits: {
399                 if (mDigits.length() != 0) {
400                     mDigits.setCursorVisible(true);
401                 }
402                 return;
403             }
404         }
405     }
406 
407     /**
408      * called for long touch events
409      */
410     @Override
onLongClick(View view)411     public boolean onLongClick(View view) {
412         int id = view.getId();
413         switch (id) {
414             case R.id.deleteButton: {
415                 mDigits.getText().clear();
416                 // TODO: The framework forgets to clear the pressed
417                 // status of disabled button. Until this is fixed,
418                 // clear manually the pressed status. b/2133127
419                 mDelete.setPressed(false);
420                 return true;
421             }
422             case R.id.zero: {
423                 keyPressed(KeyEvent.KEYCODE_PLUS);
424                 return true;
425             }
426         }
427         return false;
428     }
429 
430     @Override
onResume()431     protected void onResume() {
432         super.onResume();
433 
434         // retrieve the DTMF tone play back setting.
435         mDTMFToneEnabled = Settings.System.getInt(getContentResolver(),
436                 Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1;
437 
438         // Retrieve the haptic feedback setting.
439         mHaptic.checkSystemSetting();
440 
441         // if the mToneGenerator creation fails, just continue without it.  It is
442         // a local audio signal, and is not as important as the dtmf tone itself.
443         synchronized (mToneGeneratorLock) {
444             if (mToneGenerator == null) {
445                 try {
446                     mToneGenerator = new ToneGenerator(AudioManager.STREAM_DTMF,
447                             TONE_RELATIVE_VOLUME);
448                 } catch (RuntimeException e) {
449                     Log.w(LOG_TAG, "Exception caught while creating local tone generator: " + e);
450                     mToneGenerator = null;
451                 }
452             }
453         }
454 
455         // Disable the status bar and set the poke lock timeout to medium.
456         // There is no need to do anything with the wake lock.
457         if (DBG) Log.d(LOG_TAG, "disabling status bar, set to long timeout");
458         mStatusBarManager.disable(StatusBarManager.DISABLE_EXPAND);
459         mApp.setScreenTimeout(PhoneApp.ScreenTimeoutDuration.MEDIUM);
460 
461         updateDialAndDeleteButtonStateEnabledAttr();
462     }
463 
464     @Override
onPause()465     public void onPause() {
466         // Reenable the status bar and set the poke lock timeout to default.
467         // There is no need to do anything with the wake lock.
468         if (DBG) Log.d(LOG_TAG, "reenabling status bar and closing the dialer");
469         mStatusBarManager.disable(StatusBarManager.DISABLE_NONE);
470         mApp.setScreenTimeout(PhoneApp.ScreenTimeoutDuration.DEFAULT);
471 
472         super.onPause();
473 
474         synchronized (mToneGeneratorLock) {
475             if (mToneGenerator != null) {
476                 mToneGenerator.release();
477                 mToneGenerator = null;
478             }
479         }
480     }
481 
482     /**
483      * place the call, but check to make sure it is a viable number.
484      */
placeCall()485     private void placeCall() {
486         mLastNumber = mDigits.getText().toString();
487         if (PhoneNumberUtils.isLocalEmergencyNumber(mLastNumber, this)) {
488             if (DBG) Log.d(LOG_TAG, "placing call to " + mLastNumber);
489 
490             // place the call if it is a valid number
491             if (mLastNumber == null || !TextUtils.isGraphic(mLastNumber)) {
492                 // There is no number entered.
493                 playTone(ToneGenerator.TONE_PROP_NACK);
494                 return;
495             }
496             Intent intent = new Intent(Intent.ACTION_CALL_EMERGENCY);
497             intent.setData(Uri.fromParts(Constants.SCHEME_TEL, mLastNumber, null));
498             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
499             startActivity(intent);
500             finish();
501         } else {
502             if (DBG) Log.d(LOG_TAG, "rejecting bad requested number " + mLastNumber);
503 
504             // erase the number and throw up an alert dialog.
505             mDigits.getText().delete(0, mDigits.getText().length());
506             showDialog(BAD_EMERGENCY_NUMBER_DIALOG);
507         }
508     }
509 
510     /**
511      * Plays the specified tone for TONE_LENGTH_MS milliseconds.
512      *
513      * The tone is played locally, using the audio stream for phone calls.
514      * Tones are played only if the "Audible touch tones" user preference
515      * is checked, and are NOT played if the device is in silent mode.
516      *
517      * @param tone a tone code from {@link ToneGenerator}
518      */
playTone(int tone)519     void playTone(int tone) {
520         // if local tone playback is disabled, just return.
521         if (!mDTMFToneEnabled) {
522             return;
523         }
524 
525         // Also do nothing if the phone is in silent mode.
526         // We need to re-check the ringer mode for *every* playTone()
527         // call, rather than keeping a local flag that's updated in
528         // onResume(), since it's possible to toggle silent mode without
529         // leaving the current activity (via the ENDCALL-longpress menu.)
530         AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
531         int ringerMode = audioManager.getRingerMode();
532         if ((ringerMode == AudioManager.RINGER_MODE_SILENT)
533             || (ringerMode == AudioManager.RINGER_MODE_VIBRATE)) {
534             return;
535         }
536 
537         synchronized (mToneGeneratorLock) {
538             if (mToneGenerator == null) {
539                 Log.w(LOG_TAG, "playTone: mToneGenerator == null, tone: " + tone);
540                 return;
541             }
542 
543             // Start the new tone (will stop any playing tone)
544             mToneGenerator.startTone(tone, TONE_LENGTH_MS);
545         }
546     }
547 
createErrorMessage(String number)548     private CharSequence createErrorMessage(String number) {
549         if (!TextUtils.isEmpty(number)) {
550             return getString(R.string.dial_emergency_error, mLastNumber);
551         } else {
552             return getText(R.string.dial_emergency_empty_error).toString();
553         }
554     }
555 
556     @Override
onCreateDialog(int id)557     protected Dialog onCreateDialog(int id) {
558         AlertDialog dialog = null;
559         if (id == BAD_EMERGENCY_NUMBER_DIALOG) {
560             // construct dialog
561             dialog = new AlertDialog.Builder(this)
562                     .setTitle(getText(R.string.emergency_enable_radio_dialog_title))
563                     .setMessage(createErrorMessage(mLastNumber))
564                     .setPositiveButton(R.string.ok, null)
565                     .setCancelable(true).create();
566 
567             // blur stuff behind the dialog
568             dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
569         }
570         return dialog;
571     }
572 
573     @Override
onPrepareDialog(int id, Dialog dialog)574     protected void onPrepareDialog(int id, Dialog dialog) {
575         super.onPrepareDialog(id, dialog);
576         if (id == BAD_EMERGENCY_NUMBER_DIALOG) {
577             AlertDialog alert = (AlertDialog) dialog;
578             alert.setMessage(createErrorMessage(mLastNumber));
579         }
580     }
581 
582     /**
583      * Update the enabledness of the "Dial" and "Backspace" buttons if applicable.
584      */
updateDialAndDeleteButtonStateEnabledAttr()585     private void updateDialAndDeleteButtonStateEnabledAttr() {
586         final boolean notEmpty = mDigits.length() != 0;
587 
588         mDialButton.setEnabled(notEmpty);
589         mDelete.setEnabled(notEmpty);
590     }
591 }
592