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