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