Misc phone app code cleanup.
[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 given a CallerInfo object.
1134      * If the name is null, return defaultString as the default value, usually
1135      * context.getString(R.string.unknown).
1136      */
1137     static String getCompactNameFromCallerInfo(CallerInfo ci, Context context) {
1138         if (DBG) log("getCompactNameFromCallerInfo: info = " + ci);
1139
1140         String compactName = null;
1141         if (ci != null) {
1142             compactName = ci.name;
1143             if ((compactName == null) || (TextUtils.isEmpty(compactName))) {
1144                 compactName = ci.phoneNumber;
1145             }
1146         }
1147         // TODO: figure out UNKNOWN, PRIVATE numbers?
1148         if ((compactName == null) || (TextUtils.isEmpty(compactName))) {
1149             compactName = context.getString(R.string.unknown);
1150         }
1151         return compactName;
1152     }
1153
1154     /**
1155      * Returns true if the specified Call is a "conference call", meaning
1156      * that it owns more than one Connection object.  This information is
1157      * used to trigger certain UI changes that appear when a conference
1158      * call is active (like displaying the label "Conference call", and
1159      * enabling the "Manage conference" UI.)
1160      *
1161      * Watch out: This method simply checks the number of Connections,
1162      * *not* their states.  So if a Call has (for example) one ACTIVE
1163      * connection and one DISCONNECTED connection, this method will return
1164      * true (which is unintuitive, since the Call isn't *really* a
1165      * conference call any more.)
1166      *
1167      * @return true if the specified call has more than one connection (in any state.)
1168      */
1169     static boolean isConferenceCall(Call call) {
1170         // CDMA phones don't have the same concept of "conference call" as
1171         // GSM phones do; there's no special "conference call" state of
1172         // the UI or a "manage conference" function.  (Instead, when
1173         // you're in a 3-way call, all we can do is display the "generic"
1174         // state of the UI.)  So as far as the in-call UI is concerned,
1175         // Conference corresponds to generic display.
1176         PhoneApp app = PhoneApp.getInstance();
1177         if (app.phone.getPhoneName().equals("CDMA")) {
1178             if (app.cdmaPhoneCallState.getCurrentCallState()
1179                     == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
1180                 return true;
1181             }
1182         } else {
1183             List<Connection> connections = call.getConnections();
1184             if (connections != null && connections.size() > 1) {
1185                 return true;
1186             }
1187         }
1188         return false;
1189
1190         // TODO: We may still want to change the semantics of this method
1191         // to say that a given call is only really a conference call if
1192         // the number of ACTIVE connections, not the total number of
1193         // connections, is greater than one.  (See warning comment in the
1194         // javadoc above.)
1195         // Here's an implementation of that:
1196         //        if (connections == null) {
1197         //            return false;
1198         //        }
1199         //        int numActiveConnections = 0;
1200         //        for (Connection conn : connections) {
1201         //            if (DBG) log("  - CONN: " + conn + ", state = " + conn.getState());
1202         //            if (conn.getState() == Call.State.ACTIVE) numActiveConnections++;
1203         //            if (numActiveConnections > 1) {
1204         //                return true;
1205         //            }
1206         //        }
1207         //        return false;
1208     }
1209
1210     /**
1211      * Launch the Dialer to start a new call.
1212      * This is just a wrapper around the ACTION_DIAL intent.
1213      */
1214     static void startNewCall(final Phone phone) {
1215         final KeyguardManager keyguardManager = PhoneApp.getInstance().getKeyguardManager();
1216         if (!keyguardManager.inKeyguardRestrictedInputMode()) {
1217             internalStartNewCall(phone);
1218         } else {
1219             keyguardManager.exitKeyguardSecurely(new KeyguardManager.OnKeyguardExitResult() {
1220                 public void onKeyguardExitResult(boolean success) {
1221                     if (success) {
1222                         internalStartNewCall(phone);
1223                     }
1224                 }
1225             });
1226         }
1227     }
1228
1229     private static void internalStartNewCall(Phone phone) {
1230         // Sanity-check that this is OK given the current state of the phone.
1231         if (!okToAddCall(phone)) {
1232             Log.w(LOG_TAG, "startNewCall: can't add a new call in the current state");
1233             dumpCallState(phone);
1234             return;
1235         }
1236
1237         // if applicable, mute the call while we're showing the add call UI.
1238         if (!phone.getForegroundCall().isIdle()) {
1239             setMuteInternal(phone, true);
1240             // Inform the phone app that this mute state was NOT done
1241             // voluntarily by the User.
1242             PhoneApp.getInstance().setRestoreMuteOnInCallResume(true);
1243         }
1244
1245         Intent intent = new Intent(Intent.ACTION_DIAL);
1246         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1247
1248         // when we request the dialer come up, we also want to inform
1249         // it that we're going through the "add call" option from the
1250         // InCallScreen / PhoneUtils.
1251         intent.putExtra(ADD_CALL_MODE_KEY, true);
1252
1253         PhoneApp.getInstance().startActivity(intent);
1254     }
1255
1256     /**
1257      * Brings up the UI used to handle an incoming call.
1258      *
1259      * Originally, this brought up an IncomingCallPanel instance
1260      * (which was a subclass of Dialog) on top of whatever app
1261      * was currently running.  Now, we take you directly to the
1262      * in-call screen, whose CallCard automatically does the right
1263      * thing if there's a Call that's currently ringing.
1264      */
1265     static void showIncomingCallUi() {
1266         if (DBG) log("showIncomingCallUi()...");
1267         PhoneApp app = PhoneApp.getInstance();
1268
1269         // Before bringing up the "incoming call" UI, force any system
1270         // dialogs (like "recent tasks" or the power dialog) to close first.
1271         app.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
1272
1273         // Go directly to the in-call screen.
1274         // (No need to do anything special if we're already on the in-call
1275         // screen; it'll notice the phone state change and update itself.)
1276
1277         // But first, grab a full wake lock.  We do this here, before we
1278         // even fire off the InCallScreen intent, to make sure the
1279         // ActivityManager doesn't try to pause the InCallScreen as soon
1280         // as it comes up.  (See bug 1648751.)
1281         //
1282         // And since the InCallScreen isn't visible yet (we haven't even
1283         // fired off the intent yet), we DON'T want the screen to actually
1284         // come on right now.  So *before* acquiring the wake lock we need
1285         // to call preventScreenOn(), which tells the PowerManager that
1286         // the screen should stay off even if someone's holding a full
1287         // wake lock.  (This prevents any flicker during the "incoming
1288         // call" sequence.  The corresponding preventScreenOn(false) call
1289         // will come from the InCallScreen when it's finally ready to be
1290         // displayed.)
1291         //
1292         // TODO: this is all a temporary workaround.  The real fix is to add
1293         // an Activity attribute saying "this Activity wants to wake up the
1294         // phone when it's displayed"; that way the ActivityManager could
1295         // manage the wake locks *and* arrange for the screen to come on at
1296         // the exact moment that the InCallScreen is ready to be displayed.
1297         // (See bug 1648751.)
1298         app.preventScreenOn(true);
1299         app.requestWakeState(PhoneApp.WakeState.FULL);
1300
1301         // Fire off the InCallScreen intent.
1302         app.displayCallScreen();
1303     }
1304
1305     static void turnOnSpeaker(Context context, boolean flag) {
1306         if (DBG) log("turnOnSpeaker: " + flag);
1307         AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
1308
1309         audioManager.setSpeakerphoneOn(flag);
1310         // record the speaker-enable value
1311         sIsSpeakerEnabled = flag;
1312         if (flag) {
1313             NotificationMgr.getDefault().notifySpeakerphone();
1314         } else {
1315             NotificationMgr.getDefault().cancelSpeakerphone();
1316         }
1317
1318         // We also need to make a fresh call to PhoneApp.updateWakeState()
1319         // any time the speaker state changes, since the screen timeout is
1320         // sometimes different depending on whether or not the speaker is
1321         // in use.
1322         PhoneApp app = PhoneApp.getInstance();
1323         app.updateWakeState();
1324     }
1325
1326     /**
1327      * Restore the speaker mode, called after a wired headset disconnect
1328      * event.
1329      */
1330     static void restoreSpeakerMode(Context context) {
1331         if (DBG) log("restoreSpeakerMode, restoring to: " + sIsSpeakerEnabled);
1332
1333         // change the mode if needed.
1334         if (isSpeakerOn(context) != sIsSpeakerEnabled) {
1335             turnOnSpeaker(context, sIsSpeakerEnabled);
1336         }
1337     }
1338
1339     static boolean isSpeakerOn(Context context) {
1340         AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
1341         return audioManager.isSpeakerphoneOn();
1342     }
1343
1344     /**
1345      * Wrapper around Phone.setMute() that also updates the mute icon in
1346      * the status bar.
1347      *
1348      * All muting / unmuting from the in-call UI should go through this
1349      * wrapper.
1350      */
1351     static void setMute(Phone phone, boolean muted) {
1352         // make the call to mute the audio
1353         setMuteInternal(phone, muted);
1354
1355         // update the foreground connections to match.  This includes
1356         // all the connections on conference calls.
1357         for (Connection cn : phone.getForegroundCall().getConnections()) {
1358             if (sConnectionMuteTable.get(cn) == null) {
1359                 if (DBG) log("problem retrieving mute value for this connection.");
1360             }
1361             sConnectionMuteTable.put(cn, Boolean.valueOf(muted));
1362         }
1363     }
1364
1365     /**
1366      * Internally used muting function.  All UI calls should use {@link setMute}
1367      */
1368     static void setMuteInternal(Phone phone, boolean muted) {
1369         if (DBG) log("setMute: " + muted);
1370         phone.setMute(muted);
1371         if (muted) {
1372             NotificationMgr.getDefault().notifyMute();
1373         } else {
1374             NotificationMgr.getDefault().cancelMute();
1375         }
1376     }
1377
1378     static boolean getMute(Phone phone) {
1379         return phone.getMute();
1380     }
1381
1382     /**
1383      * A really simple wrapper around AudioManager.setMode(),
1384      * with a bit of extra logging to help debug the exact
1385      * timing (and call stacks) for all our setMode() calls.
1386      *
1387      * Also, add additional state monitoring to determine
1388      * whether or not certain calls to change the audio mode
1389      * are ignored.
1390      */
1391     /* package */ static void setAudioMode(Context context, int mode) {
1392         if (DBG) Log.d(LOG_TAG, "PhoneUtils.setAudioMode(" + audioModeToString(mode) + ")...");
1393
1394         //decide whether or not to ignore the audio setting
1395         boolean ignore = false;
1396
1397         switch (sAudioBehaviourState) {
1398             case AUDIO_RINGING:
1399                 ignore = ((mode == AudioManager.MODE_NORMAL) || (mode == AudioManager.MODE_IN_CALL));
1400                 break;
1401             case AUDIO_OFFHOOK:
1402                 ignore = ((mode == AudioManager.MODE_NORMAL) || (mode == AudioManager.MODE_RINGTONE));
1403                 break;
1404             case AUDIO_IDLE:
1405             default:
1406                 ignore = (mode == AudioManager.MODE_IN_CALL);
1407                 break;
1408         }
1409
1410         if (!ignore) {
1411             AudioManager audioManager = 
1412                     (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
1413             // Enable stack dump only when actively debugging ("new Throwable()" is expensive!)
1414             if (DBG_SETAUDIOMODE_STACK) Log.d(LOG_TAG, "Stack:", new Throwable("stack dump"));
1415             audioManager.setMode(mode);
1416         } else {
1417             if (DBG) Log.d(LOG_TAG, "PhoneUtils.setAudioMode(), state is " + sAudioBehaviourState +
1418                     " ignoring " + audioModeToString(mode) + " request");
1419         }
1420     }
1421     private static String audioModeToString(int mode) {
1422         switch (mode) {
1423             case AudioManager.MODE_INVALID: return "MODE_INVALID";
1424             case AudioManager.MODE_CURRENT: return "MODE_CURRENT";
1425             case AudioManager.MODE_NORMAL: return "MODE_NORMAL";
1426             case AudioManager.MODE_RINGTONE: return "MODE_RINGTONE";
1427             case AudioManager.MODE_IN_CALL: return "MODE_IN_CALL";
1428             default: return String.valueOf(mode);
1429         }
1430     }
1431
1432     /**
1433      * Handles the wired headset button while in-call.
1434      *
1435      * This is called from the PhoneApp, not from the InCallScreen,
1436      * since the HEADSETHOOK button means "mute or unmute the current
1437      * call" *any* time a call is active, even if the user isn't actually
1438      * on the in-call screen.
1439      *
1440      * @return true if we consumed the event.
1441      */
1442     /* package */ static boolean handleHeadsetHook(Phone phone) {
1443         if (DBG) log("handleHeadsetHook()...");
1444
1445         // If the phone is totally idle, we ignore HEADSETHOOK events
1446         // (and instead let them fall through to the media player.)
1447         if (phone.getState() == Phone.State.IDLE) {
1448             return false;
1449         }
1450
1451         // Ok, the phone is in use.
1452         // The headset button button means "Answer" if an incoming call is
1453         // ringing.  If not, it toggles the mute / unmute state.
1454         //
1455         // And in any case we *always* consume this event; this means
1456         // that the usual mediaplayer-related behavior of the headset
1457         // button will NEVER happen while the user is on a call.
1458
1459         final boolean hasRingingCall = !phone.getRingingCall().isIdle();
1460         final boolean hasActiveCall = !phone.getForegroundCall().isIdle();
1461         final boolean hasHoldingCall = !phone.getBackgroundCall().isIdle();
1462
1463         if (phone.getPhoneName().equals("CDMA")) {
1464             PhoneApp app = PhoneApp.getInstance();
1465             if (hasRingingCall) {
1466                 answerCall(phone);
1467             } else {
1468                 if (app.cdmaPhoneCallState.getCurrentCallState()
1469                         == CdmaPhoneCallState.PhoneCallState.SINGLE_ACTIVE) {
1470                     // Send a flash command to CDMA network for putting the other
1471                     // party on hold.
1472                     // For CDMA networks which do not support this the user would just
1473                     // hear a beep from the network.
1474                     // For CDMA networks which do support it it will put the other
1475                     // party on hold.
1476                     switchHoldingAndActive(phone);
1477                 }
1478
1479                 // No incoming ringing call.  Toggle the mute state.
1480                 if (getMute(phone)) {
1481                     if (DBG) log("handleHeadsetHook: UNmuting...");
1482                     setMute(phone, false);
1483                 } else {
1484                     if (DBG) log("handleHeadsetHook: muting...");
1485                     setMute(phone, true);
1486                 }
1487             }
1488         } else { // GSM
1489             if (hasRingingCall) {
1490                 // If an incoming call is ringing, answer it (just like with the
1491                 // CALL button):
1492                 if (hasActiveCall && hasHoldingCall) {
1493                     if (DBG) log("handleHeadsetHook: ringing (both lines in use) ==> answer!");
1494                     answerAndEndActive(phone);
1495                 } else {
1496                     if (DBG) log("handleHeadsetHook: ringing ==> answer!");
1497                     answerCall(phone);  // Automatically holds the current active call,
1498                                      // if there is one
1499                 }
1500             } else {
1501                 // No incoming ringing call.  Toggle the mute state.
1502                 if (getMute(phone)) {
1503                     if (DBG) log("handleHeadsetHook: UNmuting...");
1504                     setMute(phone, false);
1505                 } else {
1506                     if (DBG) log("handleHeadsetHook: muting...");
1507                     setMute(phone, true);
1508                 }
1509             }
1510         }
1511
1512         // Even if the InCallScreen is the current activity, there's no
1513         // need to force it to update, because (1) if we answered a
1514         // ringing call, the InCallScreen will imminently get a phone
1515         // state change event (causing an update), and (2) if we muted or
1516         // unmuted, the setMute() call automagically updates the status
1517         // bar, and there's no "mute" indication in the InCallScreen
1518         // itself (other than the menu item, which only ever stays
1519         // onscreen for a second anyway.)
1520
1521         return true;
1522     }
1523
1524     /**
1525      * Look for ANY connections on the phone that qualify as being
1526      * disconnected.
1527      *
1528      * @return true if we find a connection that is disconnected over
1529      * all the phone's call objects.
1530      */
1531     /* package */ static boolean hasDisconnectedConnections(Phone phone) {
1532         return hasDisconnectedConnections(phone.getForegroundCall()) ||
1533                 hasDisconnectedConnections(phone.getBackgroundCall()) ||
1534                 hasDisconnectedConnections(phone.getRingingCall());
1535     }
1536
1537     /**
1538      * Iterate over all connections in a call to see if there are any
1539      * that are not alive (disconnected or idle).
1540      *
1541      * @return true if we find a connection that is disconnected, and
1542      * pending removal via
1543      * {@link com.android.internal.telephony.gsm.GsmCall#clearDisconnected()}.
1544      */
1545     private static final boolean hasDisconnectedConnections(Call call) {
1546         // look through all connections for non-active ones.
1547         for (Connection c : call.getConnections()) {
1548             if (!c.isAlive()) {
1549                 return true;
1550             }
1551         }
1552         return false;
1553     }
1554
1555     //
1556     // Misc UI policy helper functions
1557     //
1558
1559     /**
1560      * @return true if we're allowed to swap calls, given the current
1561      * state of the Phone.
1562      */
1563     /* package */ static boolean okToSwapCalls(Phone phone) {
1564         if (phone.getPhoneName().equals("CDMA")) {
1565             // CDMA: "Swap" is enabled only when the phone reaches a *generic*.
1566             // state by either accepting a Call Waiting or by merging two calls
1567             PhoneApp app = PhoneApp.getInstance();
1568             return (app.cdmaPhoneCallState.getCurrentCallState()
1569                     == CdmaPhoneCallState.PhoneCallState.CONF_CALL);
1570         } else {
1571             // GSM: "Swap" is available if both lines are in use and there's no
1572             // incoming call.  (Actually we need to verify that the active
1573             // call really is in the ACTIVE state and the holding call really
1574             // is in the HOLDING state, since you *can't* actually swap calls
1575             // when the foreground call is DIALING or ALERTING.)
1576             return phone.getRingingCall().isIdle()
1577                     && (phone.getForegroundCall().getState() == Call.State.ACTIVE)
1578                     && (phone.getBackgroundCall().getState() == Call.State.HOLDING);
1579         }
1580     }
1581
1582     /**
1583      * @return true if we're allowed to merge calls, given the current
1584      * state of the Phone.
1585      */
1586     /* package */ static boolean okToMergeCalls(Phone phone) {
1587         if (phone.getPhoneName().equals("CDMA")) {
1588             // CDMA: "Merge" is enabled only when the user is in a 3Way call.
1589             PhoneApp app = PhoneApp.getInstance();
1590             return (app.cdmaPhoneCallState.getCurrentCallState()
1591                     == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE);
1592         } else { //GSM.
1593             // GSM: "Merge" is available if both lines are in use and there's no
1594             // incoming call, *and* the current conference isn't already
1595             // "full".
1596             return phone.getRingingCall().isIdle() && phone.canConference();
1597         }
1598     }
1599
1600     /**
1601      * @return true if the UI should let you add a new call, given the current
1602      * state of the Phone.
1603      */
1604     /* package */ static boolean okToAddCall(Phone phone) {
1605        if (phone.getPhoneName().equals("CDMA")) {
1606            // CDMA: "Add call" menu item is only enabled when the call is in
1607            // - SINGLE_ACTIVE state
1608            // - After 60 seconds of user Ignoring/Missing a Call Waiting call.
1609             PhoneApp app = PhoneApp.getInstance();
1610             return ((app.cdmaPhoneCallState.getCurrentCallState()
1611                     == CdmaPhoneCallState.PhoneCallState.SINGLE_ACTIVE)
1612                     && (app.cdmaPhoneCallState.getAddCallMenuStateAfterCallWaiting()));
1613         } else {
1614             // GSM: "Add call" is available only if ALL of the following are true:
1615             // - There's no incoming ringing call
1616             // - There's < 2 lines in use
1617             // - The foreground call is ACTIVE or IDLE or DISCONNECTED.
1618             //   (We mainly need to make sure it *isn't* DIALING or ALERTING.)
1619             final boolean hasRingingCall = !phone.getRingingCall().isIdle();
1620             final boolean hasActiveCall = !phone.getForegroundCall().isIdle();
1621             final boolean hasHoldingCall = !phone.getBackgroundCall().isIdle();
1622             final boolean allLinesTaken = hasActiveCall && hasHoldingCall;
1623             final Call.State fgCallState = phone.getForegroundCall().getState();
1624
1625             return !hasRingingCall
1626                     && !allLinesTaken
1627                     && ((fgCallState == Call.State.ACTIVE)
1628                         || (fgCallState == Call.State.IDLE)
1629                         || (fgCallState == Call.State.DISCONNECTED));
1630         }
1631     }
1632
1633     //
1634     // General phone and call state debugging/testing code
1635     //
1636
1637     /* package */ static void dumpCallState(Phone phone) {
1638         Log.d(LOG_TAG, "##### dumpCallState()");
1639         Log.d(LOG_TAG, "- Phone: " + phone + ", name = " + phone.getPhoneName()
1640               + ", state = " + phone.getState());
1641         Log.d(LOG_TAG, "-");
1642
1643         Call fgCall = phone.getForegroundCall();
1644         Log.d(LOG_TAG, "- FG call: " + fgCall);
1645         Log.d(LOG_TAG, "-  state: " + fgCall.getState());
1646         Log.d(LOG_TAG, "-  isAlive(): " + fgCall.getState().isAlive());
1647         Log.d(LOG_TAG, "-  isRinging(): " + fgCall.getState().isRinging());
1648         Log.d(LOG_TAG, "-  isDialing(): " + fgCall.getState().isDialing());
1649         Log.d(LOG_TAG, "-  isIdle(): " + fgCall.isIdle());
1650         Log.d(LOG_TAG, "-  hasConnections: " + fgCall.hasConnections());
1651         Log.d(LOG_TAG, "-");
1652
1653         Call bgCall = phone.getBackgroundCall();
1654         Log.d(LOG_TAG, "- BG call: " + bgCall);
1655         Log.d(LOG_TAG, "-  state: " + bgCall.getState());
1656         Log.d(LOG_TAG, "-  isAlive(): " + bgCall.getState().isAlive());
1657         Log.d(LOG_TAG, "-  isRinging(): " + bgCall.getState().isRinging());
1658         Log.d(LOG_TAG, "-  isDialing(): " + bgCall.getState().isDialing());
1659         Log.d(LOG_TAG, "-  isIdle(): " + bgCall.isIdle());
1660         Log.d(LOG_TAG, "-  hasConnections: " + bgCall.hasConnections());
1661         Log.d(LOG_TAG, "-");
1662
1663         Call ringingCall = phone.getRingingCall();
1664         Log.d(LOG_TAG, "- RINGING call: " + ringingCall);
1665         Log.d(LOG_TAG, "-  state: " + ringingCall.getState());
1666         Log.d(LOG_TAG, "-  isAlive(): " + ringingCall.getState().isAlive());
1667         Log.d(LOG_TAG, "-  isRinging(): " + ringingCall.getState().isRinging());
1668         Log.d(LOG_TAG, "-  isDialing(): " + ringingCall.getState().isDialing());
1669         Log.d(LOG_TAG, "-  isIdle(): " + ringingCall.isIdle());
1670         Log.d(LOG_TAG, "-  hasConnections: " + ringingCall.hasConnections());
1671         Log.d(LOG_TAG, "-");
1672
1673         final boolean hasRingingCall = !phone.getRingingCall().isIdle();
1674         final boolean hasActiveCall = !phone.getForegroundCall().isIdle();
1675         final boolean hasHoldingCall = !phone.getBackgroundCall().isIdle();
1676         final boolean allLinesTaken = hasActiveCall && hasHoldingCall;
1677         Log.d(LOG_TAG, "- hasRingingCall: " + hasRingingCall);
1678         Log.d(LOG_TAG, "- hasActiveCall: " + hasActiveCall);
1679         Log.d(LOG_TAG, "- hasHoldingCall: " + hasHoldingCall);
1680         Log.d(LOG_TAG, "- allLinesTaken: " + allLinesTaken);
1681
1682         // Watch out: the isRinging() call below does NOT tell us anything
1683         // about the state of the telephony layer; it merely tells us whether
1684         // the Ringer manager is currently playing the ringtone.
1685         boolean ringing = PhoneApp.getInstance().getRinger().isRinging();
1686         Log.d(LOG_TAG, "- ringing (Ringer manager state): " + ringing);
1687         Log.d(LOG_TAG, "-----");
1688     }
1689
1690     private static void log(String msg) {
1691         Log.d(LOG_TAG, msg);
1692     }
1693 }