PhoneUtils.java revision 5ccda616708b298d6f71598b6ac23a94cd6033f8
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.AlertDialog;
20import android.app.Dialog;
21import android.app.ProgressDialog;
22import android.bluetooth.IBluetoothHeadsetPhone;
23import android.content.ActivityNotFoundException;
24import android.content.ComponentName;
25import android.content.Context;
26import android.content.DialogInterface;
27import android.content.Intent;
28import android.content.res.Configuration;
29import android.media.AudioManager;
30import android.net.Uri;
31import android.os.Handler;
32import android.os.Message;
33import android.os.PersistableBundle;
34import android.os.RemoteException;
35import android.os.SystemProperties;
36import android.telecom.PhoneAccount;
37import android.telecom.PhoneAccountHandle;
38import android.telecom.VideoProfile;
39import android.telephony.CarrierConfigManager;
40import android.telephony.PhoneNumberUtils;
41import android.telephony.SubscriptionManager;
42import android.text.TextUtils;
43import android.util.Log;
44import android.view.ContextThemeWrapper;
45import android.view.KeyEvent;
46import android.view.LayoutInflater;
47import android.view.View;
48import android.view.WindowManager;
49import android.widget.EditText;
50import android.widget.Toast;
51
52import com.android.internal.telephony.Call;
53import com.android.internal.telephony.CallManager;
54import com.android.internal.telephony.CallStateException;
55import com.android.internal.telephony.CallerInfo;
56import com.android.internal.telephony.CallerInfoAsyncQuery;
57import com.android.internal.telephony.Connection;
58import com.android.internal.telephony.IccCard;
59import com.android.internal.telephony.MmiCode;
60import com.android.internal.telephony.Phone;
61import com.android.internal.telephony.PhoneConstants;
62import com.android.internal.telephony.PhoneFactory;
63import com.android.internal.telephony.TelephonyCapabilities;
64import com.android.internal.telephony.TelephonyProperties;
65import com.android.internal.telephony.sip.SipPhone;
66import com.android.phone.CallGatewayManager.RawGatewayInfo;
67import com.android.services.telephony.TelephonyConnectionService;
68
69import java.util.Arrays;
70import java.util.List;
71
72/**
73 * Misc utilities for the Phone app.
74 */
75public class PhoneUtils {
76    private static final String LOG_TAG = "PhoneUtils";
77    private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
78
79    // Do not check in with VDBG = true, since that may write PII to the system log.
80    private static final boolean VDBG = false;
81
82    /** Control stack trace for Audio Mode settings */
83    private static final boolean DBG_SETAUDIOMODE_STACK = false;
84
85    /** Identifier for the "Add Call" intent extra. */
86    static final String ADD_CALL_MODE_KEY = "add_call_mode";
87
88    // Return codes from placeCall()
89    static final int CALL_STATUS_DIALED = 0;  // The number was successfully dialed
90    static final int CALL_STATUS_DIALED_MMI = 1;  // The specified number was an MMI code
91    static final int CALL_STATUS_FAILED = 2;  // The call failed
92
93    // State of the Phone's audio modes
94    // Each state can move to the other states, but within the state only certain
95    //  transitions for AudioManager.setMode() are allowed.
96    static final int AUDIO_IDLE = 0;  /** audio behaviour at phone idle */
97    static final int AUDIO_RINGING = 1;  /** audio behaviour while ringing */
98    static final int AUDIO_OFFHOOK = 2;  /** audio behaviour while in call. */
99
100    // USSD string length for MMI operations
101    static final int MIN_USSD_LEN = 1;
102    static final int MAX_USSD_LEN = 160;
103
104    /** Speaker state, persisting between wired headset connection events */
105    private static boolean sIsSpeakerEnabled = false;
106
107    /** Static handler for the connection/mute tracking */
108    private static ConnectionHandler mConnectionHandler;
109
110    /** Phone state changed event*/
111    private static final int PHONE_STATE_CHANGED = -1;
112
113    /** check status then decide whether answerCall */
114    private static final int MSG_CHECK_STATUS_ANSWERCALL = 100;
115
116    /** poll phone DISCONNECTING status interval */
117    private static final int DISCONNECTING_POLLING_INTERVAL_MS = 200;
118
119    /** poll phone DISCONNECTING status times limit */
120    private static final int DISCONNECTING_POLLING_TIMES_LIMIT = 8;
121
122    /** Define for not a special CNAP string */
123    private static final int CNAP_SPECIAL_CASE_NO = -1;
124
125    /** Noise suppression status as selected by user */
126    private static boolean sIsNoiseSuppressionEnabled = true;
127
128    /**
129     * Theme to use for dialogs displayed by utility methods in this class. This is needed
130     * because these dialogs are displayed using the application context, which does not resolve
131     * the dialog theme correctly.
132     */
133    private static final int THEME = AlertDialog.THEME_DEVICE_DEFAULT_LIGHT;
134
135    private static class FgRingCalls {
136        private Call fgCall;
137        private Call ringing;
138        public FgRingCalls(Call fg, Call ring) {
139            fgCall = fg;
140            ringing = ring;
141        }
142    }
143
144    /** USSD information used to aggregate all USSD messages */
145    private static AlertDialog sUssdDialog = null;
146    private static StringBuilder sUssdMsg = new StringBuilder();
147
148    /**
149     * Handler that tracks the connections and updates the value of the
150     * Mute settings for each connection as needed.
151     */
152    private static class ConnectionHandler extends Handler {
153        @Override
154        public void handleMessage(Message msg) {
155            switch (msg.what) {
156                case MSG_CHECK_STATUS_ANSWERCALL:
157                    FgRingCalls frC = (FgRingCalls) msg.obj;
158                    // wait for finishing disconnecting
159                    // before check the ringing call state
160                    if ((frC.fgCall != null) &&
161                        (frC.fgCall.getState() == Call.State.DISCONNECTING) &&
162                        (msg.arg1 < DISCONNECTING_POLLING_TIMES_LIMIT)) {
163                        Message retryMsg =
164                            mConnectionHandler.obtainMessage(MSG_CHECK_STATUS_ANSWERCALL);
165                        retryMsg.arg1 = 1 + msg.arg1;
166                        retryMsg.obj = msg.obj;
167                        mConnectionHandler.sendMessageDelayed(retryMsg,
168                            DISCONNECTING_POLLING_INTERVAL_MS);
169                    // since hangupActiveCall() also accepts the ringing call
170                    // check if the ringing call was already answered or not
171                    // only answer it when the call still is ringing
172                    } else if (frC.ringing.isRinging()) {
173                        if (msg.arg1 == DISCONNECTING_POLLING_TIMES_LIMIT) {
174                            Log.e(LOG_TAG, "DISCONNECTING time out");
175                        }
176                        answerCall(frC.ringing);
177                    }
178                    break;
179            }
180        }
181    }
182
183    /**
184     * Register the ConnectionHandler with the phone, to receive connection events
185     */
186    public static void initializeConnectionHandler(CallManager cm) {
187        if (mConnectionHandler == null) {
188            mConnectionHandler = new ConnectionHandler();
189        }
190
191        // pass over cm as user.obj
192        cm.registerForPreciseCallStateChanged(mConnectionHandler, PHONE_STATE_CHANGED, cm);
193
194    }
195
196    /** This class is never instantiated. */
197    private PhoneUtils() {
198    }
199
200    /**
201     * Answer the currently-ringing call.
202     *
203     * @return true if we answered the call, or false if there wasn't
204     *         actually a ringing incoming call, or some other error occurred.
205     *
206     * @see #answerAndEndHolding(CallManager, Call)
207     * @see #answerAndEndActive(CallManager, Call)
208     */
209    /* package */ static boolean answerCall(Call ringingCall) {
210        log("answerCall(" + ringingCall + ")...");
211        final PhoneGlobals app = PhoneGlobals.getInstance();
212        final CallNotifier notifier = app.notifier;
213
214        final Phone phone = ringingCall.getPhone();
215        final boolean phoneIsCdma = (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA);
216        boolean answered = false;
217        IBluetoothHeadsetPhone btPhone = null;
218
219        if (phoneIsCdma) {
220            // Stop any signalInfo tone being played when a Call waiting gets answered
221            if (ringingCall.getState() == Call.State.WAITING) {
222                notifier.stopSignalInfoTone();
223            }
224        }
225
226        if (ringingCall != null && ringingCall.isRinging()) {
227            if (DBG) log("answerCall: call state = " + ringingCall.getState());
228            try {
229                if (phoneIsCdma) {
230                    if (app.cdmaPhoneCallState.getCurrentCallState()
231                            == CdmaPhoneCallState.PhoneCallState.IDLE) {
232                        // This is the FIRST incoming call being answered.
233                        // Set the Phone Call State to SINGLE_ACTIVE
234                        app.cdmaPhoneCallState.setCurrentCallState(
235                                CdmaPhoneCallState.PhoneCallState.SINGLE_ACTIVE);
236                    } else {
237                        // This is the CALL WAITING call being answered.
238                        // Set the Phone Call State to CONF_CALL
239                        app.cdmaPhoneCallState.setCurrentCallState(
240                                CdmaPhoneCallState.PhoneCallState.CONF_CALL);
241                        // Enable "Add Call" option after answering a Call Waiting as the user
242                        // should be allowed to add another call in case one of the parties
243                        // drops off
244                        app.cdmaPhoneCallState.setAddCallMenuStateAfterCallWaiting(true);
245                    }
246                }
247
248                final boolean isRealIncomingCall = isRealIncomingCall(ringingCall.getState());
249
250                //if (DBG) log("sPhone.acceptCall");
251                app.mCM.acceptCall(ringingCall);
252                answered = true;
253
254                setAudioMode();
255            } catch (CallStateException ex) {
256                Log.w(LOG_TAG, "answerCall: caught " + ex, ex);
257
258                if (phoneIsCdma) {
259                    // restore the cdmaPhoneCallState and btPhone.cdmaSetSecondCallState:
260                    app.cdmaPhoneCallState.setCurrentCallState(
261                            app.cdmaPhoneCallState.getPreviousCallState());
262                    if (btPhone != null) {
263                        try {
264                            btPhone.cdmaSetSecondCallState(false);
265                        } catch (RemoteException e) {
266                            Log.e(LOG_TAG, Log.getStackTraceString(new Throwable()));
267                        }
268                    }
269                }
270            }
271        }
272        return answered;
273    }
274
275    /**
276     * Hangs up all active calls.
277     */
278    static void hangupAllCalls(CallManager cm) {
279        final Call ringing = cm.getFirstActiveRingingCall();
280        final Call fg = cm.getActiveFgCall();
281        final Call bg = cm.getFirstActiveBgCall();
282
283        // We go in reverse order, BG->FG->RINGING because hanging up a ringing call or an active
284        // call can move a bg call to a fg call which would force us to loop over each call
285        // several times.  This ordering works best to ensure we dont have any more calls.
286        if (bg != null && !bg.isIdle()) {
287            hangup(bg);
288        }
289        if (fg != null && !fg.isIdle()) {
290            hangup(fg);
291        }
292        if (ringing != null && !ringing.isIdle()) {
293            hangupRingingCall(fg);
294        }
295    }
296
297    /**
298     * Smart "hang up" helper method which hangs up exactly one connection,
299     * based on the current Phone state, as follows:
300     * <ul>
301     * <li>If there's a ringing call, hang that up.
302     * <li>Else if there's a foreground call, hang that up.
303     * <li>Else if there's a background call, hang that up.
304     * <li>Otherwise do nothing.
305     * </ul>
306     * @return true if we successfully hung up, or false
307     *              if there were no active calls at all.
308     */
309    static boolean hangup(CallManager cm) {
310        boolean hungup = false;
311        Call ringing = cm.getFirstActiveRingingCall();
312        Call fg = cm.getActiveFgCall();
313        Call bg = cm.getFirstActiveBgCall();
314
315        if (!ringing.isIdle()) {
316            log("hangup(): hanging up ringing call");
317            hungup = hangupRingingCall(ringing);
318        } else if (!fg.isIdle()) {
319            log("hangup(): hanging up foreground call");
320            hungup = hangup(fg);
321        } else if (!bg.isIdle()) {
322            log("hangup(): hanging up background call");
323            hungup = hangup(bg);
324        } else {
325            // No call to hang up!  This is unlikely in normal usage,
326            // since the UI shouldn't be providing an "End call" button in
327            // the first place.  (But it *can* happen, rarely, if an
328            // active call happens to disconnect on its own right when the
329            // user is trying to hang up..)
330            log("hangup(): no active call to hang up");
331        }
332        if (DBG) log("==> hungup = " + hungup);
333
334        return hungup;
335    }
336
337    static boolean hangupRingingCall(Call ringing) {
338        if (DBG) log("hangup ringing call");
339        int phoneType = ringing.getPhone().getPhoneType();
340        Call.State state = ringing.getState();
341
342        if (state == Call.State.INCOMING) {
343            // Regular incoming call (with no other active calls)
344            log("hangupRingingCall(): regular incoming call: hangup()");
345            return hangup(ringing);
346        } else {
347            // Unexpected state: the ringing call isn't INCOMING or
348            // WAITING, so there's no reason to have called
349            // hangupRingingCall() in the first place.
350            // (Presumably the incoming call went away at the exact moment
351            // we got here, so just do nothing.)
352            Log.w(LOG_TAG, "hangupRingingCall: no INCOMING or WAITING call");
353            return false;
354        }
355    }
356
357    static boolean hangupActiveCall(Call foreground) {
358        if (DBG) log("hangup active call");
359        return hangup(foreground);
360    }
361
362    static boolean hangupHoldingCall(Call background) {
363        if (DBG) log("hangup holding call");
364        return hangup(background);
365    }
366
367    /**
368     * Used in CDMA phones to end the complete Call session
369     * @param phone the Phone object.
370     * @return true if *any* call was successfully hung up
371     */
372    static boolean hangupRingingAndActive(Phone phone) {
373        boolean hungUpRingingCall = false;
374        boolean hungUpFgCall = false;
375        Call ringingCall = phone.getRingingCall();
376        Call fgCall = phone.getForegroundCall();
377
378        // Hang up any Ringing Call
379        if (!ringingCall.isIdle()) {
380            log("hangupRingingAndActive: Hang up Ringing Call");
381            hungUpRingingCall = hangupRingingCall(ringingCall);
382        }
383
384        // Hang up any Active Call
385        if (!fgCall.isIdle()) {
386            log("hangupRingingAndActive: Hang up Foreground Call");
387            hungUpFgCall = hangupActiveCall(fgCall);
388        }
389
390        return hungUpRingingCall || hungUpFgCall;
391    }
392
393    /**
394     * Trivial wrapper around Call.hangup(), except that we return a
395     * boolean success code rather than throwing CallStateException on
396     * failure.
397     *
398     * @return true if the call was successfully hung up, or false
399     *         if the call wasn't actually active.
400     */
401    static boolean hangup(Call call) {
402        try {
403            CallManager cm = PhoneGlobals.getInstance().mCM;
404
405            if (call.getState() == Call.State.ACTIVE && cm.hasActiveBgCall()) {
406                // handle foreground call hangup while there is background call
407                log("- hangup(Call): hangupForegroundResumeBackground...");
408                cm.hangupForegroundResumeBackground(cm.getFirstActiveBgCall());
409            } else {
410                log("- hangup(Call): regular hangup()...");
411                call.hangup();
412            }
413            return true;
414        } catch (CallStateException ex) {
415            Log.e(LOG_TAG, "Call hangup: caught " + ex, ex);
416        }
417
418        return false;
419    }
420
421    /**
422     * Trivial wrapper around Connection.hangup(), except that we silently
423     * do nothing (rather than throwing CallStateException) if the
424     * connection wasn't actually active.
425     */
426    static void hangup(Connection c) {
427        try {
428            if (c != null) {
429                c.hangup();
430            }
431        } catch (CallStateException ex) {
432            Log.w(LOG_TAG, "Connection hangup: caught " + ex, ex);
433        }
434    }
435
436    static boolean answerAndEndHolding(CallManager cm, Call ringing) {
437        if (DBG) log("end holding & answer waiting: 1");
438        if (!hangupHoldingCall(cm.getFirstActiveBgCall())) {
439            Log.e(LOG_TAG, "end holding failed!");
440            return false;
441        }
442
443        if (DBG) log("end holding & answer waiting: 2");
444        return answerCall(ringing);
445
446    }
447
448    /**
449     * Answers the incoming call specified by "ringing", and ends the currently active phone call.
450     *
451     * This method is useful when's there's an incoming call which we cannot manage with the
452     * current call. e.g. when you are having a phone call with CDMA network and has received
453     * a SIP call, then we won't expect our telephony can manage those phone calls simultaneously.
454     * Note that some types of network may allow multiple phone calls at once; GSM allows to hold
455     * an ongoing phone call, so we don't need to end the active call. The caller of this method
456     * needs to check if the network allows multiple phone calls or not.
457     *
458     * @see #answerCall(Call)
459     * @see InCallScreen#internalAnswerCall()
460     */
461    /* package */ static boolean answerAndEndActive(CallManager cm, Call ringing) {
462        if (DBG) log("answerAndEndActive()...");
463
464        // Unlike the answerCall() method, we *don't* need to stop the
465        // ringer or change audio modes here since the user is already
466        // in-call, which means that the audio mode is already set
467        // correctly, and that we wouldn't have started the ringer in the
468        // first place.
469
470        // hanging up the active call also accepts the waiting call
471        // while active call and waiting call are from the same phone
472        // i.e. both from GSM phone
473        Call fgCall = cm.getActiveFgCall();
474        if (!hangupActiveCall(fgCall)) {
475            Log.w(LOG_TAG, "end active call failed!");
476            return false;
477        }
478
479        mConnectionHandler.removeMessages(MSG_CHECK_STATUS_ANSWERCALL);
480        Message msg = mConnectionHandler.obtainMessage(MSG_CHECK_STATUS_ANSWERCALL);
481        msg.arg1 = 1;
482        msg.obj = new FgRingCalls(fgCall, ringing);
483        mConnectionHandler.sendMessage(msg);
484
485        return true;
486    }
487
488    /**
489     * For a CDMA phone, advance the call state upon making a new
490     * outgoing call.
491     *
492     * <pre>
493     *   IDLE -> SINGLE_ACTIVE
494     * or
495     *   SINGLE_ACTIVE -> THRWAY_ACTIVE
496     * </pre>
497     * @param app The phone instance.
498     */
499    private static void updateCdmaCallStateOnNewOutgoingCall(PhoneGlobals app,
500            Connection connection) {
501        if (app.cdmaPhoneCallState.getCurrentCallState() ==
502            CdmaPhoneCallState.PhoneCallState.IDLE) {
503            // This is the first outgoing call. Set the Phone Call State to ACTIVE
504            app.cdmaPhoneCallState.setCurrentCallState(
505                CdmaPhoneCallState.PhoneCallState.SINGLE_ACTIVE);
506        } else {
507            // This is the second outgoing call. Set the Phone Call State to 3WAY
508            app.cdmaPhoneCallState.setCurrentCallState(
509                CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE);
510
511            // TODO: Remove this code.
512            //app.getCallModeler().setCdmaOutgoing3WayCall(connection);
513        }
514    }
515
516    /**
517     * @see placeCall below
518     */
519    public static int placeCall(Context context, Phone phone, String number, Uri contactRef,
520            boolean isEmergencyCall) {
521        return placeCall(context, phone, number, contactRef, isEmergencyCall,
522                CallGatewayManager.EMPTY_INFO, null);
523    }
524
525    /**
526     * Dial the number using the phone passed in.
527     *
528     * If the connection is establised, this method issues a sync call
529     * that may block to query the caller info.
530     * TODO: Change the logic to use the async query.
531     *
532     * @param context To perform the CallerInfo query.
533     * @param phone the Phone object.
534     * @param number to be dialed as requested by the user. This is
535     * NOT the phone number to connect to. It is used only to build the
536     * call card and to update the call log. See above for restrictions.
537     * @param contactRef that triggered the call. Typically a 'tel:'
538     * uri but can also be a 'content://contacts' one.
539     * @param isEmergencyCall indicates that whether or not this is an
540     * emergency call
541     * @param gatewayUri Is the address used to setup the connection, null
542     * if not using a gateway
543     * @param callGateway Class for setting gateway data on a successful call.
544     *
545     * @return either CALL_STATUS_DIALED or CALL_STATUS_FAILED
546     */
547    public static int placeCall(Context context, Phone phone, String number, Uri contactRef,
548            boolean isEmergencyCall, RawGatewayInfo gatewayInfo, CallGatewayManager callGateway) {
549        final Uri gatewayUri = gatewayInfo.gatewayUri;
550
551        if (VDBG) {
552            log("placeCall()... number: '" + number + "'"
553                    + ", GW:'" + gatewayUri + "'"
554                    + ", contactRef:" + contactRef
555                    + ", isEmergencyCall: " + isEmergencyCall);
556        } else {
557            log("placeCall()... number: " + toLogSafePhoneNumber(number)
558                    + ", GW: " + (gatewayUri != null ? "non-null" : "null")
559                    + ", emergency? " + isEmergencyCall);
560        }
561        final PhoneGlobals app = PhoneGlobals.getInstance();
562
563        boolean useGateway = false;
564        if (null != gatewayUri &&
565            !isEmergencyCall &&
566            PhoneUtils.isRoutableViaGateway(number)) {  // Filter out MMI, OTA and other codes.
567            useGateway = true;
568        }
569
570        int status = CALL_STATUS_DIALED;
571        Connection connection;
572        String numberToDial;
573        if (useGateway) {
574            // TODO: 'tel' should be a constant defined in framework base
575            // somewhere (it is in webkit.)
576            if (null == gatewayUri || !PhoneAccount.SCHEME_TEL.equals(gatewayUri.getScheme())) {
577                Log.e(LOG_TAG, "Unsupported URL:" + gatewayUri);
578                return CALL_STATUS_FAILED;
579            }
580
581            // We can use getSchemeSpecificPart because we don't allow #
582            // in the gateway numbers (treated a fragment delim.) However
583            // if we allow more complex gateway numbers sequence (with
584            // passwords or whatnot) that use #, this may break.
585            // TODO: Need to support MMI codes.
586            numberToDial = gatewayUri.getSchemeSpecificPart();
587        } else {
588            numberToDial = number;
589        }
590
591        // Remember if the phone state was in IDLE state before this call.
592        // After calling CallManager#dial(), getState() will return different state.
593        final boolean initiallyIdle = app.mCM.getState() == PhoneConstants.State.IDLE;
594
595        try {
596            connection = app.mCM.dial(phone, numberToDial, VideoProfile.STATE_AUDIO_ONLY);
597        } catch (CallStateException ex) {
598            // CallStateException means a new outgoing call is not currently
599            // possible: either no more call slots exist, or there's another
600            // call already in the process of dialing or ringing.
601            Log.w(LOG_TAG, "Exception from app.mCM.dial()", ex);
602            return CALL_STATUS_FAILED;
603
604            // Note that it's possible for CallManager.dial() to return
605            // null *without* throwing an exception; that indicates that
606            // we dialed an MMI (see below).
607        }
608
609        int phoneType = phone.getPhoneType();
610
611        // On GSM phones, null is returned for MMI codes
612        if (null == connection) {
613            status = CALL_STATUS_FAILED;
614        } else {
615            // Now that the call is successful, we can save the gateway info for the call
616            if (callGateway != null) {
617                callGateway.setGatewayInfoForConnection(connection, gatewayInfo);
618            }
619
620            if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
621                updateCdmaCallStateOnNewOutgoingCall(app, connection);
622            }
623
624            if (gatewayUri == null) {
625                // phone.dial() succeeded: we're now in a normal phone call.
626                // attach the URI to the CallerInfo Object if it is there,
627                // otherwise just attach the Uri Reference.
628                // if the uri does not have a "content" scheme, then we treat
629                // it as if it does NOT have a unique reference.
630                String content = context.getContentResolver().SCHEME_CONTENT;
631                if ((contactRef != null) && (contactRef.getScheme().equals(content))) {
632                    Object userDataObject = connection.getUserData();
633                    if (userDataObject == null) {
634                        connection.setUserData(contactRef);
635                    } else {
636                        // TODO: This branch is dead code, we have
637                        // just created the connection which has
638                        // no user data (null) by default.
639                        if (userDataObject instanceof CallerInfo) {
640                        ((CallerInfo) userDataObject).contactRefUri = contactRef;
641                        } else {
642                        ((CallerInfoToken) userDataObject).currentInfo.contactRefUri =
643                            contactRef;
644                        }
645                    }
646                }
647            }
648
649            startGetCallerInfo(context, connection, null, null, gatewayInfo);
650
651            setAudioMode();
652        }
653
654        return status;
655    }
656
657    /* package */ static String toLogSafePhoneNumber(String number) {
658        // For unknown number, log empty string.
659        if (number == null) {
660            return "";
661        }
662
663        if (VDBG) {
664            // When VDBG is true we emit PII.
665            return number;
666        }
667
668        // Do exactly same thing as Uri#toSafeString() does, which will enable us to compare
669        // sanitized phone numbers.
670        StringBuilder builder = new StringBuilder();
671        for (int i = 0; i < number.length(); i++) {
672            char c = number.charAt(i);
673            if (c == '-' || c == '@' || c == '.') {
674                builder.append(c);
675            } else {
676                builder.append('x');
677            }
678        }
679        return builder.toString();
680    }
681
682    /**
683     * Wrapper function to control when to send an empty Flash command to the network.
684     * Mainly needed for CDMA networks, such as scenarios when we need to send a blank flash
685     * to the network prior to placing a 3-way call for it to be successful.
686     */
687    static void sendEmptyFlash(Phone phone) {
688        if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
689            Call fgCall = phone.getForegroundCall();
690            if (fgCall.getState() == Call.State.ACTIVE) {
691                // Send the empty flash
692                if (DBG) Log.d(LOG_TAG, "onReceive: (CDMA) sending empty flash to network");
693                switchHoldingAndActive(phone.getBackgroundCall());
694            }
695        }
696    }
697
698    static void swap() {
699        final PhoneGlobals mApp = PhoneGlobals.getInstance();
700        if (!okToSwapCalls(mApp.mCM)) {
701            // TODO: throw an error instead?
702            return;
703        }
704
705        // Swap the fg and bg calls.
706        // In the future we may provide some way for user to choose among
707        // multiple background calls, for now, always act on the first background call.
708        PhoneUtils.switchHoldingAndActive(mApp.mCM.getFirstActiveBgCall());
709    }
710
711    /**
712     * @param heldCall is the background call want to be swapped
713     */
714    static void switchHoldingAndActive(Call heldCall) {
715        log("switchHoldingAndActive()...");
716        try {
717            CallManager cm = PhoneGlobals.getInstance().mCM;
718            if (heldCall.isIdle()) {
719                // no heldCall, so it is to hold active call
720                cm.switchHoldingAndActive(cm.getFgPhone().getBackgroundCall());
721            } else {
722                // has particular heldCall, so to switch
723                cm.switchHoldingAndActive(heldCall);
724            }
725            setAudioMode(cm);
726        } catch (CallStateException ex) {
727            Log.w(LOG_TAG, "switchHoldingAndActive: caught " + ex, ex);
728        }
729    }
730
731    static void mergeCalls() {
732        mergeCalls(PhoneGlobals.getInstance().mCM);
733    }
734
735    static void mergeCalls(CallManager cm) {
736        int phoneType = cm.getFgPhone().getPhoneType();
737        if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
738            log("mergeCalls(): CDMA...");
739            PhoneGlobals app = PhoneGlobals.getInstance();
740            if (app.cdmaPhoneCallState.getCurrentCallState()
741                    == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
742                // Set the Phone Call State to conference
743                app.cdmaPhoneCallState.setCurrentCallState(
744                        CdmaPhoneCallState.PhoneCallState.CONF_CALL);
745
746                // Send flash cmd
747                // TODO: Need to change the call from switchHoldingAndActive to
748                // something meaningful as we are not actually trying to swap calls but
749                // instead are merging two calls by sending a Flash command.
750                log("- sending flash...");
751                switchHoldingAndActive(cm.getFirstActiveBgCall());
752            }
753        } else {
754            try {
755                log("mergeCalls(): calling cm.conference()...");
756                cm.conference(cm.getFirstActiveBgCall());
757            } catch (CallStateException ex) {
758                Log.w(LOG_TAG, "mergeCalls: caught " + ex, ex);
759            }
760        }
761    }
762
763    static void separateCall(Connection c) {
764        try {
765            if (DBG) log("separateCall: " + toLogSafePhoneNumber(c.getAddress()));
766            c.separate();
767        } catch (CallStateException ex) {
768            Log.w(LOG_TAG, "separateCall: caught " + ex, ex);
769        }
770    }
771
772    /**
773     * Handle the MMIInitiate message and put up an alert that lets
774     * the user cancel the operation, if applicable.
775     *
776     * @param context context to get strings.
777     * @param mmiCode the MmiCode object being started.
778     * @param buttonCallbackMessage message to post when button is clicked.
779     * @param previousAlert a previous alert used in this activity.
780     * @return the dialog handle
781     */
782    static Dialog displayMMIInitiate(Context context,
783                                          MmiCode mmiCode,
784                                          Message buttonCallbackMessage,
785                                          Dialog previousAlert) {
786        if (DBG) log("displayMMIInitiate: " + mmiCode);
787        if (previousAlert != null) {
788            previousAlert.dismiss();
789        }
790
791        // The UI paradigm we are using now requests that all dialogs have
792        // user interaction, and that any other messages to the user should
793        // be by way of Toasts.
794        //
795        // In adhering to this request, all MMI initiating "OK" dialogs
796        // (non-cancelable MMIs) that end up being closed when the MMI
797        // completes (thereby showing a completion dialog) are being
798        // replaced with Toasts.
799        //
800        // As a side effect, moving to Toasts for the non-cancelable MMIs
801        // also means that buttonCallbackMessage (which was tied into "OK")
802        // is no longer invokable for these dialogs.  This is not a problem
803        // since the only callback messages we supported were for cancelable
804        // MMIs anyway.
805        //
806        // A cancelable MMI is really just a USSD request. The term
807        // "cancelable" here means that we can cancel the request when the
808        // system prompts us for a response, NOT while the network is
809        // processing the MMI request.  Any request to cancel a USSD while
810        // the network is NOT ready for a response may be ignored.
811        //
812        // With this in mind, we replace the cancelable alert dialog with
813        // a progress dialog, displayed until we receive a request from
814        // the the network.  For more information, please see the comments
815        // in the displayMMIComplete() method below.
816        //
817        // Anything that is NOT a USSD request is a normal MMI request,
818        // which will bring up a toast (desribed above).
819
820        boolean isCancelable = (mmiCode != null) && mmiCode.isCancelable();
821
822        if (!isCancelable) {
823            if (DBG) log("not a USSD code, displaying status toast.");
824            CharSequence text = context.getText(R.string.mmiStarted);
825            Toast.makeText(context, text, Toast.LENGTH_SHORT)
826                .show();
827            return null;
828        } else {
829            if (DBG) log("running USSD code, displaying indeterminate progress.");
830
831            // create the indeterminate progress dialog and display it.
832            ProgressDialog pd = new ProgressDialog(context, THEME);
833            pd.setMessage(context.getText(R.string.ussdRunning));
834            pd.setCancelable(false);
835            pd.setIndeterminate(true);
836            pd.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
837
838            pd.show();
839
840            return pd;
841        }
842
843    }
844
845    /**
846     * Handle the MMIComplete message and fire off an intent to display
847     * the message.
848     *
849     * @param context context to get strings.
850     * @param mmiCode MMI result.
851     * @param previousAlert a previous alert used in this activity.
852     */
853    static void displayMMIComplete(final Phone phone, Context context, final MmiCode mmiCode,
854            Message dismissCallbackMessage,
855            AlertDialog previousAlert) {
856        final PhoneGlobals app = PhoneGlobals.getInstance();
857        CharSequence text;
858        int title = 0;  // title for the progress dialog, if needed.
859        MmiCode.State state = mmiCode.getState();
860
861        if (DBG) log("displayMMIComplete: state=" + state);
862
863        switch (state) {
864            case PENDING:
865                // USSD code asking for feedback from user.
866                text = mmiCode.getMessage();
867                if (DBG) log("- using text from PENDING MMI message: '" + text + "'");
868                break;
869            case CANCELLED:
870                text = null;
871                break;
872            case COMPLETE:
873                if (app.getPUKEntryActivity() != null) {
874                    // if an attempt to unPUK the device was made, we specify
875                    // the title and the message here.
876                    title = com.android.internal.R.string.PinMmi;
877                    text = context.getText(R.string.puk_unlocked);
878                    break;
879                }
880                // All other conditions for the COMPLETE mmi state will cause
881                // the case to fall through to message logic in common with
882                // the FAILED case.
883
884            case FAILED:
885                text = mmiCode.getMessage();
886                if (DBG) log("- using text from MMI message: '" + text + "'");
887                break;
888            default:
889                throw new IllegalStateException("Unexpected MmiCode state: " + state);
890        }
891
892        if (previousAlert != null) {
893            previousAlert.dismiss();
894        }
895
896        // Check to see if a UI exists for the PUK activation.  If it does
897        // exist, then it indicates that we're trying to unblock the PUK.
898        if ((app.getPUKEntryActivity() != null) && (state == MmiCode.State.COMPLETE)) {
899            if (DBG) log("displaying PUK unblocking progress dialog.");
900
901            // create the progress dialog, make sure the flags and type are
902            // set correctly.
903            ProgressDialog pd = new ProgressDialog(app, THEME);
904            pd.setTitle(title);
905            pd.setMessage(text);
906            pd.setCancelable(false);
907            pd.setIndeterminate(true);
908            pd.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
909            pd.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
910
911            // display the dialog
912            pd.show();
913
914            // indicate to the Phone app that the progress dialog has
915            // been assigned for the PUK unlock / SIM READY process.
916            app.setPukEntryProgressDialog(pd);
917
918        } else {
919            // In case of failure to unlock, we'll need to reset the
920            // PUK unlock activity, so that the user may try again.
921            if (app.getPUKEntryActivity() != null) {
922                app.setPukEntryActivity(null);
923            }
924
925            // A USSD in a pending state means that it is still
926            // interacting with the user.
927            if (state != MmiCode.State.PENDING) {
928                if (DBG) log("MMI code has finished running.");
929
930                if (DBG) log("Extended NW displayMMIInitiate (" + text + ")");
931                if (text == null || text.length() == 0)
932                    return;
933
934                // displaying system alert dialog on the screen instead of
935                // using another activity to display the message.  This
936                // places the message at the forefront of the UI.
937
938                if (sUssdDialog == null) {
939                    sUssdDialog = new AlertDialog.Builder(context, THEME)
940                            .setPositiveButton(R.string.ok, null)
941                            .setCancelable(true)
942                            .setOnDismissListener(new DialogInterface.OnDismissListener() {
943                                @Override
944                                public void onDismiss(DialogInterface dialog) {
945                                    sUssdMsg.setLength(0);
946                                }
947                            })
948                            .create();
949
950                    sUssdDialog.getWindow().setType(
951                            WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
952                    sUssdDialog.getWindow().addFlags(
953                            WindowManager.LayoutParams.FLAG_DIM_BEHIND);
954                }
955                if (sUssdMsg.length() != 0) {
956                    sUssdMsg
957                            .insert(0, "\n")
958                            .insert(0, app.getResources().getString(R.string.ussd_dialog_sep))
959                            .insert(0, "\n");
960                }
961                sUssdMsg.insert(0, text);
962                sUssdDialog.setMessage(sUssdMsg.toString());
963                sUssdDialog.show();
964            } else {
965                if (DBG) log("USSD code has requested user input. Constructing input dialog.");
966
967                // USSD MMI code that is interacting with the user.  The
968                // basic set of steps is this:
969                //   1. User enters a USSD request
970                //   2. We recognize the request and displayMMIInitiate
971                //      (above) creates a progress dialog.
972                //   3. Request returns and we get a PENDING or COMPLETE
973                //      message.
974                //   4. These MMI messages are caught in the PhoneApp
975                //      (onMMIComplete) and the InCallScreen
976                //      (mHandler.handleMessage) which bring up this dialog
977                //      and closes the original progress dialog,
978                //      respectively.
979                //   5. If the message is anything other than PENDING,
980                //      we are done, and the alert dialog (directly above)
981                //      displays the outcome.
982                //   6. If the network is requesting more information from
983                //      the user, the MMI will be in a PENDING state, and
984                //      we display this dialog with the message.
985                //   7. User input, or cancel requests result in a return
986                //      to step 1.  Keep in mind that this is the only
987                //      time that a USSD should be canceled.
988
989                // inflate the layout with the scrolling text area for the dialog.
990                ContextThemeWrapper contextThemeWrapper =
991                        new ContextThemeWrapper(context, R.style.DialerAlertDialogTheme);
992                LayoutInflater inflater = (LayoutInflater) contextThemeWrapper.getSystemService(
993                        Context.LAYOUT_INFLATER_SERVICE);
994                View dialogView = inflater.inflate(R.layout.dialog_ussd_response, null);
995
996                // get the input field.
997                final EditText inputText = (EditText) dialogView.findViewById(R.id.input_field);
998
999                // specify the dialog's click listener, with SEND and CANCEL logic.
1000                final DialogInterface.OnClickListener mUSSDDialogListener =
1001                    new DialogInterface.OnClickListener() {
1002                        public void onClick(DialogInterface dialog, int whichButton) {
1003                            switch (whichButton) {
1004                                case DialogInterface.BUTTON_POSITIVE:
1005                                    // As per spec 24.080, valid length of ussd string
1006                                    // is 1 - 160. If length is out of the range then
1007                                    // display toast message & Cancel MMI operation.
1008                                    if (inputText.length() < MIN_USSD_LEN
1009                                            || inputText.length() > MAX_USSD_LEN) {
1010                                        Toast.makeText(app,
1011                                                app.getResources().getString(R.string.enter_input,
1012                                                MIN_USSD_LEN, MAX_USSD_LEN),
1013                                                Toast.LENGTH_LONG).show();
1014                                        if (mmiCode.isCancelable()) {
1015                                            mmiCode.cancel();
1016                                        }
1017                                    } else {
1018                                        phone.sendUssdResponse(inputText.getText().toString());
1019                                    }
1020                                    break;
1021                                case DialogInterface.BUTTON_NEGATIVE:
1022                                    if (mmiCode.isCancelable()) {
1023                                        mmiCode.cancel();
1024                                    }
1025                                    break;
1026                            }
1027                        }
1028                    };
1029
1030                // build the dialog
1031                final AlertDialog newDialog = new AlertDialog.Builder(contextThemeWrapper)
1032                        .setMessage(text)
1033                        .setView(dialogView)
1034                        .setPositiveButton(R.string.send_button, mUSSDDialogListener)
1035                        .setNegativeButton(R.string.cancel, mUSSDDialogListener)
1036                        .setCancelable(false)
1037                        .create();
1038
1039                // attach the key listener to the dialog's input field and make
1040                // sure focus is set.
1041                final View.OnKeyListener mUSSDDialogInputListener =
1042                    new View.OnKeyListener() {
1043                        public boolean onKey(View v, int keyCode, KeyEvent event) {
1044                            switch (keyCode) {
1045                                case KeyEvent.KEYCODE_CALL:
1046                                case KeyEvent.KEYCODE_ENTER:
1047                                    if(event.getAction() == KeyEvent.ACTION_DOWN) {
1048                                        phone.sendUssdResponse(inputText.getText().toString());
1049                                        newDialog.dismiss();
1050                                    }
1051                                    return true;
1052                            }
1053                            return false;
1054                        }
1055                    };
1056                inputText.setOnKeyListener(mUSSDDialogInputListener);
1057                inputText.requestFocus();
1058
1059                // set the window properties of the dialog
1060                newDialog.getWindow().setType(
1061                        WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
1062                newDialog.getWindow().addFlags(
1063                        WindowManager.LayoutParams.FLAG_DIM_BEHIND);
1064
1065                // now show the dialog!
1066                newDialog.show();
1067
1068                newDialog.getButton(DialogInterface.BUTTON_POSITIVE)
1069                        .setTextColor(context.getResources().getColor(R.color.dialer_theme_color));
1070                newDialog.getButton(DialogInterface.BUTTON_NEGATIVE)
1071                        .setTextColor(context.getResources().getColor(R.color.dialer_theme_color));
1072            }
1073        }
1074    }
1075
1076    /**
1077     * Cancels the current pending MMI operation, if applicable.
1078     * @return true if we canceled an MMI operation, or false
1079     *         if the current pending MMI wasn't cancelable
1080     *         or if there was no current pending MMI at all.
1081     *
1082     * @see displayMMIInitiate
1083     */
1084    static boolean cancelMmiCode(Phone phone) {
1085        List<? extends MmiCode> pendingMmis = phone.getPendingMmiCodes();
1086        int count = pendingMmis.size();
1087        if (DBG) log("cancelMmiCode: num pending MMIs = " + count);
1088
1089        boolean canceled = false;
1090        if (count > 0) {
1091            // assume that we only have one pending MMI operation active at a time.
1092            // I don't think it's possible to enter multiple MMI codes concurrently
1093            // in the phone UI, because during the MMI operation, an Alert panel
1094            // is displayed, which prevents more MMI code from being entered.
1095            MmiCode mmiCode = pendingMmis.get(0);
1096            if (mmiCode.isCancelable()) {
1097                mmiCode.cancel();
1098                canceled = true;
1099            }
1100        }
1101        return canceled;
1102    }
1103
1104    public static class VoiceMailNumberMissingException extends Exception {
1105        VoiceMailNumberMissingException() {
1106            super();
1107        }
1108
1109        VoiceMailNumberMissingException(String msg) {
1110            super(msg);
1111        }
1112    }
1113
1114    /**
1115     * Given an Intent (which is presumably the ACTION_CALL intent that
1116     * initiated this outgoing call), figure out the actual phone number we
1117     * should dial.
1118     *
1119     * Note that the returned "number" may actually be a SIP address,
1120     * if the specified intent contains a sip: URI.
1121     *
1122     * This method is basically a wrapper around PhoneUtils.getNumberFromIntent(),
1123     * except it's also aware of the EXTRA_ACTUAL_NUMBER_TO_DIAL extra.
1124     * (That extra, if present, tells us the exact string to pass down to the
1125     * telephony layer.  It's guaranteed to be safe to dial: it's either a PSTN
1126     * phone number with separators and keypad letters stripped out, or a raw
1127     * unencoded SIP address.)
1128     *
1129     * @return the phone number corresponding to the specified Intent, or null
1130     *   if the Intent has no action or if the intent's data is malformed or
1131     *   missing.
1132     *
1133     * @throws VoiceMailNumberMissingException if the intent
1134     *   contains a "voicemail" URI, but there's no voicemail
1135     *   number configured on the device.
1136     */
1137    public static String getInitialNumber(Intent intent)
1138            throws PhoneUtils.VoiceMailNumberMissingException {
1139        if (DBG) log("getInitialNumber(): " + intent);
1140
1141        String action = intent.getAction();
1142        if (TextUtils.isEmpty(action)) {
1143            return null;
1144        }
1145
1146        // If the EXTRA_ACTUAL_NUMBER_TO_DIAL extra is present, get the phone
1147        // number from there.  (That extra takes precedence over the actual data
1148        // included in the intent.)
1149        if (intent.hasExtra(OutgoingCallBroadcaster.EXTRA_ACTUAL_NUMBER_TO_DIAL)) {
1150            String actualNumberToDial =
1151                    intent.getStringExtra(OutgoingCallBroadcaster.EXTRA_ACTUAL_NUMBER_TO_DIAL);
1152            if (DBG) {
1153                log("==> got EXTRA_ACTUAL_NUMBER_TO_DIAL; returning '"
1154                        + toLogSafePhoneNumber(actualNumberToDial) + "'");
1155            }
1156            return actualNumberToDial;
1157        }
1158
1159        return getNumberFromIntent(PhoneGlobals.getInstance(), intent);
1160    }
1161
1162    /**
1163     * Gets the phone number to be called from an intent.  Requires a Context
1164     * to access the contacts database, and a Phone to access the voicemail
1165     * number.
1166     *
1167     * <p>If <code>phone</code> is <code>null</code>, the function will return
1168     * <code>null</code> for <code>voicemail:</code> URIs;
1169     * if <code>context</code> is <code>null</code>, the function will return
1170     * <code>null</code> for person/phone URIs.</p>
1171     *
1172     * <p>If the intent contains a <code>sip:</code> URI, the returned
1173     * "number" is actually the SIP address.
1174     *
1175     * @param context a context to use (or
1176     * @param intent the intent
1177     *
1178     * @throws VoiceMailNumberMissingException if <code>intent</code> contains
1179     *         a <code>voicemail:</code> URI, but <code>phone</code> does not
1180     *         have a voicemail number set.
1181     *
1182     * @return the phone number (or SIP address) that would be called by the intent,
1183     *         or <code>null</code> if the number cannot be found.
1184     */
1185    private static String getNumberFromIntent(Context context, Intent intent)
1186            throws VoiceMailNumberMissingException {
1187        Uri uri = intent.getData();
1188        String scheme = uri.getScheme();
1189
1190        // The sip: scheme is simple: just treat the rest of the URI as a
1191        // SIP address.
1192        if (PhoneAccount.SCHEME_SIP.equals(scheme)) {
1193            return uri.getSchemeSpecificPart();
1194        }
1195
1196        // Otherwise, let PhoneNumberUtils.getNumberFromIntent() handle
1197        // the other cases (i.e. tel: and voicemail: and contact: URIs.)
1198
1199        final String number = PhoneNumberUtils.getNumberFromIntent(intent, context);
1200
1201        // Check for a voicemail-dialing request.  If the voicemail number is
1202        // empty, throw a VoiceMailNumberMissingException.
1203        if (PhoneAccount.SCHEME_VOICEMAIL.equals(scheme) &&
1204                (number == null || TextUtils.isEmpty(number)))
1205            throw new VoiceMailNumberMissingException();
1206
1207        return number;
1208    }
1209
1210    /**
1211     * Returns the caller-id info corresponding to the specified Connection.
1212     * (This is just a simple wrapper around CallerInfo.getCallerInfo(): we
1213     * extract a phone number from the specified Connection, and feed that
1214     * number into CallerInfo.getCallerInfo().)
1215     *
1216     * The returned CallerInfo may be null in certain error cases, like if the
1217     * specified Connection was null, or if we weren't able to get a valid
1218     * phone number from the Connection.
1219     *
1220     * Finally, if the getCallerInfo() call did succeed, we save the resulting
1221     * CallerInfo object in the "userData" field of the Connection.
1222     *
1223     * NOTE: This API should be avoided, with preference given to the
1224     * asynchronous startGetCallerInfo API.
1225     */
1226    static CallerInfo getCallerInfo(Context context, Connection c) {
1227        CallerInfo info = null;
1228
1229        if (c != null) {
1230            //See if there is a URI attached.  If there is, this means
1231            //that there is no CallerInfo queried yet, so we'll need to
1232            //replace the URI with a full CallerInfo object.
1233            Object userDataObject = c.getUserData();
1234            if (userDataObject instanceof Uri) {
1235                info = CallerInfo.getCallerInfo(context, (Uri) userDataObject);
1236                if (info != null) {
1237                    c.setUserData(info);
1238                }
1239            } else {
1240                if (userDataObject instanceof CallerInfoToken) {
1241                    //temporary result, while query is running
1242                    info = ((CallerInfoToken) userDataObject).currentInfo;
1243                } else {
1244                    //final query result
1245                    info = (CallerInfo) userDataObject;
1246                }
1247                if (info == null) {
1248                    // No URI, or Existing CallerInfo, so we'll have to make do with
1249                    // querying a new CallerInfo using the connection's phone number.
1250                    String number = c.getAddress();
1251
1252                    if (DBG) log("getCallerInfo: number = " + toLogSafePhoneNumber(number));
1253
1254                    if (!TextUtils.isEmpty(number)) {
1255                        info = CallerInfo.getCallerInfo(context, number);
1256                        if (info != null) {
1257                            c.setUserData(info);
1258                        }
1259                    }
1260                }
1261            }
1262        }
1263        return info;
1264    }
1265
1266    /**
1267     * Class returned by the startGetCallerInfo call to package a temporary
1268     * CallerInfo Object, to be superceded by the CallerInfo Object passed
1269     * into the listener when the query with token mAsyncQueryToken is complete.
1270     */
1271    public static class CallerInfoToken {
1272        /**indicates that there will no longer be updates to this request.*/
1273        public boolean isFinal;
1274
1275        public CallerInfo currentInfo;
1276        public CallerInfoAsyncQuery asyncQuery;
1277    }
1278
1279    /**
1280     * Start a CallerInfo Query based on the earliest connection in the call.
1281     */
1282    static CallerInfoToken startGetCallerInfo(Context context, Call call,
1283            CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie) {
1284        Connection conn = null;
1285        int phoneType = call.getPhone().getPhoneType();
1286        if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
1287            conn = call.getLatestConnection();
1288        } else if ((phoneType == PhoneConstants.PHONE_TYPE_GSM)
1289                || (phoneType == PhoneConstants.PHONE_TYPE_SIP)
1290                || (phoneType == PhoneConstants.PHONE_TYPE_IMS)
1291                || (phoneType == PhoneConstants.PHONE_TYPE_THIRD_PARTY)) {
1292            conn = call.getEarliestConnection();
1293        } else {
1294            throw new IllegalStateException("Unexpected phone type: " + phoneType);
1295        }
1296
1297        return startGetCallerInfo(context, conn, listener, cookie);
1298    }
1299
1300    static CallerInfoToken startGetCallerInfo(Context context, Connection c,
1301            CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie) {
1302        return startGetCallerInfo(context, c, listener, cookie, null);
1303    }
1304
1305    /**
1306     * place a temporary callerinfo object in the hands of the caller and notify
1307     * caller when the actual query is done.
1308     */
1309    static CallerInfoToken startGetCallerInfo(Context context, Connection c,
1310            CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie,
1311            RawGatewayInfo info) {
1312        CallerInfoToken cit;
1313
1314        if (c == null) {
1315            //TODO: perhaps throw an exception here.
1316            cit = new CallerInfoToken();
1317            cit.asyncQuery = null;
1318            return cit;
1319        }
1320
1321        Object userDataObject = c.getUserData();
1322
1323        // There are now 3 states for the Connection's userData object:
1324        //
1325        //   (1) Uri - query has not been executed yet
1326        //
1327        //   (2) CallerInfoToken - query is executing, but has not completed.
1328        //
1329        //   (3) CallerInfo - query has executed.
1330        //
1331        // In each case we have slightly different behaviour:
1332        //   1. If the query has not been executed yet (Uri or null), we start
1333        //      query execution asynchronously, and note it by attaching a
1334        //      CallerInfoToken as the userData.
1335        //   2. If the query is executing (CallerInfoToken), we've essentially
1336        //      reached a state where we've received multiple requests for the
1337        //      same callerInfo.  That means that once the query is complete,
1338        //      we'll need to execute the additional listener requested.
1339        //   3. If the query has already been executed (CallerInfo), we just
1340        //      return the CallerInfo object as expected.
1341        //   4. Regarding isFinal - there are cases where the CallerInfo object
1342        //      will not be attached, like when the number is empty (caller id
1343        //      blocking).  This flag is used to indicate that the
1344        //      CallerInfoToken object is going to be permanent since no
1345        //      query results will be returned.  In the case where a query
1346        //      has been completed, this flag is used to indicate to the caller
1347        //      that the data will not be updated since it is valid.
1348        //
1349        //      Note: For the case where a number is NOT retrievable, we leave
1350        //      the CallerInfo as null in the CallerInfoToken.  This is
1351        //      something of a departure from the original code, since the old
1352        //      code manufactured a CallerInfo object regardless of the query
1353        //      outcome.  From now on, we will append an empty CallerInfo
1354        //      object, to mirror previous behaviour, and to avoid Null Pointer
1355        //      Exceptions.
1356
1357        if (userDataObject instanceof Uri) {
1358            // State (1): query has not been executed yet
1359
1360            //create a dummy callerinfo, populate with what we know from URI.
1361            cit = new CallerInfoToken();
1362            cit.currentInfo = new CallerInfo();
1363            cit.asyncQuery = CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context,
1364                    (Uri) userDataObject, sCallerInfoQueryListener, c);
1365            cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie);
1366            cit.isFinal = false;
1367
1368            c.setUserData(cit);
1369
1370            if (DBG) log("startGetCallerInfo: query based on Uri: " + userDataObject);
1371
1372        } else if (userDataObject == null) {
1373            // No URI, or Existing CallerInfo, so we'll have to make do with
1374            // querying a new CallerInfo using the connection's phone number.
1375            String number = c.getAddress();
1376
1377            if (info != null && info != CallGatewayManager.EMPTY_INFO) {
1378                // Gateway number, the connection number is actually the gateway number.
1379                // need to lookup via dialed number.
1380                number = info.trueNumber;
1381            }
1382
1383            if (DBG) {
1384                log("PhoneUtils.startGetCallerInfo: new query for phone number...");
1385                log("- number (address): " + toLogSafePhoneNumber(number));
1386                log("- c: " + c);
1387                log("- phone: " + c.getCall().getPhone());
1388                int phoneType = c.getCall().getPhone().getPhoneType();
1389                log("- phoneType: " + phoneType);
1390                switch (phoneType) {
1391                    case PhoneConstants.PHONE_TYPE_NONE: log("  ==> PHONE_TYPE_NONE"); break;
1392                    case PhoneConstants.PHONE_TYPE_GSM: log("  ==> PHONE_TYPE_GSM"); break;
1393                    case PhoneConstants.PHONE_TYPE_IMS: log("  ==> PHONE_TYPE_IMS"); break;
1394                    case PhoneConstants.PHONE_TYPE_CDMA: log("  ==> PHONE_TYPE_CDMA"); break;
1395                    case PhoneConstants.PHONE_TYPE_SIP: log("  ==> PHONE_TYPE_SIP"); break;
1396                    case PhoneConstants.PHONE_TYPE_THIRD_PARTY:
1397                        log("  ==> PHONE_TYPE_THIRD_PARTY");
1398                        break;
1399                    default: log("  ==> Unknown phone type"); break;
1400                }
1401            }
1402
1403            cit = new CallerInfoToken();
1404            cit.currentInfo = new CallerInfo();
1405
1406            // Store CNAP information retrieved from the Connection (we want to do this
1407            // here regardless of whether the number is empty or not).
1408            cit.currentInfo.cnapName =  c.getCnapName();
1409            cit.currentInfo.name = cit.currentInfo.cnapName; // This can still get overwritten
1410                                                             // by ContactInfo later
1411            cit.currentInfo.numberPresentation = c.getNumberPresentation();
1412            cit.currentInfo.namePresentation = c.getCnapNamePresentation();
1413
1414            if (VDBG) {
1415                log("startGetCallerInfo: number = " + number);
1416                log("startGetCallerInfo: CNAP Info from FW(1): name="
1417                    + cit.currentInfo.cnapName
1418                    + ", Name/Number Pres=" + cit.currentInfo.numberPresentation);
1419            }
1420
1421            // handling case where number is null (caller id hidden) as well.
1422            if (!TextUtils.isEmpty(number)) {
1423                // Check for special CNAP cases and modify the CallerInfo accordingly
1424                // to be sure we keep the right information to display/log later
1425                number = modifyForSpecialCnapCases(context, cit.currentInfo, number,
1426                        cit.currentInfo.numberPresentation);
1427
1428                cit.currentInfo.phoneNumber = number;
1429                // For scenarios where we may receive a valid number from the network but a
1430                // restricted/unavailable presentation, we do not want to perform a contact query
1431                // (see note on isFinal above). So we set isFinal to true here as well.
1432                if (cit.currentInfo.numberPresentation != PhoneConstants.PRESENTATION_ALLOWED) {
1433                    cit.isFinal = true;
1434                } else {
1435                    if (DBG) log("==> Actually starting CallerInfoAsyncQuery.startQuery()...");
1436                    cit.asyncQuery = CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context,
1437                            number, sCallerInfoQueryListener, c);
1438                    cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie);
1439                    cit.isFinal = false;
1440                }
1441            } else {
1442                // This is the case where we are querying on a number that
1443                // is null or empty, like a caller whose caller id is
1444                // blocked or empty (CLIR).  The previous behaviour was to
1445                // throw a null CallerInfo object back to the user, but
1446                // this departure is somewhat cleaner.
1447                if (DBG) log("startGetCallerInfo: No query to start, send trivial reply.");
1448                cit.isFinal = true; // please see note on isFinal, above.
1449            }
1450
1451            c.setUserData(cit);
1452
1453            if (DBG) {
1454                log("startGetCallerInfo: query based on number: " + toLogSafePhoneNumber(number));
1455            }
1456
1457        } else if (userDataObject instanceof CallerInfoToken) {
1458            // State (2): query is executing, but has not completed.
1459
1460            // just tack on this listener to the queue.
1461            cit = (CallerInfoToken) userDataObject;
1462
1463            // handling case where number is null (caller id hidden) as well.
1464            if (cit.asyncQuery != null) {
1465                cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie);
1466
1467                if (DBG) log("startGetCallerInfo: query already running, adding listener: " +
1468                        listener.getClass().toString());
1469            } else {
1470                // handling case where number/name gets updated later on by the network
1471                String updatedNumber = c.getAddress();
1472
1473                if (info != null) {
1474                    // Gateway number, the connection number is actually the gateway number.
1475                    // need to lookup via dialed number.
1476                    updatedNumber = info.trueNumber;
1477                }
1478
1479                if (DBG) {
1480                    log("startGetCallerInfo: updatedNumber initially = "
1481                            + toLogSafePhoneNumber(updatedNumber));
1482                }
1483                if (!TextUtils.isEmpty(updatedNumber)) {
1484                    // Store CNAP information retrieved from the Connection
1485                    cit.currentInfo.cnapName =  c.getCnapName();
1486                    // This can still get overwritten by ContactInfo
1487                    cit.currentInfo.name = cit.currentInfo.cnapName;
1488                    cit.currentInfo.numberPresentation = c.getNumberPresentation();
1489                    cit.currentInfo.namePresentation = c.getCnapNamePresentation();
1490
1491                    updatedNumber = modifyForSpecialCnapCases(context, cit.currentInfo,
1492                            updatedNumber, cit.currentInfo.numberPresentation);
1493
1494                    cit.currentInfo.phoneNumber = updatedNumber;
1495                    if (DBG) {
1496                        log("startGetCallerInfo: updatedNumber="
1497                                + toLogSafePhoneNumber(updatedNumber));
1498                    }
1499                    if (VDBG) {
1500                        log("startGetCallerInfo: CNAP Info from FW(2): name="
1501                                + cit.currentInfo.cnapName
1502                                + ", Name/Number Pres=" + cit.currentInfo.numberPresentation);
1503                    } else if (DBG) {
1504                        log("startGetCallerInfo: CNAP Info from FW(2)");
1505                    }
1506                    // For scenarios where we may receive a valid number from the network but a
1507                    // restricted/unavailable presentation, we do not want to perform a contact query
1508                    // (see note on isFinal above). So we set isFinal to true here as well.
1509                    if (cit.currentInfo.numberPresentation != PhoneConstants.PRESENTATION_ALLOWED) {
1510                        cit.isFinal = true;
1511                    } else {
1512                        cit.asyncQuery = CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context,
1513                                updatedNumber, sCallerInfoQueryListener, c);
1514                        cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie);
1515                        cit.isFinal = false;
1516                    }
1517                } else {
1518                    if (DBG) log("startGetCallerInfo: No query to attach to, send trivial reply.");
1519                    if (cit.currentInfo == null) {
1520                        cit.currentInfo = new CallerInfo();
1521                    }
1522                    // Store CNAP information retrieved from the Connection
1523                    cit.currentInfo.cnapName = c.getCnapName();  // This can still get
1524                                                                 // overwritten by ContactInfo
1525                    cit.currentInfo.name = cit.currentInfo.cnapName;
1526                    cit.currentInfo.numberPresentation = c.getNumberPresentation();
1527                    cit.currentInfo.namePresentation = c.getCnapNamePresentation();
1528
1529                    if (VDBG) {
1530                        log("startGetCallerInfo: CNAP Info from FW(3): name="
1531                                + cit.currentInfo.cnapName
1532                                + ", Name/Number Pres=" + cit.currentInfo.numberPresentation);
1533                    } else if (DBG) {
1534                        log("startGetCallerInfo: CNAP Info from FW(3)");
1535                    }
1536                    cit.isFinal = true; // please see note on isFinal, above.
1537                }
1538            }
1539        } else {
1540            // State (3): query is complete.
1541
1542            // The connection's userDataObject is a full-fledged
1543            // CallerInfo instance.  Wrap it in a CallerInfoToken and
1544            // return it to the user.
1545
1546            cit = new CallerInfoToken();
1547            cit.currentInfo = (CallerInfo) userDataObject;
1548            cit.asyncQuery = null;
1549            cit.isFinal = true;
1550            // since the query is already done, call the listener.
1551            if (DBG) log("startGetCallerInfo: query already done, returning CallerInfo");
1552            if (DBG) log("==> cit.currentInfo = " + cit.currentInfo);
1553        }
1554        return cit;
1555    }
1556
1557    /**
1558     * Static CallerInfoAsyncQuery.OnQueryCompleteListener instance that
1559     * we use with all our CallerInfoAsyncQuery.startQuery() requests.
1560     */
1561    private static final int QUERY_TOKEN = -1;
1562    static CallerInfoAsyncQuery.OnQueryCompleteListener sCallerInfoQueryListener =
1563        new CallerInfoAsyncQuery.OnQueryCompleteListener () {
1564            /**
1565             * When the query completes, we stash the resulting CallerInfo
1566             * object away in the Connection's "userData" (where it will
1567             * later be retrieved by the in-call UI.)
1568             */
1569            public void onQueryComplete(int token, Object cookie, CallerInfo ci) {
1570                if (DBG) log("query complete, updating connection.userdata");
1571                Connection conn = (Connection) cookie;
1572
1573                // Added a check if CallerInfo is coming from ContactInfo or from Connection.
1574                // If no ContactInfo, then we want to use CNAP information coming from network
1575                if (DBG) log("- onQueryComplete: CallerInfo:" + ci);
1576                if (ci.contactExists || ci.isEmergencyNumber() || ci.isVoiceMailNumber()) {
1577                    // If the number presentation has not been set by
1578                    // the ContactInfo, use the one from the
1579                    // connection.
1580
1581                    // TODO: Need a new util method to merge the info
1582                    // from the Connection in a CallerInfo object.
1583                    // Here 'ci' is a new CallerInfo instance read
1584                    // from the DB. It has lost all the connection
1585                    // info preset before the query (see PhoneUtils
1586                    // line 1334). We should have a method to merge
1587                    // back into this new instance the info from the
1588                    // connection object not set by the DB. If the
1589                    // Connection already has a CallerInfo instance in
1590                    // userData, then we could use this instance to
1591                    // fill 'ci' in. The same routine could be used in
1592                    // PhoneUtils.
1593                    if (0 == ci.numberPresentation) {
1594                        ci.numberPresentation = conn.getNumberPresentation();
1595                    }
1596                } else {
1597                    // No matching contact was found for this number.
1598                    // Return a new CallerInfo based solely on the CNAP
1599                    // information from the network.
1600
1601                    CallerInfo newCi = getCallerInfo(null, conn);
1602
1603                    // ...but copy over the (few) things we care about
1604                    // from the original CallerInfo object:
1605                    if (newCi != null) {
1606                        newCi.phoneNumber = ci.phoneNumber; // To get formatted phone number
1607                        newCi.geoDescription = ci.geoDescription; // To get geo description string
1608                        ci = newCi;
1609                    }
1610                }
1611
1612                if (DBG) log("==> Stashing CallerInfo " + ci + " into the connection...");
1613                conn.setUserData(ci);
1614            }
1615        };
1616
1617
1618    /**
1619     * Returns a single "name" for the specified given a CallerInfo object.
1620     * If the name is null, return defaultString as the default value, usually
1621     * context.getString(R.string.unknown).
1622     */
1623    static String getCompactNameFromCallerInfo(CallerInfo ci, Context context) {
1624        if (DBG) log("getCompactNameFromCallerInfo: info = " + ci);
1625
1626        String compactName = null;
1627        if (ci != null) {
1628            if (TextUtils.isEmpty(ci.name)) {
1629                // Perform any modifications for special CNAP cases to
1630                // the phone number being displayed, if applicable.
1631                compactName = modifyForSpecialCnapCases(context, ci, ci.phoneNumber,
1632                                                        ci.numberPresentation);
1633            } else {
1634                // Don't call modifyForSpecialCnapCases on regular name. See b/2160795.
1635                compactName = ci.name;
1636            }
1637        }
1638
1639        if ((compactName == null) || (TextUtils.isEmpty(compactName))) {
1640            // If we're still null/empty here, then check if we have a presentation
1641            // string that takes precedence that we could return, otherwise display
1642            // "unknown" string.
1643            if (ci != null && ci.numberPresentation == PhoneConstants.PRESENTATION_RESTRICTED) {
1644                compactName = context.getString(R.string.private_num);
1645            } else if (ci != null && ci.numberPresentation == PhoneConstants.PRESENTATION_PAYPHONE) {
1646                compactName = context.getString(R.string.payphone);
1647            } else {
1648                compactName = context.getString(R.string.unknown);
1649            }
1650        }
1651        if (VDBG) log("getCompactNameFromCallerInfo: compactName=" + compactName);
1652        return compactName;
1653    }
1654
1655    /**
1656     * Returns true if the specified Call is a "conference call", meaning
1657     * that it owns more than one Connection object.  This information is
1658     * used to trigger certain UI changes that appear when a conference
1659     * call is active (like displaying the label "Conference call", and
1660     * enabling the "Manage conference" UI.)
1661     *
1662     * Watch out: This method simply checks the number of Connections,
1663     * *not* their states.  So if a Call has (for example) one ACTIVE
1664     * connection and one DISCONNECTED connection, this method will return
1665     * true (which is unintuitive, since the Call isn't *really* a
1666     * conference call any more.)
1667     *
1668     * @return true if the specified call has more than one connection (in any state.)
1669     */
1670    static boolean isConferenceCall(Call call) {
1671        // CDMA phones don't have the same concept of "conference call" as
1672        // GSM phones do; there's no special "conference call" state of
1673        // the UI or a "manage conference" function.  (Instead, when
1674        // you're in a 3-way call, all we can do is display the "generic"
1675        // state of the UI.)  So as far as the in-call UI is concerned,
1676        // Conference corresponds to generic display.
1677        final PhoneGlobals app = PhoneGlobals.getInstance();
1678        int phoneType = call.getPhone().getPhoneType();
1679        if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
1680            CdmaPhoneCallState.PhoneCallState state = app.cdmaPhoneCallState.getCurrentCallState();
1681            if ((state == CdmaPhoneCallState.PhoneCallState.CONF_CALL)
1682                    || ((state == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE)
1683                    && !app.cdmaPhoneCallState.IsThreeWayCallOrigStateDialing())) {
1684                return true;
1685            }
1686        } else {
1687            List<Connection> connections = call.getConnections();
1688            if (connections != null && connections.size() > 1) {
1689                return true;
1690            }
1691        }
1692        return false;
1693
1694        // TODO: We may still want to change the semantics of this method
1695        // to say that a given call is only really a conference call if
1696        // the number of ACTIVE connections, not the total number of
1697        // connections, is greater than one.  (See warning comment in the
1698        // javadoc above.)
1699        // Here's an implementation of that:
1700        //        if (connections == null) {
1701        //            return false;
1702        //        }
1703        //        int numActiveConnections = 0;
1704        //        for (Connection conn : connections) {
1705        //            if (DBG) log("  - CONN: " + conn + ", state = " + conn.getState());
1706        //            if (conn.getState() == Call.State.ACTIVE) numActiveConnections++;
1707        //            if (numActiveConnections > 1) {
1708        //                return true;
1709        //            }
1710        //        }
1711        //        return false;
1712    }
1713
1714    /**
1715     * Launch the Dialer to start a new call.
1716     * This is just a wrapper around the ACTION_DIAL intent.
1717     */
1718    /* package */ static boolean startNewCall(final CallManager cm) {
1719        final PhoneGlobals app = PhoneGlobals.getInstance();
1720
1721        // Sanity-check that this is OK given the current state of the phone.
1722        if (!okToAddCall(cm)) {
1723            Log.w(LOG_TAG, "startNewCall: can't add a new call in the current state");
1724            dumpCallManager();
1725            return false;
1726        }
1727
1728        Intent intent = new Intent(Intent.ACTION_DIAL);
1729        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1730
1731        // when we request the dialer come up, we also want to inform
1732        // it that we're going through the "add call" option from the
1733        // InCallScreen / PhoneUtils.
1734        intent.putExtra(ADD_CALL_MODE_KEY, true);
1735        try {
1736            app.startActivity(intent);
1737        } catch (ActivityNotFoundException e) {
1738            // This is rather rare but possible.
1739            // Note: this method is used even when the phone is encrypted. At that moment
1740            // the system may not find any Activity which can accept this Intent.
1741            Log.e(LOG_TAG, "Activity for adding calls isn't found.");
1742            return false;
1743        }
1744
1745        return true;
1746    }
1747
1748    /**
1749     * Turns on/off speaker.
1750     *
1751     * @param context Context
1752     * @param flag True when speaker should be on. False otherwise.
1753     * @param store True when the settings should be stored in the device.
1754     */
1755    /* package */ static void turnOnSpeaker(Context context, boolean flag, boolean store) {
1756        if (DBG) log("turnOnSpeaker(flag=" + flag + ", store=" + store + ")...");
1757        final PhoneGlobals app = PhoneGlobals.getInstance();
1758
1759        AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
1760        audioManager.setSpeakerphoneOn(flag);
1761
1762        // record the speaker-enable value
1763        if (store) {
1764            sIsSpeakerEnabled = flag;
1765        }
1766
1767        // We also need to make a fresh call to PhoneApp.updateWakeState()
1768        // any time the speaker state changes, since the screen timeout is
1769        // sometimes different depending on whether or not the speaker is
1770        // in use.
1771        app.updateWakeState();
1772
1773        app.mCM.setEchoSuppressionEnabled();
1774    }
1775
1776    /**
1777     * Restore the speaker mode, called after a wired headset disconnect
1778     * event.
1779     */
1780    static void restoreSpeakerMode(Context context) {
1781        if (DBG) log("restoreSpeakerMode, restoring to: " + sIsSpeakerEnabled);
1782
1783        // change the mode if needed.
1784        if (isSpeakerOn(context) != sIsSpeakerEnabled) {
1785            turnOnSpeaker(context, sIsSpeakerEnabled, false);
1786        }
1787    }
1788
1789    static boolean isSpeakerOn(Context context) {
1790        AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
1791        return audioManager.isSpeakerphoneOn();
1792    }
1793
1794
1795    static void turnOnNoiseSuppression(Context context, boolean flag, boolean store) {
1796        if (DBG) log("turnOnNoiseSuppression: " + flag);
1797        AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
1798
1799        PersistableBundle b = PhoneGlobals.getInstance().getCarrierConfig();
1800        if (!b.getBoolean(CarrierConfigManager.KEY_HAS_IN_CALL_NOISE_SUPPRESSION_BOOL)) {
1801            return;
1802        }
1803
1804        if (flag) {
1805            audioManager.setParameters("noise_suppression=auto");
1806        } else {
1807            audioManager.setParameters("noise_suppression=off");
1808        }
1809
1810        // record the speaker-enable value
1811        if (store) {
1812            sIsNoiseSuppressionEnabled = flag;
1813        }
1814
1815        // TODO: implement and manage ICON
1816
1817    }
1818
1819    static void restoreNoiseSuppression(Context context) {
1820        if (DBG) log("restoreNoiseSuppression, restoring to: " + sIsNoiseSuppressionEnabled);
1821
1822        PersistableBundle b = PhoneGlobals.getInstance().getCarrierConfig();
1823        if (!b.getBoolean(CarrierConfigManager.KEY_HAS_IN_CALL_NOISE_SUPPRESSION_BOOL)) {
1824            return;
1825        }
1826
1827        // change the mode if needed.
1828        if (isNoiseSuppressionOn(context) != sIsNoiseSuppressionEnabled) {
1829            turnOnNoiseSuppression(context, sIsNoiseSuppressionEnabled, false);
1830        }
1831    }
1832
1833    static boolean isNoiseSuppressionOn(Context context) {
1834
1835        PersistableBundle b = PhoneGlobals.getInstance().getCarrierConfig();
1836        if (!b.getBoolean(CarrierConfigManager.KEY_HAS_IN_CALL_NOISE_SUPPRESSION_BOOL)) {
1837            return false;
1838        }
1839
1840        AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
1841        String noiseSuppression = audioManager.getParameters("noise_suppression");
1842        if (DBG) log("isNoiseSuppressionOn: " + noiseSuppression);
1843        if (noiseSuppression.contains("off")) {
1844            return false;
1845        } else {
1846            return true;
1847        }
1848    }
1849
1850    static boolean isInEmergencyCall(CallManager cm) {
1851        for (Connection cn : cm.getActiveFgCall().getConnections()) {
1852            if (PhoneNumberUtils.isLocalEmergencyNumber(PhoneGlobals.getInstance(),
1853                    cn.getAddress())) {
1854                return true;
1855            }
1856        }
1857        return false;
1858    }
1859
1860    /**
1861     * Get the mute state of foreground phone, which has the current
1862     * foreground call
1863     */
1864    static boolean getMute() {
1865        return false;
1866    }
1867
1868    /* package */ static void setAudioMode() {
1869    }
1870
1871    /**
1872     * Sets the audio mode per current phone state.
1873     */
1874    /* package */ static void setAudioMode(CallManager cm) {
1875    }
1876
1877    /**
1878     * Look for ANY connections on the phone that qualify as being
1879     * disconnected.
1880     *
1881     * @return true if we find a connection that is disconnected over
1882     * all the phone's call objects.
1883     */
1884    /* package */ static boolean hasDisconnectedConnections(Phone phone) {
1885        return hasDisconnectedConnections(phone.getForegroundCall()) ||
1886                hasDisconnectedConnections(phone.getBackgroundCall()) ||
1887                hasDisconnectedConnections(phone.getRingingCall());
1888    }
1889
1890    /**
1891     * Iterate over all connections in a call to see if there are any
1892     * that are not alive (disconnected or idle).
1893     *
1894     * @return true if we find a connection that is disconnected, and
1895     * pending removal via
1896     * {@link com.android.internal.telephony.gsm.GsmCall#clearDisconnected()}.
1897     */
1898    private static final boolean hasDisconnectedConnections(Call call) {
1899        // look through all connections for non-active ones.
1900        for (Connection c : call.getConnections()) {
1901            if (!c.isAlive()) {
1902                return true;
1903            }
1904        }
1905        return false;
1906    }
1907
1908    //
1909    // Misc UI policy helper functions
1910    //
1911
1912    /**
1913     * @return true if we're allowed to hold calls, given the current
1914     * state of the Phone.
1915     */
1916    /* package */ static boolean okToHoldCall(CallManager cm) {
1917        final Call fgCall = cm.getActiveFgCall();
1918        final boolean hasHoldingCall = cm.hasActiveBgCall();
1919        final Call.State fgCallState = fgCall.getState();
1920
1921        // The "Hold" control is disabled entirely if there's
1922        // no way to either hold or unhold in the current state.
1923        final boolean okToHold = (fgCallState == Call.State.ACTIVE) && !hasHoldingCall;
1924        final boolean okToUnhold = cm.hasActiveBgCall() && (fgCallState == Call.State.IDLE);
1925        final boolean canHold = okToHold || okToUnhold;
1926
1927        return canHold;
1928    }
1929
1930    /**
1931     * @return true if we support holding calls, given the current
1932     * state of the Phone.
1933     */
1934    /* package */ static boolean okToSupportHold(CallManager cm) {
1935        boolean supportsHold = false;
1936
1937        final Call fgCall = cm.getActiveFgCall();
1938        final boolean hasHoldingCall = cm.hasActiveBgCall();
1939        final Call.State fgCallState = fgCall.getState();
1940
1941        if (TelephonyCapabilities.supportsHoldAndUnhold(fgCall.getPhone())) {
1942            // This phone has the concept of explicit "Hold" and "Unhold" actions.
1943            supportsHold = true;
1944        } else if (hasHoldingCall && (fgCallState == Call.State.IDLE)) {
1945            // Even when foreground phone device doesn't support hold/unhold, phone devices
1946            // for background holding calls may do.
1947            final Call bgCall = cm.getFirstActiveBgCall();
1948            if (bgCall != null &&
1949                    TelephonyCapabilities.supportsHoldAndUnhold(bgCall.getPhone())) {
1950                supportsHold = true;
1951            }
1952        }
1953        return supportsHold;
1954    }
1955
1956    /**
1957     * @return true if we're allowed to swap calls, given the current
1958     * state of the Phone.
1959     */
1960    /* package */ static boolean okToSwapCalls(CallManager cm) {
1961        int phoneType = cm.getDefaultPhone().getPhoneType();
1962        if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
1963            // CDMA: "Swap" is enabled only when the phone reaches a *generic*.
1964            // state by either accepting a Call Waiting or by merging two calls
1965            PhoneGlobals app = PhoneGlobals.getInstance();
1966            return (app.cdmaPhoneCallState.getCurrentCallState()
1967                    == CdmaPhoneCallState.PhoneCallState.CONF_CALL);
1968        } else if ((phoneType == PhoneConstants.PHONE_TYPE_GSM)
1969                || (phoneType == PhoneConstants.PHONE_TYPE_SIP)
1970                || (phoneType == PhoneConstants.PHONE_TYPE_IMS)
1971                || (phoneType == PhoneConstants.PHONE_TYPE_THIRD_PARTY)) {
1972            // GSM: "Swap" is available if both lines are in use and there's no
1973            // incoming call.  (Actually we need to verify that the active
1974            // call really is in the ACTIVE state and the holding call really
1975            // is in the HOLDING state, since you *can't* actually swap calls
1976            // when the foreground call is DIALING or ALERTING.)
1977            return !cm.hasActiveRingingCall()
1978                    && (cm.getActiveFgCall().getState() == Call.State.ACTIVE)
1979                    && (cm.getFirstActiveBgCall().getState() == Call.State.HOLDING);
1980        } else {
1981            throw new IllegalStateException("Unexpected phone type: " + phoneType);
1982        }
1983    }
1984
1985    /**
1986     * @return true if we're allowed to merge calls, given the current
1987     * state of the Phone.
1988     */
1989    /* package */ static boolean okToMergeCalls(CallManager cm) {
1990        int phoneType = cm.getFgPhone().getPhoneType();
1991        if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
1992            // CDMA: "Merge" is enabled only when the user is in a 3Way call.
1993            PhoneGlobals app = PhoneGlobals.getInstance();
1994            return ((app.cdmaPhoneCallState.getCurrentCallState()
1995                    == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE)
1996                    && !app.cdmaPhoneCallState.IsThreeWayCallOrigStateDialing());
1997        } else {
1998            // GSM: "Merge" is available if both lines are in use and there's no
1999            // incoming call, *and* the current conference isn't already
2000            // "full".
2001            // TODO: shall move all okToMerge logic to CallManager
2002            return !cm.hasActiveRingingCall() && cm.hasActiveFgCall()
2003                    && cm.hasActiveBgCall()
2004                    && cm.canConference(cm.getFirstActiveBgCall());
2005        }
2006    }
2007
2008    /**
2009     * @return true if the UI should let you add a new call, given the current
2010     * state of the Phone.
2011     */
2012    /* package */ static boolean okToAddCall(CallManager cm) {
2013        Phone phone = cm.getActiveFgCall().getPhone();
2014
2015        // "Add call" is never allowed in emergency callback mode (ECM).
2016        if (isPhoneInEcm(phone)) {
2017            return false;
2018        }
2019
2020        int phoneType = phone.getPhoneType();
2021        final Call.State fgCallState = cm.getActiveFgCall().getState();
2022        if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
2023           // CDMA: "Add call" button is only enabled when:
2024           // - ForegroundCall is in ACTIVE state
2025           // - After 30 seconds of user Ignoring/Missing a Call Waiting call.
2026            PhoneGlobals app = PhoneGlobals.getInstance();
2027            return ((fgCallState == Call.State.ACTIVE)
2028                    && (app.cdmaPhoneCallState.getAddCallMenuStateAfterCallWaiting()));
2029        } else if ((phoneType == PhoneConstants.PHONE_TYPE_GSM)
2030                || (phoneType == PhoneConstants.PHONE_TYPE_SIP)
2031                || (phoneType == PhoneConstants.PHONE_TYPE_IMS)
2032                || (phoneType == PhoneConstants.PHONE_TYPE_THIRD_PARTY)) {
2033            // GSM: "Add call" is available only if ALL of the following are true:
2034            // - There's no incoming ringing call
2035            // - There's < 2 lines in use
2036            // - The foreground call is ACTIVE or IDLE or DISCONNECTED.
2037            //   (We mainly need to make sure it *isn't* DIALING or ALERTING.)
2038            final boolean hasRingingCall = cm.hasActiveRingingCall();
2039            final boolean hasActiveCall = cm.hasActiveFgCall();
2040            final boolean hasHoldingCall = cm.hasActiveBgCall();
2041            final boolean allLinesTaken = hasActiveCall && hasHoldingCall;
2042
2043            return !hasRingingCall
2044                    && !allLinesTaken
2045                    && ((fgCallState == Call.State.ACTIVE)
2046                        || (fgCallState == Call.State.IDLE)
2047                        || (fgCallState == Call.State.DISCONNECTED));
2048        } else {
2049            throw new IllegalStateException("Unexpected phone type: " + phoneType);
2050        }
2051    }
2052
2053    /**
2054     * Based on the input CNAP number string,
2055     * @return _RESTRICTED or _UNKNOWN for all the special CNAP strings.
2056     * Otherwise, return CNAP_SPECIAL_CASE_NO.
2057     */
2058    private static int checkCnapSpecialCases(String n) {
2059        if (n.equals("PRIVATE") ||
2060                n.equals("P") ||
2061                n.equals("RES")) {
2062            if (DBG) log("checkCnapSpecialCases, PRIVATE string: " + n);
2063            return PhoneConstants.PRESENTATION_RESTRICTED;
2064        } else if (n.equals("UNAVAILABLE") ||
2065                n.equals("UNKNOWN") ||
2066                n.equals("UNA") ||
2067                n.equals("U")) {
2068            if (DBG) log("checkCnapSpecialCases, UNKNOWN string: " + n);
2069            return PhoneConstants.PRESENTATION_UNKNOWN;
2070        } else {
2071            if (DBG) log("checkCnapSpecialCases, normal str. number: " + n);
2072            return CNAP_SPECIAL_CASE_NO;
2073        }
2074    }
2075
2076    /**
2077     * Handles certain "corner cases" for CNAP. When we receive weird phone numbers
2078     * from the network to indicate different number presentations, convert them to
2079     * expected number and presentation values within the CallerInfo object.
2080     * @param number number we use to verify if we are in a corner case
2081     * @param presentation presentation value used to verify if we are in a corner case
2082     * @return the new String that should be used for the phone number
2083     */
2084    /* package */ static String modifyForSpecialCnapCases(Context context, CallerInfo ci,
2085            String number, int presentation) {
2086        // Obviously we return number if ci == null, but still return number if
2087        // number == null, because in these cases the correct string will still be
2088        // displayed/logged after this function returns based on the presentation value.
2089        if (ci == null || number == null) return number;
2090
2091        if (DBG) {
2092            log("modifyForSpecialCnapCases: initially, number="
2093                    + toLogSafePhoneNumber(number)
2094                    + ", presentation=" + presentation + " ci " + ci);
2095        }
2096
2097        // "ABSENT NUMBER" is a possible value we could get from the network as the
2098        // phone number, so if this happens, change it to "Unknown" in the CallerInfo
2099        // and fix the presentation to be the same.
2100        final String[] absentNumberValues =
2101                context.getResources().getStringArray(R.array.absent_num);
2102        if (Arrays.asList(absentNumberValues).contains(number)
2103                && presentation == PhoneConstants.PRESENTATION_ALLOWED) {
2104            number = context.getString(R.string.unknown);
2105            ci.numberPresentation = PhoneConstants.PRESENTATION_UNKNOWN;
2106        }
2107
2108        // Check for other special "corner cases" for CNAP and fix them similarly. Corner
2109        // cases only apply if we received an allowed presentation from the network, so check
2110        // if we think we have an allowed presentation, or if the CallerInfo presentation doesn't
2111        // match the presentation passed in for verification (meaning we changed it previously
2112        // because it's a corner case and we're being called from a different entry point).
2113        if (ci.numberPresentation == PhoneConstants.PRESENTATION_ALLOWED
2114                || (ci.numberPresentation != presentation
2115                        && presentation == PhoneConstants.PRESENTATION_ALLOWED)) {
2116            int cnapSpecialCase = checkCnapSpecialCases(number);
2117            if (cnapSpecialCase != CNAP_SPECIAL_CASE_NO) {
2118                // For all special strings, change number & numberPresentation.
2119                if (cnapSpecialCase == PhoneConstants.PRESENTATION_RESTRICTED) {
2120                    number = context.getString(R.string.private_num);
2121                } else if (cnapSpecialCase == PhoneConstants.PRESENTATION_UNKNOWN) {
2122                    number = context.getString(R.string.unknown);
2123                }
2124                if (DBG) {
2125                    log("SpecialCnap: number=" + toLogSafePhoneNumber(number)
2126                            + "; presentation now=" + cnapSpecialCase);
2127                }
2128                ci.numberPresentation = cnapSpecialCase;
2129            }
2130        }
2131        if (DBG) {
2132            log("modifyForSpecialCnapCases: returning number string="
2133                    + toLogSafePhoneNumber(number));
2134        }
2135        return number;
2136    }
2137
2138    //
2139    // Support for 3rd party phone service providers.
2140    //
2141
2142    /**
2143     * Check if a phone number can be route through a 3rd party
2144     * gateway. The number must be a global phone number in numerical
2145     * form (1-800-666-SEXY won't work).
2146     *
2147     * MMI codes and the like cannot be used as a dial number for the
2148     * gateway either.
2149     *
2150     * @param number To be dialed via a 3rd party gateway.
2151     * @return true If the number can be routed through the 3rd party network.
2152     */
2153    private static boolean isRoutableViaGateway(String number) {
2154        if (TextUtils.isEmpty(number)) {
2155            return false;
2156        }
2157        number = PhoneNumberUtils.stripSeparators(number);
2158        if (!number.equals(PhoneNumberUtils.convertKeypadLettersToDigits(number))) {
2159            return false;
2160        }
2161        number = PhoneNumberUtils.extractNetworkPortion(number);
2162        return PhoneNumberUtils.isGlobalPhoneNumber(number);
2163    }
2164
2165    /**
2166     * Returns whether the phone is in ECM ("Emergency Callback Mode") or not.
2167     */
2168    /* package */ static boolean isPhoneInEcm(Phone phone) {
2169        if ((phone != null) && TelephonyCapabilities.supportsEcm(phone)) {
2170            // For phones that support ECM, return true iff PROPERTY_INECM_MODE == "true".
2171            // TODO: There ought to be a better API for this than just
2172            // exposing a system property all the way up to the app layer,
2173            // probably a method like "inEcm()" provided by the telephony
2174            // layer.
2175            String ecmMode =
2176                    SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE);
2177            if (ecmMode != null) {
2178                return ecmMode.equals("true");
2179            }
2180        }
2181        return false;
2182    }
2183
2184    /**
2185     * Returns the most appropriate Phone object to handle a call
2186     * to the specified number.
2187     *
2188     * @param cm the CallManager.
2189     * @param scheme the scheme from the data URI that the number originally came from.
2190     * @param number the phone number, or SIP address.
2191     */
2192    public static Phone pickPhoneBasedOnNumber(CallManager cm, String scheme, String number,
2193            String primarySipUri, ComponentName thirdPartyCallComponent) {
2194        if (DBG) {
2195            log("pickPhoneBasedOnNumber: scheme " + scheme
2196                    + ", number " + toLogSafePhoneNumber(number)
2197                    + ", sipUri "
2198                    + (primarySipUri != null ? Uri.parse(primarySipUri).toSafeString() : "null")
2199                    + ", thirdPartyCallComponent: " + thirdPartyCallComponent);
2200        }
2201
2202        if (primarySipUri != null) {
2203            Phone phone = getSipPhoneFromUri(cm, primarySipUri);
2204            if (phone != null) return phone;
2205        }
2206
2207        return cm.getDefaultPhone();
2208    }
2209
2210    public static Phone getSipPhoneFromUri(CallManager cm, String target) {
2211        for (Phone phone : cm.getAllPhones()) {
2212            if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_SIP) {
2213                String sipUri = ((SipPhone) phone).getSipUri();
2214                if (target.equals(sipUri)) {
2215                    if (DBG) log("- pickPhoneBasedOnNumber:" +
2216                            "found SipPhone! obj = " + phone + ", "
2217                            + phone.getClass());
2218                    return phone;
2219                }
2220            }
2221        }
2222        return null;
2223    }
2224
2225    /**
2226     * Returns true when the given call is in INCOMING state and there's no foreground phone call,
2227     * meaning the call is the first real incoming call the phone is having.
2228     */
2229    public static boolean isRealIncomingCall(Call.State state) {
2230        return (state == Call.State.INCOMING && !PhoneGlobals.getInstance().mCM.hasActiveFgCall());
2231    }
2232
2233    public static String getPresentationString(Context context, int presentation) {
2234        String name = context.getString(R.string.unknown);
2235        if (presentation == PhoneConstants.PRESENTATION_RESTRICTED) {
2236            name = context.getString(R.string.private_num);
2237        } else if (presentation == PhoneConstants.PRESENTATION_PAYPHONE) {
2238            name = context.getString(R.string.payphone);
2239        }
2240        return name;
2241    }
2242
2243    public static void sendViewNotificationAsync(Context context, Uri contactUri) {
2244        if (DBG) Log.d(LOG_TAG, "Send view notification to Contacts (uri: " + contactUri + ")");
2245        Intent intent = new Intent("com.android.contacts.VIEW_NOTIFICATION", contactUri);
2246        intent.setClassName("com.android.contacts",
2247                "com.android.contacts.ViewNotificationService");
2248        context.startService(intent);
2249    }
2250
2251    //
2252    // General phone and call state debugging/testing code
2253    //
2254
2255    /* package */ static void dumpCallState(Phone phone) {
2256        PhoneGlobals app = PhoneGlobals.getInstance();
2257        Log.d(LOG_TAG, "dumpCallState():");
2258        Log.d(LOG_TAG, "- Phone: " + phone + ", name = " + phone.getPhoneName()
2259              + ", state = " + phone.getState());
2260
2261        StringBuilder b = new StringBuilder(128);
2262
2263        Call call = phone.getForegroundCall();
2264        b.setLength(0);
2265        b.append("  - FG call: ").append(call.getState());
2266        b.append(" isAlive ").append(call.getState().isAlive());
2267        b.append(" isRinging ").append(call.getState().isRinging());
2268        b.append(" isDialing ").append(call.getState().isDialing());
2269        b.append(" isIdle ").append(call.isIdle());
2270        b.append(" hasConnections ").append(call.hasConnections());
2271        Log.d(LOG_TAG, b.toString());
2272
2273        call = phone.getBackgroundCall();
2274        b.setLength(0);
2275        b.append("  - BG call: ").append(call.getState());
2276        b.append(" isAlive ").append(call.getState().isAlive());
2277        b.append(" isRinging ").append(call.getState().isRinging());
2278        b.append(" isDialing ").append(call.getState().isDialing());
2279        b.append(" isIdle ").append(call.isIdle());
2280        b.append(" hasConnections ").append(call.hasConnections());
2281        Log.d(LOG_TAG, b.toString());
2282
2283        call = phone.getRingingCall();
2284        b.setLength(0);
2285        b.append("  - RINGING call: ").append(call.getState());
2286        b.append(" isAlive ").append(call.getState().isAlive());
2287        b.append(" isRinging ").append(call.getState().isRinging());
2288        b.append(" isDialing ").append(call.getState().isDialing());
2289        b.append(" isIdle ").append(call.isIdle());
2290        b.append(" hasConnections ").append(call.hasConnections());
2291        Log.d(LOG_TAG, b.toString());
2292
2293
2294        final boolean hasRingingCall = !phone.getRingingCall().isIdle();
2295        final boolean hasActiveCall = !phone.getForegroundCall().isIdle();
2296        final boolean hasHoldingCall = !phone.getBackgroundCall().isIdle();
2297        final boolean allLinesTaken = hasActiveCall && hasHoldingCall;
2298        b.setLength(0);
2299        b.append("  - hasRingingCall ").append(hasRingingCall);
2300        b.append(" hasActiveCall ").append(hasActiveCall);
2301        b.append(" hasHoldingCall ").append(hasHoldingCall);
2302        b.append(" allLinesTaken ").append(allLinesTaken);
2303        Log.d(LOG_TAG, b.toString());
2304
2305        // On CDMA phones, dump out the CdmaPhoneCallState too:
2306        if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
2307            if (app.cdmaPhoneCallState != null) {
2308                Log.d(LOG_TAG, "  - CDMA call state: "
2309                      + app.cdmaPhoneCallState.getCurrentCallState());
2310            } else {
2311                Log.d(LOG_TAG, "  - CDMA device, but null cdmaPhoneCallState!");
2312            }
2313        }
2314    }
2315
2316    private static void log(String msg) {
2317        Log.d(LOG_TAG, msg);
2318    }
2319
2320    static void dumpCallManager() {
2321        Call call;
2322        CallManager cm = PhoneGlobals.getInstance().mCM;
2323        StringBuilder b = new StringBuilder(128);
2324
2325
2326
2327        Log.d(LOG_TAG, "############### dumpCallManager() ##############");
2328        // TODO: Don't log "cm" itself, since CallManager.toString()
2329        // already spews out almost all this same information.
2330        // We should fix CallManager.toString() to be more minimal, and
2331        // use an explicit dumpState() method for the verbose dump.
2332        // Log.d(LOG_TAG, "CallManager: " + cm
2333        //         + ", state = " + cm.getState());
2334        Log.d(LOG_TAG, "CallManager: state = " + cm.getState());
2335        b.setLength(0);
2336        call = cm.getActiveFgCall();
2337        b.append(" - FG call: ").append(cm.hasActiveFgCall()? "YES ": "NO ");
2338        b.append(call);
2339        b.append( "  State: ").append(cm.getActiveFgCallState());
2340        b.append( "  Conn: ").append(cm.getFgCallConnections());
2341        Log.d(LOG_TAG, b.toString());
2342        b.setLength(0);
2343        call = cm.getFirstActiveBgCall();
2344        b.append(" - BG call: ").append(cm.hasActiveBgCall()? "YES ": "NO ");
2345        b.append(call);
2346        b.append( "  State: ").append(cm.getFirstActiveBgCall().getState());
2347        b.append( "  Conn: ").append(cm.getBgCallConnections());
2348        Log.d(LOG_TAG, b.toString());
2349        b.setLength(0);
2350        call = cm.getFirstActiveRingingCall();
2351        b.append(" - RINGING call: ").append(cm.hasActiveRingingCall()? "YES ": "NO ");
2352        b.append(call);
2353        b.append( "  State: ").append(cm.getFirstActiveRingingCall().getState());
2354        Log.d(LOG_TAG, b.toString());
2355
2356
2357
2358        for (Phone phone : CallManager.getInstance().getAllPhones()) {
2359            if (phone != null) {
2360                Log.d(LOG_TAG, "Phone: " + phone + ", name = " + phone.getPhoneName()
2361                        + ", state = " + phone.getState());
2362                b.setLength(0);
2363                call = phone.getForegroundCall();
2364                b.append(" - FG call: ").append(call);
2365                b.append( "  State: ").append(call.getState());
2366                b.append( "  Conn: ").append(call.hasConnections());
2367                Log.d(LOG_TAG, b.toString());
2368                b.setLength(0);
2369                call = phone.getBackgroundCall();
2370                b.append(" - BG call: ").append(call);
2371                b.append( "  State: ").append(call.getState());
2372                b.append( "  Conn: ").append(call.hasConnections());
2373                Log.d(LOG_TAG, b.toString());b.setLength(0);
2374                call = phone.getRingingCall();
2375                b.append(" - RINGING call: ").append(call);
2376                b.append( "  State: ").append(call.getState());
2377                b.append( "  Conn: ").append(call.hasConnections());
2378                Log.d(LOG_TAG, b.toString());
2379            }
2380        }
2381
2382        Log.d(LOG_TAG, "############## END dumpCallManager() ###############");
2383    }
2384
2385    /**
2386     * @return if the context is in landscape orientation.
2387     */
2388    public static boolean isLandscape(Context context) {
2389        return context.getResources().getConfiguration().orientation
2390                == Configuration.ORIENTATION_LANDSCAPE;
2391    }
2392
2393    public static PhoneAccountHandle makePstnPhoneAccountHandle(String id) {
2394        return makePstnPhoneAccountHandleWithPrefix(id, "", false);
2395    }
2396
2397    public static PhoneAccountHandle makePstnPhoneAccountHandle(int phoneId) {
2398        return makePstnPhoneAccountHandle(PhoneFactory.getPhone(phoneId));
2399    }
2400
2401    public static PhoneAccountHandle makePstnPhoneAccountHandle(Phone phone) {
2402        return makePstnPhoneAccountHandleWithPrefix(phone, "", false);
2403    }
2404
2405    public static PhoneAccountHandle makePstnPhoneAccountHandleWithPrefix(
2406            Phone phone, String prefix, boolean isEmergency) {
2407        // TODO: Should use some sort of special hidden flag to decorate this account as
2408        // an emergency-only account
2409        String id = isEmergency ? "E" : prefix + String.valueOf(phone.getIccSerialNumber());
2410        return makePstnPhoneAccountHandleWithPrefix(id, prefix, isEmergency);
2411    }
2412
2413    public static PhoneAccountHandle makePstnPhoneAccountHandleWithPrefix(
2414            String id, String prefix, boolean isEmergency) {
2415        ComponentName pstnConnectionServiceName = getPstnConnectionServiceName();
2416        return new PhoneAccountHandle(pstnConnectionServiceName, id);
2417    }
2418
2419    public static int getSubIdForPhoneAccount(PhoneAccount phoneAccount) {
2420        if (phoneAccount != null
2421                && phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
2422            return getSubIdForPhoneAccountHandle(phoneAccount.getAccountHandle());
2423        }
2424        return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
2425    }
2426
2427    public static int getSubIdForPhoneAccountHandle(PhoneAccountHandle handle) {
2428        if (handle != null && handle.getComponentName().equals(getPstnConnectionServiceName())) {
2429            Phone phone = getPhoneFromIccId(handle.getId());
2430            if (phone != null) {
2431                return phone.getSubId();
2432            }
2433        }
2434        return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
2435    }
2436
2437    /**
2438     * Determine if a given phone account corresponds to an active SIM
2439     *
2440     * @param sm An instance of the subscription manager so it is not recreated for each calling of
2441     * this method.
2442     * @param handle The handle for the phone account to check
2443     * @return {@code true} If there is an active SIM for this phone account,
2444     * {@code false} otherwise.
2445     */
2446    public static boolean isPhoneAccountActive(SubscriptionManager sm, PhoneAccountHandle handle) {
2447        return sm.getActiveSubscriptionInfoForIccIndex(handle.getId()) != null;
2448    }
2449
2450    private static ComponentName getPstnConnectionServiceName() {
2451        return new ComponentName(PhoneGlobals.getInstance(), TelephonyConnectionService.class);
2452    }
2453
2454    private static Phone getPhoneFromIccId(String iccId) {
2455        if (!TextUtils.isEmpty(iccId)) {
2456            for (Phone phone : PhoneFactory.getPhones()) {
2457                String phoneIccId = phone.getIccSerialNumber();
2458                if (iccId.equals(phoneIccId)) {
2459                    return phone;
2460                }
2461            }
2462        }
2463        return null;
2464    }
2465
2466    /**
2467     * Register ICC status for all phones.
2468     */
2469    static final void registerIccStatus(Handler handler, int event) {
2470        for (Phone phone : PhoneFactory.getPhones()) {
2471            IccCard sim = phone.getIccCard();
2472            if (sim != null) {
2473                if (VDBG) Log.v(LOG_TAG, "register for ICC status, phone " + phone.getPhoneId());
2474                sim.registerForNetworkLocked(handler, event, phone);
2475            }
2476        }
2477    }
2478
2479    /**
2480     * Set the radio power on/off state for all phones.
2481     *
2482     * @param enabled true means on, false means off.
2483     */
2484    static final void setRadioPower(boolean enabled) {
2485        for (Phone phone : PhoneFactory.getPhones()) {
2486            phone.setRadioPower(enabled);
2487        }
2488    }
2489}
2490