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