• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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.contacts.dialpad;
18 
19 import com.android.contacts.ContactsUtils;
20 import com.android.contacts.R;
21 import com.android.contacts.SpecialCharSequenceMgr;
22 import com.android.contacts.activities.DialtactsActivity;
23 import com.android.contacts.activities.DialtactsActivity.ViewPagerVisibilityListener;
24 import com.android.contacts.util.PhoneNumberFormatter;
25 import com.android.internal.telephony.ITelephony;
26 import com.android.phone.CallLogAsync;
27 import com.android.phone.HapticFeedback;
28 
29 import android.app.Activity;
30 import android.app.AlertDialog;
31 import android.app.Dialog;
32 import android.app.DialogFragment;
33 import android.app.Fragment;
34 import android.content.Context;
35 import android.content.DialogInterface;
36 import android.content.Intent;
37 import android.content.res.Resources;
38 import android.database.Cursor;
39 import android.graphics.Bitmap;
40 import android.graphics.BitmapFactory;
41 import android.media.AudioManager;
42 import android.media.ToneGenerator;
43 import android.net.Uri;
44 import android.os.Bundle;
45 import android.os.RemoteException;
46 import android.os.ServiceManager;
47 import android.os.SystemProperties;
48 import android.provider.Contacts.Intents.Insert;
49 import android.provider.Contacts.People;
50 import android.provider.Contacts.Phones;
51 import android.provider.Contacts.PhonesColumns;
52 import android.provider.Settings;
53 import android.telephony.PhoneNumberUtils;
54 import android.telephony.PhoneStateListener;
55 import android.telephony.TelephonyManager;
56 import android.text.Editable;
57 import android.text.TextUtils;
58 import android.text.TextWatcher;
59 import android.text.method.DialerKeyListener;
60 import android.util.Log;
61 import android.view.KeyEvent;
62 import android.view.LayoutInflater;
63 import android.view.Menu;
64 import android.view.MenuInflater;
65 import android.view.MenuItem;
66 import android.view.View;
67 import android.view.View.OnClickListener;
68 import android.view.ViewConfiguration;
69 import android.view.ViewGroup;
70 import android.widget.AdapterView;
71 import android.widget.BaseAdapter;
72 import android.widget.Button;
73 import android.widget.EditText;
74 import android.widget.ImageView;
75 import android.widget.ListView;
76 import android.widget.PopupMenu;
77 import android.widget.TextView;
78 
79 import java.util.HashSet;
80 import java.util.Set;
81 
82 /**
83  * Fragment that displays a twelve-key phone dialpad.
84  */
85 public class DialpadFragment extends Fragment
86         implements View.OnClickListener,
87         View.OnLongClickListener, View.OnKeyListener,
88         AdapterView.OnItemClickListener, TextWatcher,
89         PopupMenu.OnMenuItemClickListener,
90         ViewPagerVisibilityListener {
91     private static final String TAG = DialpadFragment.class.getSimpleName();
92 
93     private static final String EMPTY_NUMBER = "";
94 
95     /** The length of DTMF tones in milliseconds */
96     private static final int TONE_LENGTH_MS = 150;
97 
98     /** The DTMF tone volume relative to other sounds in the stream */
99     private static final int TONE_RELATIVE_VOLUME = 80;
100 
101     /** Stream type used to play the DTMF tones off call, and mapped to the volume control keys */
102     private static final int DIAL_TONE_STREAM_TYPE = AudioManager.STREAM_MUSIC;
103 
104     public interface Listener {
onSearchButtonPressed()105         public void onSearchButtonPressed();
106     }
107 
108     /**
109      * View (usually FrameLayout) containing mDigits field. This can be null, in which mDigits
110      * isn't enclosed by the container.
111      */
112     private View mDigitsContainer;
113     private EditText mDigits;
114 
115     private View mDelete;
116     private ToneGenerator mToneGenerator;
117     private Object mToneGeneratorLock = new Object();
118     private View mDialpad;
119     private View mAdditionalButtonsRow;
120 
121     private View mSearchButton;
122     private Listener mListener;
123 
124     private View mDialButton;
125     private ListView mDialpadChooser;
126     private DialpadChooserAdapter mDialpadChooserAdapter;
127 
128     /**
129      * Regular expression prohibiting manual phone call. Can be empty, which means "no rule".
130      */
131     private String mProhibitedPhoneNumberRegexp;
132 
133     private boolean mShowOptionsMenu;
134 
135 
136     // Last number dialed, retrieved asynchronously from the call DB
137     // in onCreate. This number is displayed when the user hits the
138     // send key and cleared in onPause.
139     CallLogAsync mCallLog = new CallLogAsync();
140     private String mLastNumberDialed = EMPTY_NUMBER;
141 
142     // determines if we want to playback local DTMF tones.
143     private boolean mDTMFToneEnabled;
144 
145     // Vibration (haptic feedback) for dialer key presses.
146     private HapticFeedback mHaptic = new HapticFeedback();
147 
148     /** Identifier for the "Add Call" intent extra. */
149     static final String ADD_CALL_MODE_KEY = "add_call_mode";
150 
151     /**
152      * Identifier for intent extra for sending an empty Flash message for
153      * CDMA networks. This message is used by the network to simulate a
154      * press/depress of the "hookswitch" of a landline phone. Aka "empty flash".
155      *
156      * TODO: Using an intent extra to tell the phone to send this flash is a
157      * temporary measure. To be replaced with an ITelephony call in the future.
158      * TODO: Keep in sync with the string defined in OutgoingCallBroadcaster.java
159      * in Phone app until this is replaced with the ITelephony API.
160      */
161     static final String EXTRA_SEND_EMPTY_FLASH
162             = "com.android.phone.extra.SEND_EMPTY_FLASH";
163 
164     private String mCurrentCountryIso;
165 
166     private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
167         /**
168          * Listen for phone state changes so that we can take down the
169          * "dialpad chooser" if the phone becomes idle while the
170          * chooser UI is visible.
171          */
172         @Override
173         public void onCallStateChanged(int state, String incomingNumber) {
174             // Log.i(TAG, "PhoneStateListener.onCallStateChanged: "
175             //       + state + ", '" + incomingNumber + "'");
176             if ((state == TelephonyManager.CALL_STATE_IDLE) && dialpadChooserVisible()) {
177                 // Log.i(TAG, "Call ended with dialpad chooser visible!  Taking it down...");
178                 // Note there's a race condition in the UI here: the
179                 // dialpad chooser could conceivably disappear (on its
180                 // own) at the exact moment the user was trying to select
181                 // one of the choices, which would be confusing.  (But at
182                 // least that's better than leaving the dialpad chooser
183                 // onscreen, but useless...)
184                 showDialpadChooser(false);
185             }
186         }
187     };
188 
189     private boolean mWasEmptyBeforeTextChange;
190 
beforeTextChanged(CharSequence s, int start, int count, int after)191     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
192         mWasEmptyBeforeTextChange = TextUtils.isEmpty(s);
193     }
194 
onTextChanged(CharSequence input, int start, int before, int changeCount)195     public void onTextChanged(CharSequence input, int start, int before, int changeCount) {
196         if (mWasEmptyBeforeTextChange != TextUtils.isEmpty(input)) {
197             final Activity activity = getActivity();
198             if (activity != null) {
199                 activity.invalidateOptionsMenu();
200             }
201         }
202 
203         // DTMF Tones do not need to be played here any longer -
204         // the DTMF dialer handles that functionality now.
205     }
206 
afterTextChanged(Editable input)207     public void afterTextChanged(Editable input) {
208         if (SpecialCharSequenceMgr.handleChars(getActivity(), input.toString(), mDigits)) {
209             // A special sequence was entered, clear the digits
210             mDigits.getText().clear();
211         }
212 
213         if (isDigitsEmpty()) {
214             mDigits.setCursorVisible(false);
215         }
216 
217         updateDialAndDeleteButtonEnabledState();
218     }
219 
220     @Override
onCreate(Bundle state)221     public void onCreate(Bundle state) {
222         super.onCreate(state);
223 
224         mCurrentCountryIso = ContactsUtils.getCurrentCountryIso(getActivity());
225 
226         try {
227             mHaptic.init(getActivity(),
228                          getResources().getBoolean(R.bool.config_enable_dialer_key_vibration));
229         } catch (Resources.NotFoundException nfe) {
230              Log.e(TAG, "Vibrate control bool missing.", nfe);
231         }
232 
233         setHasOptionsMenu(true);
234 
235         mProhibitedPhoneNumberRegexp = getResources().getString(
236                 R.string.config_prohibited_phone_number_regexp);
237     }
238 
239     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState)240     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
241         View fragmentView = inflater.inflate(R.layout.dialpad_fragment, container, false);
242 
243         // Load up the resources for the text field.
244         Resources r = getResources();
245 
246         mDigitsContainer = fragmentView.findViewById(R.id.digits_container);
247         mDigits = (EditText) fragmentView.findViewById(R.id.digits);
248         mDigits.setKeyListener(DialerKeyListener.getInstance());
249         mDigits.setOnClickListener(this);
250         mDigits.setOnKeyListener(this);
251         mDigits.setOnLongClickListener(this);
252         mDigits.addTextChangedListener(this);
253 
254         PhoneNumberFormatter.setPhoneNumberFormattingTextWatcher(getActivity(), mDigits);
255 
256         // Soft menu button should appear only when there's no hardware menu button.
257         final View overflowMenuButton = fragmentView.findViewById(R.id.overflow_menu);
258         if (overflowMenuButton != null) {
259             if (ViewConfiguration.get(getActivity()).hasPermanentMenuKey()) {
260                 overflowMenuButton.setVisibility(View.GONE);
261             } else {
262                 overflowMenuButton.setOnClickListener(this);
263             }
264         }
265 
266         // Check for the presence of the keypad
267         View oneButton = fragmentView.findViewById(R.id.one);
268         if (oneButton != null) {
269             setupKeypad(fragmentView);
270         }
271 
272         mAdditionalButtonsRow = fragmentView.findViewById(R.id.dialpadAdditionalButtons);
273 
274         mSearchButton = mAdditionalButtonsRow.findViewById(R.id.searchButton);
275         if (mSearchButton != null) {
276             mSearchButton.setOnClickListener(this);
277         }
278 
279         // Check whether we should show the onscreen "Dial" button.
280         mDialButton = mAdditionalButtonsRow.findViewById(R.id.dialButton);
281 
282         if (r.getBoolean(R.bool.config_show_onscreen_dial_button)) {
283             mDialButton.setOnClickListener(this);
284         } else {
285             mDialButton.setVisibility(View.GONE); // It's VISIBLE by default
286             mDialButton = null;
287         }
288 
289         mDelete = mAdditionalButtonsRow.findViewById(R.id.deleteButton);
290         mDelete.setOnClickListener(this);
291         mDelete.setOnLongClickListener(this);
292 
293         mDialpad = fragmentView.findViewById(R.id.dialpad);  // This is null in landscape mode.
294 
295         // In landscape we put the keyboard in phone mode.
296         if (null == mDialpad) {
297             mDigits.setInputType(android.text.InputType.TYPE_CLASS_PHONE);
298         } else {
299             mDigits.setCursorVisible(false);
300         }
301 
302         // Set up the "dialpad chooser" UI; see showDialpadChooser().
303         mDialpadChooser = (ListView) fragmentView.findViewById(R.id.dialpadChooser);
304         mDialpadChooser.setOnItemClickListener(this);
305 
306         configureScreenFromIntent(getActivity().getIntent());
307 
308         return fragmentView;
309     }
310 
isLayoutReady()311     private boolean isLayoutReady() {
312         return mDigits != null;
313     }
314 
getDigitsWidget()315     public EditText getDigitsWidget() {
316         return mDigits;
317     }
318 
319     /**
320      * @return true when {@link #mDigits} is actually filled by the Intent.
321      */
fillDigitsIfNecessary(Intent intent)322     private boolean fillDigitsIfNecessary(Intent intent) {
323         final String action = intent.getAction();
324         if (Intent.ACTION_DIAL.equals(action) || Intent.ACTION_VIEW.equals(action)) {
325             Uri uri = intent.getData();
326             if (uri != null) {
327                 if ("tel".equals(uri.getScheme())) {
328                     // Put the requested number into the input area
329                     String data = uri.getSchemeSpecificPart();
330                     setFormattedDigits(data, null);
331                     return true;
332                 } else {
333                     String type = intent.getType();
334                     if (People.CONTENT_ITEM_TYPE.equals(type)
335                             || Phones.CONTENT_ITEM_TYPE.equals(type)) {
336                         // Query the phone number
337                         Cursor c = getActivity().getContentResolver().query(intent.getData(),
338                                 new String[] {PhonesColumns.NUMBER, PhonesColumns.NUMBER_KEY},
339                                 null, null, null);
340                         if (c != null) {
341                             try {
342                                 if (c.moveToFirst()) {
343                                     // Put the number into the input area
344                                     setFormattedDigits(c.getString(0), c.getString(1));
345                                     return true;
346                                 }
347                             } finally {
348                                 c.close();
349                             }
350                         }
351                     }
352                 }
353             }
354         }
355 
356         return false;
357     }
358 
359     /**
360      * @see #showDialpadChooser(boolean)
361      */
needToShowDialpadChooser(Intent intent, boolean isAddCallMode)362     private static boolean needToShowDialpadChooser(Intent intent, boolean isAddCallMode) {
363         final String action = intent.getAction();
364 
365         boolean needToShowDialpadChooser = false;
366 
367         if (Intent.ACTION_DIAL.equals(action) || Intent.ACTION_VIEW.equals(action)) {
368             Uri uri = intent.getData();
369             if (uri == null) {
370                 // ACTION_DIAL or ACTION_VIEW with no data.
371                 // This behaves basically like ACTION_MAIN: If there's
372                 // already an active call, bring up an intermediate UI to
373                 // make the user confirm what they really want to do.
374                 // Be sure *not* to show the dialpad chooser if this is an
375                 // explicit "Add call" action, though.
376                 if (!isAddCallMode && phoneIsInUse()) {
377                     needToShowDialpadChooser = true;
378                 }
379             }
380         } else if (Intent.ACTION_MAIN.equals(action)) {
381             // The MAIN action means we're bringing up a blank dialer
382             // (e.g. by selecting the Home shortcut, or tabbing over from
383             // Contacts or Call log.)
384             //
385             // At this point, IF there's already an active call, there's a
386             // good chance that the user got here accidentally (but really
387             // wanted the in-call dialpad instead).  So we bring up an
388             // intermediate UI to make the user confirm what they really
389             // want to do.
390             if (phoneIsInUse()) {
391                 // Log.i(TAG, "resolveIntent(): phone is in use; showing dialpad chooser!");
392                 needToShowDialpadChooser = true;
393             }
394         }
395 
396         return needToShowDialpadChooser;
397     }
398 
isAddCallMode(Intent intent)399     private static boolean isAddCallMode(Intent intent) {
400         final String action = intent.getAction();
401         if (Intent.ACTION_DIAL.equals(action) || Intent.ACTION_VIEW.equals(action)) {
402             // see if we are "adding a call" from the InCallScreen; false by default.
403             return intent.getBooleanExtra(ADD_CALL_MODE_KEY, false);
404         } else {
405             return false;
406         }
407     }
408 
409     /**
410      * Checks the given Intent and changes dialpad's UI state. For example, if the Intent requires
411      * the screen to enter "Add Call" mode, this method will show correct UI for the mode.
412      */
configureScreenFromIntent(Intent intent)413     public void configureScreenFromIntent(Intent intent) {
414         if (!isLayoutReady()) {
415             // This happens typically when parent's Activity#onNewIntent() is called while
416             // Fragment#onCreateView() isn't called yet, and thus we cannot configure Views at
417             // this point. onViewCreate() should call this method after preparing layouts, so
418             // just ignore this call now.
419             Log.i(TAG,
420                     "Screen configuration is requested before onCreateView() is called. Ignored");
421             return;
422         }
423 
424         boolean needToShowDialpadChooser = false;
425 
426         final boolean isAddCallMode = isAddCallMode(intent);
427         if (!isAddCallMode) {
428             final boolean digitsFilled = fillDigitsIfNecessary(intent);
429             if (!digitsFilled) {
430                 needToShowDialpadChooser = needToShowDialpadChooser(intent, isAddCallMode);
431             }
432         }
433         showDialpadChooser(needToShowDialpadChooser);
434     }
435 
setFormattedDigits(String data, String normalizedNumber)436     private void setFormattedDigits(String data, String normalizedNumber) {
437         // strip the non-dialable numbers out of the data string.
438         String dialString = PhoneNumberUtils.extractNetworkPortion(data);
439         dialString =
440                 PhoneNumberUtils.formatNumber(dialString, normalizedNumber, mCurrentCountryIso);
441         if (!TextUtils.isEmpty(dialString)) {
442             Editable digits = mDigits.getText();
443             digits.replace(0, digits.length(), dialString);
444             // for some reason this isn't getting called in the digits.replace call above..
445             // but in any case, this will make sure the background drawable looks right
446             afterTextChanged(digits);
447         }
448     }
449 
setupKeypad(View fragmentView)450     private void setupKeypad(View fragmentView) {
451         // Setup the listeners for the buttons
452         View view = fragmentView.findViewById(R.id.one);
453         view.setOnClickListener(this);
454         view.setOnLongClickListener(this);
455 
456         fragmentView.findViewById(R.id.two).setOnClickListener(this);
457         fragmentView.findViewById(R.id.three).setOnClickListener(this);
458         fragmentView.findViewById(R.id.four).setOnClickListener(this);
459         fragmentView.findViewById(R.id.five).setOnClickListener(this);
460         fragmentView.findViewById(R.id.six).setOnClickListener(this);
461         fragmentView.findViewById(R.id.seven).setOnClickListener(this);
462         fragmentView.findViewById(R.id.eight).setOnClickListener(this);
463         fragmentView.findViewById(R.id.nine).setOnClickListener(this);
464         fragmentView.findViewById(R.id.star).setOnClickListener(this);
465 
466         view = fragmentView.findViewById(R.id.zero);
467         view.setOnClickListener(this);
468         view.setOnLongClickListener(this);
469 
470         fragmentView.findViewById(R.id.pound).setOnClickListener(this);
471     }
472 
473     @Override
onResume()474     public void onResume() {
475         super.onResume();
476 
477         // Query the last dialed number. Do it first because hitting
478         // the DB is 'slow'. This call is asynchronous.
479         queryLastOutgoingCall();
480 
481         // retrieve the DTMF tone play back setting.
482         mDTMFToneEnabled = Settings.System.getInt(getActivity().getContentResolver(),
483                 Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1;
484 
485         // Retrieve the haptic feedback setting.
486         mHaptic.checkSystemSetting();
487 
488         // if the mToneGenerator creation fails, just continue without it.  It is
489         // a local audio signal, and is not as important as the dtmf tone itself.
490         synchronized (mToneGeneratorLock) {
491             if (mToneGenerator == null) {
492                 try {
493                     // we want the user to be able to control the volume of the dial tones
494                     // outside of a call, so we use the stream type that is also mapped to the
495                     // volume control keys for this activity
496                     mToneGenerator = new ToneGenerator(DIAL_TONE_STREAM_TYPE, TONE_RELATIVE_VOLUME);
497                     getActivity().setVolumeControlStream(DIAL_TONE_STREAM_TYPE);
498                 } catch (RuntimeException e) {
499                     Log.w(TAG, "Exception caught while creating local tone generator: " + e);
500                     mToneGenerator = null;
501                 }
502             }
503         }
504 
505         Activity parent = getActivity();
506         if (parent instanceof DialtactsActivity) {
507             // See if we were invoked with a DIAL intent. If we were, fill in the appropriate
508             // digits in the dialer field.
509             fillDigitsIfNecessary(parent.getIntent());
510         }
511 
512         // While we're in the foreground, listen for phone state changes,
513         // purely so that we can take down the "dialpad chooser" if the
514         // phone becomes idle while the chooser UI is visible.
515         TelephonyManager telephonyManager =
516                 (TelephonyManager) getActivity().getSystemService(Context.TELEPHONY_SERVICE);
517         telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
518 
519         // Potentially show hint text in the mDigits field when the user
520         // hasn't typed any digits yet.  (If there's already an active call,
521         // this hint text will remind the user that he's about to add a new
522         // call.)
523         //
524         // TODO: consider adding better UI for the case where *both* lines
525         // are currently in use.  (Right now we let the user try to add
526         // another call, but that call is guaranteed to fail.  Perhaps the
527         // entire dialer UI should be disabled instead.)
528         if (phoneIsInUse()) {
529             mDigits.setHint(R.string.dialerDialpadHintText);
530         } else {
531             // Common case; no hint necessary.
532             mDigits.setHint(null);
533 
534             // Also, a sanity-check: the "dialpad chooser" UI should NEVER
535             // be visible if the phone is idle!
536             showDialpadChooser(false);
537         }
538 
539         updateDialAndDeleteButtonEnabledState();
540     }
541 
542     @Override
onPause()543     public void onPause() {
544         super.onPause();
545 
546         // Stop listening for phone state changes.
547         TelephonyManager telephonyManager =
548                 (TelephonyManager) getActivity().getSystemService(Context.TELEPHONY_SERVICE);
549         telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
550 
551         synchronized (mToneGeneratorLock) {
552             if (mToneGenerator != null) {
553                 mToneGenerator.release();
554                 mToneGenerator = null;
555             }
556         }
557         // TODO: I wonder if we should not check if the AsyncTask that
558         // lookup the last dialed number has completed.
559         mLastNumberDialed = EMPTY_NUMBER;  // Since we are going to query again, free stale number.
560     }
561 
562     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)563     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
564         super.onCreateOptionsMenu(menu, inflater);
565         if (mShowOptionsMenu && ViewConfiguration.get(getActivity()).hasPermanentMenuKey() &&
566                 isLayoutReady() && mDialpadChooser != null) {
567             inflater.inflate(R.menu.dialpad_options, menu);
568         }
569     }
570 
571     @Override
onPrepareOptionsMenu(Menu menu)572     public void onPrepareOptionsMenu(Menu menu) {
573         // Hardware menu key should be available and Views should already be ready.
574         if (mShowOptionsMenu && ViewConfiguration.get(getActivity()).hasPermanentMenuKey() &&
575                 isLayoutReady() && mDialpadChooser != null) {
576              setupMenuItems(menu);
577         }
578     }
579 
setupMenuItems(Menu menu)580     private void setupMenuItems(Menu menu) {
581         final MenuItem callSettingsMenuItem = menu.findItem(R.id.menu_call_settings_dialpad);
582         final MenuItem addToContactMenuItem = menu.findItem(R.id.menu_add_contacts);
583         final MenuItem twoSecPauseMenuItem = menu.findItem(R.id.menu_2s_pause);
584         final MenuItem waitMenuItem = menu.findItem(R.id.menu_add_wait);
585 
586         final Activity activity = getActivity();
587         if (activity != null && ViewConfiguration.get(activity).hasPermanentMenuKey()) {
588             // Call settings should be available via its parent Activity.
589             callSettingsMenuItem.setVisible(false);
590         } else {
591             callSettingsMenuItem.setVisible(true);
592             callSettingsMenuItem.setIntent(DialtactsActivity.getCallSettingsIntent());
593         }
594 
595         // We show "add to contacts", "2sec pause", and "add wait" menus only when the user is
596         // seeing usual dialpads and has typed at least one digit.
597         // We never show a menu if the "choose dialpad" UI is up.
598         if (dialpadChooserVisible() || isDigitsEmpty()) {
599             addToContactMenuItem.setVisible(false);
600             twoSecPauseMenuItem.setVisible(false);
601             waitMenuItem.setVisible(false);
602         } else {
603             final CharSequence digits = mDigits.getText();
604 
605             // Put the current digits string into an intent
606             addToContactMenuItem.setIntent(getAddToContactIntent(digits));
607             addToContactMenuItem.setVisible(true);
608 
609             // Check out whether to show Pause & Wait option menu items
610             int selectionStart;
611             int selectionEnd;
612             String strDigits = digits.toString();
613 
614             selectionStart = mDigits.getSelectionStart();
615             selectionEnd = mDigits.getSelectionEnd();
616 
617             if (selectionStart != -1) {
618                 if (selectionStart > selectionEnd) {
619                     // swap it as we want start to be less then end
620                     int tmp = selectionStart;
621                     selectionStart = selectionEnd;
622                     selectionEnd = tmp;
623                 }
624 
625                 if (selectionStart != 0) {
626                     // Pause can be visible if cursor is not in the begining
627                     twoSecPauseMenuItem.setVisible(true);
628 
629                     // For Wait to be visible set of condition to meet
630                     waitMenuItem.setVisible(showWait(selectionStart, selectionEnd, strDigits));
631                 } else {
632                     // cursor in the beginning both pause and wait to be invisible
633                     twoSecPauseMenuItem.setVisible(false);
634                     waitMenuItem.setVisible(false);
635                 }
636             } else {
637                 twoSecPauseMenuItem.setVisible(true);
638 
639                 // cursor is not selected so assume new digit is added to the end
640                 int strLength = strDigits.length();
641                 waitMenuItem.setVisible(showWait(strLength, strLength, strDigits));
642             }
643         }
644     }
645 
getAddToContactIntent(CharSequence digits)646     private static Intent getAddToContactIntent(CharSequence digits) {
647         final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
648         intent.putExtra(Insert.PHONE, digits);
649         intent.setType(People.CONTENT_ITEM_TYPE);
650         return intent;
651     }
652 
keyPressed(int keyCode)653     private void keyPressed(int keyCode) {
654         mHaptic.vibrate();
655         KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
656         mDigits.onKeyDown(keyCode, event);
657 
658         // If the cursor is at the end of the text we hide it.
659         final int length = mDigits.length();
660         if (length == mDigits.getSelectionStart() && length == mDigits.getSelectionEnd()) {
661             mDigits.setCursorVisible(false);
662         }
663     }
664 
onKey(View view, int keyCode, KeyEvent event)665     public boolean onKey(View view, int keyCode, KeyEvent event) {
666         switch (view.getId()) {
667             case R.id.digits:
668                 if (keyCode == KeyEvent.KEYCODE_ENTER) {
669                     dialButtonPressed();
670                     return true;
671                 }
672                 break;
673         }
674         return false;
675     }
676 
677     @Override
onClick(View view)678     public void onClick(View view) {
679         switch (view.getId()) {
680             case R.id.one: {
681                 playTone(ToneGenerator.TONE_DTMF_1);
682                 keyPressed(KeyEvent.KEYCODE_1);
683                 return;
684             }
685             case R.id.two: {
686                 playTone(ToneGenerator.TONE_DTMF_2);
687                 keyPressed(KeyEvent.KEYCODE_2);
688                 return;
689             }
690             case R.id.three: {
691                 playTone(ToneGenerator.TONE_DTMF_3);
692                 keyPressed(KeyEvent.KEYCODE_3);
693                 return;
694             }
695             case R.id.four: {
696                 playTone(ToneGenerator.TONE_DTMF_4);
697                 keyPressed(KeyEvent.KEYCODE_4);
698                 return;
699             }
700             case R.id.five: {
701                 playTone(ToneGenerator.TONE_DTMF_5);
702                 keyPressed(KeyEvent.KEYCODE_5);
703                 return;
704             }
705             case R.id.six: {
706                 playTone(ToneGenerator.TONE_DTMF_6);
707                 keyPressed(KeyEvent.KEYCODE_6);
708                 return;
709             }
710             case R.id.seven: {
711                 playTone(ToneGenerator.TONE_DTMF_7);
712                 keyPressed(KeyEvent.KEYCODE_7);
713                 return;
714             }
715             case R.id.eight: {
716                 playTone(ToneGenerator.TONE_DTMF_8);
717                 keyPressed(KeyEvent.KEYCODE_8);
718                 return;
719             }
720             case R.id.nine: {
721                 playTone(ToneGenerator.TONE_DTMF_9);
722                 keyPressed(KeyEvent.KEYCODE_9);
723                 return;
724             }
725             case R.id.zero: {
726                 playTone(ToneGenerator.TONE_DTMF_0);
727                 keyPressed(KeyEvent.KEYCODE_0);
728                 return;
729             }
730             case R.id.pound: {
731                 playTone(ToneGenerator.TONE_DTMF_P);
732                 keyPressed(KeyEvent.KEYCODE_POUND);
733                 return;
734             }
735             case R.id.star: {
736                 playTone(ToneGenerator.TONE_DTMF_S);
737                 keyPressed(KeyEvent.KEYCODE_STAR);
738                 return;
739             }
740             case R.id.deleteButton: {
741                 keyPressed(KeyEvent.KEYCODE_DEL);
742                 return;
743             }
744             case R.id.dialButton: {
745                 mHaptic.vibrate();  // Vibrate here too, just like we do for the regular keys
746                 dialButtonPressed();
747                 return;
748             }
749             case R.id.searchButton: {
750                 mHaptic.vibrate();
751                 if (mListener != null) {
752                     mListener.onSearchButtonPressed();
753                 }
754                 return;
755             }
756             case R.id.digits: {
757                 if (!isDigitsEmpty()) {
758                     mDigits.setCursorVisible(true);
759                 }
760                 return;
761             }
762             case R.id.overflow_menu: {
763                 PopupMenu popup = constructPopupMenu(view);
764                 if (popup != null) {
765                     popup.show();
766                 }
767             }
768         }
769     }
770 
constructPopupMenu(View anchorView)771     private PopupMenu constructPopupMenu(View anchorView) {
772         final Context context = getActivity();
773         if (context == null) {
774             return null;
775         }
776         final PopupMenu popupMenu = new PopupMenu(context, anchorView);
777         final Menu menu = popupMenu.getMenu();
778         popupMenu.inflate(R.menu.dialpad_options);
779         popupMenu.setOnMenuItemClickListener(this);
780         setupMenuItems(menu);
781         return popupMenu;
782     }
783 
onLongClick(View view)784     public boolean onLongClick(View view) {
785         final Editable digits = mDigits.getText();
786         int id = view.getId();
787         switch (id) {
788             case R.id.deleteButton: {
789                 digits.clear();
790                 // TODO: The framework forgets to clear the pressed
791                 // status of disabled button. Until this is fixed,
792                 // clear manually the pressed status. b/2133127
793                 mDelete.setPressed(false);
794                 return true;
795             }
796             case R.id.one: {
797                 if (isDigitsEmpty()) {
798                     if (isVoicemailAvailable()) {
799                         callVoicemail();
800                     } else if (getActivity() != null) {
801                         DialogFragment dialogFragment = ErrorDialogFragment.newInstance(
802                                 R.string.dialog_voicemail_not_ready_title,
803                                 R.string.dialog_voicemail_not_ready_message);
804                         dialogFragment.show(getFragmentManager(), "voicemail_not_ready");
805                     }
806                     return true;
807                 }
808                 return false;
809             }
810             case R.id.zero: {
811                 keyPressed(KeyEvent.KEYCODE_PLUS);
812                 return true;
813             }
814             case R.id.digits: {
815                 // Right now EditText does not show the "paste" option when cursor is not visible.
816                 // To show that, make the cursor visible, and return false, letting the EditText
817                 // show the option by itself.
818                 mDigits.setCursorVisible(true);
819                 return false;
820             }
821         }
822         return false;
823     }
824 
callVoicemail()825     public void callVoicemail() {
826         startActivity(newVoicemailIntent());
827         mDigits.getText().clear(); // TODO: Fix bug 1745781
828         getActivity().finish();
829     }
830 
831     public static class ErrorDialogFragment extends DialogFragment {
832         private int mTitleResId;
833         private Integer mMessageResId;  // can be null
834 
835         private static final String ARG_TITLE_RES_ID = "argTitleResId";
836         private static final String ARG_MESSAGE_RES_ID = "argMessageResId";
837 
newInstance(int titleResId)838         public static ErrorDialogFragment newInstance(int titleResId) {
839             return newInstanceInter(titleResId, null);
840         }
841 
newInstance(int titleResId, int messageResId)842         public static ErrorDialogFragment newInstance(int titleResId, int messageResId) {
843             return newInstanceInter(titleResId, messageResId);
844         }
845 
newInstanceInter( int titleResId, Integer messageResId)846         private static ErrorDialogFragment newInstanceInter(
847                 int titleResId, Integer messageResId) {
848             final ErrorDialogFragment fragment = new ErrorDialogFragment();
849             final Bundle args = new Bundle();
850             args.putInt(ARG_TITLE_RES_ID, titleResId);
851             if (messageResId != null) {
852                 args.putInt(ARG_MESSAGE_RES_ID, messageResId);
853             }
854             fragment.setArguments(args);
855             return fragment;
856         }
857 
858         @Override
onCreate(Bundle savedInstanceState)859         public void onCreate(Bundle savedInstanceState) {
860             super.onCreate(savedInstanceState);
861             mTitleResId = getArguments().getInt(ARG_TITLE_RES_ID);
862             if (getArguments().containsKey(ARG_MESSAGE_RES_ID)) {
863                 mMessageResId = getArguments().getInt(ARG_MESSAGE_RES_ID);
864             }
865         }
866 
867         @Override
onCreateDialog(Bundle savedInstanceState)868         public Dialog onCreateDialog(Bundle savedInstanceState) {
869             AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
870             builder.setTitle(mTitleResId)
871                     .setPositiveButton(android.R.string.ok,
872                             new DialogInterface.OnClickListener() {
873                                 @Override
874                                 public void onClick(DialogInterface dialog, int which) {
875                                     dismiss();
876                                 }
877                             });
878             if (mMessageResId != null) {
879                 builder.setMessage(mMessageResId);
880             }
881             return builder.create();
882         }
883     }
884 
885     /**
886      * In most cases, when the dial button is pressed, there is a
887      * number in digits area. Pack it in the intent, start the
888      * outgoing call broadcast as a separate task and finish this
889      * activity.
890      *
891      * When there is no digit and the phone is CDMA and off hook,
892      * we're sending a blank flash for CDMA. CDMA networks use Flash
893      * messages when special processing needs to be done, mainly for
894      * 3-way or call waiting scenarios. Presumably, here we're in a
895      * special 3-way scenario where the network needs a blank flash
896      * before being able to add the new participant.  (This is not the
897      * case with all 3-way calls, just certain CDMA infrastructures.)
898      *
899      * Otherwise, there is no digit, display the last dialed
900      * number. Don't finish since the user may want to edit it. The
901      * user needs to press the dial button again, to dial it (general
902      * case described above).
903      */
dialButtonPressed()904     public void dialButtonPressed() {
905         if (isDigitsEmpty()) { // No number entered.
906             if (phoneIsCdma() && phoneIsOffhook()) {
907                 // This is really CDMA specific. On GSM is it possible
908                 // to be off hook and wanted to add a 3rd party using
909                 // the redial feature.
910                 startActivity(newFlashIntent());
911             } else {
912                 if (!TextUtils.isEmpty(mLastNumberDialed)) {
913                     // Recall the last number dialed.
914                     mDigits.setText(mLastNumberDialed);
915 
916                     // ...and move the cursor to the end of the digits string,
917                     // so you'll be able to delete digits using the Delete
918                     // button (just as if you had typed the number manually.)
919                     //
920                     // Note we use mDigits.getText().length() here, not
921                     // mLastNumberDialed.length(), since the EditText widget now
922                     // contains a *formatted* version of mLastNumberDialed (due to
923                     // mTextWatcher) and its length may have changed.
924                     mDigits.setSelection(mDigits.getText().length());
925                 } else {
926                     // There's no "last number dialed" or the
927                     // background query is still running. There's
928                     // nothing useful for the Dial button to do in
929                     // this case.  Note: with a soft dial button, this
930                     // can never happens since the dial button is
931                     // disabled under these conditons.
932                     playTone(ToneGenerator.TONE_PROP_NACK);
933                 }
934             }
935         } else {
936             final String number = mDigits.getText().toString();
937 
938             // "persist.radio.otaspdial" is a temporary hack needed for one carrier's automated
939             // test equipment.
940             // TODO: clean it up.
941             if (number != null
942                     && !TextUtils.isEmpty(mProhibitedPhoneNumberRegexp)
943                     && number.matches(mProhibitedPhoneNumberRegexp)
944                     && (SystemProperties.getInt("persist.radio.otaspdial", 0) != 1)) {
945                 Log.i(TAG, "The phone number is prohibited explicitly by a rule.");
946                 if (getActivity() != null) {
947                     DialogFragment dialogFragment = ErrorDialogFragment.newInstance(
948                                     R.string.dialog_phone_call_prohibited_title);
949                     dialogFragment.show(getFragmentManager(), "phone_prohibited_dialog");
950                 }
951 
952                 // Clear the digits just in case.
953                 mDigits.getText().clear();
954             } else {
955                 final Intent intent = newDialNumberIntent(number);
956                 if (getActivity() instanceof DialtactsActivity) {
957                     intent.putExtra(DialtactsActivity.EXTRA_CALL_ORIGIN,
958                             DialtactsActivity.CALL_ORIGIN_DIALTACTS);
959                 }
960                 startActivity(intent);
961                 mDigits.getText().clear();  // TODO: Fix bug 1745781
962                 getActivity().finish();
963             }
964         }
965     }
966 
967     /**
968      * Plays the specified tone for TONE_LENGTH_MS milliseconds.
969      *
970      * The tone is played locally, using the audio stream for phone calls.
971      * Tones are played only if the "Audible touch tones" user preference
972      * is checked, and are NOT played if the device is in silent mode.
973      *
974      * @param tone a tone code from {@link ToneGenerator}
975      */
playTone(int tone)976     void playTone(int tone) {
977         // if local tone playback is disabled, just return.
978         if (!mDTMFToneEnabled) {
979             return;
980         }
981 
982         // Also do nothing if the phone is in silent mode.
983         // We need to re-check the ringer mode for *every* playTone()
984         // call, rather than keeping a local flag that's updated in
985         // onResume(), since it's possible to toggle silent mode without
986         // leaving the current activity (via the ENDCALL-longpress menu.)
987         AudioManager audioManager =
988                 (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE);
989         int ringerMode = audioManager.getRingerMode();
990         if ((ringerMode == AudioManager.RINGER_MODE_SILENT)
991             || (ringerMode == AudioManager.RINGER_MODE_VIBRATE)) {
992             return;
993         }
994 
995         synchronized (mToneGeneratorLock) {
996             if (mToneGenerator == null) {
997                 Log.w(TAG, "playTone: mToneGenerator == null, tone: " + tone);
998                 return;
999             }
1000 
1001             // Start the new tone (will stop any playing tone)
1002             mToneGenerator.startTone(tone, TONE_LENGTH_MS);
1003         }
1004     }
1005 
1006     /**
1007      * Brings up the "dialpad chooser" UI in place of the usual Dialer
1008      * elements (the textfield/button and the dialpad underneath).
1009      *
1010      * We show this UI if the user brings up the Dialer while a call is
1011      * already in progress, since there's a good chance we got here
1012      * accidentally (and the user really wanted the in-call dialpad instead).
1013      * So in this situation we display an intermediate UI that lets the user
1014      * explicitly choose between the in-call dialpad ("Use touch tone
1015      * keypad") and the regular Dialer ("Add call").  (Or, the option "Return
1016      * to call in progress" just goes back to the in-call UI with no dialpad
1017      * at all.)
1018      *
1019      * @param enabled If true, show the "dialpad chooser" instead
1020      *                of the regular Dialer UI
1021      */
showDialpadChooser(boolean enabled)1022     private void showDialpadChooser(boolean enabled) {
1023         // Check if onCreateView() is already called by checking one of View objects.
1024         if (!isLayoutReady()) {
1025             return;
1026         }
1027 
1028         if (enabled) {
1029             // Log.i(TAG, "Showing dialpad chooser!");
1030             if (mDigitsContainer != null) {
1031                 mDigitsContainer.setVisibility(View.GONE);
1032             } else {
1033                 // mDigits is not enclosed by the container. Make the digits field itself gone.
1034                 mDigits.setVisibility(View.GONE);
1035             }
1036             if (mDialpad != null) mDialpad.setVisibility(View.GONE);
1037             mAdditionalButtonsRow.setVisibility(View.GONE);
1038             mDialpadChooser.setVisibility(View.VISIBLE);
1039 
1040             // Instantiate the DialpadChooserAdapter and hook it up to the
1041             // ListView.  We do this only once.
1042             if (mDialpadChooserAdapter == null) {
1043                 mDialpadChooserAdapter = new DialpadChooserAdapter(getActivity());
1044             }
1045             mDialpadChooser.setAdapter(mDialpadChooserAdapter);
1046         } else {
1047             // Log.i(TAG, "Displaying normal Dialer UI.");
1048             if (mDigitsContainer != null) {
1049                 mDigitsContainer.setVisibility(View.VISIBLE);
1050             } else {
1051                 mDigits.setVisibility(View.VISIBLE);
1052             }
1053             if (mDialpad != null) mDialpad.setVisibility(View.VISIBLE);
1054             mAdditionalButtonsRow.setVisibility(View.VISIBLE);
1055             mDialpadChooser.setVisibility(View.GONE);
1056         }
1057     }
1058 
1059     /**
1060      * @return true if we're currently showing the "dialpad chooser" UI.
1061      */
dialpadChooserVisible()1062     private boolean dialpadChooserVisible() {
1063         return mDialpadChooser.getVisibility() == View.VISIBLE;
1064     }
1065 
1066     /**
1067      * Simple list adapter, binding to an icon + text label
1068      * for each item in the "dialpad chooser" list.
1069      */
1070     private static class DialpadChooserAdapter extends BaseAdapter {
1071         private LayoutInflater mInflater;
1072 
1073         // Simple struct for a single "choice" item.
1074         static class ChoiceItem {
1075             String text;
1076             Bitmap icon;
1077             int id;
1078 
ChoiceItem(String s, Bitmap b, int i)1079             public ChoiceItem(String s, Bitmap b, int i) {
1080                 text = s;
1081                 icon = b;
1082                 id = i;
1083             }
1084         }
1085 
1086         // IDs for the possible "choices":
1087         static final int DIALPAD_CHOICE_USE_DTMF_DIALPAD = 101;
1088         static final int DIALPAD_CHOICE_RETURN_TO_CALL = 102;
1089         static final int DIALPAD_CHOICE_ADD_NEW_CALL = 103;
1090 
1091         private static final int NUM_ITEMS = 3;
1092         private ChoiceItem mChoiceItems[] = new ChoiceItem[NUM_ITEMS];
1093 
DialpadChooserAdapter(Context context)1094         public DialpadChooserAdapter(Context context) {
1095             // Cache the LayoutInflate to avoid asking for a new one each time.
1096             mInflater = LayoutInflater.from(context);
1097 
1098             // Initialize the possible choices.
1099             // TODO: could this be specified entirely in XML?
1100 
1101             // - "Use touch tone keypad"
1102             mChoiceItems[0] = new ChoiceItem(
1103                     context.getString(R.string.dialer_useDtmfDialpad),
1104                     BitmapFactory.decodeResource(context.getResources(),
1105                                                  R.drawable.ic_dialer_fork_tt_keypad),
1106                     DIALPAD_CHOICE_USE_DTMF_DIALPAD);
1107 
1108             // - "Return to call in progress"
1109             mChoiceItems[1] = new ChoiceItem(
1110                     context.getString(R.string.dialer_returnToInCallScreen),
1111                     BitmapFactory.decodeResource(context.getResources(),
1112                                                  R.drawable.ic_dialer_fork_current_call),
1113                     DIALPAD_CHOICE_RETURN_TO_CALL);
1114 
1115             // - "Add call"
1116             mChoiceItems[2] = new ChoiceItem(
1117                     context.getString(R.string.dialer_addAnotherCall),
1118                     BitmapFactory.decodeResource(context.getResources(),
1119                                                  R.drawable.ic_dialer_fork_add_call),
1120                     DIALPAD_CHOICE_ADD_NEW_CALL);
1121         }
1122 
getCount()1123         public int getCount() {
1124             return NUM_ITEMS;
1125         }
1126 
1127         /**
1128          * Return the ChoiceItem for a given position.
1129          */
getItem(int position)1130         public Object getItem(int position) {
1131             return mChoiceItems[position];
1132         }
1133 
1134         /**
1135          * Return a unique ID for each possible choice.
1136          */
getItemId(int position)1137         public long getItemId(int position) {
1138             return position;
1139         }
1140 
1141         /**
1142          * Make a view for each row.
1143          */
getView(int position, View convertView, ViewGroup parent)1144         public View getView(int position, View convertView, ViewGroup parent) {
1145             // When convertView is non-null, we can reuse it (there's no need
1146             // to reinflate it.)
1147             if (convertView == null) {
1148                 convertView = mInflater.inflate(R.layout.dialpad_chooser_list_item, null);
1149             }
1150 
1151             TextView text = (TextView) convertView.findViewById(R.id.text);
1152             text.setText(mChoiceItems[position].text);
1153 
1154             ImageView icon = (ImageView) convertView.findViewById(R.id.icon);
1155             icon.setImageBitmap(mChoiceItems[position].icon);
1156 
1157             return convertView;
1158         }
1159     }
1160 
1161     /**
1162      * Handle clicks from the dialpad chooser.
1163      */
onItemClick(AdapterView parent, View v, int position, long id)1164     public void onItemClick(AdapterView parent, View v, int position, long id) {
1165         DialpadChooserAdapter.ChoiceItem item =
1166                 (DialpadChooserAdapter.ChoiceItem) parent.getItemAtPosition(position);
1167         int itemId = item.id;
1168         switch (itemId) {
1169             case DialpadChooserAdapter.DIALPAD_CHOICE_USE_DTMF_DIALPAD:
1170                 // Log.i(TAG, "DIALPAD_CHOICE_USE_DTMF_DIALPAD");
1171                 // Fire off an intent to go back to the in-call UI
1172                 // with the dialpad visible.
1173                 returnToInCallScreen(true);
1174                 break;
1175 
1176             case DialpadChooserAdapter.DIALPAD_CHOICE_RETURN_TO_CALL:
1177                 // Log.i(TAG, "DIALPAD_CHOICE_RETURN_TO_CALL");
1178                 // Fire off an intent to go back to the in-call UI
1179                 // (with the dialpad hidden).
1180                 returnToInCallScreen(false);
1181                 break;
1182 
1183             case DialpadChooserAdapter.DIALPAD_CHOICE_ADD_NEW_CALL:
1184                 // Log.i(TAG, "DIALPAD_CHOICE_ADD_NEW_CALL");
1185                 // Ok, guess the user really did want to be here (in the
1186                 // regular Dialer) after all.  Bring back the normal Dialer UI.
1187                 showDialpadChooser(false);
1188                 break;
1189 
1190             default:
1191                 Log.w(TAG, "onItemClick: unexpected itemId: " + itemId);
1192                 break;
1193         }
1194     }
1195 
1196     /**
1197      * Returns to the in-call UI (where there's presumably a call in
1198      * progress) in response to the user selecting "use touch tone keypad"
1199      * or "return to call" from the dialpad chooser.
1200      */
returnToInCallScreen(boolean showDialpad)1201     private void returnToInCallScreen(boolean showDialpad) {
1202         try {
1203             ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
1204             if (phone != null) phone.showCallScreenWithDialpad(showDialpad);
1205         } catch (RemoteException e) {
1206             Log.w(TAG, "phone.showCallScreenWithDialpad() failed", e);
1207         }
1208 
1209         // Finally, finish() ourselves so that we don't stay on the
1210         // activity stack.
1211         // Note that we do this whether or not the showCallScreenWithDialpad()
1212         // call above had any effect or not!  (That call is a no-op if the
1213         // phone is idle, which can happen if the current call ends while
1214         // the dialpad chooser is up.  In this case we can't show the
1215         // InCallScreen, and there's no point staying here in the Dialer,
1216         // so we just take the user back where he came from...)
1217         getActivity().finish();
1218     }
1219 
1220     /**
1221      * @return true if the phone is "in use", meaning that at least one line
1222      *              is active (ie. off hook or ringing or dialing).
1223      */
phoneIsInUse()1224     public static boolean phoneIsInUse() {
1225         boolean phoneInUse = false;
1226         try {
1227             ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
1228             if (phone != null) phoneInUse = !phone.isIdle();
1229         } catch (RemoteException e) {
1230             Log.w(TAG, "phone.isIdle() failed", e);
1231         }
1232         return phoneInUse;
1233     }
1234 
1235     /**
1236      * @return true if the phone is a CDMA phone type
1237      */
phoneIsCdma()1238     private boolean phoneIsCdma() {
1239         boolean isCdma = false;
1240         try {
1241             ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
1242             if (phone != null) {
1243                 isCdma = (phone.getActivePhoneType() == TelephonyManager.PHONE_TYPE_CDMA);
1244             }
1245         } catch (RemoteException e) {
1246             Log.w(TAG, "phone.getActivePhoneType() failed", e);
1247         }
1248         return isCdma;
1249     }
1250 
1251     /**
1252      * @return true if the phone state is OFFHOOK
1253      */
phoneIsOffhook()1254     private boolean phoneIsOffhook() {
1255         boolean phoneOffhook = false;
1256         try {
1257             ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
1258             if (phone != null) phoneOffhook = phone.isOffhook();
1259         } catch (RemoteException e) {
1260             Log.w(TAG, "phone.isOffhook() failed", e);
1261         }
1262         return phoneOffhook;
1263     }
1264 
1265     /**
1266      * Returns true whenever any one of the options from the menu is selected.
1267      * Code changes to support dialpad options
1268      */
1269     @Override
onOptionsItemSelected(MenuItem item)1270     public boolean onOptionsItemSelected(MenuItem item) {
1271         switch (item.getItemId()) {
1272             case R.id.menu_2s_pause:
1273                 updateDialString(",");
1274                 return true;
1275             case R.id.menu_add_wait:
1276                 updateDialString(";");
1277                 return true;
1278             default:
1279                 return false;
1280         }
1281     }
1282 
1283     @Override
onMenuItemClick(MenuItem item)1284     public boolean onMenuItemClick(MenuItem item) {
1285         return onOptionsItemSelected(item);
1286     }
1287 
1288     /**
1289      * Updates the dial string (mDigits) after inserting a Pause character (,)
1290      * or Wait character (;).
1291      */
updateDialString(String newDigits)1292     private void updateDialString(String newDigits) {
1293         int selectionStart;
1294         int selectionEnd;
1295 
1296         // SpannableStringBuilder editable_text = new SpannableStringBuilder(mDigits.getText());
1297         int anchor = mDigits.getSelectionStart();
1298         int point = mDigits.getSelectionEnd();
1299 
1300         selectionStart = Math.min(anchor, point);
1301         selectionEnd = Math.max(anchor, point);
1302 
1303         Editable digits = mDigits.getText();
1304         if (selectionStart != -1) {
1305             if (selectionStart == selectionEnd) {
1306                 // then there is no selection. So insert the pause at this
1307                 // position and update the mDigits.
1308                 digits.replace(selectionStart, selectionStart, newDigits);
1309             } else {
1310                 digits.replace(selectionStart, selectionEnd, newDigits);
1311                 // Unselect: back to a regular cursor, just pass the character inserted.
1312                 mDigits.setSelection(selectionStart + 1);
1313             }
1314         } else {
1315             int len = mDigits.length();
1316             digits.replace(len, len, newDigits);
1317         }
1318     }
1319 
1320     /**
1321      * Update the enabledness of the "Dial" and "Backspace" buttons if applicable.
1322      */
updateDialAndDeleteButtonEnabledState()1323     private void updateDialAndDeleteButtonEnabledState() {
1324         final boolean digitsNotEmpty = !isDigitsEmpty();
1325 
1326         if (mDialButton != null) {
1327             // On CDMA phones, if we're already on a call, we *always*
1328             // enable the Dial button (since you can press it without
1329             // entering any digits to send an empty flash.)
1330             if (phoneIsCdma() && phoneIsOffhook()) {
1331                 mDialButton.setEnabled(true);
1332             } else {
1333                 // Common case: GSM, or CDMA but not on a call.
1334                 // Enable the Dial button if some digits have
1335                 // been entered, or if there is a last dialed number
1336                 // that could be redialed.
1337                 mDialButton.setEnabled(digitsNotEmpty ||
1338                         !TextUtils.isEmpty(mLastNumberDialed));
1339             }
1340         }
1341         mDelete.setEnabled(digitsNotEmpty);
1342     }
1343 
1344     /**
1345      * Check if voicemail is enabled/accessible.
1346      *
1347      * @return true if voicemail is enabled and accessibly. Note that this can be false
1348      * "temporarily" after the app boot.
1349      * @see TelephonyManager#getVoiceMailNumber()
1350      */
isVoicemailAvailable()1351     private boolean isVoicemailAvailable() {
1352         try {
1353             return (TelephonyManager.getDefault().getVoiceMailNumber() != null);
1354         } catch (SecurityException se) {
1355             // Possibly no READ_PHONE_STATE privilege.
1356             Log.w(TAG, "SecurityException is thrown. Maybe privilege isn't sufficient.");
1357         }
1358         return false;
1359     }
1360 
1361     /**
1362      * This function return true if Wait menu item can be shown
1363      * otherwise returns false. Assumes the passed string is non-empty
1364      * and the 0th index check is not required.
1365      */
showWait(int start, int end, String digits)1366     private static boolean showWait(int start, int end, String digits) {
1367         if (start == end) {
1368             // visible false in this case
1369             if (start > digits.length()) return false;
1370 
1371             // preceding char is ';', so visible should be false
1372             if (digits.charAt(start - 1) == ';') return false;
1373 
1374             // next char is ';', so visible should be false
1375             if ((digits.length() > start) && (digits.charAt(start) == ';')) return false;
1376         } else {
1377             // visible false in this case
1378             if (start > digits.length() || end > digits.length()) return false;
1379 
1380             // In this case we need to just check for ';' preceding to start
1381             // or next to end
1382             if (digits.charAt(start - 1) == ';') return false;
1383         }
1384         return true;
1385     }
1386 
1387     /**
1388      * @return true if the widget with the phone number digits is empty.
1389      */
isDigitsEmpty()1390     private boolean isDigitsEmpty() {
1391         return mDigits.length() == 0;
1392     }
1393 
1394     /**
1395      * Starts the asyn query to get the last dialed/outgoing
1396      * number. When the background query finishes, mLastNumberDialed
1397      * is set to the last dialed number or an empty string if none
1398      * exists yet.
1399      */
queryLastOutgoingCall()1400     private void queryLastOutgoingCall() {
1401         mLastNumberDialed = EMPTY_NUMBER;
1402         CallLogAsync.GetLastOutgoingCallArgs lastCallArgs =
1403                 new CallLogAsync.GetLastOutgoingCallArgs(
1404                     getActivity(),
1405                     new CallLogAsync.OnLastOutgoingCallComplete() {
1406                         public void lastOutgoingCall(String number) {
1407                             // TODO: Filter out emergency numbers if
1408                             // the carrier does not want redial for
1409                             // these.
1410                             mLastNumberDialed = number;
1411                             updateDialAndDeleteButtonEnabledState();
1412                         }
1413                     });
1414         mCallLog.getLastOutgoingCall(lastCallArgs);
1415     }
1416 
1417     // Helpers for the call intents.
newVoicemailIntent()1418     private Intent newVoicemailIntent() {
1419         final Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
1420                                          Uri.fromParts("voicemail", EMPTY_NUMBER, null));
1421         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1422         return intent;
1423     }
1424 
newFlashIntent()1425     private Intent newFlashIntent() {
1426         final Intent intent = newDialNumberIntent(EMPTY_NUMBER);
1427         intent.putExtra(EXTRA_SEND_EMPTY_FLASH, true);
1428         return intent;
1429     }
1430 
newDialNumberIntent(String number)1431     private Intent newDialNumberIntent(String number) {
1432         final Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
1433                                          Uri.fromParts("tel", number, null));
1434         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1435         return intent;
1436     }
1437 
setListener(Listener listener)1438     public void setListener(Listener listener) {
1439         mListener = listener;
1440     }
1441 
1442     @Override
onVisibilityChanged(boolean visible)1443     public void onVisibilityChanged(boolean visible) {
1444         mShowOptionsMenu = visible;
1445     }
1446 }
1447