PhoneUtils.java revision 3d04d787659f87a21e7414b7e13c1d53be6b859f
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, "PhoneUtils: 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 gatewayNumber Is the phone number that will be dialed to
557     * setup the connection.
558     * @return either CALL_STATUS_DIALED or CALL_STATUS_FAILED
559     */
560    static int placeCallVia(Context context, Phone phone,
561                            String number, Uri contactRef, String gatewayNumber) {
562        if (DBG) log("placeCallVia: '" + number + "' GW:'" + gatewayNumber + "'");
563
564        Connection connection;
565        try {
566            connection = phone.dial(gatewayNumber);
567        } catch (CallStateException ex) {
568            Log.e(LOG_TAG, "PhoneUtils: Exception dialing gateway", ex);
569            connection = null;
570        }
571
572        if (null == connection) {
573            Log.e(LOG_TAG, "PhoneUtils: Got null connection.");
574            return CALL_STATUS_FAILED;
575        }
576
577        PhoneApp app = PhoneApp.getInstance();
578        final boolean is_cdma = phone.getPhoneName().equals("CDMA");
579
580        if (is_cdma) {
581            updateCdmaCallStateOnNewOutgoingCall(app);
582        }
583        PhoneUtils.setAudioControlState(PhoneUtils.AUDIO_OFFHOOK);
584
585        // Clean up the number to be displayed.
586        if (is_cdma) {
587            number = CdmaConnection.formatDialString(number);
588        }
589        number = PhoneNumberUtils.extractNetworkPortion(number);
590        number = PhoneNumberUtils.convertKeypadLettersToDigits(number);
591        number = PhoneNumberUtils.formatNumber(number);
592
593        // Get the caller info synchronously because we need the final
594        // CallerInfo object to update the dialed number with the one
595        // requested by the user (and not the provider's gateway number).
596        CallerInfo info = null;
597
598        if (ContentResolver.SCHEME_CONTENT.equals(contactRef.getScheme())) {
599            info = CallerInfo.getCallerInfo(context, contactRef);
600        }
601
602        // Fallback, lookup contact using the phone number if the
603        // contact's URI scheme was not content:// or if is was but
604        // the lookup failed.
605        if (null == info) {
606            info = CallerInfo.getCallerInfo(context, number);
607        }
608        info.phoneNumber = number;
609        connection.setUserData(info);
610
611        setAudioMode(phone.getContext(), AudioManager.MODE_IN_CALL);
612        return CALL_STATUS_DIALED;
613    }
614
615    static void switchHoldingAndActive(Phone phone) {
616        try {
617            if (DBG) log("switchHoldingAndActive");
618            phone.switchHoldingAndActive();
619        } catch (CallStateException ex) {
620            Log.w(LOG_TAG, "switchHoldingAndActive: caught " + ex, ex);
621        }
622    }
623
624    /**
625     * Restore the mute setting from the earliest connection of the
626     * foreground call.
627     */
628    static Boolean restoreMuteState(Phone phone) {
629        //get the earliest connection
630        Connection c = phone.getForegroundCall().getEarliestConnection();
631
632        // only do this if connection is not null.
633        if (c != null) {
634
635            // retrieve the mute value.
636            Boolean shouldMute;
637            if (phone.getPhoneName().equals("CDMA") &&
638                    PhoneApp.getInstance().cdmaPhoneCallState.getCurrentCallState() ==
639                    CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
640                shouldMute = sConnectionMuteTable.get(
641                        phone.getForegroundCall().getLatestConnection());
642            } else {
643                shouldMute = sConnectionMuteTable.get(
644                        phone.getForegroundCall().getEarliestConnection());
645            }
646            if (shouldMute == null) {
647                if (DBG) log("problem retrieving mute value for this connection.");
648                shouldMute = Boolean.FALSE;
649            }
650
651            // set the mute value and return the result.
652            setMute (phone, shouldMute.booleanValue());
653            return shouldMute;
654        }
655        return Boolean.valueOf(getMute (phone));
656    }
657
658    static void mergeCalls(Phone phone) {
659        if (phone.getPhoneName().equals("GSM")) {
660            try {
661                if (DBG) log("mergeCalls");
662                phone.conference();
663            } catch (CallStateException ex) {
664                Log.w(LOG_TAG, "mergeCalls: caught " + ex, ex);
665            }
666        } else { // CDMA
667            if (DBG) log("mergeCalls");
668            PhoneApp app = PhoneApp.getInstance();
669            if (app.cdmaPhoneCallState.getCurrentCallState()
670                    == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
671                // Send flash cmd
672                // TODO(Moto): Need to change the call from switchHoldingAndActive to
673                // something meaningful as we are not actually trying to swap calls but
674                // instead are merging two calls by sending a Flash command.
675                switchHoldingAndActive(phone);
676
677                // Set the Phone Call State to conference
678                app.cdmaPhoneCallState.setCurrentCallState(
679                        CdmaPhoneCallState.PhoneCallState.CONF_CALL);
680            }
681        }
682    }
683
684    static void separateCall(Connection c) {
685        try {
686            if (DBG) log("separateCall: " + c.getAddress());
687            c.separate();
688        } catch (CallStateException ex) {
689            Log.w(LOG_TAG, "separateCall: caught " + ex, ex);
690        }
691    }
692
693    /**
694     * Handle the MMIInitiate message and put up an alert that lets
695     * the user cancel the operation, if applicable.
696     *
697     * @param context context to get strings.
698     * @param mmiCode the MmiCode object being started.
699     * @param buttonCallbackMessage message to post when button is clicked.
700     * @param previousAlert a previous alert used in this activity.
701     * @return the dialog handle
702     */
703    static Dialog displayMMIInitiate(Context context,
704                                          MmiCode mmiCode,
705                                          Message buttonCallbackMessage,
706                                          Dialog previousAlert) {
707        if (DBG) log("displayMMIInitiate: " + mmiCode);
708        if (previousAlert != null) {
709            previousAlert.dismiss();
710        }
711
712        // The UI paradigm we are using now requests that all dialogs have
713        // user interaction, and that any other messages to the user should
714        // be by way of Toasts.
715        //
716        // In adhering to this request, all MMI initiating "OK" dialogs
717        // (non-cancelable MMIs) that end up being closed when the MMI
718        // completes (thereby showing a completion dialog) are being
719        // replaced with Toasts.
720        //
721        // As a side effect, moving to Toasts for the non-cancelable MMIs
722        // also means that buttonCallbackMessage (which was tied into "OK")
723        // is no longer invokable for these dialogs.  This is not a problem
724        // since the only callback messages we supported were for cancelable
725        // MMIs anyway.
726        //
727        // A cancelable MMI is really just a USSD request. The term
728        // "cancelable" here means that we can cancel the request when the
729        // system prompts us for a response, NOT while the network is
730        // processing the MMI request.  Any request to cancel a USSD while
731        // the network is NOT ready for a response may be ignored.
732        //
733        // With this in mind, we replace the cancelable alert dialog with
734        // a progress dialog, displayed until we receive a request from
735        // the the network.  For more information, please see the comments
736        // in the displayMMIComplete() method below.
737        //
738        // Anything that is NOT a USSD request is a normal MMI request,
739        // which will bring up a toast (desribed above).
740        // Optional code for Extended USSD running prompt
741        if (mNwService != null) {
742            if (DBG) log("running USSD code, displaying indeterminate progress.");
743            // create the indeterminate progress dialog and display it.
744            ProgressDialog pd = new ProgressDialog(context);
745            CharSequence textmsg = "";
746            try {
747                textmsg = mNwService.getMmiRunningText();
748
749            } catch (RemoteException e) {
750                mNwService = null;
751                textmsg = context.getText(R.string.ussdRunning);
752            }
753            if (DBG) log("Extended NW displayMMIInitiate (" + textmsg+ ")");
754            pd.setMessage(textmsg);
755            pd.setCancelable(false);
756            pd.setIndeterminate(true);
757            pd.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
758            pd.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
759            pd.show();
760            // trigger a 15 seconds timeout to clear this progress dialog
761            mMmiTimeoutCbMsg = buttonCallbackMessage;
762            try {
763                mMmiTimeoutCbMsg.getTarget().sendMessageDelayed(buttonCallbackMessage, 15000);
764            } catch(NullPointerException e) {
765                mMmiTimeoutCbMsg = null;
766            }
767            return pd;
768        }
769
770        boolean isCancelable = (mmiCode != null) && mmiCode.isCancelable();
771
772        if (!isCancelable) {
773            if (DBG) log("not a USSD code, displaying status toast.");
774            CharSequence text = context.getText(R.string.mmiStarted);
775            Toast.makeText(context, text, Toast.LENGTH_SHORT)
776                .show();
777            return null;
778        } else {
779            if (DBG) log("running USSD code, displaying indeterminate progress.");
780
781            // create the indeterminate progress dialog and display it.
782            ProgressDialog pd = new ProgressDialog(context);
783            pd.setMessage(context.getText(R.string.ussdRunning));
784            pd.setCancelable(false);
785            pd.setIndeterminate(true);
786            pd.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
787
788            pd.show();
789
790            return pd;
791        }
792
793    }
794
795    /**
796     * Handle the MMIComplete message and fire off an intent to display
797     * the message.
798     *
799     * @param context context to get strings.
800     * @param mmiCode MMI result.
801     * @param previousAlert a previous alert used in this activity.
802     */
803    static void displayMMIComplete(final Phone phone, Context context, final MmiCode mmiCode,
804            Message dismissCallbackMessage,
805            AlertDialog previousAlert) {
806        CharSequence text;
807        int title = 0;  // title for the progress dialog, if needed.
808        MmiCode.State state = mmiCode.getState();
809
810        if (DBG) log("displayMMIComplete: state=" + state);
811        // Clear timeout trigger message
812        if(mMmiTimeoutCbMsg != null) {
813            try{
814                mMmiTimeoutCbMsg.getTarget().removeMessages(mMmiTimeoutCbMsg.what);
815                if (DBG) log("Extended NW displayMMIComplete removeMsg");
816            } catch (NullPointerException e) {
817            }
818            mMmiTimeoutCbMsg = null;
819        }
820
821
822        switch (state) {
823            case PENDING:
824                // USSD code asking for feedback from user.
825                text = mmiCode.getMessage();
826                if (DBG) log("- using text from PENDING MMI message: '" + text + "'");
827                break;
828            case CANCELLED:
829                text = context.getText(R.string.mmiCancelled);
830                break;
831            case COMPLETE:
832                if (PhoneApp.getInstance().getPUKEntryActivity() != null) {
833                    // if an attempt to unPUK the device was made, we specify
834                    // the title and the message here.
835                    title = com.android.internal.R.string.PinMmi;
836                    text = context.getText(R.string.puk_unlocked);
837                    break;
838                }
839                // All other conditions for the COMPLETE mmi state will cause
840                // the case to fall through to message logic in common with
841                // the FAILED case.
842
843            case FAILED:
844                text = mmiCode.getMessage();
845                if (DBG) log("- using text from MMI message: '" + text + "'");
846                break;
847            default:
848                throw new IllegalStateException("Unexpected MmiCode state: " + state);
849        }
850
851        if (previousAlert != null) {
852            previousAlert.dismiss();
853        }
854
855        // Check to see if a UI exists for the PUK activation.  If it does
856        // exist, then it indicates that we're trying to unblock the PUK.
857        PhoneApp app = PhoneApp.getInstance();
858        if ((app.getPUKEntryActivity() != null) && (state == MmiCode.State.COMPLETE)) {
859            if (DBG) log("displaying PUK unblocking progress dialog.");
860
861            // create the progress dialog, make sure the flags and type are
862            // set correctly.
863            ProgressDialog pd = new ProgressDialog(app);
864            pd.setTitle(title);
865            pd.setMessage(text);
866            pd.setCancelable(false);
867            pd.setIndeterminate(true);
868            pd.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
869            pd.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
870
871            // display the dialog
872            pd.show();
873
874            // indicate to the Phone app that the progress dialog has
875            // been assigned for the PUK unlock / SIM READY process.
876            app.setPukEntryProgressDialog(pd);
877
878        } else {
879            // In case of failure to unlock, we'll need to reset the
880            // PUK unlock activity, so that the user may try again.
881            if (app.getPUKEntryActivity() != null) {
882                app.setPukEntryActivity(null);
883            }
884
885            // A USSD in a pending state means that it is still
886            // interacting with the user.
887            if (state != MmiCode.State.PENDING) {
888                if (DBG) log("MMI code has finished running.");
889
890                // Replace response message with Extended Mmi wording
891                if (mNwService != null) {
892                    try {
893                        text = mNwService.getUserMessage(text);
894                    } catch (RemoteException e) {
895                        mNwService = null;
896                    }
897                    if (DBG) log("Extended NW displayMMIInitiate (" + text + ")");
898                    if (text == null || text.length() == 0)
899                        return;
900                }
901
902                // displaying system alert dialog on the screen instead of
903                // using another activity to display the message.  This
904                // places the message at the forefront of the UI.
905                AlertDialog newDialog = new AlertDialog.Builder(context)
906                        .setMessage(text)
907                        .setPositiveButton(R.string.ok, null)
908                        .setCancelable(true)
909                        .create();
910
911                newDialog.getWindow().setType(
912                        WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
913                newDialog.getWindow().addFlags(
914                        WindowManager.LayoutParams.FLAG_DIM_BEHIND);
915
916                newDialog.show();
917            } else {
918                if (DBG) log("USSD code has requested user input. Constructing input dialog.");
919
920                // USSD MMI code that is interacting with the user.  The
921                // basic set of steps is this:
922                //   1. User enters a USSD request
923                //   2. We recognize the request and displayMMIInitiate
924                //      (above) creates a progress dialog.
925                //   3. Request returns and we get a PENDING or COMPLETE
926                //      message.
927                //   4. These MMI messages are caught in the PhoneApp
928                //      (onMMIComplete) and the InCallScreen
929                //      (mHandler.handleMessage) which bring up this dialog
930                //      and closes the original progress dialog,
931                //      respectively.
932                //   5. If the message is anything other than PENDING,
933                //      we are done, and the alert dialog (directly above)
934                //      displays the outcome.
935                //   6. If the network is requesting more information from
936                //      the user, the MMI will be in a PENDING state, and
937                //      we display this dialog with the message.
938                //   7. User input, or cancel requests result in a return
939                //      to step 1.  Keep in mind that this is the only
940                //      time that a USSD should be canceled.
941
942                // inflate the layout with the scrolling text area for the dialog.
943                LayoutInflater inflater = (LayoutInflater) context.getSystemService(
944                        Context.LAYOUT_INFLATER_SERVICE);
945                View dialogView = inflater.inflate(R.layout.dialog_ussd_response, null);
946
947                // get the input field.
948                final EditText inputText = (EditText) dialogView.findViewById(R.id.input_field);
949
950                // specify the dialog's click listener, with SEND and CANCEL logic.
951                final DialogInterface.OnClickListener mUSSDDialogListener =
952                    new DialogInterface.OnClickListener() {
953                        public void onClick(DialogInterface dialog, int whichButton) {
954                            switch (whichButton) {
955                                case DialogInterface.BUTTON1:
956                                    phone.sendUssdResponse(inputText.getText().toString());
957                                    break;
958                                case DialogInterface.BUTTON2:
959                                    if (mmiCode.isCancelable()) {
960                                        mmiCode.cancel();
961                                    }
962                                    break;
963                            }
964                        }
965                    };
966
967                // build the dialog
968                final AlertDialog newDialog = new AlertDialog.Builder(context)
969                        .setMessage(text)
970                        .setView(dialogView)
971                        .setPositiveButton(R.string.send_button, mUSSDDialogListener)
972                        .setNegativeButton(R.string.cancel, mUSSDDialogListener)
973                        .setCancelable(false)
974                        .create();
975
976                // attach the key listener to the dialog's input field and make
977                // sure focus is set.
978                final View.OnKeyListener mUSSDDialogInputListener =
979                    new View.OnKeyListener() {
980                        public boolean onKey(View v, int keyCode, KeyEvent event) {
981                            switch (keyCode) {
982                                case KeyEvent.KEYCODE_CALL:
983                                case KeyEvent.KEYCODE_ENTER:
984                                    phone.sendUssdResponse(inputText.getText().toString());
985                                    newDialog.dismiss();
986                                    return true;
987                            }
988                            return false;
989                        }
990                    };
991                inputText.setOnKeyListener(mUSSDDialogInputListener);
992                inputText.requestFocus();
993
994                // set the window properties of the dialog
995                newDialog.getWindow().setType(
996                        WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
997                newDialog.getWindow().addFlags(
998                        WindowManager.LayoutParams.FLAG_DIM_BEHIND);
999
1000                // now show the dialog!
1001                newDialog.show();
1002            }
1003        }
1004    }
1005
1006    /**
1007     * Cancels the current pending MMI operation, if applicable.
1008     * @return true if we canceled an MMI operation, or false
1009     *         if the current pending MMI wasn't cancelable
1010     *         or if there was no current pending MMI at all.
1011     *
1012     * @see displayMMIInitiate
1013     */
1014    static boolean cancelMmiCode(Phone phone) {
1015        List<? extends MmiCode> pendingMmis = phone.getPendingMmiCodes();
1016        int count = pendingMmis.size();
1017        if (DBG) log("cancelMmiCode: num pending MMIs = " + count);
1018
1019        boolean canceled = false;
1020        if (count > 0) {
1021            // assume that we only have one pending MMI operation active at a time.
1022            // I don't think it's possible to enter multiple MMI codes concurrently
1023            // in the phone UI, because during the MMI operation, an Alert panel
1024            // is displayed, which prevents more MMI code from being entered.
1025            MmiCode mmiCode = pendingMmis.get(0);
1026            if (mmiCode.isCancelable()) {
1027                mmiCode.cancel();
1028                canceled = true;
1029            }
1030        }
1031
1032        //clear timeout message and pre-set MMI command
1033        if (mNwService != null) {
1034            try {
1035                mNwService.clearMmiString();
1036            } catch (RemoteException e) {
1037                mNwService = null;
1038            }
1039        }
1040        if (mMmiTimeoutCbMsg != null) {
1041            mMmiTimeoutCbMsg = null;
1042        }
1043        return canceled;
1044    }
1045
1046    public static class VoiceMailNumberMissingException extends Exception {
1047        VoiceMailNumberMissingException() {
1048            super();
1049        }
1050
1051        VoiceMailNumberMissingException(String msg) {
1052            super(msg);
1053        }
1054    }
1055
1056    /**
1057     * Gets the phone number to be called from an intent.  Requires a Context
1058     * to access the contacts database, and a Phone to access the voicemail
1059     * number.
1060     *
1061     * <p>If <code>phone</code> is <code>null</code>, the function will return
1062     * <code>null</code> for <code>voicemail:</code> URIs;
1063     * if <code>context</code> is <code>null</code>, the function will return
1064     * <code>null</code> for person/phone URIs.</p>
1065     *
1066     * @param context a context to use (or
1067     * @param phone the phone on which the number would be called
1068     * @param intent the intent
1069     *
1070     * @throws VoiceMailNumberMissingException if <code>intent</code> contains
1071     *         a <code>voicemail:</code> URI, but <code>phone</code> does not
1072     *         have a voicemail number set.
1073     *
1074     * @return the phone number that would be called by the intent,
1075     *         or <code>null</code> if the number cannot be found.
1076     */
1077    static String getNumberFromIntent(Context context, Phone phone, Intent intent)
1078            throws VoiceMailNumberMissingException {
1079        final String number = PhoneNumberUtils.getNumberFromIntent(intent, context);
1080
1081        // Check for a voicemail-dailing request.  If the voicemail number is
1082        // empty, throw a VoiceMailNumberMissingException.
1083        if (intent.getData().getScheme().equals("voicemail") &&
1084                (number == null || TextUtils.isEmpty(number)))
1085            throw new VoiceMailNumberMissingException();
1086
1087        return number;
1088    }
1089
1090    /**
1091     * Returns the caller-id info corresponding to the specified Connection.
1092     * (This is just a simple wrapper around CallerInfo.getCallerInfo(): we
1093     * extract a phone number from the specified Connection, and feed that
1094     * number into CallerInfo.getCallerInfo().)
1095     *
1096     * The returned CallerInfo may be null in certain error cases, like if the
1097     * specified Connection was null, or if we weren't able to get a valid
1098     * phone number from the Connection.
1099     *
1100     * Finally, if the getCallerInfo() call did succeed, we save the resulting
1101     * CallerInfo object in the "userData" field of the Connection.
1102     *
1103     * NOTE: This API should be avoided, with preference given to the
1104     * asynchronous startGetCallerInfo API.
1105     */
1106    static CallerInfo getCallerInfo(Context context, Connection c) {
1107        CallerInfo info = null;
1108
1109        if (c != null) {
1110            //See if there is a URI attached.  If there is, this means
1111            //that there is no CallerInfo queried yet, so we'll need to
1112            //replace the URI with a full CallerInfo object.
1113            Object userDataObject = c.getUserData();
1114            if (userDataObject instanceof Uri) {
1115                info = CallerInfo.getCallerInfo(context, (Uri) userDataObject);
1116                if (info != null) {
1117                    c.setUserData(info);
1118                }
1119            } else {
1120                if (userDataObject instanceof CallerInfoToken) {
1121                    //temporary result, while query is running
1122                    info = ((CallerInfoToken) userDataObject).currentInfo;
1123                } else {
1124                    //final query result
1125                    info = (CallerInfo) userDataObject;
1126                }
1127                if (info == null) {
1128                    // No URI, or Existing CallerInfo, so we'll have to make do with
1129                    // querying a new CallerInfo using the connection's phone number.
1130                    String number = c.getAddress();
1131
1132                    if (DBG) log("getCallerInfo: number = " + number);
1133
1134                    if (!TextUtils.isEmpty(number)) {
1135                        info = CallerInfo.getCallerInfo(context, number);
1136                        if (info != null) {
1137                            c.setUserData(info);
1138                        }
1139                    }
1140                }
1141            }
1142        }
1143        return info;
1144    }
1145
1146    /**
1147     * Class returned by the startGetCallerInfo call to package a temporary
1148     * CallerInfo Object, to be superceded by the CallerInfo Object passed
1149     * into the listener when the query with token mAsyncQueryToken is complete.
1150     */
1151    public static class CallerInfoToken {
1152        /**indicates that there will no longer be updates to this request.*/
1153        public boolean isFinal;
1154
1155        public CallerInfo currentInfo;
1156        public CallerInfoAsyncQuery asyncQuery;
1157    }
1158
1159    /**
1160     * Start a CallerInfo Query based on the earliest connection in the call.
1161     */
1162    static CallerInfoToken startGetCallerInfo(Context context, Call call,
1163            CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie) {
1164        PhoneApp app = PhoneApp.getInstance();
1165        Connection conn = null;
1166        if (app.phone.getPhoneName().equals("CDMA")) {
1167            conn = call.getLatestConnection();
1168        } else {
1169            conn = call.getEarliestConnection();
1170        }
1171
1172        return startGetCallerInfo(context, conn, listener, cookie);
1173    }
1174
1175    /**
1176     * place a temporary callerinfo object in the hands of the caller and notify
1177     * caller when the actual query is done.
1178     */
1179    static CallerInfoToken startGetCallerInfo(Context context, Connection c,
1180            CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie) {
1181        CallerInfoToken cit;
1182
1183        if (c == null) {
1184            //TODO: perhaps throw an exception here.
1185            cit = new CallerInfoToken();
1186            cit.asyncQuery = null;
1187            return cit;
1188        }
1189
1190        // There are now 3 states for the userdata.
1191        //   1. Uri - query has not been executed yet
1192        //   2. CallerInfoToken - query is executing, but has not completed.
1193        //   3. CallerInfo - query has executed.
1194        // In each case we have slightly different behaviour:
1195        //   1. If the query has not been executed yet (Uri or null), we start
1196        //      query execution asynchronously, and note it by attaching a
1197        //      CallerInfoToken as the userData.
1198        //   2. If the query is executing (CallerInfoToken), we've essentially
1199        //      reached a state where we've received multiple requests for the
1200        //      same callerInfo.  That means that once the query is complete,
1201        //      we'll need to execute the additional listener requested.
1202        //   3. If the query has already been executed (CallerInfo), we just
1203        //      return the CallerInfo object as expected.
1204        //   4. Regarding isFinal - there are cases where the CallerInfo object
1205        //      will not be attached, like when the number is empty (caller id
1206        //      blocking).  This flag is used to indicate that the
1207        //      CallerInfoToken object is going to be permanent since no
1208        //      query results will be returned.  In the case where a query
1209        //      has been completed, this flag is used to indicate to the caller
1210        //      that the data will not be updated since it is valid.
1211        //
1212        //      Note: For the case where a number is NOT retrievable, we leave
1213        //      the CallerInfo as null in the CallerInfoToken.  This is
1214        //      something of a departure from the original code, since the old
1215        //      code manufactured a CallerInfo object regardless of the query
1216        //      outcome.  From now on, we will append an empty CallerInfo
1217        //      object, to mirror previous behaviour, and to avoid Null Pointer
1218        //      Exceptions.
1219        Object userDataObject = c.getUserData();
1220        if (userDataObject instanceof Uri) {
1221            //create a dummy callerinfo, populate with what we know from URI.
1222            cit = new CallerInfoToken();
1223            cit.currentInfo = new CallerInfo();
1224            cit.asyncQuery = CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context,
1225                    (Uri) userDataObject, sCallerInfoQueryListener, c);
1226            cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie);
1227            cit.isFinal = false;
1228
1229            c.setUserData(cit);
1230
1231            if (DBG) log("startGetCallerInfo: query based on Uri: " + userDataObject);
1232
1233        } else if (userDataObject == null) {
1234            // No URI, or Existing CallerInfo, so we'll have to make do with
1235            // querying a new CallerInfo using the connection's phone number.
1236            String number = c.getAddress();
1237
1238            cit = new CallerInfoToken();
1239            cit.currentInfo = new CallerInfo();
1240
1241            if (DBG) log("startGetCallerInfo: number = " + number);
1242
1243            // handling case where number is null (caller id hidden) as well.
1244            if (!TextUtils.isEmpty(number)) {
1245                // Store CNAP information retrieved from the Connection
1246                cit.currentInfo.cnapName =  c.getCnapName();
1247                cit.currentInfo.name = cit.currentInfo.cnapName; // This can still get overwritten
1248                                                                 // by ContactInfo later
1249                cit.currentInfo.numberPresentation = c.getNumberPresentation();
1250                cit.currentInfo.namePresentation = c.getCnapNamePresentation();
1251                if (DBG) log("startGetCallerInfo: CNAP Info from FW: name="
1252                        + cit.currentInfo.cnapName
1253                        + ", Name/Number Pres=" + cit.currentInfo.numberPresentation);
1254
1255                // Check for special CNAP cases and modify the CallerInfo accordingly
1256                // to be sure we keep the right information to display/log later
1257                number = modifyForSpecialCnapCases(context, cit.currentInfo, number,
1258                        cit.currentInfo.numberPresentation);
1259
1260                cit.currentInfo.phoneNumber = number;
1261                cit.asyncQuery = CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context,
1262                        number, sCallerInfoQueryListener, c);
1263                cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie);
1264                cit.isFinal = false;
1265            } else {
1266                // This is the case where we are querying on a number that
1267                // is null or empty, like a caller whose caller id is
1268                // blocked or empty (CLIR).  The previous behaviour was to
1269                // throw a null CallerInfo object back to the user, but
1270                // this departure is somewhat cleaner.
1271                if (DBG) log("startGetCallerInfo: No query to start, send trivial reply.");
1272                cit.isFinal = true; // please see note on isFinal, above.
1273            }
1274
1275            c.setUserData(cit);
1276
1277            if (DBG) log("startGetCallerInfo: query based on number: " + number);
1278
1279        } else if (userDataObject instanceof CallerInfoToken) {
1280            // query is running, just tack on this listener to the queue.
1281            cit = (CallerInfoToken) userDataObject;
1282
1283            // handling case where number is null (caller id hidden) as well.
1284            if (cit.asyncQuery != null) {
1285                cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie);
1286
1287                if (DBG) log("startGetCallerInfo: query already running, adding listener: " +
1288                        listener.getClass().toString());
1289            } else {
1290                // handling case where number/name gets updated later on by the network
1291                String updatedNumber = c.getAddress();
1292                if (DBG) log("startGetCallerInfo: updatedNumber = " + updatedNumber);
1293                if (!TextUtils.isEmpty(updatedNumber)) {
1294                    cit.currentInfo.phoneNumber = updatedNumber;
1295
1296                    // Store CNAP information retrieved from the Connection
1297                    cit.currentInfo.cnapName =  c.getCnapName();
1298                    // This can still get overwritten by ContactInfo
1299                    cit.currentInfo.name = cit.currentInfo.cnapName;
1300                    cit.currentInfo.numberPresentation = c.getNumberPresentation();
1301                    cit.currentInfo.namePresentation = c.getCnapNamePresentation();
1302
1303                    if (DBG) log("startGetCallerInfo: CNAP Info from FW: name="
1304                            + cit.currentInfo.cnapName
1305                            + ", Name/Number Pres=" + cit.currentInfo.numberPresentation);
1306                    cit.asyncQuery = CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context,
1307                            updatedNumber, sCallerInfoQueryListener, c);
1308                    cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie);
1309                    cit.isFinal = false;
1310                } else {
1311                    if (DBG) log("startGetCallerInfo: No query to attach to, send trivial reply.");
1312                    if (cit.currentInfo == null) {
1313                        cit.currentInfo = new CallerInfo();
1314                    }
1315                    cit.isFinal = true; // please see note on isFinal, above.
1316                }
1317            }
1318        } else {
1319            cit = new CallerInfoToken();
1320            cit.currentInfo = (CallerInfo) userDataObject;
1321            cit.asyncQuery = null;
1322            cit.isFinal = true;
1323            // since the query is already done, call the listener.
1324            if (DBG) log("startGetCallerInfo: query already done, returning CallerInfo");
1325        }
1326        return cit;
1327    }
1328
1329    /**
1330     * Implemented for CallerInfo.OnCallerInfoQueryCompleteListener interface.
1331     * Updates the connection's userData when called.
1332     */
1333    private static final int QUERY_TOKEN = -1;
1334    static CallerInfoAsyncQuery.OnQueryCompleteListener sCallerInfoQueryListener =
1335        new CallerInfoAsyncQuery.OnQueryCompleteListener () {
1336            public void onQueryComplete(int token, Object cookie, CallerInfo ci) {
1337                if (DBG) log("query complete, updating connection.userdata");
1338
1339                // Added a check if CallerInfo is coming from ContactInfo or from Connection.
1340                // If no ContactInfo, then we want to use CNAP information coming from network
1341                if (DBG) log("- onQueryComplete: contactExists=" + ci.contactExists);
1342                if (ci.contactExists) {
1343                    ((Connection) cookie).setUserData(ci);
1344                } else {
1345                    CallerInfo newCi = getCallerInfo(null, (Connection) cookie);
1346                    if (newCi != null) {
1347                        newCi.phoneNumber = ci.phoneNumber; // To get formatted phone number
1348                        ((Connection) cookie).setUserData(newCi);
1349                    }
1350                    else ((Connection) cookie).setUserData(ci);
1351                }
1352            }
1353        };
1354
1355    static void saveToContact(Context context, String number) {
1356        Intent intent = new Intent(Contacts.Intents.Insert.ACTION,
1357                Contacts.People.CONTENT_URI);
1358        intent.putExtra(Contacts.Intents.Insert.PHONE, number);
1359        context.startActivity(intent);
1360    }
1361
1362    /**
1363     * Returns a single "name" for the specified given a CallerInfo object.
1364     * If the name is null, return defaultString as the default value, usually
1365     * context.getString(R.string.unknown).
1366     */
1367    static String getCompactNameFromCallerInfo(CallerInfo ci, Context context) {
1368        if (DBG) log("getCompactNameFromCallerInfo: info = " + ci);
1369
1370        String compactName = null;
1371        if (ci != null) {
1372            compactName = ci.name;
1373            if ((compactName == null) || (TextUtils.isEmpty(compactName))) {
1374                compactName = ci.phoneNumber;
1375            }
1376        }
1377        // TODO: figure out UNKNOWN, PRIVATE numbers?
1378        if ((compactName == null) || (TextUtils.isEmpty(compactName))) {
1379            compactName = context.getString(R.string.unknown);
1380        }
1381        return compactName;
1382    }
1383
1384    /**
1385     * Returns true if the specified Call is a "conference call", meaning
1386     * that it owns more than one Connection object.  This information is
1387     * used to trigger certain UI changes that appear when a conference
1388     * call is active (like displaying the label "Conference call", and
1389     * enabling the "Manage conference" UI.)
1390     *
1391     * Watch out: This method simply checks the number of Connections,
1392     * *not* their states.  So if a Call has (for example) one ACTIVE
1393     * connection and one DISCONNECTED connection, this method will return
1394     * true (which is unintuitive, since the Call isn't *really* a
1395     * conference call any more.)
1396     *
1397     * @return true if the specified call has more than one connection (in any state.)
1398     */
1399    static boolean isConferenceCall(Call call) {
1400        // CDMA phones don't have the same concept of "conference call" as
1401        // GSM phones do; there's no special "conference call" state of
1402        // the UI or a "manage conference" function.  (Instead, when
1403        // you're in a 3-way call, all we can do is display the "generic"
1404        // state of the UI.)  So as far as the in-call UI is concerned,
1405        // Conference corresponds to generic display.
1406        PhoneApp app = PhoneApp.getInstance();
1407        if (app.phone.getPhoneName().equals("CDMA")) {
1408            if (app.cdmaPhoneCallState.getCurrentCallState()
1409                    == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
1410                return true;
1411            }
1412        } else {
1413            List<Connection> connections = call.getConnections();
1414            if (connections != null && connections.size() > 1) {
1415                return true;
1416            }
1417        }
1418        return false;
1419
1420        // TODO: We may still want to change the semantics of this method
1421        // to say that a given call is only really a conference call if
1422        // the number of ACTIVE connections, not the total number of
1423        // connections, is greater than one.  (See warning comment in the
1424        // javadoc above.)
1425        // Here's an implementation of that:
1426        //        if (connections == null) {
1427        //            return false;
1428        //        }
1429        //        int numActiveConnections = 0;
1430        //        for (Connection conn : connections) {
1431        //            if (DBG) log("  - CONN: " + conn + ", state = " + conn.getState());
1432        //            if (conn.getState() == Call.State.ACTIVE) numActiveConnections++;
1433        //            if (numActiveConnections > 1) {
1434        //                return true;
1435        //            }
1436        //        }
1437        //        return false;
1438    }
1439
1440    /**
1441     * Launch the Dialer to start a new call.
1442     * This is just a wrapper around the ACTION_DIAL intent.
1443     */
1444    static void startNewCall(final Phone phone) {
1445        final KeyguardManager keyguardManager = PhoneApp.getInstance().getKeyguardManager();
1446        if (!keyguardManager.inKeyguardRestrictedInputMode()) {
1447            internalStartNewCall(phone);
1448        } else {
1449            keyguardManager.exitKeyguardSecurely(new KeyguardManager.OnKeyguardExitResult() {
1450                public void onKeyguardExitResult(boolean success) {
1451                    if (success) {
1452                        internalStartNewCall(phone);
1453                    }
1454                }
1455            });
1456        }
1457    }
1458
1459    private static void internalStartNewCall(Phone phone) {
1460        // Sanity-check that this is OK given the current state of the phone.
1461        if (!okToAddCall(phone)) {
1462            Log.w(LOG_TAG, "startNewCall: can't add a new call in the current state");
1463            dumpCallState(phone);
1464            return;
1465        }
1466
1467        // if applicable, mute the call while we're showing the add call UI.
1468        if (!phone.getForegroundCall().isIdle()) {
1469            setMuteInternal(phone, true);
1470            // Inform the phone app that this mute state was NOT done
1471            // voluntarily by the User.
1472            PhoneApp.getInstance().setRestoreMuteOnInCallResume(true);
1473        }
1474
1475        Intent intent = new Intent(Intent.ACTION_DIAL);
1476        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1477
1478        // when we request the dialer come up, we also want to inform
1479        // it that we're going through the "add call" option from the
1480        // InCallScreen / PhoneUtils.
1481        intent.putExtra(ADD_CALL_MODE_KEY, true);
1482
1483        PhoneApp.getInstance().startActivity(intent);
1484    }
1485
1486    /**
1487     * Brings up the UI used to handle an incoming call.
1488     *
1489     * Originally, this brought up an IncomingCallPanel instance
1490     * (which was a subclass of Dialog) on top of whatever app
1491     * was currently running.  Now, we take you directly to the
1492     * in-call screen, whose CallCard automatically does the right
1493     * thing if there's a Call that's currently ringing.
1494     */
1495    static void showIncomingCallUi() {
1496        if (DBG) log("showIncomingCallUi()...");
1497        PhoneApp app = PhoneApp.getInstance();
1498
1499        // Before bringing up the "incoming call" UI, force any system
1500        // dialogs (like "recent tasks" or the power dialog) to close first.
1501        try {
1502            ActivityManagerNative.getDefault().closeSystemDialogs("call");
1503        } catch (RemoteException e) {
1504        }
1505
1506        // Go directly to the in-call screen.
1507        // (No need to do anything special if we're already on the in-call
1508        // screen; it'll notice the phone state change and update itself.)
1509
1510        // But first, grab a full wake lock.  We do this here, before we
1511        // even fire off the InCallScreen intent, to make sure the
1512        // ActivityManager doesn't try to pause the InCallScreen as soon
1513        // as it comes up.  (See bug 1648751.)
1514        //
1515        // And since the InCallScreen isn't visible yet (we haven't even
1516        // fired off the intent yet), we DON'T want the screen to actually
1517        // come on right now.  So *before* acquiring the wake lock we need
1518        // to call preventScreenOn(), which tells the PowerManager that
1519        // the screen should stay off even if someone's holding a full
1520        // wake lock.  (This prevents any flicker during the "incoming
1521        // call" sequence.  The corresponding preventScreenOn(false) call
1522        // will come from the InCallScreen when it's finally ready to be
1523        // displayed.)
1524        //
1525        // TODO: this is all a temporary workaround.  The real fix is to add
1526        // an Activity attribute saying "this Activity wants to wake up the
1527        // phone when it's displayed"; that way the ActivityManager could
1528        // manage the wake locks *and* arrange for the screen to come on at
1529        // the exact moment that the InCallScreen is ready to be displayed.
1530        // (See bug 1648751.)
1531        app.preventScreenOn(true);
1532        app.requestWakeState(PhoneApp.WakeState.FULL);
1533
1534        // Fire off the InCallScreen intent.
1535        app.displayCallScreen();
1536    }
1537
1538    static void turnOnSpeaker(Context context, boolean flag) {
1539        if (DBG) log("turnOnSpeaker: " + flag);
1540        AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
1541
1542        audioManager.setSpeakerphoneOn(flag);
1543        // record the speaker-enable value
1544        sIsSpeakerEnabled = flag;
1545        if (flag) {
1546            NotificationMgr.getDefault().notifySpeakerphone();
1547        } else {
1548            NotificationMgr.getDefault().cancelSpeakerphone();
1549        }
1550
1551        // We also need to make a fresh call to PhoneApp.updateWakeState()
1552        // any time the speaker state changes, since the screen timeout is
1553        // sometimes different depending on whether or not the speaker is
1554        // in use.
1555        PhoneApp app = PhoneApp.getInstance();
1556        app.updateWakeState();
1557
1558        // Update the Proximity sensor based on speaker state
1559        app.updateProximitySensorMode(app.phone.getState());
1560    }
1561
1562    /**
1563     * Restore the speaker mode, called after a wired headset disconnect
1564     * event.
1565     */
1566    static void restoreSpeakerMode(Context context) {
1567        if (DBG) log("restoreSpeakerMode, restoring to: " + sIsSpeakerEnabled);
1568
1569        // change the mode if needed.
1570        if (isSpeakerOn(context) != sIsSpeakerEnabled) {
1571            turnOnSpeaker(context, sIsSpeakerEnabled);
1572        }
1573    }
1574
1575    static boolean isSpeakerOn(Context context) {
1576        AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
1577        return audioManager.isSpeakerphoneOn();
1578    }
1579
1580    /**
1581     * Wrapper around Phone.setMute() that also updates the mute icon in
1582     * the status bar.
1583     *
1584     * All muting / unmuting from the in-call UI should go through this
1585     * wrapper.
1586     */
1587    static void setMute(Phone phone, boolean muted) {
1588        // make the call to mute the audio
1589        setMuteInternal(phone, muted);
1590
1591        // update the foreground connections to match.  This includes
1592        // all the connections on conference calls.
1593        for (Connection cn : phone.getForegroundCall().getConnections()) {
1594            if (sConnectionMuteTable.get(cn) == null) {
1595                if (DBG) log("problem retrieving mute value for this connection.");
1596            }
1597            sConnectionMuteTable.put(cn, Boolean.valueOf(muted));
1598        }
1599    }
1600
1601    /**
1602     * Internally used muting function.  All UI calls should use {@link setMute}
1603     */
1604    static void setMuteInternal(Phone phone, boolean muted) {
1605        if (DBG) log("setMute: " + muted);
1606        phone.setMute(muted);
1607        if (muted) {
1608            NotificationMgr.getDefault().notifyMute();
1609        } else {
1610            NotificationMgr.getDefault().cancelMute();
1611        }
1612    }
1613
1614    static boolean getMute(Phone phone) {
1615        return phone.getMute();
1616    }
1617
1618    /**
1619     * A really simple wrapper around AudioManager.setMode(),
1620     * with a bit of extra logging to help debug the exact
1621     * timing (and call stacks) for all our setMode() calls.
1622     *
1623     * Also, add additional state monitoring to determine
1624     * whether or not certain calls to change the audio mode
1625     * are ignored.
1626     */
1627    /* package */ static void setAudioMode(Context context, int mode) {
1628        if (DBG) Log.d(LOG_TAG, "PhoneUtils.setAudioMode(" + audioModeToString(mode) + ")...");
1629
1630        //decide whether or not to ignore the audio setting
1631        boolean ignore = false;
1632
1633        switch (sAudioBehaviourState) {
1634            case AUDIO_RINGING:
1635                ignore = ((mode == AudioManager.MODE_NORMAL) || (mode == AudioManager.MODE_IN_CALL));
1636                break;
1637            case AUDIO_OFFHOOK:
1638                ignore = ((mode == AudioManager.MODE_NORMAL) || (mode == AudioManager.MODE_RINGTONE));
1639                break;
1640            case AUDIO_IDLE:
1641            default:
1642                ignore = (mode == AudioManager.MODE_IN_CALL);
1643                break;
1644        }
1645
1646        if (!ignore) {
1647            AudioManager audioManager =
1648                    (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
1649            // Enable stack dump only when actively debugging ("new Throwable()" is expensive!)
1650            if (DBG_SETAUDIOMODE_STACK) Log.d(LOG_TAG, "Stack:", new Throwable("stack dump"));
1651            audioManager.setMode(mode);
1652        } else {
1653            if (DBG) Log.d(LOG_TAG, "PhoneUtils.setAudioMode(), state is " + sAudioBehaviourState +
1654                    " ignoring " + audioModeToString(mode) + " request");
1655        }
1656    }
1657    private static String audioModeToString(int mode) {
1658        switch (mode) {
1659            case AudioManager.MODE_INVALID: return "MODE_INVALID";
1660            case AudioManager.MODE_CURRENT: return "MODE_CURRENT";
1661            case AudioManager.MODE_NORMAL: return "MODE_NORMAL";
1662            case AudioManager.MODE_RINGTONE: return "MODE_RINGTONE";
1663            case AudioManager.MODE_IN_CALL: return "MODE_IN_CALL";
1664            default: return String.valueOf(mode);
1665        }
1666    }
1667
1668    /**
1669     * Handles the wired headset button while in-call.
1670     *
1671     * This is called from the PhoneApp, not from the InCallScreen,
1672     * since the HEADSETHOOK button means "mute or unmute the current
1673     * call" *any* time a call is active, even if the user isn't actually
1674     * on the in-call screen.
1675     *
1676     * @return true if we consumed the event.
1677     */
1678    /* package */ static boolean handleHeadsetHook(Phone phone) {
1679        if (DBG) log("handleHeadsetHook()...");
1680
1681        // If the phone is totally idle, we ignore HEADSETHOOK events
1682        // (and instead let them fall through to the media player.)
1683        if (phone.getState() == Phone.State.IDLE) {
1684            return false;
1685        }
1686
1687        // Ok, the phone is in use.
1688        // The headset button button means "Answer" if an incoming call is
1689        // ringing.  If not, it toggles the mute / unmute state.
1690        //
1691        // And in any case we *always* consume this event; this means
1692        // that the usual mediaplayer-related behavior of the headset
1693        // button will NEVER happen while the user is on a call.
1694
1695        final boolean hasRingingCall = !phone.getRingingCall().isIdle();
1696        final boolean hasActiveCall = !phone.getForegroundCall().isIdle();
1697        final boolean hasHoldingCall = !phone.getBackgroundCall().isIdle();
1698
1699        if (phone.getPhoneName().equals("CDMA")) {
1700            PhoneApp app = PhoneApp.getInstance();
1701            if (hasRingingCall) {
1702                answerCall(phone);
1703            } else {
1704                if (app.cdmaPhoneCallState.getCurrentCallState()
1705                        == CdmaPhoneCallState.PhoneCallState.SINGLE_ACTIVE) {
1706                    // Send a flash command to CDMA network for putting the other
1707                    // party on hold.
1708                    // For CDMA networks which do not support this the user would just
1709                    // hear a beep from the network.
1710                    // For CDMA networks which do support it it will put the other
1711                    // party on hold.
1712                    switchHoldingAndActive(phone);
1713                }
1714
1715                // No incoming ringing call.  Toggle the mute state.
1716                if (getMute(phone)) {
1717                    if (DBG) log("handleHeadsetHook: UNmuting...");
1718                    setMute(phone, false);
1719                } else {
1720                    if (DBG) log("handleHeadsetHook: muting...");
1721                    setMute(phone, true);
1722                }
1723            }
1724        } else { // GSM
1725            if (hasRingingCall) {
1726                // If an incoming call is ringing, answer it (just like with the
1727                // CALL button):
1728                if (hasActiveCall && hasHoldingCall) {
1729                    if (DBG) log("handleHeadsetHook: ringing (both lines in use) ==> answer!");
1730                    answerAndEndActive(phone);
1731                } else {
1732                    if (DBG) log("handleHeadsetHook: ringing ==> answer!");
1733                    answerCall(phone);  // Automatically holds the current active call,
1734                                     // if there is one
1735                }
1736            } else {
1737                // No incoming ringing call.  Toggle the mute state.
1738                if (getMute(phone)) {
1739                    if (DBG) log("handleHeadsetHook: UNmuting...");
1740                    setMute(phone, false);
1741                } else {
1742                    if (DBG) log("handleHeadsetHook: muting...");
1743                    setMute(phone, true);
1744                }
1745            }
1746        }
1747
1748        // Even if the InCallScreen is the current activity, there's no
1749        // need to force it to update, because (1) if we answered a
1750        // ringing call, the InCallScreen will imminently get a phone
1751        // state change event (causing an update), and (2) if we muted or
1752        // unmuted, the setMute() call automagically updates the status
1753        // bar, and there's no "mute" indication in the InCallScreen
1754        // itself (other than the menu item, which only ever stays
1755        // onscreen for a second anyway.)
1756
1757        return true;
1758    }
1759
1760    /**
1761     * Look for ANY connections on the phone that qualify as being
1762     * disconnected.
1763     *
1764     * @return true if we find a connection that is disconnected over
1765     * all the phone's call objects.
1766     */
1767    /* package */ static boolean hasDisconnectedConnections(Phone phone) {
1768        return hasDisconnectedConnections(phone.getForegroundCall()) ||
1769                hasDisconnectedConnections(phone.getBackgroundCall()) ||
1770                hasDisconnectedConnections(phone.getRingingCall());
1771    }
1772
1773    /**
1774     * Iterate over all connections in a call to see if there are any
1775     * that are not alive (disconnected or idle).
1776     *
1777     * @return true if we find a connection that is disconnected, and
1778     * pending removal via
1779     * {@link com.android.internal.telephony.gsm.GsmCall#clearDisconnected()}.
1780     */
1781    private static final boolean hasDisconnectedConnections(Call call) {
1782        // look through all connections for non-active ones.
1783        for (Connection c : call.getConnections()) {
1784            if (!c.isAlive()) {
1785                return true;
1786            }
1787        }
1788        return false;
1789    }
1790
1791    //
1792    // Misc UI policy helper functions
1793    //
1794
1795    /**
1796     * @return true if we're allowed to swap calls, given the current
1797     * state of the Phone.
1798     */
1799    /* package */ static boolean okToSwapCalls(Phone phone) {
1800        if (phone.getPhoneName().equals("CDMA")) {
1801            // CDMA: "Swap" is enabled only when the phone reaches a *generic*.
1802            // state by either accepting a Call Waiting or by merging two calls
1803            PhoneApp app = PhoneApp.getInstance();
1804            return (app.cdmaPhoneCallState.getCurrentCallState()
1805                    == CdmaPhoneCallState.PhoneCallState.CONF_CALL);
1806        } else {
1807            // GSM: "Swap" is available if both lines are in use and there's no
1808            // incoming call.  (Actually we need to verify that the active
1809            // call really is in the ACTIVE state and the holding call really
1810            // is in the HOLDING state, since you *can't* actually swap calls
1811            // when the foreground call is DIALING or ALERTING.)
1812            return phone.getRingingCall().isIdle()
1813                    && (phone.getForegroundCall().getState() == Call.State.ACTIVE)
1814                    && (phone.getBackgroundCall().getState() == Call.State.HOLDING);
1815        }
1816    }
1817
1818    /**
1819     * @return true if we're allowed to merge calls, given the current
1820     * state of the Phone.
1821     */
1822    /* package */ static boolean okToMergeCalls(Phone phone) {
1823        if (phone.getPhoneName().equals("CDMA")) {
1824            // CDMA: "Merge" is enabled only when the user is in a 3Way call.
1825            PhoneApp app = PhoneApp.getInstance();
1826            return (app.cdmaPhoneCallState.getCurrentCallState()
1827                    == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE);
1828        } else { //GSM.
1829            // GSM: "Merge" is available if both lines are in use and there's no
1830            // incoming call, *and* the current conference isn't already
1831            // "full".
1832            return phone.getRingingCall().isIdle() && phone.canConference();
1833        }
1834    }
1835
1836    /**
1837     * @return true if the UI should let you add a new call, given the current
1838     * state of the Phone.
1839     */
1840    /* package */ static boolean okToAddCall(Phone phone) {
1841       if (phone.getPhoneName().equals("CDMA")) {
1842           // CDMA: "Add call" menu item is only enabled when the call is in
1843           // - SINGLE_ACTIVE state
1844           // - After 60 seconds of user Ignoring/Missing a Call Waiting call.
1845            PhoneApp app = PhoneApp.getInstance();
1846            return ((app.cdmaPhoneCallState.getCurrentCallState()
1847                    == CdmaPhoneCallState.PhoneCallState.SINGLE_ACTIVE)
1848                    && (app.cdmaPhoneCallState.getAddCallMenuStateAfterCallWaiting()));
1849        } else {
1850            // GSM: "Add call" is available only if ALL of the following are true:
1851            // - There's no incoming ringing call
1852            // - There's < 2 lines in use
1853            // - The foreground call is ACTIVE or IDLE or DISCONNECTED.
1854            //   (We mainly need to make sure it *isn't* DIALING or ALERTING.)
1855            final boolean hasRingingCall = !phone.getRingingCall().isIdle();
1856            final boolean hasActiveCall = !phone.getForegroundCall().isIdle();
1857            final boolean hasHoldingCall = !phone.getBackgroundCall().isIdle();
1858            final boolean allLinesTaken = hasActiveCall && hasHoldingCall;
1859            final Call.State fgCallState = phone.getForegroundCall().getState();
1860
1861            return !hasRingingCall
1862                    && !allLinesTaken
1863                    && ((fgCallState == Call.State.ACTIVE)
1864                        || (fgCallState == Call.State.IDLE)
1865                        || (fgCallState == Call.State.DISCONNECTED));
1866        }
1867    }
1868
1869    /**
1870     * Based on the input CNAP number string,
1871     * @return _RESTRICTED or _UNKNOWN for all the special CNAP strings.
1872     * Otherwise, return CNAP_SPECIAL_CASE_NO.
1873     */
1874    private static int checkCnapSpecialCases(String n) {
1875        if (n.equals("PRIVATE") ||
1876                n.equals("P") ||
1877                n.equals("RES")) {
1878            if (DBG) log("checkCnapSpecialCases, PRIVATE string: " + n);
1879            return Connection.PRESENTATION_RESTRICTED;
1880        } else if (n.equals("UNAVAILABLE") ||
1881                n.equals("UNKNOWN") ||
1882                n.equals("UNA") ||
1883                n.equals("U")) {
1884            if (DBG) log("checkCnapSpecialCases, UNKNOWN string: " + n);
1885            return Connection.PRESENTATION_UNKNOWN;
1886        } else {
1887            if (DBG) log("checkCnapSpecialCases, normal str. number: " + n);
1888            return CNAP_SPECIAL_CASE_NO;
1889        }
1890    }
1891
1892    /**
1893     * Handles certain "corner cases" for CNAP. When we receive weird phone numbers
1894     * from the network to indicate different number presentations, convert them to
1895     * expected number and presentation values within the CallerInfo object.
1896     * @param number number we use to verify if we are in a corner case
1897     * @param presentation presentation value used to verify if we are in a corner case
1898     * @return the new String that should be used for the phone number
1899     */
1900    /* package */ static String modifyForSpecialCnapCases(Context context, CallerInfo ci,
1901            String number, int presentation) {
1902        if (ci == null) return number;
1903
1904        // "ABSENT NUMBER" is a possible value we could get from the network as the
1905        // phone number, so if this happens, change it to "Unknown" in the CallerInfo
1906        // and fix the presentation to be the same.
1907        if (number.equals(context.getString(R.string.absent_num))) {
1908            number = context.getString(R.string.unknown);
1909            ci.numberPresentation = Connection.PRESENTATION_UNKNOWN;
1910        }
1911
1912        // Check for other special "corner cases" for CNAP and fix them similarly.
1913        if (ci.numberPresentation == Connection.PRESENTATION_ALLOWED
1914                || ci.numberPresentation != presentation) {
1915            int cnapSpecialCase = checkCnapSpecialCases(number);
1916            if (cnapSpecialCase != CNAP_SPECIAL_CASE_NO) {
1917                // For all special strings, change number & numberPresentation.
1918                if (cnapSpecialCase == Connection.PRESENTATION_RESTRICTED) {
1919                    number = context.getString(R.string.private_num);
1920                } else if (cnapSpecialCase == Connection.PRESENTATION_UNKNOWN) {
1921                    number = context.getString(R.string.unknown);
1922                }
1923                if (DBG) log("SpecialCnap: number=" + number
1924                        + "; presentation now=" + cnapSpecialCase);
1925                ci.numberPresentation = cnapSpecialCase;
1926            }
1927        }
1928        return number;
1929    }
1930
1931    //
1932    // Support for 3rd party phone service providers.
1933    //
1934
1935    /**
1936     * Copy all the expected extras set when a 3rd party provider is
1937     * used from the source intent to the destination one.  Checks all
1938     * the required extras are present, if any is missing, none will
1939     * be copied.
1940     * @param src Intent which may contain the provider's extras.
1941     * @param dst Intent where a copy of the extras will be added if applicable.
1942     */
1943    /* package */ static void copyPhoneProviderExtras(Intent src, Intent dst) {
1944        final boolean hasBadge = src.hasExtra(InCallScreen.EXTRA_PROVIDER_BADGE);
1945        final boolean hasNumber = src.hasExtra(InCallScreen.EXTRA_PROVIDER_NUMBER);
1946
1947        if (hasBadge && hasNumber) {
1948            dst.putExtra(InCallScreen.EXTRA_PROVIDER_BADGE,
1949                         src.getParcelableExtra(InCallScreen.EXTRA_PROVIDER_BADGE));
1950            dst.putExtra(InCallScreen.EXTRA_PROVIDER_NUMBER,
1951                         src.getStringExtra(InCallScreen.EXTRA_PROVIDER_NUMBER));
1952        }
1953    }
1954
1955    //
1956    // General phone and call state debugging/testing code
1957    //
1958
1959    /* package */ static void dumpCallState(Phone phone) {
1960        Log.d(LOG_TAG, "##### dumpCallState()");
1961        Log.d(LOG_TAG, "- Phone: " + phone + ", name = " + phone.getPhoneName()
1962              + ", state = " + phone.getState());
1963        Log.d(LOG_TAG, "-");
1964
1965        Call fgCall = phone.getForegroundCall();
1966        Log.d(LOG_TAG, "- FG call: " + fgCall);
1967        Log.d(LOG_TAG, "-  state: " + fgCall.getState());
1968        Log.d(LOG_TAG, "-  isAlive(): " + fgCall.getState().isAlive());
1969        Log.d(LOG_TAG, "-  isRinging(): " + fgCall.getState().isRinging());
1970        Log.d(LOG_TAG, "-  isDialing(): " + fgCall.getState().isDialing());
1971        Log.d(LOG_TAG, "-  isIdle(): " + fgCall.isIdle());
1972        Log.d(LOG_TAG, "-  hasConnections: " + fgCall.hasConnections());
1973        Log.d(LOG_TAG, "-");
1974
1975        Call bgCall = phone.getBackgroundCall();
1976        Log.d(LOG_TAG, "- BG call: " + bgCall);
1977        Log.d(LOG_TAG, "-  state: " + bgCall.getState());
1978        Log.d(LOG_TAG, "-  isAlive(): " + bgCall.getState().isAlive());
1979        Log.d(LOG_TAG, "-  isRinging(): " + bgCall.getState().isRinging());
1980        Log.d(LOG_TAG, "-  isDialing(): " + bgCall.getState().isDialing());
1981        Log.d(LOG_TAG, "-  isIdle(): " + bgCall.isIdle());
1982        Log.d(LOG_TAG, "-  hasConnections: " + bgCall.hasConnections());
1983        Log.d(LOG_TAG, "-");
1984
1985        Call ringingCall = phone.getRingingCall();
1986        Log.d(LOG_TAG, "- RINGING call: " + ringingCall);
1987        Log.d(LOG_TAG, "-  state: " + ringingCall.getState());
1988        Log.d(LOG_TAG, "-  isAlive(): " + ringingCall.getState().isAlive());
1989        Log.d(LOG_TAG, "-  isRinging(): " + ringingCall.getState().isRinging());
1990        Log.d(LOG_TAG, "-  isDialing(): " + ringingCall.getState().isDialing());
1991        Log.d(LOG_TAG, "-  isIdle(): " + ringingCall.isIdle());
1992        Log.d(LOG_TAG, "-  hasConnections: " + ringingCall.hasConnections());
1993        Log.d(LOG_TAG, "-");
1994
1995        final boolean hasRingingCall = !phone.getRingingCall().isIdle();
1996        final boolean hasActiveCall = !phone.getForegroundCall().isIdle();
1997        final boolean hasHoldingCall = !phone.getBackgroundCall().isIdle();
1998        final boolean allLinesTaken = hasActiveCall && hasHoldingCall;
1999        Log.d(LOG_TAG, "- hasRingingCall: " + hasRingingCall);
2000        Log.d(LOG_TAG, "- hasActiveCall: " + hasActiveCall);
2001        Log.d(LOG_TAG, "- hasHoldingCall: " + hasHoldingCall);
2002        Log.d(LOG_TAG, "- allLinesTaken: " + allLinesTaken);
2003
2004        // Watch out: the isRinging() call below does NOT tell us anything
2005        // about the state of the telephony layer; it merely tells us whether
2006        // the Ringer manager is currently playing the ringtone.
2007        boolean ringing = PhoneApp.getInstance().getRinger().isRinging();
2008        Log.d(LOG_TAG, "- ringing (Ringer manager state): " + ringing);
2009        Log.d(LOG_TAG, "-----");
2010    }
2011
2012    private static void log(String msg) {
2013        Log.d(LOG_TAG, msg);
2014    }
2015}
2016