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