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