935e36fd465173f5950e1b4e70c7bf592b46d3fd
[android/platform/packages/apps/Phone.git] / src / com / android / phone / PhoneUtils.java
1 /*
2  * Copyright (C) 2006 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 com.android.internal.telephony.Call;
20 import com.android.internal.telephony.CallStateException;
21 import com.android.internal.telephony.CallerInfo;
22 import com.android.internal.telephony.CallerInfoAsyncQuery;
23 import com.android.internal.telephony.Connection;
24 import com.android.internal.telephony.MmiCode;
25 import com.android.internal.telephony.Phone;
26
27 import android.app.AlertDialog;
28 import android.app.Dialog;
29 import android.app.KeyguardManager;
30 import android.app.ProgressDialog;
31 import android.content.Context;
32 import android.content.DialogInterface;
33 import android.content.Intent;
34 import android.media.AudioManager;
35 import android.net.Uri;
36 import android.os.AsyncResult;
37 import android.os.Handler;
38 import android.os.Message;
39 import android.provider.Contacts;
40 import android.telephony.PhoneNumberUtils;
41 import android.text.TextUtils;
42 import android.util.Log;
43 import android.view.KeyEvent;
44 import android.view.LayoutInflater;
45 import android.view.View;
46 import android.view.WindowManager;
47 import android.widget.EditText;
48 import android.widget.Toast;
49
50 import java.util.Hashtable;
51 import java.util.Iterator;
52 import java.util.List;
53
54 /**
55  * Misc utilities for the Phone app.
56  */
57 public class PhoneUtils {
58     private static final String LOG_TAG = "PhoneUtils";
59     private static final boolean DBG = false;
60     /** Control stack trace for Audio Mode settings */
61     private static final boolean DBG_SETAUDIOMODE_STACK = false;
62
63     /** Identifier for the "Add Call" intent extra. */
64     static final String ADD_CALL_MODE_KEY = "add_call_mode";
65
66     // Return codes from placeCall()
67     static final int CALL_STATUS_DIALED = 0;  // The number was successfully dialed
68     static final int CALL_STATUS_DIALED_MMI = 1;  // The specified number was an MMI code
69     static final int CALL_STATUS_FAILED = 2;  // The call failed
70
71     // State of the Phone's audio modes
72     // Each state can move to the other states, but within the state only certain
73     //  transitions for AudioManager.setMode() are allowed.
74     static final int AUDIO_IDLE = 0;  /** audio behaviour at phone idle */
75     static final int AUDIO_RINGING = 1;  /** audio behaviour while ringing */
76     static final int AUDIO_OFFHOOK = 2;  /** audio behaviour while in call. */
77     private static int sAudioBehaviourState = AUDIO_IDLE;
78
79     /** Speaker state, persisting between wired headset connection events */
80     private static boolean sIsSpeakerEnabled = false;
81
82     /** Hash table to store mute (Boolean) values based upon the connection.*/
83     private static Hashtable<Connection, Boolean> sConnectionMuteTable =
84         new Hashtable<Connection, Boolean>();
85
86     /** Static handler for the connection/mute tracking */
87     private static ConnectionHandler mConnectionHandler;
88
89     /** Phone state changed event*/
90     private static final int PHONE_STATE_CHANGED = -1;
91
92     /**
93      * Handler that tracks the connections and updates the value of the
94      * Mute settings for each connection as needed.
95      */
96     private static class ConnectionHandler extends Handler {
97         public void handleMessage(Message msg) {
98             AsyncResult ar = (AsyncResult) msg.obj;
99             switch (msg.what) {
100                 case PHONE_STATE_CHANGED:
101                     if (DBG) log("ConnectionHandler: updating mute state for each connection");
102
103                     Phone phone = (Phone) ar.userObj;
104
105                     // update the foreground connections, if there are new connections.
106                     List<Connection> fgConnections = phone.getForegroundCall().getConnections();
107                     for (Connection cn : fgConnections) {
108                         if (sConnectionMuteTable.get(cn) == null) {
109                             sConnectionMuteTable.put(cn, Boolean.FALSE);
110                         }
111                     }
112
113                     // update the background connections, if there are new connections.
114                     List<Connection> bgConnections = phone.getBackgroundCall().getConnections();
115                     for (Connection cn : bgConnections) {
116                         if (sConnectionMuteTable.get(cn) == null) {
117                             sConnectionMuteTable.put(cn, Boolean.FALSE);
118                         }
119                     }
120
121                     // Check to see if there are any lingering connections here
122                     // (disconnected connections), use old-school iterators to avoid
123                     // concurrent modification exceptions.
124                     Connection cn;
125                     for (Iterator<Connection> cnlist = sConnectionMuteTable.keySet().iterator();
126                             cnlist.hasNext();) {
127                         cn = cnlist.next();
128                         if (!fgConnections.contains(cn) && !bgConnections.contains(cn)) {
129                             if (DBG) log("connection: " + cn + "not accounted for, removing.");
130                             cnlist.remove();
131                         }
132                     }
133
134                     // Restore the mute state of the foreground call if we're not IDLE,
135                     // otherwise just clear the mute state. This is really saying that
136                     // as long as there is one or more connections, we should update
137                     // the mute state with the earliest connection on the foreground
138                     // call, and that with no connections, we should be back to a
139                     // non-mute state.
140                     if (phone.getState() != Phone.State.IDLE) {
141                         restoreMuteState(phone);
142                     } else {
143                         setMuteInternal(phone, false);
144                     }
145
146                     break;
147             }
148         }
149     }
150
151     /**
152      * Register the ConnectionHandler with the phone, to receive connection events
153      */
154     public static void initializeConnectionHandler(Phone phone) {
155         if (mConnectionHandler == null) {
156             mConnectionHandler = new ConnectionHandler();
157         }
158
159         phone.registerForPhoneStateChanged(mConnectionHandler, PHONE_STATE_CHANGED, phone);
160     }
161
162     /** This class is never instantiated. */
163     private PhoneUtils() {
164     }
165
166     //static method to set the audio control state.
167     static void setAudioControlState(int newState) {
168         sAudioBehaviourState = newState;
169     }
170
171     /**
172      * Answer the currently-ringing call.
173      *
174      * @return true if we answered the call, or false if there wasn't
175      *         actually a ringing incoming call, or some other error occurred.
176      *
177      * @see answerAndEndHolding()
178      * @see answerAndEndActive()
179      */
180     static boolean answerCall(Phone phone) {
181         if (DBG) log("answerCall()...");
182
183         // If the ringer is currently ringing and/or vibrating, stop it
184         // right now (before actually answering the call.)
185         PhoneApp.getInstance().getRinger().stopRing();
186
187         PhoneUtils.setAudioControlState(PhoneUtils.AUDIO_OFFHOOK);
188
189         boolean answered = false;
190         Call call = phone.getRingingCall();
191
192         if (call != null && call.isRinging()) {
193             if (DBG) log("answerCall: call state = " + call.getState());
194             try {
195                 //if (DBG) log("sPhone.acceptCall");
196                 phone.acceptCall();
197                 answered = true;
198                 setAudioMode(phone.getContext(), AudioManager.MODE_IN_CALL);
199             } catch (CallStateException ex) {
200                 Log.w(LOG_TAG, "answerCall: caught " + ex, ex);
201             }
202         }
203         return answered;
204     }
205
206     /**
207      * Smart "hang up" helper method which hangs up exactly one connection,
208      * based on the current Phone state, as follows:
209      * <ul>
210      * <li>If there's a ringing call, hang that up.
211      * <li>Else if there's a foreground call, hang that up.
212      * <li>Else if there's a background call, hang that up.
213      * <li>Otherwise do nothing.
214      * </ul>
215      * @return true if we successfully hung up, or false
216      *              if there were no active calls at all.
217      */
218     static boolean hangup(Phone phone) {
219         boolean hungup = false;
220         Call ringing = phone.getRingingCall();
221         Call fg = phone.getForegroundCall();
222         Call bg = phone.getBackgroundCall();
223
224         if (!ringing.isIdle()) {
225             if (DBG) log("HANGUP ringing call");
226             hungup = hangup(ringing);
227         } else if (!fg.isIdle()) {
228             if (DBG) log("HANGUP foreground call");
229             hungup = hangup(fg);
230         } else if (!bg.isIdle()) {
231             if (DBG) log("HANGUP background call");
232             hungup = hangup(bg);
233         }
234
235         if (DBG) log("hungup=" + hungup);
236
237         return hungup;
238     }
239
240     static boolean hangupRingingCall(Phone phone) {
241         if (DBG) log("hangup ringing call");
242         return hangup(phone.getRingingCall());
243     }
244
245     static boolean hangupActiveCall(Phone phone) {
246         if (DBG) log("hangup active call");
247         return hangup(phone.getForegroundCall());
248     }
249
250     static boolean hangupHoldingCall(Phone phone) {
251         if (DBG) log("hangup holding call");
252         return hangup(phone.getBackgroundCall());
253     }
254
255     static boolean hangup(Call call) {
256         try {
257             call.hangup();
258             return true;
259         } catch (CallStateException ex) {
260             Log.e(LOG_TAG, "Call hangup: caught " + ex, ex);
261         }
262
263         return false;
264     }
265
266     static void hangup(Connection c) {
267         try {
268             if (c != null) {
269                 c.hangup();
270             }
271         } catch (CallStateException ex) {
272             Log.w(LOG_TAG, "Connection hangup: caught " + ex, ex);
273         }
274     }
275
276     static boolean answerAndEndHolding(Phone phone) {
277         if (DBG) log("end holding & answer waiting: 1");
278         if (!hangupHoldingCall(phone)) {
279             Log.e(LOG_TAG, "end holding failed!");
280             return false;
281         }
282
283         if (DBG) log("end holding & answer waiting: 2");
284         return answerCall(phone);
285
286     }
287
288     static boolean answerAndEndActive(Phone phone) {
289         if (DBG) log("answerAndEndActive()...");
290
291         // Unlike the answerCall() method, we *don't* need to stop the
292         // ringer or change audio modes here since the user is already
293         // in-call, which means that the audio mode is already set
294         // correctly, and that we wouldn't have started the ringer in the
295         // first place.
296
297         // hanging up the active call also accepts the waiting call
298         return hangupActiveCall(phone);
299     }
300
301     /**
302      * Dial the number using the phone passed in.
303      *
304      * @param phone the Phone object.
305      * @param number the number to be dialed.
306      * @return either CALL_STATUS_DIALED, CALL_STATUS_DIALED_MMI, or CALL_STATUS_FAILED
307      */
308     static int placeCall(Phone phone, String number, Uri contactRef) {
309         int status = CALL_STATUS_DIALED;
310         try {
311             if (DBG) log("placeCall: '" + number + "'...");
312
313             Connection cn = phone.dial(number);
314             if (DBG) log("===> phone.dial() returned: " + cn);
315
316             // Presently, null is returned for MMI codes
317             if (cn == null) {
318                 if (DBG) log("dialed MMI code: " + number);
319                 status = CALL_STATUS_DIALED_MMI;
320             } else {
321                 PhoneUtils.setAudioControlState(PhoneUtils.AUDIO_OFFHOOK);
322
323                 // phone.dial() succeeded: we're now in a normal phone call.
324                 // attach the URI to the CallerInfo Object if it is there,
325                 // otherwise just attach the Uri Reference.
326                 // if the uri does not have a "content" scheme, then we treat
327                 // it as if it does NOT have a unique reference.
328                 String content = phone.getContext().getContentResolver().SCHEME_CONTENT;
329                 if ((contactRef != null) && (contactRef.getScheme().equals(content))) {
330                     Object userDataObject = cn.getUserData();
331                     if (userDataObject == null) {
332                         cn.setUserData(contactRef);
333                     } else {
334                         if (userDataObject instanceof CallerInfo) {
335                             ((CallerInfo) userDataObject).contactRefUri = contactRef;
336                         } else {
337                             ((CallerInfoToken) userDataObject).currentInfo.contactRefUri =
338                                 contactRef;
339                         }
340                     }
341                 }
342                 setAudioMode(phone.getContext(), AudioManager.MODE_IN_CALL);
343             }
344         } catch (CallStateException ex) {
345             Log.w(LOG_TAG, "PhoneUtils: Exception from phone.dial()", ex);
346             status = CALL_STATUS_FAILED;
347         }
348
349         return status;
350     }
351
352     static void switchHoldingAndActive(Phone phone) {
353         try {
354             if (DBG) log("switchHoldingAndActive");
355             phone.switchHoldingAndActive();
356         } catch (CallStateException ex) {
357             Log.w(LOG_TAG, "switchHoldingAndActive: caught " + ex, ex);
358         }
359     }
360
361     /**
362      * Restore the mute setting from the earliest connection of the
363      * foreground call.
364      */
365     static Boolean restoreMuteState(Phone phone) {
366         //get the earliest connection
367         Connection c = phone.getForegroundCall().getEarliestConnection();
368
369         // only do this if connection is not null.
370         if (c != null) {
371
372             // retrieve the mute value.
373             Boolean shouldMute = sConnectionMuteTable.get(
374                     phone.getForegroundCall().getEarliestConnection());
375             if (shouldMute == null) {
376                 if (DBG) log("problem retrieving mute value for this connection.");
377                 shouldMute = Boolean.FALSE;
378             }
379
380             // set the mute value and return the result.
381             setMute (phone, shouldMute.booleanValue());
382             return shouldMute;
383         }
384         return Boolean.valueOf(getMute (phone));
385     }
386
387     static void mergeCalls(Phone phone) {
388         try {
389             if (DBG) log("mergeCalls");
390             phone.conference();
391         } catch (CallStateException ex) {
392             Log.w(LOG_TAG, "mergeCalls: caught " + ex, ex);
393         }
394     }
395
396     static void separateCall(Connection c) {
397         try {
398             if (DBG) log("separateCall: " + c.getAddress());
399             c.separate();
400         } catch (CallStateException ex) {
401             Log.w(LOG_TAG, "separateCall: caught " + ex, ex);
402         }
403     }
404
405     /**
406      * Handle the MMIInitiate message and put up an alert that lets
407      * the user cancel the operation, if applicable.
408      *
409      * @param context context to get strings.
410      * @param mmiCode the MmiCode object being started.
411      * @param buttonCallbackMessage message to post when button is clicked.
412      * @param previousAlert a previous alert used in this activity.
413      * @return the dialog handle
414      */
415     static Dialog displayMMIInitiate(Context context,
416                                           MmiCode mmiCode,
417                                           Message buttonCallbackMessage,
418                                           Dialog previousAlert) {
419         if (DBG) log("displayMMIInitiate: " + mmiCode);
420         if (previousAlert != null) {
421             previousAlert.dismiss();
422         }
423
424         // The UI paradigm we are using now requests that all dialogs have
425         // user interaction, and that any other messages to the user should
426         // be by way of Toasts.
427         //
428         // In adhering to this request, all MMI initiating "OK" dialogs
429         // (non-cancelable MMIs) that end up being closed when the MMI
430         // completes (thereby showing a completion dialog) are being
431         // replaced with Toasts.
432         //
433         // As a side effect, moving to Toasts for the non-cancelable MMIs
434         // also means that buttonCallbackMessage (which was tied into "OK")
435         // is no longer invokable for these dialogs.  This is not a problem
436         // since the only callback messages we supported were for cancelable
437         // MMIs anyway.
438         //
439         // A cancelable MMI is really just a USSD request. The term
440         // "cancelable" here means that we can cancel the request when the
441         // system prompts us for a response, NOT while the network is
442         // processing the MMI request.  Any request to cancel a USSD while
443         // the network is NOT ready for a response may be ignored.
444         //
445         // With this in mind, we replace the cancelable alert dialog with
446         // a progress dialog, displayed until we receive a request from
447         // the the network.  For more information, please see the comments
448         // in the displayMMIComplete() method below.
449         //
450         // Anything that is NOT a USSD request is a normal MMI request,
451         // which will bring up a toast (desribed above).
452         boolean isCancelable = (mmiCode != null) && mmiCode.isCancelable();
453
454         if (!isCancelable) {
455             if (DBG) log("not a USSD code, displaying status toast.");
456             CharSequence text = context.getText(R.string.mmiStarted);
457             Toast.makeText(context, text, Toast.LENGTH_SHORT)
458                 .show();
459             return null;
460         } else {
461             if (DBG) log("running USSD code, displaying indeterminate progress.");
462
463             // create the indeterminate progress dialog and display it.
464             ProgressDialog pd = new ProgressDialog(context);
465             pd.setMessage(context.getText(R.string.ussdRunning));
466             pd.setCancelable(false);
467             pd.setIndeterminate(true);
468             pd.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
469
470             pd.show();
471
472             return pd;
473         }
474
475     }
476
477     /**
478      * Handle the MMIComplete message and fire off an intent to display
479      * the message.
480      *
481      * @param context context to get strings.
482      * @param mmiCode MMI result.
483      * @param previousAlert a previous alert used in this activity.
484      */
485     static void displayMMIComplete(final Phone phone, Context context, final MmiCode mmiCode,
486             Message dismissCallbackMessage,
487             AlertDialog previousAlert) {
488         CharSequence text;
489         int title = 0;  // title for the progress dialog, if needed.
490         MmiCode.State state = mmiCode.getState();
491
492         if (DBG) log("displayMMIComplete: state=" + state);
493
494         switch (state) {
495             case PENDING:
496                 // USSD code asking for feedback from user.
497                 text = mmiCode.getMessage();
498                 if (DBG) log("- using text from PENDING MMI message: '" + text + "'");
499                 break;
500             case CANCELLED:
501                 text = context.getText(R.string.mmiCancelled);
502                 break;
503             case COMPLETE:
504                 if (PhoneApp.getInstance().getPUKEntryActivity() != null) {
505                     // if an attempt to unPUK the device was made, we specify
506                     // the title and the message here.
507                     title = com.android.internal.R.string.PinMmi;
508                     text = context.getText(R.string.puk_unlocked);
509                     break;
510                 }
511                 // All other conditions for the COMPLETE mmi state will cause
512                 // the case to fall through to message logic in common with
513                 // the FAILED case.
514
515             case FAILED:
516                 text = mmiCode.getMessage();
517                 if (DBG) log("- using text from MMI message: '" + text + "'");
518                 break;
519             default:
520                 throw new IllegalStateException("Unexpected MmiCode state: " + state);
521         }
522
523         if (previousAlert != null) {
524             previousAlert.dismiss();
525         }
526
527         // Check to see if a UI exists for the PUK activation.  If it does
528         // exist, then it indicates that we're trying to unblock the PUK.
529         PhoneApp app = PhoneApp.getInstance();
530         if ((app.getPUKEntryActivity() != null) && (state == MmiCode.State.COMPLETE)){
531             if (DBG) log("displaying PUK unblocking progress dialog.");
532
533             // create the progress dialog, make sure the flags and type are
534             // set correctly.
535             ProgressDialog pd = new ProgressDialog(app);
536             pd.setTitle(title);
537             pd.setMessage(text);
538             pd.setCancelable(false);
539             pd.setIndeterminate(true);
540             pd.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
541             pd.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
542
543             // display the dialog
544             pd.show();
545
546             // indicate to the Phone app that the progress dialog has
547             // been assigned for the PUK unlock / SIM READY process.
548             app.setPukEntryProgressDialog(pd);
549
550         } else {
551             // In case of failure to unlock, we'll need to reset the
552             // PUK unlock activity, so that the user may try again.
553             if (app.getPUKEntryActivity() != null) {
554                 app.setPukEntryActivity(null);
555             }
556
557             // A USSD in a pending state means that it is still
558             // interacting with the user.
559             if (state != MmiCode.State.PENDING) {
560                 if (DBG) log("MMI code has finished running.");
561
562                 // displaying system alert dialog on the screen instead of
563                 // using another activity to display the message.  This
564                 // places the message at the forefront of the UI.
565                 AlertDialog newDialog = new AlertDialog.Builder(context)
566                         .setMessage(text)
567                         .setPositiveButton(R.string.ok, null)
568                         .setCancelable(true)
569                         .create();
570
571                 newDialog.getWindow().setType(
572                         WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
573                 newDialog.getWindow().addFlags(
574                         WindowManager.LayoutParams.FLAG_DIM_BEHIND);
575
576                 newDialog.show();
577             } else {
578                 if (DBG) log("USSD code has requested user input. Constructing input dialog.");
579
580                 // USSD MMI code that is interacting with the user.  The
581                 // basic set of steps is this:
582                 //   1. User enters a USSD request
583                 //   2. We recognize the request and displayMMIInitiate
584                 //      (above) creates a progress dialog.
585                 //   3. Request returns and we get a PENDING or COMPLETE
586                 //      message.
587                 //   4. These MMI messages are caught in the PhoneApp
588                 //      (onMMIComplete) and the InCallScreen
589                 //      (mHandler.handleMessage) which bring up this dialog
590                 //      and closes the original progress dialog,
591                 //      respectively.
592                 //   5. If the message is anything other than PENDING,
593                 //      we are done, and the alert dialog (directly above)
594                 //      displays the outcome.
595                 //   6. If the network is requesting more information from
596                 //      the user, the MMI will be in a PENDING state, and
597                 //      we display this dialog with the message.
598                 //   7. User input, or cancel requests result in a return
599                 //      to step 1.  Keep in mind that this is the only
600                 //      time that a USSD should be canceled.
601
602                 // inflate the layout with the scrolling text area for the dialog.
603                 LayoutInflater inflater = (LayoutInflater) context.getSystemService(
604                         Context.LAYOUT_INFLATER_SERVICE);
605                 View dialogView = inflater.inflate(R.layout.dialog_ussd_response, null);
606
607                 // get the input field.
608                 final EditText inputText = (EditText) dialogView.findViewById(R.id.input_field);
609
610                 // specify the dialog's click listener, with SEND and CANCEL logic.
611                 final DialogInterface.OnClickListener mUSSDDialogListener =
612                     new DialogInterface.OnClickListener() {
613                         public void onClick(DialogInterface dialog, int whichButton) {
614                             switch (whichButton) {
615                                 case DialogInterface.BUTTON1:
616                                     phone.sendUssdResponse(inputText.getText().toString());
617                                     break;
618                                 case DialogInterface.BUTTON2:
619                                     if (mmiCode.isCancelable()) {
620                                         mmiCode.cancel();
621                                     }
622                                     break;
623                             }
624                         }
625                     };
626
627                 // build the dialog
628                 final AlertDialog newDialog = new AlertDialog.Builder(context)
629                         .setMessage(text)
630                         .setView(dialogView)
631                         .setPositiveButton(R.string.send_button, mUSSDDialogListener)
632                         .setNegativeButton(R.string.cancel, mUSSDDialogListener)
633                         .setCancelable(false)
634                         .create();
635
636                 // attach the key listener to the dialog's input field and make
637                 // sure focus is set.
638                 final View.OnKeyListener mUSSDDialogInputListener =
639                     new View.OnKeyListener() {
640                         public boolean onKey(View v, int keyCode, KeyEvent event) {
641                             switch (keyCode) {
642                                 case KeyEvent.KEYCODE_CALL:
643                                 case KeyEvent.KEYCODE_ENTER:
644                                     phone.sendUssdResponse(inputText.getText().toString());
645                                     newDialog.dismiss();
646                                     return true;
647                             }
648                             return false;
649                         }
650                     };
651                 inputText.setOnKeyListener(mUSSDDialogInputListener);
652                 inputText.requestFocus();
653
654                 // set the window properties of the dialog
655                 newDialog.getWindow().setType(
656                         WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
657                 newDialog.getWindow().addFlags(
658                         WindowManager.LayoutParams.FLAG_DIM_BEHIND);
659
660                 // now show the dialog!
661                 newDialog.show();
662             }
663         }
664     }
665
666     /**
667      * Cancels the current pending MMI operation, if applicable.
668      * @return true if we canceled an MMI operation, or false
669      *         if the current pending MMI wasn't cancelable
670      *         or if there was no current pending MMI at all.
671      *
672      * @see displayMMIInitiate
673      */
674     static boolean cancelMmiCode(Phone phone) {
675         List<? extends MmiCode> pendingMmis = phone.getPendingMmiCodes();
676         int count = pendingMmis.size();
677         if (DBG) log("cancelMmiCode: num pending MMIs = " + count);
678
679         boolean canceled = false;
680         if (count > 0) {
681             // assume that we only have one pending MMI operation active at a time.
682             // I don't think it's possible to enter multiple MMI codes concurrently
683             // in the phone UI, because during the MMI operation, an Alert panel
684             // is displayed, which prevents more MMI code from being entered.
685             MmiCode mmiCode = pendingMmis.get(0);
686             if (mmiCode.isCancelable()) {
687                 mmiCode.cancel();
688                 canceled = true;
689             }
690         }
691
692         return canceled;
693     }
694
695     public static class VoiceMailNumberMissingException extends Exception {
696         VoiceMailNumberMissingException() {
697             super();
698         }
699
700         VoiceMailNumberMissingException(String msg) {
701             super(msg);
702         }
703     }
704
705     /**
706      * Gets the phone number to be called from an intent.  Requires a Context
707      * to access the contacts database, and a Phone to access the voicemail
708      * number.
709      *
710      * <p>If <code>phone</code> is <code>null</code>, the function will return
711      * <code>null</code> for <code>voicemail:</code> URIs;
712      * if <code>context</code> is <code>null</code>, the function will return
713      * <code>null</code> for person/phone URIs.</p>
714      *
715      * @param context a context to use (or
716      * @param phone the phone on which the number would be called
717      * @param intent the intent
718      *
719      * @throws VoiceMailNumberMissingException if <code>intent</code> contains
720      *         a <code>voicemail:</code> URI, but <code>phone</code> does not
721      *         have a voicemail number set.
722      *
723      * @return the phone number that would be called by the intent,
724      *         or <code>null</code> if the number cannot be found.
725      */
726     static String getNumberFromIntent(Context context, Phone phone, Intent intent)
727             throws VoiceMailNumberMissingException {
728         final String number = PhoneNumberUtils.getNumberFromIntent(intent, context);
729
730         // Check for a voicemail-dailing request.  If the voicemail number is
731         // empty, throw a VoiceMailNumberMissingException.
732         if (intent.getData().getScheme().equals("voicemail") &&
733                 (number == null || TextUtils.isEmpty(number)))
734             throw new VoiceMailNumberMissingException();
735
736         return number;
737     }
738
739     /**
740      * Returns the caller-id info corresponding to the specified Connection.
741      * (This is just a simple wrapper around CallerInfo.getCallerInfo(): we
742      * extract a phone number from the specified Connection, and feed that
743      * number into CallerInfo.getCallerInfo().)
744      *
745      * The returned CallerInfo may be null in certain error cases, like if the
746      * specified Connection was null, or if we weren't able to get a valid
747      * phone number from the Connection.
748      *
749      * Finally, if the getCallerInfo() call did succeed, we save the resulting
750      * CallerInfo object in the "userData" field of the Connection.
751      *
752      * NOTE: This API should be avoided, with preference given to the
753      * asynchronous startGetCallerInfo API.
754      */
755     static CallerInfo getCallerInfo(Context context, Connection c) {
756         CallerInfo info = null;
757
758         if (c != null) {
759             //See if there is a URI attached.  If there is, this means
760             //that there is no CallerInfo queried yet, so we'll need to
761             //replace the URI with a full CallerInfo object.
762             Object userDataObject = c.getUserData();
763             if (userDataObject instanceof Uri) {
764                 info = CallerInfo.getCallerInfo(context, (Uri) userDataObject);
765                 if (info != null) {
766                     c.setUserData(info);
767                 }
768             } else {
769                 if (userDataObject instanceof CallerInfoToken) {
770                     //temporary result, while query is running
771                     info = ((CallerInfoToken) userDataObject).currentInfo;
772                 } else {
773                     //final query result
774                     info = (CallerInfo) userDataObject;
775                 }
776                 if (info == null) {
777                     // No URI, or Existing CallerInfo, so we'll have to make do with
778                     // querying a new CallerInfo using the connection's phone number.
779                     String number = c.getAddress();
780
781                     if (DBG) log("getCallerInfo: number = " + number);
782
783                     if (!TextUtils.isEmpty(number)) {
784                         info = CallerInfo.getCallerInfo(context, number);
785                         if (info != null) {
786                             c.setUserData(info);
787                         }
788                     }
789                 }
790             }
791         }
792         return info;
793     }
794
795     /**
796      * Class returned by the startGetCallerInfo call to package a temporary
797      * CallerInfo Object, to be superceded by the CallerInfo Object passed
798      * into the listener when the query with token mAsyncQueryToken is complete.
799      */
800     public static class CallerInfoToken {
801         /**indicates that there will no longer be updates to this request.*/
802         public boolean isFinal;
803
804         public CallerInfo currentInfo;
805         public CallerInfoAsyncQuery asyncQuery;
806     }
807
808     /**
809      * Start a CallerInfo Query based on the earliest connection in the call.
810      */
811     static CallerInfoToken startGetCallerInfo(Context context, Call call,
812             CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie) {
813         Connection conn = call.getEarliestConnection();
814         return startGetCallerInfo(context, conn, listener, cookie);
815     }
816
817     /**
818      * place a temporary callerinfo object in the hands of the caller and notify
819      * caller when the actual query is done.
820      */
821     static CallerInfoToken startGetCallerInfo(Context context, Connection c,
822             CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie) {
823         CallerInfoToken cit;
824
825         if (c == null) {
826             //TODO: perhaps throw an exception here.
827             cit = new CallerInfoToken();
828             cit.asyncQuery = null;
829             return cit;
830         }
831
832         // There are now 3 states for the userdata.
833         //   1. Uri - query has not been executed yet
834         //   2. CallerInfoToken - query is executing, but has not completed.
835         //   3. CallerInfo - query has executed.
836         // In each case we have slightly different behaviour:
837         //   1. If the query has not been executed yet (Uri or null), we start
838         //      query execution asynchronously, and note it by attaching a
839         //      CallerInfoToken as the userData.
840         //   2. If the query is executing (CallerInfoToken), we've essentially
841         //      reached a state where we've received multiple requests for the
842         //      same callerInfo.  That means that once the query is complete,
843         //      we'll need to execute the additional listener requested.
844         //   3. If the query has already been executed (CallerInfo), we just
845         //      return the CallerInfo object as expected.
846         //   4. Regarding isFinal - there are cases where the CallerInfo object
847         //      will not be attached, like when the number is empty (caller id
848         //      blocking).  This flag is used to indicate that the
849         //      CallerInfoToken object is going to be permanent since no
850         //      query results will be returned.  In the case where a query
851         //      has been completed, this flag is used to indicate to the caller
852         //      that the data will not be updated since it is valid.
853         //
854         //      Note: For the case where a number is NOT retrievable, we leave
855         //      the CallerInfo as null in the CallerInfoToken.  This is
856         //      something of a departure from the original code, since the old
857         //      code manufactured a CallerInfo object regardless of the query
858         //      outcome.  From now on, we will append an empty CallerInfo
859         //      object, to mirror previous behaviour, and to avoid Null Pointer
860         //      Exceptions.
861         Object userDataObject = c.getUserData();
862         if (userDataObject instanceof Uri) {
863             //create a dummy callerinfo, populate with what we know from URI.
864             cit = new CallerInfoToken();
865             cit.currentInfo = new CallerInfo();
866             cit.asyncQuery = CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context,
867                     (Uri) userDataObject, sCallerInfoQueryListener, c);
868             cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie);
869             cit.isFinal = false;
870
871             c.setUserData(cit);
872
873             if (DBG) log("startGetCallerInfo: query based on Uri: " + userDataObject);
874
875         } else if (userDataObject == null) {
876             // No URI, or Existing CallerInfo, so we'll have to make do with
877             // querying a new CallerInfo using the connection's phone number.
878             String number = c.getAddress();
879
880             cit = new CallerInfoToken();
881             cit.currentInfo = new CallerInfo();
882
883             if (DBG) log("startGetCallerInfo: number = " + number);
884
885             // handling case where number is null (caller id hidden) as well.
886             if (!TextUtils.isEmpty(number)) {
887                 cit.currentInfo.phoneNumber = number;
888                 cit.asyncQuery = CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context,
889                         number, sCallerInfoQueryListener, c);
890                 cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie);
891                 cit.isFinal = false;
892             } else {
893                 // This is the case where we are querying on a number that
894                 // is null or empty, like a caller whose caller id is
895                 // blocked or empty (CLIR).  The previous behaviour was to
896                 // throw a null CallerInfo object back to the user, but
897                 // this departure is somewhat cleaner.
898                 if (DBG) log("startGetCallerInfo: No query to start, send trivial reply.");
899                 cit.isFinal = true; // please see note on isFinal, above.
900             }
901
902             c.setUserData(cit);
903
904             if (DBG) log("startGetCallerInfo: query based on number: " + number);
905
906         } else if (userDataObject instanceof CallerInfoToken) {
907             // query is running, just tack on this listener to the queue.
908             cit = (CallerInfoToken) userDataObject;
909
910             // handling case where number is null (caller id hidden) as well.
911             if (cit.asyncQuery != null) {
912                 cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie);
913
914                 if (DBG) log("startGetCallerInfo: query already running, adding listener: " +
915                         listener.getClass().toString());
916             } else {
917                 if (DBG) log("startGetCallerInfo: No query to attach to, send trivial reply.");
918                 if (cit.currentInfo == null) {
919                     cit.currentInfo = new CallerInfo();
920                 }
921                 cit.isFinal = true; // please see note on isFinal, above.
922             }
923         } else {
924             cit = new CallerInfoToken();
925             cit.currentInfo = (CallerInfo) userDataObject;
926             cit.asyncQuery = null;
927             cit.isFinal = true;
928             // since the query is already done, call the listener.
929             if (DBG) log("startGetCallerInfo: query already done, returning CallerInfo");
930         }
931         return cit;
932     }
933
934     /**
935      * Implemented for CallerInfo.OnCallerInfoQueryCompleteListener interface.
936      * Updates the connection's userData when called.
937      */
938     private static final int QUERY_TOKEN = -1;
939     static CallerInfoAsyncQuery.OnQueryCompleteListener sCallerInfoQueryListener =
940         new CallerInfoAsyncQuery.OnQueryCompleteListener () {
941             public void onQueryComplete(int token, Object cookie, CallerInfo ci){
942                 if (DBG) log("query complete, updating connection.userdata");
943
944                 ((Connection) cookie).setUserData(ci);
945             }
946         };
947
948     static void saveToContact(Context context, String number) {
949         Intent intent = new Intent(Contacts.Intents.Insert.ACTION,
950                 Contacts.People.CONTENT_URI);
951         intent.putExtra(Contacts.Intents.Insert.PHONE, number);
952         context.startActivity(intent);
953     }
954
955     /**
956      * Returns a single "name" for the specified Connection.
957      * This may be the caller name, the phone number, or a generic "unknown"
958      * string, depending on what info is available.
959      *
960      * NOTE: This API should be avoided, with preference given to the
961      * asynchronous startGetCallerInfo API, used in conjunction with
962      * getCompactNameFromCallerInfo().
963      */
964     static String getCompactName(Context context, Connection conn) {
965         CallerInfo info = getCallerInfo(context, conn);
966         if (DBG) log("getCompactName: info = " + info);
967
968         String compactName = null;
969         if (info != null) {
970             compactName = info.name;
971             if (compactName == null) {
972                 compactName = info.phoneNumber;
973             }
974         }
975         // TODO: figure out UNKNOWN, PRIVATE numbers?
976         if (compactName == null) {
977             compactName = context.getString(R.string.unknown);
978         }
979         return compactName;
980     }
981
982     /**
983      * Returns a single "name" for the specified Call.
984      * If the call only has a single connection, this is
985      * just like calling getCompactName() on that connection.
986      * But if this call has more than one connection,
987      * return a generic string like "Conference call".
988      *
989      * NOTE: This API should be avoided, with preference given to the
990      * asynchronous startGetCallerInfo API, used in conjunction with
991      * getCompactNameFromCallerInfo().
992      */
993     static String getCompactName(Context context, Call call) {
994         if (isConferenceCall(call)) {
995             return context.getString(R.string.confCall);
996         }
997         Connection conn = call.getEarliestConnection();  // may be null
998         return getCompactName(context, conn);  // OK if conn is null
999     }
1000
1001     /**
1002      * Returns a single "name" for the specified given a CallerInfo object.
1003      * If the name is null, return defaultString as the default value, usually
1004      * context.getString(R.string.unknown).
1005      */
1006     static String getCompactNameFromCallerInfo(CallerInfo ci, Context context) {
1007         if (DBG) log("getCompactNameFromCallerInfo: info = " + ci);
1008
1009         String compactName = null;
1010         if (ci != null) {
1011             compactName = ci.name;
1012             if (compactName == null) {
1013                 compactName = ci.phoneNumber;
1014             }
1015         }
1016         // TODO: figure out UNKNOWN, PRIVATE numbers?
1017         if (compactName == null) {
1018             compactName = context.getString(R.string.unknown);
1019         }
1020         return compactName;
1021     }
1022
1023     /**
1024      * Returns true if the specified Call is a "conference call", meaning
1025      * that it owns more than one Connection object.
1026      *
1027      * Watch out: This method simply checks the number of Connections,
1028      * *not* their states.  So if a Call has (for example) one ACTIVE
1029      * connection and one DISCONNECTED connection, this method will return
1030      * true (which is unintuitive, since the Call isn't *really* a
1031      * conference call any more.)
1032      *
1033      * @return true if the specified call has more than one connection (in any state.)
1034      */
1035     static boolean isConferenceCall(Call call) {
1036         List<Connection> connections = call.getConnections();
1037         if (connections != null && connections.size() > 1) {
1038             return true;
1039         }
1040         return false;
1041
1042         // TODO: We may still want to change the semantics of this method
1043         // to say that a given call is only really a conference call if
1044         // the number of ACTIVE connections, not the total number of
1045         // connections, is greater than one.  (See warning comment in the
1046         // javadoc above.)
1047         // Here's an implementation of that:
1048         //        if (connections == null) {
1049         //            return false;
1050         //        }
1051         //        int numActiveConnections = 0;
1052         //        for (Connection conn : connections) {
1053         //            if (DBG) log("  - CONN: " + conn + ", state = " + conn.getState());
1054         //            if (conn.getState() == Call.State.ACTIVE) numActiveConnections++;
1055         //            if (numActiveConnections > 1) {
1056         //                return true;
1057         //            }
1058         //        }
1059         //        return false;
1060     }
1061
1062     /**
1063      * Launch the Dialer to start a new call.
1064      * This is just a wrapper around the ACTION_DIAL intent.
1065      */
1066     static void startNewCall(final Phone phone) {
1067         final KeyguardManager keyguardManager = PhoneApp.getInstance().getKeyguardManager();
1068         if (!keyguardManager.inKeyguardRestrictedInputMode()) {
1069             internalStartNewCall(phone);
1070         } else {
1071             keyguardManager.exitKeyguardSecurely(new KeyguardManager.OnKeyguardExitResult() {
1072                 public void onKeyguardExitResult(boolean success) {
1073                     if (success) {
1074                         internalStartNewCall(phone);
1075                     }
1076                 }
1077             });
1078         }
1079     }
1080
1081     private static void internalStartNewCall(Phone phone) {
1082         // Sanity-check that this is OK given the current state of the phone.
1083         if (!okToAddCall(phone)) {
1084             Log.w(LOG_TAG, "startNewCall: can't add a new call in the current state");
1085             dumpCallState(phone);
1086             return;
1087         }
1088
1089         // if applicable, mute the call while we're showing the add call UI.
1090         if (!phone.getForegroundCall().isIdle()) {
1091             setMuteInternal(phone, true);
1092             // Inform the phone app that this mute state was NOT done
1093             // voluntarily by the User.
1094             PhoneApp.getInstance().setRestoreMuteOnInCallResume(true);
1095         }
1096
1097         Intent intent = new Intent(Intent.ACTION_DIAL);
1098         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1099
1100         // when we request the dialer come up, we also want to inform
1101         // it that we're going through the "add call" option from the
1102         // InCallScreen / PhoneUtils.
1103         intent.putExtra(ADD_CALL_MODE_KEY, true);
1104
1105         PhoneApp.getInstance().startActivity(intent);
1106     }
1107
1108     /**
1109      * Brings up the UI used to handle an incoming call.
1110      *
1111      * Originally, this brought up an IncomingCallPanel instance
1112      * (which was a subclass of Dialog) on top of whatever app
1113      * was currently running.  Now, we take you directly to the
1114      * in-call screen, whose CallCard automatically does the right
1115      * thing if there's a Call that's currently ringing.
1116      */
1117     static void showIncomingCallUi() {
1118         if (DBG) log("showIncomingCallUi()...");
1119         PhoneApp app = PhoneApp.getInstance();
1120
1121         // Before bringing up the "incoming call" UI, force any system
1122         // dialogs (like "recent tasks" or the power dialog) to close first.
1123         app.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
1124
1125         // Go directly to the in-call screen.
1126         // (No need to do anything special if we're already on the in-call
1127         // screen; it'll notice the phone state change and update itself.)
1128         app.displayCallScreen();
1129     }
1130
1131     static void turnOnSpeaker(Context context, boolean flag) {
1132         if (DBG) log("turnOnSpeaker: " + flag);
1133         AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
1134
1135         audioManager.setSpeakerphoneOn(flag);
1136         // record the speaker-enable value
1137         sIsSpeakerEnabled = flag;
1138         if (flag) {
1139             NotificationMgr.getDefault().notifySpeakerphone();
1140         } else {
1141             NotificationMgr.getDefault().cancelSpeakerphone();
1142         }
1143
1144         // We also need to make a fresh call to PhoneApp.updateWakeState()
1145         // any time the speaker state changes, since the screen timeout is
1146         // sometimes different depending on whether or not the speaker is
1147         // in use.
1148         PhoneApp app = PhoneApp.getInstance();
1149         app.updateWakeState();
1150     }
1151
1152     /**
1153      * Restore the speaker mode, called after a wired headset disconnect
1154      * event.
1155      */
1156     static void restoreSpeakerMode(Context context) {
1157         if (DBG) log("restoreSpeakerMode, restoring to: " + sIsSpeakerEnabled);
1158
1159         // change the mode if needed.
1160         if (isSpeakerOn(context) != sIsSpeakerEnabled) {
1161             turnOnSpeaker(context, sIsSpeakerEnabled);
1162         }
1163     }
1164
1165     static boolean isSpeakerOn(Context context) {
1166         AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
1167         return audioManager.isSpeakerphoneOn();
1168     }
1169
1170     /**
1171      * Wrapper around Phone.setMute() that also updates the mute icon in
1172      * the status bar.
1173      *
1174      * All muting / unmuting from the in-call UI should go through this
1175      * wrapper.
1176      */
1177     static void setMute(Phone phone, boolean muted) {
1178         // make the call to mute the audio
1179         setMuteInternal(phone, muted);
1180
1181         // update the foreground connections to match.  This includes
1182         // all the connections on conference calls.
1183         for (Connection cn : phone.getForegroundCall().getConnections()) {
1184             if (sConnectionMuteTable.get(cn) == null) {
1185                 if (DBG) log("problem retrieving mute value for this connection.");
1186             }
1187             sConnectionMuteTable.put(cn, Boolean.valueOf(muted));
1188         }
1189     }
1190
1191     /**
1192      * Internally used muting function.  All UI calls should use {@link setMute}
1193      */
1194     static void setMuteInternal(Phone phone, boolean muted) {
1195         if (DBG) log("setMute: " + muted);
1196         phone.setMute(muted);
1197         if (muted) {
1198             NotificationMgr.getDefault().notifyMute();
1199         } else {
1200             NotificationMgr.getDefault().cancelMute();
1201         }
1202     }
1203
1204     static boolean getMute(Phone phone) {
1205         return phone.getMute();
1206     }
1207
1208     /**
1209      * A really simple wrapper around AudioManager.setMode(),
1210      * with a bit of extra logging to help debug the exact
1211      * timing (and call stacks) for all our setMode() calls.
1212      *
1213      * Also, add additional state monitoring to determine
1214      * whether or not certain calls to change the audio mode
1215      * are ignored.
1216      */
1217     /* package */ static void setAudioMode(Context context, int mode) {
1218         if (DBG) Log.d(LOG_TAG, "PhoneUtils.setAudioMode(" + audioModeToString(mode) + ")...");
1219
1220         //decide whether or not to ignore the audio setting
1221         boolean ignore = false;
1222
1223         switch (sAudioBehaviourState) {
1224             case AUDIO_RINGING:
1225                 ignore = ((mode == AudioManager.MODE_NORMAL) || (mode == AudioManager.MODE_IN_CALL));
1226                 break;
1227             case AUDIO_OFFHOOK:
1228                 ignore = ((mode == AudioManager.MODE_NORMAL) || (mode == AudioManager.MODE_RINGTONE));
1229                 break;
1230             case AUDIO_IDLE:
1231             default:
1232                 ignore = (mode == AudioManager.MODE_IN_CALL);
1233                 break;
1234         }
1235
1236         if (!ignore) {
1237             AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
1238             // Enable stack dump only when actively debugging ("new Throwable()" is expensive!)
1239             if (DBG_SETAUDIOMODE_STACK) Log.d(LOG_TAG, "Stack:", new Throwable("stack dump"));
1240             audioManager.setMode(mode);
1241         } else {
1242             if (DBG) Log.d(LOG_TAG, "PhoneUtils.setAudioMode(), state is " + sAudioBehaviourState +
1243                     " ignoring " + audioModeToString(mode) + " request");
1244         }
1245     }
1246     private static String audioModeToString(int mode) {
1247         switch (mode) {
1248             case AudioManager.MODE_INVALID: return "MODE_INVALID";
1249             case AudioManager.MODE_CURRENT: return "MODE_CURRENT";
1250             case AudioManager.MODE_NORMAL: return "MODE_NORMAL";
1251             case AudioManager.MODE_RINGTONE: return "MODE_RINGTONE";
1252             case AudioManager.MODE_IN_CALL: return "MODE_IN_CALL";
1253             default: return String.valueOf(mode);
1254         }
1255     }
1256
1257     /**
1258      * Handles the wired headset button while in-call.
1259      *
1260      * This is called from the PhoneApp, not from the InCallScreen,
1261      * since the HEADSETHOOK button means "mute or unmute the current
1262      * call" *any* time a call is active, even if the user isn't actually
1263      * on the in-call screen.
1264      *
1265      * @return true if we consumed the event.
1266      */
1267     /* package */ static boolean handleHeadsetHook(Phone phone) {
1268         if (DBG) log("handleHeadsetHook()...");
1269
1270         // If the phone is totally idle, we ignore HEADSETHOOK events
1271         // (and instead let them fall through to the media player.)
1272         if (phone.getState() == Phone.State.IDLE) {
1273             return false;
1274         }
1275
1276         // Ok, the phone is in use.
1277         // The headset button button means "Answer" if an incoming call is
1278         // ringing.  If not, it toggles the mute / unmute state.
1279         //
1280         // And in any case we *always* consume this event; this means
1281         // that the usual mediaplayer-related behavior of the headset
1282         // button will NEVER happen while the user is on a call.
1283
1284         final boolean hasRingingCall = !phone.getRingingCall().isIdle();
1285         final boolean hasActiveCall = !phone.getForegroundCall().isIdle();
1286         final boolean hasHoldingCall = !phone.getBackgroundCall().isIdle();
1287
1288         if (hasRingingCall) {
1289             // If an incoming call is ringing, answer it (just like with the
1290             // CALL button):
1291             if (hasActiveCall && hasHoldingCall) {
1292                 if (DBG) log("handleHeadsetHook: ringing (both lines in use) ==> answer!");
1293                 answerAndEndActive(phone);
1294             } else {
1295                 if (DBG) log("handleHeadsetHook: ringing ==> answer!");
1296                 answerCall(phone);  // Automatically holds the current active call,
1297                                      // if there is one
1298             }
1299         } else {
1300             // No incoming ringing call.  Toggle the mute state.
1301             if (getMute(phone)) {
1302                 if (DBG) log("handleHeadsetHook: UNmuting...");
1303                 setMute(phone, false);
1304             } else {
1305                 if (DBG) log("handleHeadsetHook: muting...");
1306                 setMute(phone, true);
1307             }
1308         }
1309
1310         // Even if the InCallScreen is the current activity, there's no
1311         // need to force it to update, because (1) if we answered a
1312         // ringing call, the InCallScreen will imminently get a phone
1313         // state change event (causing an update), and (2) if we muted or
1314         // unmuted, the setMute() call automagically updates the status
1315         // bar, and there's no "mute" indication in the InCallScreen
1316         // itself (other than the menu item, which only ever stays
1317         // onscreen for a second anyway.)
1318
1319         return true;
1320     }
1321
1322     /**
1323      * Look for ANY connections on the phone that qualify as being
1324      * disconnected.
1325      *
1326      * @return true if we find a connection that is disconnected over
1327      * all the phone's call objects.
1328      */
1329     /* package */ static boolean hasDisconnectedConnections(Phone phone) {
1330         return hasDisconnectedConnections(phone.getForegroundCall()) ||
1331                 hasDisconnectedConnections(phone.getBackgroundCall()) ||
1332                 hasDisconnectedConnections(phone.getRingingCall());
1333     }
1334
1335     /**
1336      * Iterate over all connections in a call to see if there are any
1337      * that are not alive (disconnected or idle).
1338      *
1339      * @return true if we find a connection that is disconnected, and
1340      * pending removal via
1341      * {@link com.android.internal.telephony.gsm.GSMCall#clearDisconnected()}.
1342      */
1343     private static final boolean hasDisconnectedConnections(Call call) {
1344         // look through all connections for non-active ones.
1345         for (Connection c : call.getConnections()) {
1346             if (!c.isAlive()) {
1347                 return true;
1348             }
1349         }
1350         return false;
1351     }
1352
1353     //
1354     // Misc UI policy helper functions
1355     //
1356
1357     /**
1358      * @return true if we're allowed to swap calls, given the current
1359      * state of the Phone.
1360      */
1361     /* package */ static boolean okToSwapCalls(Phone phone) {
1362         // "Swap" is available if both lines are in use and there's no
1363         // incoming call.  (Actually we need to verify that the active
1364         // call really is in the ACTIVE state and the holding call really
1365         // is in the HOLDING state, since you *can't* actually swap calls
1366         // when the foreground call is DIALING or ALERTING.)
1367         return phone.getRingingCall().isIdle()
1368                 && (phone.getForegroundCall().getState() == Call.State.ACTIVE)
1369                 && (phone.getBackgroundCall().getState() == Call.State.HOLDING);
1370     }
1371
1372     /**
1373      * @return true if we're allowed to merge calls, given the current
1374      * state of the Phone.
1375      */
1376     /* package */ static boolean okToMergeCalls(Phone phone) {
1377         // "Merge" is available if both lines are in use and there's no
1378         // incoming call, *and* the current conference isn't already
1379         // "full".
1380         return phone.getRingingCall().isIdle() && phone.canConference();
1381     }
1382
1383     /**
1384      * @return true if the UI should let you add a new call, given the current
1385      * state of the Phone.
1386      */
1387     /* package */ static boolean okToAddCall(Phone phone) {
1388         // "Add call" is available only if ALL of the following are true:
1389         // - There's no incoming ringing call
1390         // - There's < 2 lines in use
1391         // - The foreground call is ACTIVE or IDLE or DISCONNECTED.
1392         //   (We mainly need to make sure it *isn't* DIALING or ALERTING.)
1393
1394         final boolean hasRingingCall = !phone.getRingingCall().isIdle();
1395         final boolean hasActiveCall = !phone.getForegroundCall().isIdle();
1396         final boolean hasHoldingCall = !phone.getBackgroundCall().isIdle();
1397         final boolean allLinesTaken = hasActiveCall && hasHoldingCall;
1398         final Call.State fgCallState = phone.getForegroundCall().getState();
1399
1400         return !hasRingingCall
1401                 && !allLinesTaken
1402                 && ((fgCallState == Call.State.ACTIVE)
1403                     || (fgCallState == Call.State.IDLE)
1404                     || (fgCallState == Call.State.DISCONNECTED));
1405     }
1406
1407
1408     //
1409     // General phone and call state debugging/testing code
1410     //
1411
1412     /* package */ static void dumpCallState(Phone phone) {
1413         Log.d(LOG_TAG, "############## dumpCallState() #############");
1414         Log.d(LOG_TAG, "---");
1415         Log.d(LOG_TAG, "--- Phone: " + phone);
1416         Log.d(LOG_TAG, "--- Overall Phone state: " + phone.getState());
1417         Log.d(LOG_TAG, "---");
1418
1419         Call fgCall = phone.getForegroundCall();
1420         Log.d(LOG_TAG, "--- FG call: " + fgCall);
1421         Log.d(LOG_TAG, "--- FG call state: " + fgCall.getState());
1422         Log.d(LOG_TAG, "--- FG call isAlive(): " + fgCall.getState().isAlive());
1423         Log.d(LOG_TAG, "--- FG call isRinging(): " + fgCall.getState().isRinging());
1424         Log.d(LOG_TAG, "--- FG call isDialing(): " + fgCall.getState().isDialing());
1425         Log.d(LOG_TAG, "--- FG call isIdle(): " + fgCall.isIdle());
1426         Log.d(LOG_TAG, "--- FG call hasConnections: " + fgCall.hasConnections());
1427         Log.d(LOG_TAG, "---");
1428
1429         Call bgCall = phone.getBackgroundCall();
1430         Log.d(LOG_TAG, "--- BG call: " + bgCall);
1431         Log.d(LOG_TAG, "--- BG call state: " + bgCall.getState());
1432         Log.d(LOG_TAG, "--- BG call isAlive(): " + bgCall.getState().isAlive());
1433         Log.d(LOG_TAG, "--- BG call isRinging(): " + bgCall.getState().isRinging());
1434         Log.d(LOG_TAG, "--- BG call isDialing(): " + bgCall.getState().isDialing());
1435         Log.d(LOG_TAG, "--- BG call isIdle(): " + bgCall.isIdle());
1436         Log.d(LOG_TAG, "--- BG call hasConnections: " + bgCall.hasConnections());
1437         Log.d(LOG_TAG, "---");
1438
1439         Call ringingCall = phone.getRingingCall();
1440         Log.d(LOG_TAG, "--- RINGING call: " + ringingCall);
1441         Log.d(LOG_TAG, "--- RINGING call state: " + ringingCall.getState());
1442         Log.d(LOG_TAG, "--- RINGING call isAlive(): " + ringingCall.getState().isAlive());
1443         Log.d(LOG_TAG, "--- RINGING call isRinging(): " + ringingCall.getState().isRinging());
1444         Log.d(LOG_TAG, "--- RINGING call isDialing(): " + ringingCall.getState().isDialing());
1445         Log.d(LOG_TAG, "--- RINGING call isIdle(): " + ringingCall.isIdle());
1446         Log.d(LOG_TAG, "--- RINGING call hasConnections: " + ringingCall.hasConnections());
1447         Log.d(LOG_TAG, "---");
1448
1449         final boolean hasRingingCall = !phone.getRingingCall().isIdle();
1450         final boolean hasActiveCall = !phone.getForegroundCall().isIdle();
1451         final boolean hasHoldingCall = !phone.getBackgroundCall().isIdle();
1452         final boolean allLinesTaken = hasActiveCall && hasHoldingCall;
1453         Log.d(LOG_TAG, "--- hasRingingCall: " + hasRingingCall);
1454         Log.d(LOG_TAG, "--- hasActiveCall: " + hasActiveCall);
1455         Log.d(LOG_TAG, "--- hasHoldingCall: " + hasHoldingCall);
1456         Log.d(LOG_TAG, "--- allLinesTaken: " + allLinesTaken);
1457
1458         // Watch out: the isRinging() call below does NOT tell us anything
1459         // about the state of the telephony layer; it merely tells us whether
1460         // the Ringer manager is currently playing the ringtone.
1461         boolean ringing = PhoneApp.getInstance().getRinger().isRinging();
1462         Log.d(LOG_TAG, "--- ringing (Ringer manager state): " + ringing);
1463         Log.d(LOG_TAG, "---");
1464         Log.d(LOG_TAG, "---------------------");
1465     }
1466
1467
1468     private static void log(String msg) {
1469         Log.d(LOG_TAG, "[PhoneUtils] " + msg);
1470     }
1471 }