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